From 6da1bf4c4c9b879054b475cf2bc96861ffa8d03d Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Wed, 18 Sep 2019 14:44:50 +0200 Subject: [PATCH 001/183] WIP: navigate down in the hierarchy of InnerBlocks WIP: add hierarchy of click-down WIP: add hierarchy of click other element same parent EOD: click-down logic EOD: click-down logic fix WIP: dim unfocus element WIP: add navigate up button on floating toolbar EOD: change color of FloatingToolbar arrow to white change focus border color from gray to blue EOD: click-down logic lock onFocus on AztecView when block is not selected change dashed border color apply style to keep same innerblock size work with styles work with styles adjust borders margin pass isParentSelected to RichText to unlock onClick event --- .../src/components/block-list/block.native.js | 37 +++++++++-- .../components/block-list/block.native.scss | 56 ++++++++++++++++- .../src/components/block-list/index.native.js | 11 +++- .../components/block-list/style.native.scss | 3 +- .../src/components/caption/index.native.js | 1 + .../media-placeholder/index.native.js | 4 +- packages/block-editor/src/store/selectors.js | 62 +++++++++++++++++++ .../block-library/src/image/edit.native.js | 7 ++- .../src/media-text/edit.native.js | 1 + .../src/media-text/media-container.native.js | 1 + .../block-library/src/video/edit.native.js | 1 + packages/components/src/icon-button/index.js | 2 + .../src/components/post-title/index.native.js | 16 ++++- .../components/post-title/style.native.scss | 4 ++ .../rich-text/src/component/index.native.js | 6 +- 15 files changed, 196 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 456c5fad6ac2ab..f4f82ee304b656 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -40,8 +40,9 @@ class BlockListBlock extends Component { } onFocus() { - if ( ! this.props.isSelected ) { - this.props.onSelect(); + const { firstToSelect, isSelected, onSelect } = this.props; + if ( ! isSelected ) { + onSelect( firstToSelect ); } } @@ -117,6 +118,10 @@ class BlockListBlock extends Component { showFloatingToolbar, parentId, isFirstBlock, + parentId, + isDashed, + isDimmed, + isGroup, } = this.props; const borderColor = isSelected ? focusedBorderColor : 'transparent'; @@ -147,7 +152,13 @@ class BlockListBlock extends Component { { showTitle && this.renderBlockTitle() } { isValid && this.getBlockForType() } { ! isValid && @@ -170,9 +181,12 @@ export default compose( [ isBlockSelected, __unstableGetBlockWithoutInnerBlocks, getBlockHierarchyRootClientId, - getBlock, getBlockRootClientId, + getSelectedBlockClientId, + getBlock, getSelectedBlock, + getBlockOrder, + getFirstToSelectBlock, } = select( 'core/block-editor' ); const order = getBlockIndex( clientId, rootClientId ); const isSelected = isBlockSelected( clientId ); @@ -198,6 +212,17 @@ export default compose( [ const showFloatingToolbar = isSelected && hasRootInnerBlocks && ! isMediaText && ! isMediaTextParent; + const parentId = getBlockRootClientId( clientId ); + const firstToSelect = getFirstToSelectBlock( clientId ); + + const selectedBlockClientId = getSelectedBlockClientId(); + const isRootSiblingsSelected = getBlockRootClientId( selectedBlockClientId ) === ''; + + const isDashed = selectedBlockClientId === parentId; + const isDimmed = ! isSelected && ! isRootSiblingsSelected && !! selectedBlockClientId && firstToSelect === clientId && ! isDashed; + + const parentId = getBlockRootClientId( clientId ); + return { icon, name: name || 'core/missing', @@ -212,6 +237,10 @@ export default compose( [ getAccessibilityLabelExtra, showFloatingToolbar, parentId, + isDashed, + isDimmed, + firstToSelect, + parentId, }; } ), withDispatch( ( dispatch, ownProps, { select } ) => { diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index e3d18ba3f7c640..63adcee69fbc3f 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -3,12 +3,64 @@ } .blockContainer { - padding-left: 16px; - padding-right: 16px; + margin-left: 16px; + margin-right: 16px; padding-top: 12px; padding-bottom: 12px; } +.blockContainerDimmed { + opacity: 0.2; +} + +.blockHolderDashedBordered { + border-top-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-right-width: 1px; + border-style: dashed; + border-color: $gray; + padding-left: 6px; + padding-right: 6px; + padding-top: 6px; + padding-bottom: 6px; + padding-bottom: 6px; + margin-right: 0px; + margin-left: 0px; + margin-bottom: 5px; + margin-top: 5px; + border-radius: 2px; +} + +.selectedInnerGroup { + padding-top: 0px; + margin-left: 0px; + margin-right: 0px; +} + +.margin { + margin-left: 9px; + margin-right: 9px; +} + +.padding { + padding-left: 0px; + padding-right: 0px; + padding-top: 0px; +} + +.dashedBorderStyle { + margin-left: 5px; + margin-right: 5px; +} + +.innerBlockContainerFocused { + padding-left: 12px; + padding-right: 12px; + padding-top: 12px; + padding-bottom: 0; // will be flushed into inline toolbar height +} + .blockContainerFocused { padding-left: 16px; padding-right: 16px; diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 05c2898899ca39..fa5c621dc9c41d 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -43,7 +43,7 @@ export class BlockList extends Component { } blockHolderBorderStyle() { - return this.props.isFullyBordered ? styles.blockHolderFullBordered : styles.blockHolderSemiBordered; + return this.props.isFullyBordered || this.props.hasFullBorder ? styles.blockHolderFullBordered : styles.blockHolderSemiBordered; } onCaretVerticalPositionChange( targetId, caretY, previousCaretY ) { @@ -172,6 +172,7 @@ export default compose( [ getBlockInsertionPoint, isBlockInsertionPointVisible, getSelectedBlock, + isBlockSelected, getBlockRootClientId, } = select( 'core/block-editor' ); @@ -206,6 +207,8 @@ export default compose( [ return ! shouldHideBlockAtIndex; }; + const hasFullBorder = !! getBlockRootClientId( selectedBlockClientId ) || isSelectedGroup; + return { blockClientIds, blockCount: getBlockCount( rootClientId ), @@ -213,8 +216,10 @@ export default compose( [ shouldShowBlockAtIndex, shouldShowInsertionPoint, selectedBlockClientId, - isFirstBlock, - selectedBlockParentId, + rootClientId, + getBlockIndex, + isBlockSelected, + hasFullBorder, }; } ), withDispatch( ( dispatch ) => { diff --git a/packages/block-editor/src/components/block-list/style.native.scss b/packages/block-editor/src/components/block-list/style.native.scss index 36be89c4c067cd..c9cd99579413ac 100644 --- a/packages/block-editor/src/components/block-list/style.native.scss +++ b/packages/block-editor/src/components/block-list/style.native.scss @@ -58,6 +58,7 @@ border-bottom-width: 1px; border-left-width: 1px; border-right-width: 1px; + border-radius: 4px; } .blockContainerFocused { @@ -68,7 +69,7 @@ } .blockHolderFocused { - border-color: $gray-lighten-30; + border-color: $blue-wordpress; } .blockHolderFocusedDark { diff --git a/packages/block-editor/src/components/caption/index.native.js b/packages/block-editor/src/components/caption/index.native.js index 68e965524c8b87..c093c44c49d593 100644 --- a/packages/block-editor/src/components/caption/index.native.js +++ b/packages/block-editor/src/components/caption/index.native.js @@ -30,6 +30,7 @@ const Caption = ( { accessible, accessibilityLabel, onBlur, onChange, onFocus, i underlineColorAndroid="transparent" textAlign={ 'center' } tagName={ '' } + isParentSelected={ shouldDisplay } /> ); diff --git a/packages/block-editor/src/components/media-placeholder/index.native.js b/packages/block-editor/src/components/media-placeholder/index.native.js index c4579e78fe6bfa..14348f1e0bf756 100644 --- a/packages/block-editor/src/components/media-placeholder/index.native.js +++ b/packages/block-editor/src/components/media-placeholder/index.native.js @@ -115,7 +115,9 @@ function MediaPlaceholder( props ) { accessibilityHint={ accessibilityHint } onPress={ ( event ) => { props.onFocus( event ); - open(); + if ( props.isBlockSelected ) { + open(); + } } }> = 0; + if ( hasCommonParent ) { + commonParentFirstChild = clientTree[ commonParentIndex - 1 ]; + } + index++; + } while ( index < selectedTree.length && ! hasCommonParent ); + + return commonParentFirstChild; +} + /** * Returns the client ID of the block adjacent one at the given reference * startClientId and modifier directionality. Defaults start startClientId to diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index ce2c1affe275a5..de1eda5e4f675a 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -114,7 +114,11 @@ export class ImageEdit extends React.Component { } onImagePressed() { - const { attributes } = this.props; + const { attributes, isSelected } = this.props; + + if ( ! isSelected ) { + return null; + } if ( this.state.isUploadInProgress ) { requestImageUploadCancelDialog( attributes.id ); @@ -275,6 +279,7 @@ export class ImageEdit extends React.Component { onSelect={ this.onSelectMediaUploadOption } icon={ this.getIcon( false ) } onFocus={ this.props.onFocus } + isBlockSelected={ this.props.isSelected } /> ); diff --git a/packages/block-library/src/media-text/edit.native.js b/packages/block-library/src/media-text/edit.native.js index 3cfb91b48ce949..2e581c992e3636 100644 --- a/packages/block-library/src/media-text/edit.native.js +++ b/packages/block-library/src/media-text/edit.native.js @@ -108,6 +108,7 @@ class MediaTextEdit extends Component { onWidthChange={ this.onWidthChange } commitWidthChange={ this.commitWidthChange } onFocus={ this.props.onFocus } + isSelected={ this.props.isSelected } { ...{ mediaAlt, mediaId, mediaType, mediaUrl, mediaPosition, mediaWidth, imageFill, focalPoint } } /> ); diff --git a/packages/block-library/src/media-text/media-container.native.js b/packages/block-library/src/media-text/media-container.native.js index 0b47812e5f5ac0..7a027a146093b9 100644 --- a/packages/block-library/src/media-text/media-container.native.js +++ b/packages/block-library/src/media-text/media-container.native.js @@ -166,6 +166,7 @@ class MediaContainer extends Component { onSelect={ this.onSelectMediaUploadOption } allowedTypes={ [ MEDIA_TYPE_IMAGE ] } onFocus={ this.props.onFocus } + isBlockSelected={ this.props.isSelected } /> ); } diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index 1a193a521e7032..82c3eb99bfb104 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -185,6 +185,7 @@ class VideoEdit extends React.Component { onSelect={ this.onSelectMediaUploadOption } icon={ this.getIcon( false, true ) } onFocus={ this.props.onFocus } + isBlockSelected={ this.props.isSelected } /> ); diff --git a/packages/components/src/icon-button/index.js b/packages/components/src/icon-button/index.js index 8c2e361eb2a371..c2a4e7f831bc66 100644 --- a/packages/components/src/icon-button/index.js +++ b/packages/components/src/icon-button/index.js @@ -25,12 +25,14 @@ function IconButton( props, ref ) { tooltip, shortcut, labelPosition, + fill, ...additionalProps } = props; const classes = classnames( 'components-icon-button', className, { 'has-text': children, } ); const tooltipText = tooltip || label; + const iconProps = fill ? { fill } : {}; // Should show the tooltip if... const showTooltip = ! additionalProps.disabled && ( diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index a47de19cfaa1c3..6b36b0c2fe7fce 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -50,6 +50,7 @@ class PostTitle extends Component { title, focusedBorderColor, borderStyle, + isDimmed, } = this.props; const decodedPlaceholder = decodeEntities( placeholder ); @@ -57,7 +58,7 @@ class PostTitle extends Component { return ( { diff --git a/packages/editor/src/components/post-title/style.native.scss b/packages/editor/src/components/post-title/style.native.scss index a583018c37e377..416cd4c0ec33b6 100644 --- a/packages/editor/src/components/post-title/style.native.scss +++ b/packages/editor/src/components/post-title/style.native.scss @@ -7,3 +7,7 @@ margin-top: 24; } +.dimmed { + opacity: 0.2; +} + diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js index 206680d3c87668..031f7e217455ee 100644 --- a/packages/rich-text/src/component/index.native.js +++ b/packages/rich-text/src/component/index.native.js @@ -771,6 +771,8 @@ export class RichText extends Component { __unstableIsSelected: isSelected, children, getStylesFromColorScheme, + blockIsSelected, + isParentSelected, } = this.props; const record = this.getRecord(); @@ -820,7 +822,8 @@ export class RichText extends Component { this.firedAfterTextChanged = false; } - const dynamicStyle = getStylesFromColorScheme( style, styles.richTextDark ); + const dynamicStyle = useStyle( style, styles.richTextDark ); + const lockOnFocus = blockIsSelected || isParentSelected ? {} : { pointerEvents: 'none' }; return ( @@ -866,6 +869,7 @@ export class RichText extends Component { disableEditingMenu={ this.props.disableEditingMenu } isMultiline={ this.isMultiline } textAlign={ this.props.textAlign } + { ...lockOnFocus } /> { isSelected && } From 6387115cdcd354e076af8a9c36be1389862b2449 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Tue, 8 Oct 2019 09:59:32 +0200 Subject: [PATCH 002/183] WIP: Navigation Dwon in Inner Block --- .../src/components/block-list/block.native.js | 29 ++++++++++++++--- .../components/block-list/block.native.scss | 31 +++++++++++++------ .../components/block-list/style.native.scss | 3 ++ 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index f4f82ee304b656..c6b5b096f2f0cb 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -122,6 +122,10 @@ class BlockListBlock extends Component { isDashed, isDimmed, isGroup, + isInnerBlock, + isChildOfSameRootBlook, + isNestedInnerBlock, + isGroupType, } = this.props; const borderColor = isSelected ? focusedBorderColor : 'transparent'; @@ -153,11 +157,18 @@ class BlockListBlock extends Component { { isValid && this.getBlockForType() } @@ -221,7 +232,11 @@ export default compose( [ const isDashed = selectedBlockClientId === parentId; const isDimmed = ! isSelected && ! isRootSiblingsSelected && !! selectedBlockClientId && firstToSelect === clientId && ! isDashed; - const parentId = getBlockRootClientId( clientId ); + const isInnerBlock = parentId && firstToSelect !== parentId + const isChildOfSameRootBlook = rootBlockId === getBlockHierarchyRootClientId( selectedBlockClientId ) + const isNestedInnerBlock = !isDashed && rootBlockId === getBlockRootClientId( firstToSelect ) + const isGroup = hasRootInnerBlocks; //blockType ==='core/group'; + const isGroupType = blockType.name === 'core/group' return { icon, @@ -240,7 +255,11 @@ export default compose( [ isDashed, isDimmed, firstToSelect, - parentId, + isGroup, + isInnerBlock, + isChildOfSameRootBlook, + isNestedInnerBlock, + isGroupType, }; } ), withDispatch( ( dispatch, ownProps, { select } ) => { diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index 63adcee69fbc3f..168749a7afee1c 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -9,6 +9,13 @@ padding-bottom: 12px; } +.blockContainerInner { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; +} + .blockContainerDimmed { opacity: 0.2; } @@ -24,18 +31,17 @@ padding-right: 6px; padding-top: 6px; padding-bottom: 6px; - padding-bottom: 6px; - margin-right: 0px; - margin-left: 0px; + margin-right: 0; + margin-left: 0; margin-bottom: 5px; margin-top: 5px; border-radius: 2px; } .selectedInnerGroup { - padding-top: 0px; - margin-left: 0px; - margin-right: 0px; + padding-top: 0; + margin-left: 0; + margin-right: 0; } .margin { @@ -43,10 +49,17 @@ margin-right: 9px; } +.marginInnerGroup { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; +} + .padding { - padding-left: 0px; - padding-right: 0px; - padding-top: 0px; + padding-left: 0; + padding-right: 0; + padding-top: 0; } .dashedBorderStyle { diff --git a/packages/block-editor/src/components/block-list/style.native.scss b/packages/block-editor/src/components/block-list/style.native.scss index c9cd99579413ac..541a35d42a19a3 100644 --- a/packages/block-editor/src/components/block-list/style.native.scss +++ b/packages/block-editor/src/components/block-list/style.native.scss @@ -6,6 +6,9 @@ .list { flex: 1; + padding-left: 0; + padding-right: 0; + margin: 0; } .switch { From e0103289d410bc4fe3638634ca08704ccc42b564 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Tue, 8 Oct 2019 12:17:07 +0200 Subject: [PATCH 003/183] merge master --- .../developers/data/data-core-block-editor.md | 26 ++++++++++++++ .../src/components/block-list/block.native.js | 36 +++++++++++-------- .../src/components/block-list/index.native.js | 5 ++- .../components/block-list/style.native.scss | 4 +++ .../media-placeholder/styles.native.scss | 4 +++ .../src/media-text/media-container.native.js | 1 - packages/components/src/icon-button/index.js | 2 -- .../rich-text/src/component/index.native.js | 2 +- 8 files changed, 58 insertions(+), 22 deletions(-) diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md index eb9201165dafd6..1c35a94f646820 100644 --- a/docs/designers-developers/developers/data/data-core-block-editor.md +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -303,6 +303,19 @@ _Returns_ - `?string`: First block client ID in the multi-selection set. +# **getFirstToSelectBlock** + +Given a block client ID, returns the next element of the hierarchy from which the block is nested which should be selected onFocus, return the block itself for root level blocks. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block from which to find first to select client ID. + +_Returns_ + +- `string`: First to select client ID + # **getGlobalBlockCount** Returns the total number of blocks, or the total number of blocks with a specific name in a post. @@ -580,6 +593,19 @@ _Returns_ - `?string`: Block Template Lock +# **getTree** + +Given a block client ID, returns the hierarchy tree of client ID. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block from which tree will be created. + +_Returns_ + +- `Array`: Hierarchy tree of client ID. + # **hasInserterItems** Determines whether there are items to show in the inserter. diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index c6b5b096f2f0cb..735aa4f8718d7e 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -118,7 +118,6 @@ class BlockListBlock extends Component { showFloatingToolbar, parentId, isFirstBlock, - parentId, isDashed, isDimmed, isGroup, @@ -156,16 +155,16 @@ class BlockListBlock extends Component { { showTitle && this.renderBlockTitle() } ); } diff --git a/packages/components/src/icon-button/index.js b/packages/components/src/icon-button/index.js index c2a4e7f831bc66..8c2e361eb2a371 100644 --- a/packages/components/src/icon-button/index.js +++ b/packages/components/src/icon-button/index.js @@ -25,14 +25,12 @@ function IconButton( props, ref ) { tooltip, shortcut, labelPosition, - fill, ...additionalProps } = props; const classes = classnames( 'components-icon-button', className, { 'has-text': children, } ); const tooltipText = tooltip || label; - const iconProps = fill ? { fill } : {}; // Should show the tooltip if... const showTooltip = ! additionalProps.disabled && ( diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js index 031f7e217455ee..178cac71125585 100644 --- a/packages/rich-text/src/component/index.native.js +++ b/packages/rich-text/src/component/index.native.js @@ -822,7 +822,7 @@ export class RichText extends Component { this.firedAfterTextChanged = false; } - const dynamicStyle = useStyle( style, styles.richTextDark ); + const dynamicStyle = getStylesFromColorScheme( style, styles.richTextDark ); const lockOnFocus = blockIsSelected || isParentSelected ? {} : { pointerEvents: 'none' }; return ( From 09359f6ba26d175942a4449502bc68ba7f84fb4a Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Tue, 8 Oct 2019 13:18:43 +0200 Subject: [PATCH 004/183] adjust style --- .../src/components/block-list/block.native.js | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 735aa4f8718d7e..124beeddc582ae 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -155,19 +155,9 @@ class BlockListBlock extends Component { { showTitle && this.renderBlockTitle() } { isValid && this.getBlockForType() } From a424c63f6d5e0f855ac709fcc340c4d342870180 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Tue, 8 Oct 2019 13:45:38 +0200 Subject: [PATCH 005/183] fix after merge --- .../src/components/block-list/block.native.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 124beeddc582ae..c27486dd223268 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -187,7 +187,6 @@ export default compose( [ getSelectedBlock, getBlockOrder, getFirstToSelectBlock, - getSelectedBlock, } = select( 'core/block-editor' ); const order = getBlockIndex( clientId, rootClientId ); const isSelected = isBlockSelected( clientId ); @@ -213,7 +212,6 @@ export default compose( [ const showFloatingToolbar = isSelected && hasRootInnerBlocks && ! isMediaText && ! isMediaTextParent; - const parentId = getBlockRootClientId( clientId ); const firstToSelect = getFirstToSelectBlock( clientId ); const selectedBlockClientId = getSelectedBlockClientId(); @@ -228,12 +226,6 @@ export default compose( [ const isGroup = hasRootInnerBlocks;//blockType ==='core/group'; const isGroupType = blockType.name === 'core/group'; - const selectedBlock = getSelectedBlock(); - const parentBlock = getBlock( parentId ); - - const isMediaText = selectedBlock && selectedBlock.name === 'core/media-text'; - const isMediaTextParent = parentBlock && parentBlock.name === 'core/media-text'; - return { icon, name: name || 'core/missing', From 2e7204bbafbd88877f30ae6592e210d067dea89f Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Tue, 8 Oct 2019 13:18:43 +0200 Subject: [PATCH 006/183] fix after merge --- .../src/components/block-list/block.native.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index c27486dd223268..8762d0f2619552 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -151,7 +151,14 @@ class BlockListBlock extends Component { accessible={ ! isSelected } accessibilityRole={ 'button' } > - + { showTitle && this.renderBlockTitle() } Date: Tue, 8 Oct 2019 09:59:32 +0200 Subject: [PATCH 007/183] playing with borders --- .../src/components/block-list/block.native.js | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 8762d0f2619552..414dc820ce85a5 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -151,20 +151,30 @@ class BlockListBlock extends Component { accessible={ ! isSelected } accessibilityRole={ 'button' } > - + isDashed && styles.dashedBorderStyle, + { borderColor } + ] + }> { showTitle && this.renderBlockTitle() } { isValid && this.getBlockForType() } From 76340c05491117ff15fdc96985c3c0a553d2d5e3 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Wed, 9 Oct 2019 10:12:02 +0200 Subject: [PATCH 008/183] working full border --- .../src/components/block-list/block.native.js | 48 +++++++++++-------- .../components/block-list/block.native.scss | 14 +++++- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 414dc820ce85a5..e8c10994c00cf5 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -151,30 +151,37 @@ class BlockListBlock extends Component { accessible={ ! isSelected } accessibilityRole={ 'button' } > - + { showTitle && this.renderBlockTitle() } { isValid && this.getBlockForType() } @@ -202,7 +209,6 @@ export default compose( [ getSelectedBlockClientId, getBlock, getSelectedBlock, - getBlockOrder, getFirstToSelectBlock, } = select( 'core/block-editor' ); const order = getBlockIndex( clientId, rootClientId ); @@ -239,8 +245,8 @@ export default compose( [ const isInnerBlock = parentId && firstToSelect !== parentId; const isChildOfSameRootBlook = rootBlockId === getBlockHierarchyRootClientId( selectedBlockClientId ); - const isNestedInnerBlock = ! isDashed && rootBlockId === getBlockRootClientId( firstToSelect ); - const isGroup = hasRootInnerBlocks;//blockType ==='core/group'; + const isNestedInnerBlock = ! isDashed && selectedBlockClientId === getBlockRootClientId( firstToSelect ); + const isGroup = hasRootInnerBlocks; const isGroupType = blockType.name === 'core/group'; return { diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index 168749a7afee1c..c18128b4659d09 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -39,11 +39,21 @@ } .selectedInnerGroup { - padding-top: 0; + margin-left: 16px; + margin-right: 16px; +} + +.noMargin { + // padding-top: 0; margin-left: 0; margin-right: 0; } +.noGroupPadding { + padding-top: 0; + padding-bottom: 0; +} + .margin { margin-left: 9px; margin-right: 9px; @@ -59,7 +69,7 @@ .padding { padding-left: 0; padding-right: 0; - padding-top: 0; + // padding-top: 0; } .dashedBorderStyle { From 3295957ee7b114a11f5354454b2316a80724259d Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Thu, 10 Oct 2019 09:27:49 +0200 Subject: [PATCH 009/183] refactor v1 --- .../src/components/block-list/block.native.js | 26 ++++++++----------- .../components/block-list/block.native.scss | 19 ++++---------- 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index e8c10994c00cf5..4a4384b9ecc1e6 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -154,9 +154,9 @@ class BlockListBlock extends Component { @@ -166,22 +166,18 @@ class BlockListBlock extends Component { style={ [ ! isSelected && ( isDashed ? styles.blockHolderDashedBordered : styles.blockContainer ), ! isSelected && isDashed && isNestedInnerBlock && styles.blockContainerInner, - ! isSelected && ! isDashed && isNestedInnerBlock && styles.noMargin, - ! isSelected && isGroup && ! parentId && styles.selectedInnerGroup, - ! isSelected && isInnerBlock && ! isChildOfSameRootBlook && ! isDashed && styles.marginInnerGroup, + ! isSelected && ! isDashed && isNestedInnerBlock && styles.horizontalMarginNone, + ! isSelected && isInnerBlock && ! isChildOfSameRootBlook && ! isDashed && styles.blockContainerInner, ! isSelected && isNestedInnerBlock && ! isDimmed && styles.blockContainerInner, - ! isSelected && isNestedInnerBlock && { paddingLeft: 0 }, - ! isSelected && isGroup && ! parentId && styles.noMargin, - ! isSelected && isGroupType && isChildOfSameRootBlook && styles.noMargin, - isDimmed && isInnerBlock && styles.selectedInnerGroup, + ! isSelected && isGroup && ! parentId && styles.horizontalMarginNone, + ! isSelected && isGroupType && isChildOfSameRootBlook && styles.horizontalMarginNone, + isDimmed && isInnerBlock && styles.marginSelectedInnerBlock, isDimmed && styles.blockContainerDimmed, isSelected && ( parentId ? styles.innerBlockContainerFocused : styles.blockContainerFocused ), - isSelected && isGroup && ! parentId && styles.padding, - isSelected && isNestedInnerBlock && styles.marginInnerGroup, - isSelected && isGroupType && styles.marginInnerGroup, - isGroupType && styles.noGroupPadding, - isGroupType && isDashed && { paddingTop: 0, paddingBottom: 0 }, - isGroupType && isNestedInnerBlock && styles.noMargin, + isSelected && isGroup && ! parentId && styles.horizontalPaddingNone, + isSelected && ( isNestedInnerBlock || isGroupType ) && styles.blockContainerInner, + ( isGroupType || ( isGroupType && isDashed ) ) && styles.verticalPaddingNone, + isGroupType && isNestedInnerBlock && styles.horizontalMarginNone, ] } > { isValid && this.getBlockForType() } diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index c18128b4659d09..8229535fa9e127 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -38,38 +38,29 @@ border-radius: 2px; } -.selectedInnerGroup { +.marginSelectedInnerBlock { margin-left: 16px; margin-right: 16px; } -.noMargin { - // padding-top: 0; +.horizontalMarginNone { margin-left: 0; margin-right: 0; } -.noGroupPadding { +.verticalPaddingNone { padding-top: 0; padding-bottom: 0; } -.margin { +.outlineBorderMargin { margin-left: 9px; margin-right: 9px; } -.marginInnerGroup { - margin-left: 0; - margin-right: 0; - padding-left: 0; - padding-right: 0; -} - -.padding { +.horizontalPaddingNone { padding-left: 0; padding-right: 0; - // padding-top: 0; } .dashedBorderStyle { From a28b17d302a9ebbbfc46ccb239b776d8177a7e89 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Thu, 10 Oct 2019 10:20:52 +0200 Subject: [PATCH 010/183] refactor v2 --- .../src/components/block-list/block.native.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 4a4384b9ecc1e6..661e864a319b43 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -120,7 +120,6 @@ class BlockListBlock extends Component { isFirstBlock, isDashed, isDimmed, - isGroup, isInnerBlock, isChildOfSameRootBlook, isNestedInnerBlock, @@ -165,19 +164,13 @@ class BlockListBlock extends Component { accessibilityLabel={ accessibilityLabel } style={ [ ! isSelected && ( isDashed ? styles.blockHolderDashedBordered : styles.blockContainer ), - ! isSelected && isDashed && isNestedInnerBlock && styles.blockContainerInner, - ! isSelected && ! isDashed && isNestedInnerBlock && styles.horizontalMarginNone, - ! isSelected && isInnerBlock && ! isChildOfSameRootBlook && ! isDashed && styles.blockContainerInner, - ! isSelected && isNestedInnerBlock && ! isDimmed && styles.blockContainerInner, - ! isSelected && isGroup && ! parentId && styles.horizontalMarginNone, - ! isSelected && isGroupType && isChildOfSameRootBlook && styles.horizontalMarginNone, + ! isSelected && ( ( isDashed || ! isDimmed ) && isNestedInnerBlock ) || ( isInnerBlock && ! isChildOfSameRootBlook && ! isDashed ) && styles.blockContainerInner, + ! isSelected && ( isGroupType && ( ! parentId || isChildOfSameRootBlook ) || isNestedInnerBlock ) && styles.horizontalMarginNone, isDimmed && isInnerBlock && styles.marginSelectedInnerBlock, isDimmed && styles.blockContainerDimmed, - isSelected && ( parentId ? styles.innerBlockContainerFocused : styles.blockContainerFocused ), - isSelected && isGroup && ! parentId && styles.horizontalPaddingNone, + isSelected && ( isInnerBlock ? styles.innerBlockContainerFocused : styles.blockContainerFocused ), isSelected && ( isNestedInnerBlock || isGroupType ) && styles.blockContainerInner, ( isGroupType || ( isGroupType && isDashed ) ) && styles.verticalPaddingNone, - isGroupType && isNestedInnerBlock && styles.horizontalMarginNone, ] } > { isValid && this.getBlockForType() } @@ -242,7 +235,6 @@ export default compose( [ const isInnerBlock = parentId && firstToSelect !== parentId; const isChildOfSameRootBlook = rootBlockId === getBlockHierarchyRootClientId( selectedBlockClientId ); const isNestedInnerBlock = ! isDashed && selectedBlockClientId === getBlockRootClientId( firstToSelect ); - const isGroup = hasRootInnerBlocks; const isGroupType = blockType.name === 'core/group'; return { @@ -262,7 +254,6 @@ export default compose( [ isDashed, isDimmed, firstToSelect, - isGroup, isInnerBlock, isChildOfSameRootBlook, isNestedInnerBlock, From 5326d44eb8d828ce93520d6bb7de5b284ee3ee71 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Thu, 10 Oct 2019 10:46:11 +0200 Subject: [PATCH 011/183] fixed dashed border android --- .../src/components/block-list/block.native.scss | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index 8229535fa9e127..561ffd94cee075 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -21,10 +21,8 @@ } .blockHolderDashedBordered { - border-top-width: 1px; - border-bottom-width: 1px; - border-left-width: 1px; - border-right-width: 1px; + border-width: 1px; + border-radius: 2px; border-style: dashed; border-color: $gray; padding-left: 6px; @@ -35,7 +33,6 @@ margin-left: 0; margin-bottom: 5px; margin-top: 5px; - border-radius: 2px; } .marginSelectedInnerBlock { From c70697c7f8d571aab995a187d369875a739db971 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Thu, 10 Oct 2019 12:18:43 +0200 Subject: [PATCH 012/183] refactor v3 --- .../src/components/block-list/block.native.js | 4 ++-- .../src/components/block-list/block.native.scss | 17 +++++++---------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 661e864a319b43..c9784411e0c763 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -154,7 +154,7 @@ class BlockListBlock extends Component { styles.blockHolder, borderStyle, isSelected && ( isGroupType || isInnerBlock || isNestedInnerBlock ) && styles.outlineBorderMargin, - isDashed && styles.dashedBorderStyle, + isDashed && styles.dashedBorderMargin, isGroupType && styles.verticalPaddingNone, { borderColor }, ] @@ -170,7 +170,7 @@ class BlockListBlock extends Component { isDimmed && styles.blockContainerDimmed, isSelected && ( isInnerBlock ? styles.innerBlockContainerFocused : styles.blockContainerFocused ), isSelected && ( isNestedInnerBlock || isGroupType ) && styles.blockContainerInner, - ( isGroupType || ( isGroupType && isDashed ) ) && styles.verticalPaddingNone, + isGroupType && styles.verticalPaddingNone, ] } > { isValid && this.getBlockForType() } diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index 561ffd94cee075..5e076e49e9c09e 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -25,10 +25,7 @@ border-radius: 2px; border-style: dashed; border-color: $gray; - padding-left: 6px; - padding-right: 6px; - padding-top: 6px; - padding-bottom: 6px; + padding: 6px; margin-right: 0; margin-left: 0; margin-bottom: 5px; @@ -50,17 +47,17 @@ padding-bottom: 0; } -.outlineBorderMargin { - margin-left: 9px; - margin-right: 9px; -} - .horizontalPaddingNone { padding-left: 0; padding-right: 0; } -.dashedBorderStyle { +.outlineBorderMargin { + margin-left: 9px; + margin-right: 9px; +} + +.dashedBorderMargin { margin-left: 5px; margin-right: 5px; } From eb7bed064b6690d3df033f5f7ce2d5b3f21491bc Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Thu, 10 Oct 2019 12:45:54 +0200 Subject: [PATCH 013/183] adjust button-block-appender margin --- .../src/components/button-block-appender/styles.native.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/block-editor/src/components/button-block-appender/styles.native.scss b/packages/block-editor/src/components/button-block-appender/styles.native.scss index 3a6549980af87d..46b4a055cd5bd4 100644 --- a/packages/block-editor/src/components/button-block-appender/styles.native.scss +++ b/packages/block-editor/src/components/button-block-appender/styles.native.scss @@ -2,6 +2,8 @@ align-items: center; justify-content: center; padding: 12px; + margin-left: 2px; + margin-right: 2px; background-color: $white; border: $border-width solid $light-gray-500; border-radius: 4px; From 2fdedb7078a7272d7421ae32f382896aa683f565 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Thu, 10 Oct 2019 13:07:07 +0200 Subject: [PATCH 014/183] refactor v4 --- .../block-editor/src/components/block-list/block.native.js | 1 - .../block-editor/src/components/block-list/block.native.scss | 5 ----- 2 files changed, 6 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index c9784411e0c763..9ab56f4e836db2 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -166,7 +166,6 @@ class BlockListBlock extends Component { ! isSelected && ( isDashed ? styles.blockHolderDashedBordered : styles.blockContainer ), ! isSelected && ( ( isDashed || ! isDimmed ) && isNestedInnerBlock ) || ( isInnerBlock && ! isChildOfSameRootBlook && ! isDashed ) && styles.blockContainerInner, ! isSelected && ( isGroupType && ( ! parentId || isChildOfSameRootBlook ) || isNestedInnerBlock ) && styles.horizontalMarginNone, - isDimmed && isInnerBlock && styles.marginSelectedInnerBlock, isDimmed && styles.blockContainerDimmed, isSelected && ( isInnerBlock ? styles.innerBlockContainerFocused : styles.blockContainerFocused ), isSelected && ( isNestedInnerBlock || isGroupType ) && styles.blockContainerInner, diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index 5e076e49e9c09e..e2b261faa828c8 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -32,11 +32,6 @@ margin-top: 5px; } -.marginSelectedInnerBlock { - margin-left: 16px; - margin-right: 16px; -} - .horizontalMarginNone { margin-left: 0; margin-right: 0; From d602fff8d6cd12821489fd42736613224b9e052c Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Thu, 10 Oct 2019 15:01:02 +0200 Subject: [PATCH 015/183] reefactor v5 --- .../src/components/block-list/block.native.js | 46 ++++++++++++++++--- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 9ab56f4e836db2..ce0f6fb62ca39e 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -121,7 +121,6 @@ class BlockListBlock extends Component { isDashed, isDimmed, isInnerBlock, - isChildOfSameRootBlook, isNestedInnerBlock, isGroupType, } = this.props; @@ -163,12 +162,9 @@ class BlockListBlock extends Component { @@ -185,6 +181,44 @@ class BlockListBlock extends Component { } } +function applySelectedStyle( { isInnerBlock, isNestedInnerBlock, isGroupType } ) { + let selectedStyle = [ isInnerBlock ? styles.innerBlockContainerFocused : styles.blockContainerFocused ]; + + if ( isNestedInnerBlock || isGroupType ) { + selectedStyle = [ ...selectedStyle, styles.blockContainerInner ]; + } + + return selectedStyle; +} + +function applyUnSelectedStyle( { isDashed, isDimmed, isNestedInnerBlock, isInnerBlock, isChildOfSameRootBlook, isGroupType, parentId } ) { + let unSelectedStyle = [ styles.blockContainer ]; + + if ( isNestedInnerBlock ) { + if ( ! isDimmed ) { + unSelectedStyle = [ ...unSelectedStyle, styles.blockContainerInner ]; + } + unSelectedStyle = [ ...unSelectedStyle, styles.horizontalMarginNone ]; + } + + if ( isDashed ) { + unSelectedStyle = [ ...unSelectedStyle, styles.blockHolderDashedBordered ]; + if ( isNestedInnerBlock ) { + unSelectedStyle = [ ...unSelectedStyle, styles.blockContainerInner ]; + } + } else if ( isInnerBlock && ! isChildOfSameRootBlook ) { + unSelectedStyle = [ ...unSelectedStyle, styles.blockContainerInner ]; + } + + if ( isGroupType ) { + if ( ! parentId || isChildOfSameRootBlook ) { + unSelectedStyle = [ ...unSelectedStyle, styles.horizontalMarginNone ]; + } + } + + return unSelectedStyle; +} + export default compose( [ withSelect( ( select, { clientId, rootClientId } ) => { const { From 497dcbc8a66d82441d6b2d16c42803912ab6ed07 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Thu, 10 Oct 2019 23:03:35 +0200 Subject: [PATCH 016/183] refactor v6 --- .../src/components/block-list/block.native.js | 99 +++++++++++-------- .../components/block-list/block.native.scss | 24 ++++- 2 files changed, 79 insertions(+), 44 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index ce0f6fb62ca39e..43b914f604b5c7 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -105,6 +105,61 @@ class BlockListBlock extends Component { return blockName; } + applySelectedStyle() { + const { + isInnerBlock, + isNestedInnerBlock, + isGroupType, + } = this.props; + + if ( isNestedInnerBlock || isGroupType ) { + return styles.nestedFocusedBlock; + } + + if ( isInnerBlock ) { + return styles.innerBlockContainerFocused; + } + return styles.blockContainerFocused; + } + + applyUnSelectedStyle() { + const { + isDashed, + isDimmed, + isNestedInnerBlock, + isInnerBlock, + isChildOfSameRootBlook, + isGroupType, + parentId, + } = this.props; + + if ( ! isDashed && isInnerBlock && ! isChildOfSameRootBlook ) { + return styles.blockContainerInner; + } + + const dashedStyle = [ styles.blockHolderDashedBordered ]; + let defaultStyle = [ styles.blockContainer ]; + + if ( isDashed ) { + defaultStyle = dashedStyle; + } + + if ( isNestedInnerBlock ) { + if ( ! isDimmed || isDashed ) { + return [ ...defaultStyle, styles.blockContainerInner ]; + } + return [ ...defaultStyle, styles.nestedBlockContainerInner ]; + } + + if ( isGroupType ) { + if ( ! parentId || isChildOfSameRootBlook ) { + return [ ...defaultStyle, styles.horizontalMarginNone ]; + } + } + + return defaultStyle; + } + render() { const { borderStyle, @@ -162,8 +217,8 @@ class BlockListBlock extends Component { { const { @@ -227,9 +244,9 @@ export default compose( [ isBlockSelected, __unstableGetBlockWithoutInnerBlocks, getBlockHierarchyRootClientId, - getBlockRootClientId, getSelectedBlockClientId, getBlock, + getBlockRootClientId, getSelectedBlock, getFirstToSelectBlock, } = select( 'core/block-editor' ); diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index e2b261faa828c8..b62e82effcb92f 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -14,6 +14,15 @@ margin-right: 0; padding-left: 0; padding-right: 0; + padding-top: 12px; + padding-bottom: 12px; +} + +.nestedBlockContainerInner { + margin-left: 0; + margin-right: 0; + padding-top: 9px; + padding-bottom: 9px; } .blockContainerDimmed { @@ -25,11 +34,11 @@ border-radius: 2px; border-style: dashed; border-color: $gray; - padding: 6px; + padding: 5px; margin-right: 0; margin-left: 0; - margin-bottom: 5px; - margin-top: 5px; + margin-bottom: 6px; + margin-top: 6px; } .horizontalMarginNone { @@ -57,6 +66,15 @@ margin-right: 5px; } +.nestedFocusedBlock { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + padding-top: 12px; + padding-bottom: 0; // will be flushed into inline toolbar height +} + .innerBlockContainerFocused { padding-left: 12px; padding-right: 12px; From ca22502fb4a6012ef1bf35e51ee94cdb50a67eff Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Fri, 11 Oct 2019 07:14:05 +0200 Subject: [PATCH 017/183] adjust for media text --- packages/block-editor/src/components/block-list/block.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 43b914f604b5c7..67d5e38a86fa22 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -279,7 +279,7 @@ export default compose( [ const selectedBlockClientId = getSelectedBlockClientId(); const isRootSiblingsSelected = getBlockRootClientId( selectedBlockClientId ) === ''; - const isDashed = selectedBlockClientId === parentId; + const isDashed = selectedBlockClientId === parentId && ! isMediaTextParent; const isDimmed = ! isSelected && ! isRootSiblingsSelected && !! selectedBlockClientId && firstToSelect === clientId && ! isDashed; const isInnerBlock = parentId && firstToSelect !== parentId; From dd06cbae30ee9bca2801994f8b8f0efe3372492a Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Fri, 11 Oct 2019 07:46:09 +0200 Subject: [PATCH 018/183] adjust focus for media text --- .../src/components/block-list/block.native.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 67d5e38a86fa22..1baf6361ebd49f 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -40,9 +40,11 @@ class BlockListBlock extends Component { } onFocus() { - const { firstToSelect, isSelected, onSelect } = this.props; + const { firstToSelect, isSelected, onSelect, isParentSelected, isMediaText } = this.props; if ( ! isSelected ) { - onSelect( firstToSelect ); + if ( ! isMediaText || ! isParentSelected ) { + onSelect( firstToSelect ); + } } } @@ -286,6 +288,7 @@ export default compose( [ const isChildOfSameRootBlook = rootBlockId === getBlockHierarchyRootClientId( selectedBlockClientId ); const isNestedInnerBlock = ! isDashed && selectedBlockClientId === getBlockRootClientId( firstToSelect ); const isGroupType = blockType.name === 'core/group'; + const isParentSelected = parentId === selectedBlockClientId; return { icon, @@ -308,6 +311,8 @@ export default compose( [ isChildOfSameRootBlook, isNestedInnerBlock, isGroupType, + isParentSelected, + isMediaText, }; } ), withDispatch( ( dispatch, ownProps, { select } ) => { From ec9781558cd4dc8bca325aecff933ce55ac690c1 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Fri, 11 Oct 2019 08:01:53 +0200 Subject: [PATCH 019/183] refactor apply styles --- .../src/components/block-list/block.native.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 1baf6361ebd49f..cd0896a9516fae 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -121,6 +121,7 @@ class BlockListBlock extends Component { if ( isInnerBlock ) { return styles.innerBlockContainerFocused; } + return styles.blockContainerFocused; } @@ -139,12 +140,7 @@ class BlockListBlock extends Component { return styles.blockContainerInner; } - const dashedStyle = [ styles.blockHolderDashedBordered ]; - let defaultStyle = [ styles.blockContainer ]; - - if ( isDashed ) { - defaultStyle = dashedStyle; - } + const defaultStyle = [ isDashed ? styles.blockHolderDashedBordered : styles.blockContainer ]; if ( isNestedInnerBlock ) { if ( ! isDimmed || isDashed ) { @@ -153,10 +149,8 @@ class BlockListBlock extends Component { return [ ...defaultStyle, styles.nestedBlockContainerInner ]; } - if ( isGroupType ) { - if ( ! parentId || isChildOfSameRootBlook ) { - return [ ...defaultStyle, styles.horizontalMarginNone ]; - } + if ( isGroupType && ( ! parentId || isChildOfSameRootBlook ) ) { + return [ ...defaultStyle, styles.horizontalMarginNone ]; } return defaultStyle; From 8b02aae5f8a799e640b28ea087ae13531c3b6fb3 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Fri, 11 Oct 2019 08:26:31 +0200 Subject: [PATCH 020/183] adjust media text media container --- packages/block-library/src/media-text/media-container.native.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-library/src/media-text/media-container.native.js b/packages/block-library/src/media-text/media-container.native.js index a647247c09bdc6..55417a29d10fd1 100644 --- a/packages/block-library/src/media-text/media-container.native.js +++ b/packages/block-library/src/media-text/media-container.native.js @@ -262,6 +262,7 @@ class MediaContainer extends Component { allowedTypes={ ALLOWED_MEDIA_TYPES } onFocus={ this.props.onFocus } onError={ this.onUploadError } + isBlockSelected={ this.props.isSelected } /> ); } From 4c1e1b9fb36715e8d4d594972841c3f9c195e7f3 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Fri, 11 Oct 2019 08:48:08 +0200 Subject: [PATCH 021/183] pass isParentSelected prop to BlockEdit --- packages/block-editor/src/components/block-list/block.native.js | 1 + packages/block-library/src/paragraph/edit.native.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index cd0896a9516fae..484d76be16131c 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -62,6 +62,7 @@ class BlockListBlock extends Component { onReplace( [] ) : undefined } placeholder={ placeholder || __( 'Start writing…' ) } + isParentSelected={ this.props.isParentSelected } /> ); From e5976c036a148ca5ab2e0dbb4bed661d55cd4ddc Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Fri, 11 Oct 2019 09:47:54 +0200 Subject: [PATCH 022/183] adjust dim style --- .../src/components/block-list/style.native.scss | 4 ---- .../editor/src/components/post-title/index.native.js | 10 +++++----- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/components/block-list/style.native.scss b/packages/block-editor/src/components/block-list/style.native.scss index e66e8cf5a5ba7c..541a35d42a19a3 100644 --- a/packages/block-editor/src/components/block-list/style.native.scss +++ b/packages/block-editor/src/components/block-list/style.native.scss @@ -79,10 +79,6 @@ border-color: $gray-70; } -.blockHolderFocusedDark { - border-color: $gray-70; -} - .blockListFooter { height: 80px; } diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index 6b36b0c2fe7fce..6b8192a4babcbc 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -105,15 +105,15 @@ export default compose( isPostTitleSelected, } = select( 'core/editor' ); - const { getSelectedBlockClientId, getBlockHierarchyRootClientId, getBlock } = select( 'core/block-editor' ); + const { getSelectedBlockClientId, getBlockRootClientId, getFirstToSelectBlock } = select( 'core/block-editor' ); const clientId = getSelectedBlockClientId(); const isAnyBlockSelected = !! clientId; - const rootBlockId = getBlockHierarchyRootClientId( clientId ); - const rootBlock = getBlock( rootBlockId ); - const hasRootInnerBlocks = rootBlock && rootBlock.innerBlocks.length !== 0; + const parentId = getBlockRootClientId( clientId ); + const firstToSelect = getFirstToSelectBlock( clientId ); + const isInnerBlock = parentId && firstToSelect !== parentId; - const isDimmed = isAnyBlockSelected && hasRootInnerBlocks; + const isDimmed = isAnyBlockSelected && isInnerBlock; return { isAnyBlockSelected, From b54fa568e76f243c53aab06a05e05359fb6006c7 Mon Sep 17 00:00:00 2001 From: lukewalczak Date: Tue, 15 Oct 2019 15:21:26 +0200 Subject: [PATCH 023/183] Move FloatingToolbar logic to get shippable temporary version --- .../block-mobile-floating-toolbar.native.js | 30 ++++--------------- .../block-mobile-floating-toolbar.native.scss | 6 ++-- .../src/components/block-list/block.native.js | 4 --- .../src/components/block-list/index.native.js | 18 ++--------- 4 files changed, 10 insertions(+), 48 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.js b/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.js index cdd54ffdcaca1b..0171af7db9a8cc 100644 --- a/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.js +++ b/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.js @@ -7,34 +7,16 @@ import { View, TouchableWithoutFeedback } from 'react-native'; * Internal dependencies */ import styles from './block-mobile-floating-toolbar.scss'; -/** - * WordPress dependencies - */ -import { createSlotFill } from '@wordpress/components'; - -const { Fill, Slot } = createSlotFill( 'FloatingToolbar' ); function FloatingToolbar( { children } ) { return ( - - { ( { innerFloatingToolbar } ) => { - return ( - - { children } - - - ); - } } - - + + { children } + + ); } -FloatingToolbar.Slot = Slot; - export default FloatingToolbar; diff --git a/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.scss b/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.scss index 713633516fca23..ad71fd5bae08d3 100644 --- a/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.scss +++ b/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.scss @@ -1,15 +1,13 @@ -.floatingToolbarFill { +.floatingToolbar { background-color: $dark-gray-500; margin: auto; min-width: 100; max-height: $floating-toolbar-height; border-radius: 22px; - flex-direction: row; z-index: 100; - top: -$floating-toolbar-height; height: $floating-toolbar-height; - position: absolute; align-items: center; justify-content: center; align-self: center; + margin-bottom: 8px; } diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 484d76be16131c..42202bc4a44f4e 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -169,7 +169,6 @@ class BlockListBlock extends Component { title, showFloatingToolbar, parentId, - isFirstBlock, isDashed, isDimmed, isInnerBlock, @@ -183,7 +182,6 @@ class BlockListBlock extends Component { return ( <> - { showFloatingToolbar && ( ! isFirstBlock || parentId === '' ) && } { showFloatingToolbar && ( @@ -249,7 +247,6 @@ export default compose( [ } = select( 'core/block-editor' ); const order = getBlockIndex( clientId, rootClientId ); const isSelected = isBlockSelected( clientId ); - const isFirstBlock = order === 0; const isLastBlock = order === getBlocks().length - 1; const block = __unstableGetBlockWithoutInnerBlocks( clientId ); const { name, attributes, isValid } = block || {}; @@ -292,7 +289,6 @@ export default compose( [ title, attributes, blockType, - isFirstBlock, isLastBlock, isSelected, isValid, diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index b8d784cf82267a..a804b2d223e9bc 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -20,7 +20,6 @@ import { KeyboardAwareFlatList, ReadableContentView } from '@wordpress/component import styles from './style.scss'; import BlockListBlock from './block'; import BlockListAppender from '../block-list-appender'; -import FloatingToolbar from './block-mobile-floating-toolbar'; const innerToolbarHeight = 44; @@ -43,7 +42,7 @@ export class BlockList extends Component { } blockHolderBorderStyle() { - return this.props.isFullyBordered || this.props.hasFullBorder ? styles.blockHolderFullBordered : styles.blockHolderSemiBordered; + return this.props.isFullyBordered ? styles.blockHolderFullBordered : styles.blockHolderSemiBordered; } onCaretVerticalPositionChange( targetId, caretY, previousCaretY ) { @@ -70,15 +69,13 @@ export class BlockList extends Component { } render() { - const { clearSelectedBlock, blockClientIds, isFullyBordered, title, header, withFooter = true, renderAppender, isFirstBlock, selectedBlockParentId } = this.props; + const { clearSelectedBlock, blockClientIds, isFullyBordered, title, header, withFooter = true, renderAppender } = this.props; - const showFloatingToolbar = isFirstBlock && selectedBlockParentId !== ''; return ( - { showFloatingToolbar && } { const shouldHideBlockAtIndex = ( ! isSelectedGroup && blockInsertionPointIsVisible && @@ -206,8 +198,6 @@ export default compose( [ return ! shouldHideBlockAtIndex; }; - const hasFullBorder = !! getBlockRootClientId( selectedBlockClientId ) || isSelectedGroup; - return { blockClientIds, blockCount: getBlockCount( rootClientId ), @@ -215,10 +205,6 @@ export default compose( [ shouldShowBlockAtIndex, shouldShowInsertionPoint, selectedBlockClientId, - isFirstBlock, - rootClientId, - selectedBlockParentId, - hasFullBorder, }; } ), withDispatch( ( dispatch ) => { From a6dcf700b4b1a90f6083a288752ddbd3fe0e672e Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Wed, 16 Oct 2019 09:33:32 +0200 Subject: [PATCH 024/183] fix after merge floating-toolbr --- .../block-editor/src/components/block-list/index.native.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index a804b2d223e9bc..c5f6ed0c163d09 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -42,7 +42,7 @@ export class BlockList extends Component { } blockHolderBorderStyle() { - return this.props.isFullyBordered ? styles.blockHolderFullBordered : styles.blockHolderSemiBordered; + return this.props.isFullyBordered || this.props.hasFullBorder ? styles.blockHolderFullBordered : styles.blockHolderSemiBordered; } onCaretVerticalPositionChange( targetId, caretY, previousCaretY ) { @@ -169,6 +169,7 @@ export default compose( [ getBlockInsertionPoint, isBlockInsertionPointVisible, getSelectedBlock, + getBlockRootClientId, } = select( 'core/block-editor' ); const selectedBlockClientId = getSelectedBlockClientId(); @@ -198,6 +199,8 @@ export default compose( [ return ! shouldHideBlockAtIndex; }; + const hasFullBorder = !! getBlockRootClientId( selectedBlockClientId ) || isSelectedGroup; + return { blockClientIds, blockCount: getBlockCount( rootClientId ), @@ -205,6 +208,7 @@ export default compose( [ shouldShowBlockAtIndex, shouldShowInsertionPoint, selectedBlockClientId, + hasFullBorder, }; } ), withDispatch( ( dispatch ) => { From 63b163f5fd6beebca2af4cc72fca86adf343142f Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Tue, 15 Oct 2019 14:01:59 +0200 Subject: [PATCH 025/183] style media-text and fix jumping behaviour --- .../src/components/block-list/block.native.js | 32 +++++++++++-------- .../components/block-list/block.native.scss | 11 +++++++ .../src/components/block-list/index.native.js | 2 +- .../block-library/src/heading/edit.native.js | 2 ++ .../src/media-text/edit.native.js | 20 +++++++++--- .../src/media-text/media-container.js | 3 +- .../src/paragraph/edit.native.js | 2 +- .../rich-text/src/component/index.native.js | 10 ++++-- 8 files changed, 59 insertions(+), 23 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 42202bc4a44f4e..cd8f5af9cc6451 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -40,11 +40,9 @@ class BlockListBlock extends Component { } onFocus() { - const { firstToSelect, isSelected, onSelect, isParentSelected, isMediaText } = this.props; + const { firstToSelectId, isSelected, onSelect } = this.props; if ( ! isSelected ) { - if ( ! isMediaText || ! isParentSelected ) { - onSelect( firstToSelect ); - } + onSelect( firstToSelectId ); } } @@ -71,6 +69,7 @@ class BlockListBlock extends Component { mergeBlocks={ this.props.mergeBlocks } onCaretVerticalPositionChange={ this.props.onCaretVerticalPositionChange } clientId={ this.props.clientId } + isInnerBlock={ this.props.isInnerBlock } /> ); } @@ -135,6 +134,7 @@ class BlockListBlock extends Component { isChildOfSameRootBlook, isGroupType, parentId, + isMediaTextParent, } = this.props; if ( ! isDashed && isInnerBlock && ! isChildOfSameRootBlook ) { @@ -143,6 +143,10 @@ class BlockListBlock extends Component { const defaultStyle = [ isDashed ? styles.blockHolderDashedBordered : styles.blockContainer ]; + if ( isMediaTextParent ) { + return [ ...defaultStyle, styles.blockContainerMediaTextInner ]; + } + if ( isNestedInnerBlock ) { if ( ! isDimmed || isDashed ) { return [ ...defaultStyle, styles.blockContainerInner ]; @@ -174,6 +178,7 @@ class BlockListBlock extends Component { isInnerBlock, isNestedInnerBlock, isGroupType, + isMediaTextParent, } = this.props; const borderColor = isSelected ? focusedBorderColor : 'transparent'; @@ -202,8 +207,8 @@ class BlockListBlock extends Component { { return ( blockInsertionPointIsVisible && diff --git a/packages/block-library/src/heading/edit.native.js b/packages/block-library/src/heading/edit.native.js index ac8a0e755278cd..29983197843ac1 100644 --- a/packages/block-library/src/heading/edit.native.js +++ b/packages/block-library/src/heading/edit.native.js @@ -23,6 +23,7 @@ const HeadingEdit = ( { onReplace, setAttributes, style, + isInnerBlock, } ) => ( @@ -57,6 +58,7 @@ const HeadingEdit = ( { onReplace={ onReplace } onRemove={ () => onReplace( [] ) } placeholder={ attributes.placeholder || __( 'Write heading…' ) } + isInnerBlock={ isInnerBlock } /> ); diff --git a/packages/block-library/src/media-text/edit.native.js b/packages/block-library/src/media-text/edit.native.js index a4b2eaab44628b..672a15456e38bf 100644 --- a/packages/block-library/src/media-text/edit.native.js +++ b/packages/block-library/src/media-text/edit.native.js @@ -133,6 +133,7 @@ class MediaTextEdit extends Component { backgroundColor, setAttributes, isMobile, + isSelected, } = this.props; const { isStackedOnMobile, @@ -150,6 +151,7 @@ class MediaTextEdit extends Component { ...( shouldStack ? styles[ 'is-stacked-on-mobile' ] : {} ), ...( shouldStack && mediaPosition === 'right' ? styles[ 'is-stacked-on-mobile.has-media-on-the-right' ] : {} ), backgroundColor: backgroundColor.color, + ...{ padding: isSelected ? 8 : 16 }, }; const innerBlockWidth = shouldStack ? 100 : ( 100 - temporaryMediaWidth ); const innerBlockWidthString = `${ innerBlockWidth }%`; @@ -187,11 +189,19 @@ class MediaTextEdit extends Component { { this.renderMediaArea() } - + + + diff --git a/packages/block-library/src/media-text/media-container.js b/packages/block-library/src/media-text/media-container.js index fce35487d6a03b..cb45eb27830047 100644 --- a/packages/block-library/src/media-text/media-container.js +++ b/packages/block-library/src/media-text/media-container.js @@ -93,7 +93,7 @@ class MediaContainer extends Component { } renderPlaceholder() { - const { onSelectMedia, className, noticeUI } = this.props; + const { onSelectMedia, className, noticeUI, style } = this.props; return ( } @@ -106,6 +106,7 @@ class MediaContainer extends Component { allowedTypes={ ALLOWED_MEDIA_TYPES } notices={ noticeUI } onError={ this.onUploadError } + style={ style } /> ); } diff --git a/packages/block-library/src/paragraph/edit.native.js b/packages/block-library/src/paragraph/edit.native.js index 46d21283531a80..ac3862ab5ac1c1 100644 --- a/packages/block-library/src/paragraph/edit.native.js +++ b/packages/block-library/src/paragraph/edit.native.js @@ -78,7 +78,7 @@ class ParagraphEdit extends Component { onReplace={ onReplace } onRemove={ onReplace ? () => onReplace( [] ) : undefined } placeholder={ placeholder || __( 'Start writing…' ) } - isParentSelected={ this.props.isParentSelected } + isInnerBlock={ this.props.isInnerBlock } /> ); diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js index 1633b1acad87a5..593d17ab8fc40a 100644 --- a/packages/rich-text/src/component/index.native.js +++ b/packages/rich-text/src/component/index.native.js @@ -111,6 +111,7 @@ export class RichText extends Component { activeFormats: [], selectedFormat: null, height: 0, + isActive: ! this.props.isInnerBlock, }; this.needsSelectionUpdate = false; this.savedContent = ''; @@ -706,6 +707,7 @@ export class RichText extends Component { // to determine if we should focus the RichText. if ( this.props.blockIsSelected && ! this.props.__unstableMobileNoFocusOnMount ) { this._editor.focus(); + this.setState( { isActive: true } ); this.onSelectionChange( this.props.selectionStart || 0, this.props.selectionEnd || 0 ); } } @@ -713,6 +715,7 @@ export class RichText extends Component { componentWillUnmount() { if ( this._editor.isFocused() && this.props.shouldBlurOnUnmount ) { this._editor.blur(); + this.setState( { isActive: false } ); } } @@ -731,11 +734,15 @@ export class RichText extends Component { if ( isSelected && ! prevIsSelected ) { this._editor.focus(); + this.setState( { isActive: true } ); // Update selection props explicitly when component is selected as Aztec won't call onSelectionChange // if its internal value hasn't change. When created, default value is 0, 0 this.onSelectionChange( this.props.selectionStart || 0, this.props.selectionEnd || 0 ); } else if ( ! isSelected && prevIsSelected ) { this._editor.blur(); + if ( this.props.isInnerBlock ) { + this.setState( { isActive: false } ); + } } } @@ -778,7 +785,6 @@ export class RichText extends Component { children, getStylesFromColorScheme, blockIsSelected, - isParentSelected, } = this.props; const record = this.getRecord(); @@ -828,7 +834,7 @@ export class RichText extends Component { this.firedAfterTextChanged = false; } - const lockOnFocus = blockIsSelected || isParentSelected ? {} : { pointerEvents: 'none' }; + const lockOnFocus = blockIsSelected || this.state.isActive ? {} : { pointerEvents: 'none' }; return ( From fb9faffb9c966d1a298a5703819524db31bac95b Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Thu, 31 Oct 2019 14:11:45 +0100 Subject: [PATCH 026/183] initial changes --- .../block-library/src/columns/edit.native.js | 296 ++++++++++++++++++ .../src/columns/editor.native.scss | 6 + packages/block-library/src/columns/icon.js | 6 +- packages/block-library/src/index.native.js | 2 + 4 files changed, 307 insertions(+), 3 deletions(-) create mode 100644 packages/block-library/src/columns/edit.native.js create mode 100644 packages/block-library/src/columns/editor.native.scss diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js new file mode 100644 index 00000000000000..9ce12ed5a7cf11 --- /dev/null +++ b/packages/block-library/src/columns/edit.native.js @@ -0,0 +1,296 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { View } from 'react-native'; +import { dropRight, times } from 'lodash'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + PanelBody, + // RangeControl, + BottomSheet, + SVG, + Path, + Toolbar, + ToolbarButton, +} from '@wordpress/components'; +import { + InspectorControls, + InnerBlocks, + BlockControls, + BlockVerticalAlignmentToolbar, +} from '@wordpress/block-editor'; +import { withDispatch, useSelect } from '@wordpress/data'; +import { createBlock } from '@wordpress/blocks'; +import { useState, useEffect } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { + getColumnsTemplate, + hasExplicitColumnWidths, + getMappedColumnWidths, + getRedistributedColumnWidths, + toWidthPrecision, +} from './utils'; +import Icon from './icon'; +import styles from './editor.scss'; + +/** + * Allowed blocks constant is passed to InnerBlocks precisely as specified here. + * The contents of the array should never change. + * The array should contain the name of each block that is allowed. + * In columns block, the only block we allow is 'core/column'. + * + * @constant + * @type {string[]} + */ +const ALLOWED_BLOCKS = [ 'core/column' ]; + +/** + * Template option choices for predefined columns layouts. + * + * @constant + * @type {Array} + */ +const TEMPLATE_OPTIONS = [ + { + title: __( 'Two columns; equal split' ), + icon: , + template: [ + [ 'core/column' ], + [ 'core/column' ], + ], + }, + { + title: __( 'Two columns; one-third, two-thirds split' ), + icon: , + template: [ + [ 'core/column', { width: 33.33 } ], + [ 'core/column', { width: 66.66 } ], + ], + }, + { + title: __( 'Two columns; two-thirds, one-third split' ), + icon: , + template: [ + [ 'core/column', { width: 66.66 } ], + [ 'core/column', { width: 33.33 } ], + ], + }, + { + title: __( 'Three columns; equal split' ), + icon: , + template: [ + [ 'core/column' ], + [ 'core/column' ], + [ 'core/column' ], + ], + }, + { + title: __( 'Three columns; wide center column' ), + icon: , + template: [ + [ 'core/column', { width: 25 } ], + [ 'core/column', { width: 50 } ], + [ 'core/column', { width: 25 } ], + ], + }, +]; + +/** + * Number of columns to assume for template in case the user opts to skip + * template option selection. + * + * @type {number} + */ +const DEFAULT_COLUMNS = 2; + +export function ColumnsEdit( { + attributes, + className, + updateAlignment, + updateColumns, + clientId, + isSelected, +} ) { + const { verticalAlignment } = attributes; + + const { count } = useSelect( ( select ) => { + return { + count: select( 'core/block-editor' ).getBlockCount( clientId ), + }; + } ); + const [ template, setTemplate ] = useState( getColumnsTemplate( count ) ); + const [ forceUseTemplate, setForceUseTemplate ] = useState( false ); + + // This is used to force the usage of the template even if the count doesn't match the template + // The count doesn't match the template once you use undo/redo (this is used to reset to the placeholder state). + useEffect( () => { + // Once the template is applied, reset it. + if ( forceUseTemplate ) { + setForceUseTemplate( false ); + } + }, [ forceUseTemplate ] ); + + const classes = classnames( className, { + [ `are-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, + } ); + + // The template selector is shown when we first insert the columns block (count === 0). + // or if there's no template available. + // The count === 0 trick is useful when you use undo/redo. + const showTemplateSelector = ( count === 0 && ! forceUseTemplate ) || ! template; +console.log(isSelected) + + if ( ! isSelected ) { + return ( + + ); + } +console.log(count) + return ( + <> + {/* { ! showTemplateSelector && ( */} + <> + + + {/* { + console.log(value) + updateColumns( count, value ) } + } + min={ 2 } + max={ 6 } + icon={ 'admin-links' } + /> + + + + + } + onClick={ () => console.log('click') } + /> + + + + + {/* ) } */} + {/* */} + { + // if ( nextTemplate === undefined ) { + // nextTemplate = getColumnsTemplate( DEFAULT_COLUMNS ); + // } + + // setTemplate( nextTemplate ); + // setForceUseTemplate( true ); + // } } + // __experimentalAllowTemplateOptionSkip + // template={ showTemplateSelector ? null : template } + // templateLock="all" + allowedBlocks={ ALLOWED_BLOCKS } /> + {/* */} + + ); +} + +export default withDispatch( ( dispatch, ownProps, registry ) => ( { + /** + * Update all child Column blocks with a new vertical alignment setting + * based on whatever alignment is passed in. This allows change to parent + * to overide anything set on a individual column basis. + * + * @param {string} verticalAlignment the vertical alignment setting + */ + updateAlignment( verticalAlignment ) { + const { clientId, setAttributes } = ownProps; + const { updateBlockAttributes } = dispatch( 'core/block-editor' ); + const { getBlockOrder } = registry.select( 'core/block-editor' ); + + // Update own alignment. + setAttributes( { verticalAlignment } ); + + // Update all child Column Blocks to match + const innerBlockClientIds = getBlockOrder( clientId ); + innerBlockClientIds.forEach( ( innerBlockClientId ) => { + updateBlockAttributes( innerBlockClientId, { + verticalAlignment, + } ); + } ); + }, + + /** + * Updates the column count, including necessary revisions to child Column + * blocks to grant required or redistribute available space. + * + * @param {number} previousColumns Previous column count. + * @param {number} newColumns New column count. + */ + updateColumns( previousColumns, newColumns ) { + const { clientId } = ownProps; + const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); + const { getBlocks } = registry.select( 'core/block-editor' ); + + let innerBlocks = getBlocks( clientId ); + const hasExplicitWidths = hasExplicitColumnWidths( innerBlocks ); + + // Redistribute available width for existing inner blocks. + const isAddingColumn = newColumns > previousColumns; + + if ( isAddingColumn && hasExplicitWidths ) { + // If adding a new column, assign width to the new column equal to + // as if it were `1 / columns` of the total available space. + const newColumnWidth = toWidthPrecision( 100 / newColumns ); + + // Redistribute in consideration of pending block insertion as + // constraining the available working width. + const widths = getRedistributedColumnWidths( innerBlocks, 100 - newColumnWidth ); + + innerBlocks = [ + ...getMappedColumnWidths( innerBlocks, widths ), + ...times( newColumns - previousColumns, () => { + return createBlock( 'core/column', { + width: newColumnWidth, + } ); + } ), + ]; + } else if ( isAddingColumn ) { + innerBlocks = [ + ...innerBlocks, + ...times( newColumns - previousColumns, () => { + return createBlock( 'core/column' ); + } ), + ]; + } else { + // The removed column will be the last of the inner blocks. + innerBlocks = dropRight( innerBlocks, previousColumns - newColumns ); + + if ( hasExplicitWidths ) { + // Redistribute as if block is already removed. + const widths = getRedistributedColumnWidths( innerBlocks, 100 ); + + innerBlocks = getMappedColumnWidths( innerBlocks, widths ); + } + } + + replaceInnerBlocks( clientId, innerBlocks, false ); + }, +} ) )( ColumnsEdit ); diff --git a/packages/block-library/src/columns/editor.native.scss b/packages/block-library/src/columns/editor.native.scss new file mode 100644 index 00000000000000..73c7706bfabfe5 --- /dev/null +++ b/packages/block-library/src/columns/editor.native.scss @@ -0,0 +1,6 @@ +.columnPlaceholder { + padding: 12px; + background-color: $white; + border: $border-width dashed $light-gray-500; + border-radius: 4px; +} \ No newline at end of file diff --git a/packages/block-library/src/columns/icon.js b/packages/block-library/src/columns/icon.js index f45aba71242c40..bcbbb79f33d867 100644 --- a/packages/block-library/src/columns/icon.js +++ b/packages/block-library/src/columns/icon.js @@ -3,6 +3,6 @@ */ import { G, Path, SVG } from '@wordpress/components'; -export default ( - -); +const Icon = ( props ) => ; + +export default Icon diff --git a/packages/block-library/src/index.native.js b/packages/block-library/src/index.native.js index 0d06a9df9f11af..29f4baf524e616 100644 --- a/packages/block-library/src/index.native.js +++ b/packages/block-library/src/index.native.js @@ -144,6 +144,8 @@ export const registerCoreBlocks = () => { mediaText, // eslint-disable-next-line no-undef !! __DEV__ ? group : null, + // eslint-disable-next-line no-undef + !! __DEV__ ? columns : null, ].forEach( registerBlock ); setDefaultBlockName( paragraph.name ); From ed9a435de43f2dc85d155a9ff728bd2473e27d19 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Mon, 4 Nov 2019 09:39:06 +0100 Subject: [PATCH 027/183] add getPrefferedColorScheme for dashed border --- .../block-editor/src/components/block-list/block.native.js | 6 ++++-- .../src/components/block-list/block.native.scss | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 32091dfa315dd5..df0e8c9dbd129a 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -13,7 +13,7 @@ import { import { Component } from '@wordpress/element'; import { ToolbarButton, Toolbar } from '@wordpress/components'; import { withDispatch, withSelect } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; +import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { getBlockType } from '@wordpress/blocks'; import { __, sprintf } from '@wordpress/i18n'; @@ -136,13 +136,14 @@ class BlockListBlock extends Component { isGroupType, parentId, isMediaTextParent, + getStylesFromColorScheme, } = this.props; if ( ! isDashed && isInnerBlock && ! isChildOfSameRootBlook ) { return styles.blockContainerInner; } - const defaultStyle = [ isDashed ? styles.blockHolderDashedBordered : styles.blockContainer ]; + const defaultStyle = [ isDashed ? getStylesFromColorScheme( styles.blockHolderDashedBordered, styles.blockHolderDashedBorderedDark ) : styles.blockContainer ]; if ( isMediaTextParent ) { return [ ...defaultStyle, styles.blockContainerMediaTextInner ]; @@ -357,4 +358,5 @@ export default compose( [ }, }; } ), + withPreferredColorScheme, ] )( BlockListBlock ); diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index 0fd7c8c678159e..af98399c9f063d 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -52,6 +52,10 @@ margin-top: 6px; } +.blockHolderDashedBorderedDark { + border-color: $gray-70; +} + .horizontalMarginNone { margin-left: 0; margin-right: 0; From aafa45216696f1666a5d3de5ae8c9c076629cea1 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Mon, 4 Nov 2019 10:10:09 +0100 Subject: [PATCH 028/183] enable FloatingToolbar for MediaText --- packages/block-editor/src/components/block-list/block.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index df0e8c9dbd129a..5b9793190fc824 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -274,7 +274,7 @@ export default compose( [ const rootBlock = getBlock( rootBlockId ); const hasRootInnerBlocks = rootBlock.innerBlocks.length !== 0; - const showFloatingToolbar = isSelected && hasRootInnerBlocks && ! isMediaText && ! isMediaTextParent; + const showFloatingToolbar = isSelected && hasRootInnerBlocks; const firstToSelectId = getFirstToSelectBlock( clientId ); From dffabf93e4210768504b9113927d7f5e002f50db Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Mon, 4 Nov 2019 10:18:27 +0100 Subject: [PATCH 029/183] refactor rich text handler --- packages/rich-text/src/component/index.native.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js index ba35c60e626690..2a0094dc1d6471 100644 --- a/packages/rich-text/src/component/index.native.js +++ b/packages/rich-text/src/component/index.native.js @@ -75,6 +75,7 @@ export class RichText extends Component { this.formatToValue.bind( this ), { maxSize: 1 } ); + this.makeActive = this.makeActive.bind( this ); // This prevents a bug in Aztec which triggers onSelectionChange twice on format change this.onSelectionChange = this.onSelectionChange.bind( this ); @@ -542,7 +543,7 @@ export class RichText extends Component { // to determine if we should focus the RichText. if ( this.props.blockIsSelected && ! this.props.__unstableMobileNoFocusOnMount ) { this._editor.focus(); - this.setState( { isActive: true } ); + this.makeActive( true ); this.onSelectionChange( this.props.selectionStart || 0, this.props.selectionEnd || 0 ); } } @@ -550,7 +551,7 @@ export class RichText extends Component { componentWillUnmount() { if ( this._editor.isFocused() && this.props.shouldBlurOnUnmount ) { this._editor.blur(); - this.setState( { isActive: false } ); + this.makeActive( false ); } } @@ -569,18 +570,22 @@ export class RichText extends Component { if ( isSelected && ! prevIsSelected ) { this._editor.focus(); - this.setState( { isActive: true } ); + this.makeActive( true ); // Update selection props explicitly when component is selected as Aztec won't call onSelectionChange // if its internal value hasn't change. When created, default value is 0, 0 this.onSelectionChange( this.props.selectionStart || 0, this.props.selectionEnd || 0 ); } else if ( ! isSelected && prevIsSelected ) { this._editor.blur(); if ( this.props.isInnerBlock ) { - this.setState( { isActive: false } ); + this.makeActive( false ); } } } + makeActive( isActive ) { + this.setState( { isActive } ); + } + willTrimSpaces( html ) { // regex for detecting spaces around block element html tags const blockHtmlElements = '(div|br|blockquote|ul|ol|li|p|pre|h1|h2|h3|h4|h5|h6|iframe|hr)'; From 4576c6aa7272b3b1af447ed02a6756ec644d7ac0 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Mon, 4 Nov 2019 21:55:21 +0100 Subject: [PATCH 030/183] change dark-mode block holder border to blue --- .../block-editor/src/components/block-list/style.native.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-list/style.native.scss b/packages/block-editor/src/components/block-list/style.native.scss index 541a35d42a19a3..efc4ff4cde6a2e 100644 --- a/packages/block-editor/src/components/block-list/style.native.scss +++ b/packages/block-editor/src/components/block-list/style.native.scss @@ -76,7 +76,7 @@ } .blockHolderFocusedDark { - border-color: $gray-70; + border-color: $blue-30; } .blockListFooter { From 90ae89473605720ecc22ba92eede2ef78e581956 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Tue, 5 Nov 2019 10:24:23 +0100 Subject: [PATCH 031/183] dark-mode for group appender and placeholder --- .../button-block-appender/index.native.js | 16 ++++++++++------ .../button-block-appender/styles.native.scss | 10 ++++++++++ packages/block-library/src/group/edit.native.js | 6 ++++-- .../block-library/src/group/editor.native.scss | 6 ++++++ 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/button-block-appender/index.native.js b/packages/block-editor/src/components/button-block-appender/index.native.js index b01d3b65109856..3c0250c0302d30 100644 --- a/packages/block-editor/src/components/button-block-appender/index.native.js +++ b/packages/block-editor/src/components/button-block-appender/index.native.js @@ -6,6 +6,7 @@ import { View } from 'react-native'; /** * WordPress dependencies */ +import { withPreferredColorScheme } from '@wordpress/compose'; import { Button, Dashicon } from '@wordpress/components'; /** @@ -14,7 +15,10 @@ import { Button, Dashicon } from '@wordpress/components'; import Inserter from '../inserter'; import styles from './styles.scss'; -function ButtonBlockAppender( { rootClientId } ) { +function ButtonBlockAppender( { rootClientId, getStylesFromColorScheme } ) { + const appenderStyle = getStylesFromColorScheme( styles.appender, styles.appenderDark ); + const addBlockButtonStyle = getStylesFromColorScheme( styles.addBlockButton, styles.addBlockButtonDark ); + return ( <> - + @@ -45,4 +49,4 @@ function ButtonBlockAppender( { rootClientId } ) { /** * @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/button-block-appender/README.md */ -export default ButtonBlockAppender; +export default withPreferredColorScheme( ButtonBlockAppender ); diff --git a/packages/block-editor/src/components/button-block-appender/styles.native.scss b/packages/block-editor/src/components/button-block-appender/styles.native.scss index 46b4a055cd5bd4..ef40ee4946d5ab 100644 --- a/packages/block-editor/src/components/button-block-appender/styles.native.scss +++ b/packages/block-editor/src/components/button-block-appender/styles.native.scss @@ -9,6 +9,11 @@ border-radius: 4px; } +.appenderDark { + background-color: $background-dark-secondary; + border: $border-width solid $gray-50; +} + .addBlockButton { color: $white; background-color: $gray; @@ -16,3 +21,8 @@ overflow: hidden; size: $icon-button-size-small; } + +.addBlockButtonDark { + color: $background-dark-secondary; + background-color: $gray-50; +} diff --git a/packages/block-library/src/group/edit.native.js b/packages/block-library/src/group/edit.native.js index 32c87e3863c881..6b860a21fdd350 100644 --- a/packages/block-library/src/group/edit.native.js +++ b/packages/block-library/src/group/edit.native.js @@ -8,7 +8,7 @@ import { View } from 'react-native'; * WordPress dependencies */ import { withSelect } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; +import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { InnerBlocks, withColors, @@ -21,10 +21,11 @@ import styles from './editor.scss'; function GroupEdit( { hasInnerBlocks, isSelected, + getStylesFromColorScheme, } ) { if ( ! isSelected && ! hasInnerBlocks ) { return ( - + ); } @@ -48,4 +49,5 @@ export default compose( [ hasInnerBlocks: !! ( block && block.innerBlocks.length ), }; } ), + withPreferredColorScheme, ] )( GroupEdit ); diff --git a/packages/block-library/src/group/editor.native.scss b/packages/block-library/src/group/editor.native.scss index 5edfa582287ef5..22e508bcaa1311 100644 --- a/packages/block-library/src/group/editor.native.scss +++ b/packages/block-library/src/group/editor.native.scss @@ -1,6 +1,12 @@ .groupPlaceholder { padding: 12px; + margin: 9px; background-color: $white; border: $border-width dashed $light-gray-500; border-radius: 4px; } + +.groupPlaceholderDark { + background-color: $black; + border: $border-width dashed $gray-70; +} From d8d4e1d2fffecd113c8fa57688d5ed3f904418b2 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Tue, 5 Nov 2019 11:01:10 +0100 Subject: [PATCH 032/183] adjust margin for group placeholder --- .../src/components/block-list/block.native.js | 5 +++++ .../button-block-appender/styles.native.scss | 8 ++++---- .../block-library/src/group/edit.native.js | 13 ++++++++++++- .../block-library/src/group/editor.native.scss | 18 +++++++++++++++++- 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 5b9793190fc824..aa449c1901b03c 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -71,6 +71,9 @@ class BlockListBlock extends Component { onCaretVerticalPositionChange={ this.props.onCaretVerticalPositionChange } clientId={ this.props.clientId } isInnerBlock={ this.props.isInnerBlock } + isDashed={ this.props.isDashed } + isChildOfSameRootBlook={ this.props.isChildOfSameRootBlook } + isGroupParent={ this.props.isGroupParent } /> ); } @@ -266,6 +269,7 @@ export default compose( [ const selectedBlock = getSelectedBlock(); const parentId = getBlockRootClientId( clientId ); const parentBlock = getBlock( parentId ); + const isGroupParent = parentBlock && parentBlock.name === 'core/group'; const isMediaText = selectedBlock && selectedBlock.name === 'core/media-text'; const isMediaTextParent = parentBlock && parentBlock.name === 'core/media-text'; @@ -313,6 +317,7 @@ export default compose( [ isParentSelected, isMediaTextParent, isMediaText, + isGroupParent, }; } ), withDispatch( ( dispatch, ownProps, { select } ) => { diff --git a/packages/block-editor/src/components/button-block-appender/styles.native.scss b/packages/block-editor/src/components/button-block-appender/styles.native.scss index ef40ee4946d5ab..55d4cf6b20089c 100644 --- a/packages/block-editor/src/components/button-block-appender/styles.native.scss +++ b/packages/block-editor/src/components/button-block-appender/styles.native.scss @@ -2,8 +2,8 @@ align-items: center; justify-content: center; padding: 12px; - margin-left: 2px; - margin-right: 2px; + margin-left: 9px; + margin-right: 9px; background-color: $white; border: $border-width solid $light-gray-500; border-radius: 4px; @@ -11,7 +11,7 @@ .appenderDark { background-color: $background-dark-secondary; - border: $border-width solid $gray-50; + border: $border-width solid $gray-70; } .addBlockButton { @@ -24,5 +24,5 @@ .addBlockButtonDark { color: $background-dark-secondary; - background-color: $gray-50; + background-color: $gray; } diff --git a/packages/block-library/src/group/edit.native.js b/packages/block-library/src/group/edit.native.js index 6b860a21fdd350..2b8b37857e72ca 100644 --- a/packages/block-library/src/group/edit.native.js +++ b/packages/block-library/src/group/edit.native.js @@ -22,10 +22,21 @@ function GroupEdit( { hasInnerBlocks, isSelected, getStylesFromColorScheme, + isInnerBlock, + isDashed, + isChildOfSameRootBlook, + isParentSelected, + isGroupParent, } ) { if ( ! isSelected && ! hasInnerBlocks ) { return ( - + ); } diff --git a/packages/block-library/src/group/editor.native.scss b/packages/block-library/src/group/editor.native.scss index 22e508bcaa1311..3d5cdbe80175ab 100644 --- a/packages/block-library/src/group/editor.native.scss +++ b/packages/block-library/src/group/editor.native.scss @@ -1,11 +1,27 @@ .groupPlaceholder { padding: 12px; - margin: 9px; + margin: 16px; background-color: $white; border: $border-width dashed $light-gray-500; border-radius: 4px; } +.groupPlaceholderInner { + border: 0; + padding: 4px; +} + +.groupPlaceholderMarginVerticalNone { + margin-top: 0; + margin-bottom: 0; + padding: 12px; +} + +.groupPlaceholderMarginHorizontalNone { + margin-left: 0; + margin-right: 0; +} + .groupPlaceholderDark { background-color: $black; border: $border-width dashed $gray-70; From 677aade342a93dbb4205ff0ba9e4e94320accff4 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Tue, 5 Nov 2019 12:24:53 +0100 Subject: [PATCH 033/183] adjust innerblock margin for media-text --- .../block-editor/src/components/block-list/block.native.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index af98399c9f063d..8cff000ac59ba6 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -25,7 +25,7 @@ padding-right: 0; padding-top: 6px; padding-bottom: 6px; - margin-bottom: 0; + margin-bottom: 6px; margin-top: 0; } From 168c3a2c0970cd3bac48c81b3035eb10fb7df107 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Tue, 5 Nov 2019 12:59:05 +0100 Subject: [PATCH 034/183] adjust group placeholder border color --- packages/block-library/src/group/editor.native.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/group/editor.native.scss b/packages/block-library/src/group/editor.native.scss index 3d5cdbe80175ab..b93b8e997a024a 100644 --- a/packages/block-library/src/group/editor.native.scss +++ b/packages/block-library/src/group/editor.native.scss @@ -2,7 +2,7 @@ padding: 12px; margin: 16px; background-color: $white; - border: $border-width dashed $light-gray-500; + border: $border-width dashed $gray; border-radius: 4px; } From e8464a0db734d85a9c44ca90de269990f3284e41 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Wed, 6 Nov 2019 12:37:07 +0100 Subject: [PATCH 035/183] adjust appender button color --- .../src/components/button-block-appender/styles.native.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/button-block-appender/styles.native.scss b/packages/block-editor/src/components/button-block-appender/styles.native.scss index 55d4cf6b20089c..db3aac95d99a8e 100644 --- a/packages/block-editor/src/components/button-block-appender/styles.native.scss +++ b/packages/block-editor/src/components/button-block-appender/styles.native.scss @@ -24,5 +24,5 @@ .addBlockButtonDark { color: $background-dark-secondary; - background-color: $gray; + background-color: $gray-40; } From 7a500e12dd20f8af6557c87cc5f283803402e365 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Wed, 6 Nov 2019 12:44:20 +0100 Subject: [PATCH 036/183] re-activate the stacking vertically on Media & Text --- packages/block-library/src/media-text/edit.native.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/media-text/edit.native.js b/packages/block-library/src/media-text/edit.native.js index 22cccb6de27273..476068792c1379 100644 --- a/packages/block-library/src/media-text/edit.native.js +++ b/packages/block-library/src/media-text/edit.native.js @@ -133,16 +133,15 @@ class MediaTextEdit extends Component { backgroundColor, setAttributes, isSelected, - // isMobile, + isMobile, } = this.props; const { - // isStackedOnMobile, + isStackedOnMobile, mediaPosition, mediaWidth, verticalAlignment, } = attributes; - const shouldStack = false; // We are temporarily not stacking until we fix alignment buttons - // const shouldStack = isStackedOnMobile && isMobile; // <<< Original line + const shouldStack = isStackedOnMobile && isMobile; const temporaryMediaWidth = shouldStack ? 100 : ( this.state.mediaWidth || mediaWidth ); const widthString = `${ temporaryMediaWidth }%`; const containerStyles = { From c8f7476230b12be7964406d9f2a531e8f4cd147b Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Thu, 7 Nov 2019 13:32:49 +0100 Subject: [PATCH 037/183] WIP: add key extractor and slider --- .../src/components/block-list/index.native.js | 3 ++ .../block-library/src/columns/edit.native.js | 41 ++++++++++++++----- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 486b8fb94968c3..3886b2f9eb99f7 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -101,6 +101,9 @@ export class BlockList extends Component { ListHeaderComponent={ header } ListEmptyComponent={ this.renderDefaultBlockAppender } ListFooterComponent={ withFooter && this.renderBlockListFooter } + listKey={(item, index) => { + return clientId; + }} /> { renderAppender && blockClientIds.length > 0 && diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 9ce12ed5a7cf11..cbcf90e31c85b7 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -11,7 +11,7 @@ import { dropRight, times } from 'lodash'; import { __ } from '@wordpress/i18n'; import { PanelBody, - // RangeControl, + RangeControl, BottomSheet, SVG, Path, @@ -50,7 +50,8 @@ import styles from './editor.scss'; * @constant * @type {string[]} */ -const ALLOWED_BLOCKS = [ 'core/column' ]; +// const ALLOWED_BLOCKS = [ 'core/column' ]; +const ALLOWED_BLOCKS = [ 'core/button', 'core/paragraph', 'core/heading', 'core/list' ]; /** * Template option choices for predefined columns layouts. @@ -128,6 +129,7 @@ export function ColumnsEdit( { } ); const [ template, setTemplate ] = useState( getColumnsTemplate( count ) ); const [ forceUseTemplate, setForceUseTemplate ] = useState( false ); + const [ columnCount, setColumnCount ] = useState( DEFAULT_COLUMNS ); // This is used to force the usage of the template even if the count doesn't match the template // The count doesn't match the template once you use undo/redo (this is used to reset to the placeholder state). @@ -146,28 +148,40 @@ export function ColumnsEdit( { // or if there's no template available. // The count === 0 trick is useful when you use undo/redo. const showTemplateSelector = ( count === 0 && ! forceUseTemplate ) || ! template; -console.log(isSelected) if ( ! isSelected ) { return ( ); } -console.log(count) + +const renderColumns = () => { + return [...Array(columnCount)].map((e, i) => + { + return i+"_"+index+'_'+item.id+"_"; + }} + /> + ) + } + return ( <> {/* { ! showTemplateSelector && ( */} <> - {/* { + onChange={ ( value ) => { console.log(value) - updateColumns( count, value ) } + setColumnCount( value ) + // updateColumns( count, value ) + } } min={ 2 } max={ 6 } @@ -191,8 +205,8 @@ console.log(count) {/* ) } */} {/* */} - { @@ -206,7 +220,12 @@ console.log(count) // __experimentalAllowTemplateOptionSkip // template={ showTemplateSelector ? null : template } // templateLock="all" - allowedBlocks={ ALLOWED_BLOCKS } /> + allowedBlocks={ ALLOWED_BLOCKS } + listKey={(item, index) => { + return "_"+index+'_'+item.id; + }} + /> */} + {/* */} ); From f94c828bc576938adebbf2c7b688c6cccaa2cd1f Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Fri, 8 Nov 2019 12:50:21 +0100 Subject: [PATCH 038/183] CR changes --- .../src/components/block-list/block.native.js | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index aa449c1901b03c..e81e8041dcdb70 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -72,7 +72,7 @@ class BlockListBlock extends Component { clientId={ this.props.clientId } isInnerBlock={ this.props.isInnerBlock } isDashed={ this.props.isDashed } - isChildOfSameRootBlook={ this.props.isChildOfSameRootBlook } + isChildOfSameRootBlock={ this.props.isChildOfSameRootBlock } isGroupParent={ this.props.isGroupParent } /> ); @@ -115,10 +115,10 @@ class BlockListBlock extends Component { const { isInnerBlock, isNestedInnerBlock, - isGroupType, + hasInnerBlock, } = this.props; - if ( isNestedInnerBlock || isGroupType ) { + if ( isNestedInnerBlock || hasInnerBlock ) { return styles.nestedFocusedBlock; } @@ -135,14 +135,14 @@ class BlockListBlock extends Component { isDimmed, isNestedInnerBlock, isInnerBlock, - isChildOfSameRootBlook, - isGroupType, + isChildOfSameRootBlock, + hasInnerBlock, parentId, isMediaTextParent, getStylesFromColorScheme, } = this.props; - if ( ! isDashed && isInnerBlock && ! isChildOfSameRootBlook ) { + if ( ! isDashed && isInnerBlock && ! isChildOfSameRootBlock ) { return styles.blockContainerInner; } @@ -159,7 +159,7 @@ class BlockListBlock extends Component { return [ ...defaultStyle, styles.nestedBlockContainerInner ]; } - if ( isGroupType && ( ! parentId || isChildOfSameRootBlook ) ) { + if ( hasInnerBlock && ( ! parentId || isChildOfSameRootBlock ) ) { return [ ...defaultStyle, styles.horizontalMarginNone ]; } @@ -182,7 +182,7 @@ class BlockListBlock extends Component { isDimmed, isInnerBlock, isNestedInnerBlock, - isGroupType, + hasInnerBlock, isMediaTextParent, } = this.props; @@ -213,9 +213,9 @@ class BlockListBlock extends Component { @@ -226,7 +226,7 @@ class BlockListBlock extends Component { ! isSelected && this.applyUnSelectedStyle(), isSelected && this.applySelectedStyle(), isDimmed && styles.blockContainerDimmed, - isGroupType && styles.verticalPaddingNone, + hasInnerBlock && styles.verticalPaddingNone, ] } > { isValid && this.getBlockForType() } @@ -253,7 +253,6 @@ export default compose( [ getSelectedBlockClientId, getBlock, getBlockRootClientId, - getSelectedBlock, getFirstToSelectBlock, } = select( 'core/block-editor' ); const order = getBlockIndex( clientId, rootClientId ); @@ -266,12 +265,10 @@ export default compose( [ const icon = blockType.icon; const getAccessibilityLabelExtra = blockType.__experimentalGetAccessibilityLabel; - const selectedBlock = getSelectedBlock(); const parentId = getBlockRootClientId( clientId ); const parentBlock = getBlock( parentId ); const isGroupParent = parentBlock && parentBlock.name === 'core/group'; - const isMediaText = selectedBlock && selectedBlock.name === 'core/media-text'; const isMediaTextParent = parentBlock && parentBlock.name === 'core/media-text'; const rootBlockId = getBlockHierarchyRootClientId( clientId ); @@ -289,9 +286,9 @@ export default compose( [ const isDimmed = ! isSelected && ! isRootSiblingsSelected && !! selectedBlockClientId && firstToSelectId === clientId && ! isDashed; const isInnerBlock = parentId && firstToSelectId !== parentId; - const isChildOfSameRootBlook = rootBlockId === getBlockHierarchyRootClientId( selectedBlockClientId ); + const isChildOfSameRootBlock = rootBlockId === getBlockHierarchyRootClientId( selectedBlockClientId ); const isNestedInnerBlock = ! isDashed && selectedBlockClientId === getBlockRootClientId( firstToSelectId ); - const isGroupType = blockType.name === 'core/group' || blockType.name === 'core/media-text'; + const hasInnerBlock = blockType.name === 'core/group' || blockType.name === 'core/media-text'; const isParentSelected = parentId === selectedBlockClientId; return { @@ -311,12 +308,11 @@ export default compose( [ isDimmed, firstToSelectId, isInnerBlock, - isChildOfSameRootBlook, + isChildOfSameRootBlock, isNestedInnerBlock, - isGroupType, + hasInnerBlock, isParentSelected, isMediaTextParent, - isMediaText, isGroupParent, }; } ), From 845f92052664113a2e577adfe912bae3c58a7ad9 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Tue, 12 Nov 2019 14:50:45 +0100 Subject: [PATCH 039/183] CR changes for Android RichText focus --- .../block-editor/src/components/block-list/block.native.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index e81e8041dcdb70..d03be6a3240d8a 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -5,6 +5,7 @@ import { View, Text, TouchableWithoutFeedback, + Platform, } from 'react-native'; /** @@ -184,6 +185,7 @@ class BlockListBlock extends Component { isNestedInnerBlock, hasInnerBlock, isMediaTextParent, + isTouchable, } = this.props; const borderColor = isSelected ? focusedBorderColor : 'transparent'; @@ -228,6 +230,7 @@ class BlockListBlock extends Component { isDimmed && styles.blockContainerDimmed, hasInnerBlock && styles.verticalPaddingNone, ] } + { ...isTouchable } > { isValid && this.getBlockForType() } { ! isValid && @@ -290,6 +293,7 @@ export default compose( [ const isNestedInnerBlock = ! isDashed && selectedBlockClientId === getBlockRootClientId( firstToSelectId ); const hasInnerBlock = blockType.name === 'core/group' || blockType.name === 'core/media-text'; const isParentSelected = parentId === selectedBlockClientId; + const isTouchable = ( Platform.OS !== 'android' || firstToSelectId !== clientId ) ? {} : { pointerEvents: 'none' }; return { icon, @@ -314,6 +318,7 @@ export default compose( [ isParentSelected, isMediaTextParent, isGroupParent, + isTouchable, }; } ), withDispatch( ( dispatch, ownProps, { select } ) => { From 35d8c8b377f1bab5062cff344fe4dda6afbd9958 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Wed, 13 Nov 2019 14:16:38 +0100 Subject: [PATCH 040/183] CR changes for MediaText margins --- .../block-editor/src/components/block-list/block.native.js | 4 ++++ packages/block-library/src/media-text/edit.native.js | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index d03be6a3240d8a..f033b4607a1c0e 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -75,6 +75,8 @@ class BlockListBlock extends Component { isDashed={ this.props.isDashed } isChildOfSameRootBlock={ this.props.isChildOfSameRootBlock } isGroupParent={ this.props.isGroupParent } + parentId={ this.props.parentId } + isMediaTextChildSelected={ this.props.isMediaTextChildSelected } /> ); } @@ -294,6 +296,7 @@ export default compose( [ const hasInnerBlock = blockType.name === 'core/group' || blockType.name === 'core/media-text'; const isParentSelected = parentId === selectedBlockClientId; const isTouchable = ( Platform.OS !== 'android' || firstToSelectId !== clientId ) ? {} : { pointerEvents: 'none' }; + const isMediaTextChildSelected = blockType.name === 'core/media-text' && getBlockRootClientId( selectedBlockClientId ) === clientId; return { icon, @@ -319,6 +322,7 @@ export default compose( [ isMediaTextParent, isGroupParent, isTouchable, + isMediaTextChildSelected, }; } ), withDispatch( ( dispatch, ownProps, { select } ) => { diff --git a/packages/block-library/src/media-text/edit.native.js b/packages/block-library/src/media-text/edit.native.js index 476068792c1379..4faec2dda67aac 100644 --- a/packages/block-library/src/media-text/edit.native.js +++ b/packages/block-library/src/media-text/edit.native.js @@ -144,6 +144,8 @@ class MediaTextEdit extends Component { const shouldStack = isStackedOnMobile && isMobile; const temporaryMediaWidth = shouldStack ? 100 : ( this.state.mediaWidth || mediaWidth ); const widthString = `${ temporaryMediaWidth }%`; + const mediaContainerPadding = ( ( this.props.isInnerBlock || this.props.parentId ) && ! this.props.isMediaTextChildSelected ) ? 0 : 16; + const mediaContainerVerticalPadding = isSelected ? 8 : 5; const containerStyles = { ...styles[ 'wp-block-media-text' ], ...styles[ `is-vertically-aligned-${ verticalAlignment }` ], @@ -151,7 +153,9 @@ class MediaTextEdit extends Component { ...( shouldStack ? styles[ 'is-stacked-on-mobile' ] : {} ), ...( shouldStack && mediaPosition === 'right' ? styles[ 'is-stacked-on-mobile.has-media-on-the-right' ] : {} ), backgroundColor: backgroundColor.color, - ...{ padding: isSelected ? 8 : 16 }, + ...{ padding: isSelected ? 8 : mediaContainerPadding }, + paddingTop: mediaContainerVerticalPadding, + paddingBottom: mediaContainerVerticalPadding, }; const innerBlockWidth = shouldStack ? 100 : ( 100 - temporaryMediaWidth ); const innerBlockWidthString = `${ innerBlockWidth }%`; From 7eba06d7a4cdcff22e0172010994358d8293c1dd Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Wed, 13 Nov 2019 16:09:11 +0100 Subject: [PATCH 041/183] CR changes for getTree selector --- packages/block-editor/src/store/selectors.js | 43 ++++++++------------ 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 5aba4bb1257ed9..c7b277382508b3 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -426,24 +426,16 @@ export function getBlockRootClientId( state, clientId ) { * * @param {Object} state Editor state. * @param {string} clientId Block from which to find root client ID. + * @param {boolean} getDependants Apply createSelector function to call getDependants. + * @param {boolean} includeClientId Include client ID in return array. * * @return {Array} ClientIDs of the parent blocks. */ -export const getBlockParents = createSelector( - ( state, clientId ) => { - const parents = []; - let current = clientId; - while ( !! state.blocks.parents[ current ] ) { - current = state.blocks.parents[ current ]; - parents.push( current ); - } - - return parents.reverse(); - }, - ( state ) => [ - state.blocks.parents, - ] -); +export const getBlockParents = ( state, clientId, getDependants = true, includeClientId = false ) => { + return getDependants ? + createSelector( getTree( state, clientId, true ), ( dependantState ) => [ dependantState.blocks.parents ] ) : + getTree( state, clientId, false, includeClientId ); +}; /** * Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. @@ -468,19 +460,20 @@ export function getBlockHierarchyRootClientId( state, clientId ) { * * @param {Object} state Editor state. * @param {string} clientId Block from which tree will be created. + * @param {boolean} reverse Get parent hierarchy in reverse order (top-most hierarchy first). + * @param {boolean} includeClientId Include client ID in return array. * * @return {Array} Hierarchy tree of client ID. */ -export function getTree( state, clientId ) { +export function getTree( state, clientId, reverse = true, includeClientId = false ) { + const parents = includeClientId ? [ clientId ] : []; let current = clientId; - const tree = [ current ]; - do { + while ( !! state.blocks.parents[ current ] ) { current = state.blocks.parents[ current ]; - if ( current ) { - tree.push( current ); - } - } while ( current ); - return tree; + parents.push( current ); + } + + return reverse ? parents.reverse() : parents; } /** @@ -493,7 +486,7 @@ export function getTree( state, clientId ) { */ export function getFirstToSelectBlock( state, clientId ) { const selectedId = getSelectedBlockClientId( state ); - const clientTree = getTree( state, clientId ); + const clientTree = getBlockParents( state, clientId, false, true ); const rootParent = clientTree[ clientTree.length - 1 ]; let index = 0; @@ -511,7 +504,7 @@ export function getFirstToSelectBlock( state, clientId ) { return rootParent; } - const selectedTree = getTree( state, selectedId ); + const selectedTree = getBlockParents( state, selectedId, false, true ); do { const commonParentIndex = clientTree.indexOf( selectedTree[ index ] ); From 48a62f4d4cffcf6d3148bef3705747f29c728b20 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Fri, 15 Nov 2019 10:58:16 +0100 Subject: [PATCH 042/183] CR changes: fix selection loop --- .../src/components/block-list/block.native.js | 5 ++--- .../block-library/src/heading/edit.native.js | 2 -- .../block-library/src/paragraph/edit.native.js | 1 - packages/rich-text/src/component/index.native.js | 16 ---------------- 4 files changed, 2 insertions(+), 22 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index f033b4607a1c0e..4646a6ec2ad798 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -5,7 +5,6 @@ import { View, Text, TouchableWithoutFeedback, - Platform, } from 'react-native'; /** @@ -225,6 +224,7 @@ class BlockListBlock extends Component { }> { showTitle && this.renderBlockTitle() } { isValid && this.getBlockForType() } { ! isValid && @@ -295,7 +294,7 @@ export default compose( [ const isNestedInnerBlock = ! isDashed && selectedBlockClientId === getBlockRootClientId( firstToSelectId ); const hasInnerBlock = blockType.name === 'core/group' || blockType.name === 'core/media-text'; const isParentSelected = parentId === selectedBlockClientId; - const isTouchable = ( Platform.OS !== 'android' || firstToSelectId !== clientId ) ? {} : { pointerEvents: 'none' }; + const isTouchable = firstToSelectId !== clientId; const isMediaTextChildSelected = blockType.name === 'core/media-text' && getBlockRootClientId( selectedBlockClientId ) === clientId; return { diff --git a/packages/block-library/src/heading/edit.native.js b/packages/block-library/src/heading/edit.native.js index 29983197843ac1..ac8a0e755278cd 100644 --- a/packages/block-library/src/heading/edit.native.js +++ b/packages/block-library/src/heading/edit.native.js @@ -23,7 +23,6 @@ const HeadingEdit = ( { onReplace, setAttributes, style, - isInnerBlock, } ) => ( @@ -58,7 +57,6 @@ const HeadingEdit = ( { onReplace={ onReplace } onRemove={ () => onReplace( [] ) } placeholder={ attributes.placeholder || __( 'Write heading…' ) } - isInnerBlock={ isInnerBlock } /> ); diff --git a/packages/block-library/src/paragraph/edit.native.js b/packages/block-library/src/paragraph/edit.native.js index ac3862ab5ac1c1..6dbcaf8a1459dd 100644 --- a/packages/block-library/src/paragraph/edit.native.js +++ b/packages/block-library/src/paragraph/edit.native.js @@ -78,7 +78,6 @@ class ParagraphEdit extends Component { onReplace={ onReplace } onRemove={ onReplace ? () => onReplace( [] ) : undefined } placeholder={ placeholder || __( 'Start writing…' ) } - isInnerBlock={ this.props.isInnerBlock } /> ); diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js index 96f057510dda4c..0af666459641e5 100644 --- a/packages/rich-text/src/component/index.native.js +++ b/packages/rich-text/src/component/index.native.js @@ -75,7 +75,6 @@ export class RichText extends Component { this.formatToValue.bind( this ), { maxSize: 1 } ); - this.makeActive = this.makeActive.bind( this ); // This prevents a bug in Aztec which triggers onSelectionChange twice on format change this.onSelectionChange = this.onSelectionChange.bind( this ); @@ -87,7 +86,6 @@ export class RichText extends Component { activeFormats: [], selectedFormat: null, height: 0, - isActive: ! this.props.isInnerBlock, }; this.needsSelectionUpdate = false; this.savedContent = ''; @@ -543,7 +541,6 @@ export class RichText extends Component { // to determine if we should focus the RichText. if ( this.props.blockIsSelected && ! this.props.__unstableMobileNoFocusOnMount ) { this._editor.focus(); - this.makeActive( true ); this.onSelectionChange( this.props.selectionStart || 0, this.props.selectionEnd || 0 ); } } @@ -551,7 +548,6 @@ export class RichText extends Component { componentWillUnmount() { if ( this._editor.isFocused() && this.props.shouldBlurOnUnmount ) { this._editor.blur(); - this.makeActive( false ); } } @@ -570,22 +566,14 @@ export class RichText extends Component { if ( isSelected && ! prevIsSelected ) { this._editor.focus(); - this.makeActive( true ); // Update selection props explicitly when component is selected as Aztec won't call onSelectionChange // if its internal value hasn't change. When created, default value is 0, 0 this.onSelectionChange( this.props.selectionStart || 0, this.props.selectionEnd || 0 ); } else if ( ! isSelected && prevIsSelected ) { this._editor.blur(); - if ( this.props.isInnerBlock ) { - this.makeActive( false ); - } } } - makeActive( isActive ) { - this.setState( { isActive } ); - } - willTrimSpaces( html ) { // regex for detecting spaces around block element html tags const blockHtmlElements = '(div|br|blockquote|ul|ol|li|p|pre|h1|h2|h3|h4|h5|h6|iframe|hr)'; @@ -624,7 +612,6 @@ export class RichText extends Component { __unstableIsSelected: isSelected, children, getStylesFromColorScheme, - blockIsSelected, } = this.props; const record = this.getRecord(); @@ -693,8 +680,6 @@ export class RichText extends Component { this.firedAfterTextChanged = false; } - const lockOnFocus = blockIsSelected || this.state.isActive ? {} : { pointerEvents: 'none' }; - return ( { children && children( { @@ -739,7 +724,6 @@ export class RichText extends Component { disableEditingMenu={ this.props.disableEditingMenu } isMultiline={ this.isMultiline } textAlign={ this.props.textAlign } - { ...lockOnFocus } /> { isSelected && } From 65b84006a1dab1ceb64945a432ccbb11182d78a0 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Fri, 15 Nov 2019 11:14:27 +0100 Subject: [PATCH 043/183] clean passing props after seletion loop issue fix --- .../block-editor/src/components/caption/index.native.js | 1 - .../src/components/media-placeholder/index.native.js | 4 +--- packages/block-library/src/image/edit.native.js | 7 +------ .../block-library/src/media-text/media-container.native.js | 1 - packages/block-library/src/video/edit.native.js | 1 - 5 files changed, 2 insertions(+), 12 deletions(-) diff --git a/packages/block-editor/src/components/caption/index.native.js b/packages/block-editor/src/components/caption/index.native.js index c093c44c49d593..68e965524c8b87 100644 --- a/packages/block-editor/src/components/caption/index.native.js +++ b/packages/block-editor/src/components/caption/index.native.js @@ -30,7 +30,6 @@ const Caption = ( { accessible, accessibilityLabel, onBlur, onChange, onFocus, i underlineColorAndroid="transparent" textAlign={ 'center' } tagName={ '' } - isParentSelected={ shouldDisplay } /> ); diff --git a/packages/block-editor/src/components/media-placeholder/index.native.js b/packages/block-editor/src/components/media-placeholder/index.native.js index 96812b702e00b1..3680401362a4cb 100644 --- a/packages/block-editor/src/components/media-placeholder/index.native.js +++ b/packages/block-editor/src/components/media-placeholder/index.native.js @@ -117,9 +117,7 @@ function MediaPlaceholder( props ) { accessibilityHint={ accessibilityHint } onPress={ ( event ) => { props.onFocus( event ); - if ( props.isBlockSelected ) { - open(); - } + open(); } }> ); diff --git a/packages/block-library/src/media-text/media-container.native.js b/packages/block-library/src/media-text/media-container.native.js index 833ff148f312e5..fea6a9671965c4 100644 --- a/packages/block-library/src/media-text/media-container.native.js +++ b/packages/block-library/src/media-text/media-container.native.js @@ -255,7 +255,6 @@ class MediaContainer extends Component { allowedTypes={ ALLOWED_MEDIA_TYPES } onFocus={ this.props.onFocus } onError={ this.onUploadError } - isBlockSelected={ this.props.isSelected } /> ); } diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index 976ad8202aa6ea..bec2dfa7143e86 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -184,7 +184,6 @@ class VideoEdit extends React.Component { onSelect={ this.onSelectMediaUploadOption } icon={ this.getIcon( false, true ) } onFocus={ this.props.onFocus } - isBlockSelected={ this.props.isSelected } /> ); From c0725551994314f0b87d737211421ff1ec4fc758 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Fri, 15 Nov 2019 16:22:04 +0100 Subject: [PATCH 044/183] replace block specific text --- .../block-editor/src/components/block-list/index.native.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 8fa25a9b6b7455..10836d145dc031 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -180,7 +180,7 @@ export default compose( [ const insertionPoint = getBlockInsertionPoint(); const blockInsertionPointIsVisible = isBlockInsertionPointVisible(); const selectedBlock = getSelectedBlock(); - const isSelectedGroup = selectedBlock && ( selectedBlock.name === 'core/group' || selectedBlock.name === 'core/media-text' ); + const hasInnerBlocks = selectedBlock && selectedBlock.innerBlocks.length; const shouldShowInsertionPointBefore = ( clientId ) => { return ( blockInsertionPointIsVisible && @@ -210,7 +210,7 @@ export default compose( [ const shouldShowBlockAtIndex = ( index ) => { const shouldHideBlockAtIndex = ( - ! isSelectedGroup && blockInsertionPointIsVisible && + ! hasInnerBlocks && blockInsertionPointIsVisible && // if `index` === `insertionPoint.index`, then block is replaceable index === insertionPoint.index && // only hide selected block @@ -219,7 +219,7 @@ export default compose( [ return ! shouldHideBlockAtIndex; }; - const hasFullBorder = !! getBlockRootClientId( selectedBlockClientId ) || isSelectedGroup; + const hasFullBorder = !! getBlockRootClientId( selectedBlockClientId ) || hasInnerBlocks; return { blockClientIds, From f06f6affd83f3da8b90a687dc36c457380f8a907 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Fri, 15 Nov 2019 16:42:40 +0100 Subject: [PATCH 045/183] replace block specific style --- .../src/components/block-list/block.native.js | 2 +- .../src/components/block-list/block.native.scss | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 4646a6ec2ad798..3629ae1f123327 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -151,7 +151,7 @@ class BlockListBlock extends Component { const defaultStyle = [ isDashed ? getStylesFromColorScheme( styles.blockHolderDashedBordered, styles.blockHolderDashedBorderedDark ) : styles.blockContainer ]; if ( isMediaTextParent ) { - return [ ...defaultStyle, styles.blockContainerMediaTextInner ]; + return [ ...defaultStyle, styles.horizontalMarginNone, styles.horizontalPaddingNone ]; } if ( isNestedInnerBlock ) { diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index 8cff000ac59ba6..381fde3b6a344e 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -18,17 +18,6 @@ padding-bottom: 12px; } -.blockContainerMediaTextInner { - margin-left: 0; - margin-right: 0; - padding-left: 0; - padding-right: 0; - padding-top: 6px; - padding-bottom: 6px; - margin-bottom: 6px; - margin-top: 0; -} - .nestedBlockContainerInner { margin-left: 0; margin-right: 0; From f2be82dfd806ecec5dd2f1d017ba94aa5797db53 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Mon, 18 Nov 2019 07:58:54 +0100 Subject: [PATCH 046/183] fix getParent selectors --- packages/block-editor/src/store/selectors.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index c7b277382508b3..c2f88d656c7f2d 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -431,11 +431,12 @@ export function getBlockRootClientId( state, clientId ) { * * @return {Array} ClientIDs of the parent blocks. */ -export const getBlockParents = ( state, clientId, getDependants = true, includeClientId = false ) => { - return getDependants ? - createSelector( getTree( state, clientId, true ), ( dependantState ) => [ dependantState.blocks.parents ] ) : - getTree( state, clientId, false, includeClientId ); -}; +export const getBlockParents = createSelector( + ( state, clientId ) => getTree( state, clientId, true ), + ( state ) => [ + state.blocks.parents, + ] +); /** * Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. @@ -486,7 +487,7 @@ export function getTree( state, clientId, reverse = true, includeClientId = fals */ export function getFirstToSelectBlock( state, clientId ) { const selectedId = getSelectedBlockClientId( state ); - const clientTree = getBlockParents( state, clientId, false, true ); + const clientTree = getTree( state, clientId, false, true ); const rootParent = clientTree[ clientTree.length - 1 ]; let index = 0; @@ -504,7 +505,7 @@ export function getFirstToSelectBlock( state, clientId ) { return rootParent; } - const selectedTree = getBlockParents( state, selectedId, false, true ); + const selectedTree = getTree( state, selectedId, false, true ); do { const commonParentIndex = clientTree.indexOf( selectedTree[ index ] ); From e47908deff630574f60ea4d69cd1701458f1574a Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Mon, 18 Nov 2019 09:11:59 +0100 Subject: [PATCH 047/183] adjust selection logic --- .../block-editor/src/components/block-list/block.native.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 3629ae1f123327..eee10eaa9da40e 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -258,6 +258,7 @@ export default compose( [ getBlock, getBlockRootClientId, getFirstToSelectBlock, + getBlockParents, } = select( 'core/block-editor' ); const order = getBlockIndex( clientId, rootClientId ); const isSelected = isBlockSelected( clientId ); @@ -294,8 +295,9 @@ export default compose( [ const isNestedInnerBlock = ! isDashed && selectedBlockClientId === getBlockRootClientId( firstToSelectId ); const hasInnerBlock = blockType.name === 'core/group' || blockType.name === 'core/media-text'; const isParentSelected = parentId === selectedBlockClientId; - const isTouchable = firstToSelectId !== clientId; const isMediaTextChildSelected = blockType.name === 'core/media-text' && getBlockRootClientId( selectedBlockClientId ) === clientId; + const isDescendantSelected = selectedBlockClientId && getBlockParents( selectedBlockClientId ).includes( clientId ); + const isTouchable = isSelected || isDescendantSelected || selectedBlockClientId === parentId || parentId === ''; return { icon, From 2674522f31a0940bc64d646880b96d001944028c Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Mon, 18 Nov 2019 10:03:46 +0100 Subject: [PATCH 048/183] fix show appender in group block --- .../src/components/block-list/index.native.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index c291cec2215d39..deb7cf5e7b0e4c 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -63,13 +63,12 @@ export class BlockList extends Component { const willShowInsertionPoint = shouldShowInsertionPointBefore(); // call without the client_id argument since this is the appender return ( - { willShowInsertionPoint ? - this.renderAddBlockSeparator() : // show the new-block indicator when we're inserting a block or - - } + { /* show the new-block indicator when we're inserting a block */ } + { willShowInsertionPoint && this.renderAddBlockSeparator() } + ); } From 84d8dadd8c351aa862c59ec87bd072b187779015 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Mon, 18 Nov 2019 10:56:36 +0100 Subject: [PATCH 049/183] fix hasInnerBlocks --- packages/block-editor/src/components/block-list/index.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 10836d145dc031..faa18991a0dc59 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -180,7 +180,7 @@ export default compose( [ const insertionPoint = getBlockInsertionPoint(); const blockInsertionPointIsVisible = isBlockInsertionPointVisible(); const selectedBlock = getSelectedBlock(); - const hasInnerBlocks = selectedBlock && selectedBlock.innerBlocks.length; + const hasInnerBlocks = selectedBlock && selectedBlock.innerBlocks.length !== 0; const shouldShowInsertionPointBefore = ( clientId ) => { return ( blockInsertionPointIsVisible && From 014e3a05fdfe9db5359cc9bf8cecb5cf8b6ab97d Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Tue, 19 Nov 2019 12:56:34 +0100 Subject: [PATCH 050/183] refactor selectors --- .../src/components/block-list/block.native.js | 4 +- packages/block-editor/src/store/selectors.js | 41 +++++++------------ .../src/components/post-title/index.native.js | 4 +- 3 files changed, 18 insertions(+), 31 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index eee10eaa9da40e..4c34e6ecf7ffd3 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -257,7 +257,7 @@ export default compose( [ getSelectedBlockClientId, getBlock, getBlockRootClientId, - getFirstToSelectBlock, + getLowestCommonAncestorWithSelectedBlock, getBlockParents, } = select( 'core/block-editor' ); const order = getBlockIndex( clientId, rootClientId ); @@ -282,7 +282,7 @@ export default compose( [ const showFloatingToolbar = isSelected && hasRootInnerBlocks; - const firstToSelectId = getFirstToSelectBlock( clientId ); + const firstToSelectId = getLowestCommonAncestorWithSelectedBlock( clientId ); const selectedBlockClientId = getSelectedBlockClientId(); const isRootSiblingsSelected = getBlockRootClientId( selectedBlockClientId ) === ''; diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index c2f88d656c7f2d..33216d5215ff38 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -426,13 +426,21 @@ export function getBlockRootClientId( state, clientId ) { * * @param {Object} state Editor state. * @param {string} clientId Block from which to find root client ID. - * @param {boolean} getDependants Apply createSelector function to call getDependants. - * @param {boolean} includeClientId Include client ID in return array. + * @param {boolean} ascending Get parent hierarchy in (top-most hierarchy first). * * @return {Array} ClientIDs of the parent blocks. */ export const getBlockParents = createSelector( - ( state, clientId ) => getTree( state, clientId, true ), + ( state, clientId, ascending = false ) => { + const parents = []; + let current = clientId; + while ( !! state.blocks.parents[ current ] ) { + current = state.blocks.parents[ current ]; + parents.push( current ); + } + + return ascending ? parents : parents.reverse(); + }, ( state ) => [ state.blocks.parents, ] @@ -456,27 +464,6 @@ export function getBlockHierarchyRootClientId( state, clientId ) { return parent; } -/** - * Given a block client ID, returns the hierarchy tree of client ID. - * - * @param {Object} state Editor state. - * @param {string} clientId Block from which tree will be created. - * @param {boolean} reverse Get parent hierarchy in reverse order (top-most hierarchy first). - * @param {boolean} includeClientId Include client ID in return array. - * - * @return {Array} Hierarchy tree of client ID. - */ -export function getTree( state, clientId, reverse = true, includeClientId = false ) { - const parents = includeClientId ? [ clientId ] : []; - let current = clientId; - while ( !! state.blocks.parents[ current ] ) { - current = state.blocks.parents[ current ]; - parents.push( current ); - } - - return reverse ? parents.reverse() : parents; -} - /** * Given a block client ID, returns the next element of the hierarchy from which the block is nested which should be selected onFocus, return the block itself for root level blocks. * @@ -485,9 +472,9 @@ export function getTree( state, clientId, reverse = true, includeClientId = fals * * @return {string} First to select client ID */ -export function getFirstToSelectBlock( state, clientId ) { +export function getLowestCommonAncestorWithSelectedBlock( state, clientId ) { const selectedId = getSelectedBlockClientId( state ); - const clientTree = getTree( state, clientId, false, true ); + const clientTree = [ clientId, ...getBlockParents( state, clientId, true ) ]; const rootParent = clientTree[ clientTree.length - 1 ]; let index = 0; @@ -505,7 +492,7 @@ export function getFirstToSelectBlock( state, clientId ) { return rootParent; } - const selectedTree = getTree( state, selectedId, false, true ); + const selectedTree = [ selectedId, ...getBlockParents( state, selectedId, true ) ]; do { const commonParentIndex = clientTree.indexOf( selectedTree[ index ] ); diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index 6b8192a4babcbc..c2ff64ee41cb46 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -105,12 +105,12 @@ export default compose( isPostTitleSelected, } = select( 'core/editor' ); - const { getSelectedBlockClientId, getBlockRootClientId, getFirstToSelectBlock } = select( 'core/block-editor' ); + const { getSelectedBlockClientId, getBlockRootClientId, getLowestCommonAncestorWithSelectedBlock } = select( 'core/block-editor' ); const clientId = getSelectedBlockClientId(); const isAnyBlockSelected = !! clientId; const parentId = getBlockRootClientId( clientId ); - const firstToSelect = getFirstToSelectBlock( clientId ); + const firstToSelect = getLowestCommonAncestorWithSelectedBlock( clientId ); const isInnerBlock = parentId && firstToSelect !== parentId; const isDimmed = isAnyBlockSelected && isInnerBlock; From 3b94490dc459908be3f3c1940bfa96e6443fc946 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Fri, 22 Nov 2019 15:16:04 +0100 Subject: [PATCH 051/183] refactor navigate-down logic --- .../developers/data/data-core-block-editor.md | 44 ++--- packages/base-styles/_variables.scss | 10 + .../src/components/block-list/block.native.js | 182 ++++++++---------- .../components/block-list/block.native.scss | 122 ++++++------ .../src/components/block-list/index.native.js | 12 +- .../components/block-list/style.native.scss | 37 ---- .../button-block-appender/styles.native.scss | 4 +- packages/block-editor/src/store/selectors.js | 45 ++--- .../block-library/src/group/edit.native.js | 9 - .../src/group/editor.native.scss | 18 +- .../src/media-text/edit.native.js | 53 ++--- .../src/media-text/media-container.js | 3 +- .../visual-editor/style.native.scss | 4 +- .../src/components/post-title/index.native.js | 15 +- 14 files changed, 213 insertions(+), 345 deletions(-) diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md index 2203bd0e0bed37..215477ddadb377 100644 --- a/docs/designers-developers/developers/data/data-core-block-editor.md +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -197,8 +197,7 @@ _Parameters_ - _state_ `Object`: Editor state. - _clientId_ `string`: Block from which to find root client ID. -- _getDependants_ `boolean`: Apply createSelector function to call getDependants. -- _includeClientId_ `boolean`: Include client ID in return array. +- _ascending_ `boolean`: Get parent hierarchy in top-most hierarchy first (false) or reversed (true). _Returns_ @@ -318,19 +317,6 @@ _Returns_ - `?string`: First block client ID in the multi-selection set. -# **getFirstToSelectBlock** - -Given a block client ID, returns the next element of the hierarchy from which the block is nested which should be selected onFocus, return the block itself for root level blocks. - -_Parameters_ - -- _state_ `Object`: Editor state. -- _clientId_ `string`: Block from which to find first to select client ID. - -_Returns_ - -- `string`: First to select client ID - # **getGlobalBlockCount** Returns the total number of blocks, or the total number of blocks with a specific name in a post. @@ -405,6 +391,19 @@ _Returns_ - `?string`: Last block client ID in the multi-selection set. +# **getLowestCommonAncestorWithSelectedBlock** + +Given a block client ID, returns the common ancestor with selected client ID. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _clientId_ `string`: Block from which to find common ancestor client ID. + +_Returns_ + +- `string`: Common ancestor client ID or undefined + # **getMultiSelectedBlockClientIds** Returns the current multi-selection set of block client IDs, or an empty @@ -625,21 +624,6 @@ _Returns_ - `?string`: Block Template Lock -# **getTree** - -Given a block client ID, returns the hierarchy tree of client ID. - -_Parameters_ - -- _state_ `Object`: Editor state. -- _clientId_ `string`: Block from which tree will be created. -- _reverse_ `boolean`: Get parent hierarchy in reverse order (top-most hierarchy first). -- _includeClientId_ `boolean`: Include client ID in return array. - -_Returns_ - -- `Array`: Hierarchy tree of client ID. - # **hasInserterItems** Determines whether there are items to show in the inserter. diff --git a/packages/base-styles/_variables.scss b/packages/base-styles/_variables.scss index 75234bd5fb7795..543b1896b94537 100644 --- a/packages/base-styles/_variables.scss +++ b/packages/base-styles/_variables.scss @@ -63,6 +63,16 @@ $block-container-side-padding: $block-side-ui-width + $block-padding + 2 * $bloc $block-bg-padding--v: $block-padding + $block-spacing + $block-side-ui-clearance; // padding for Blocks with a background color (eg: paragraph or group) $block-bg-padding--h: $block-side-ui-width + $block-side-ui-clearance; // padding for Blocks with a background color (eg: paragraph or group) +$block-edge-to-content: 16px; +$block-selected-margin: 3px; +$block-selected-border-width: 1px; +$block-selected-padding: 0; +$block-selected-child-margin: 5px; +$block-selected-child-border-width: 1px; +$block-selected-child-padding: 0; +$block-selected-to-content: $block-edge-to-content - $block-selected-margin - $block-selected-border-width; +$block-selected-child-to-content: $block-selected-to-content - $block-selected-child-margin - $block-selected-child-border-width; + // Buttons & UI Widgets $radius-round-rectangle: 4px; $radius-round: 50%; diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 4c34e6ecf7ffd3..6945bbc110e809 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -34,10 +34,6 @@ class BlockListBlock extends Component { this.insertBlocksAfter = this.insertBlocksAfter.bind( this ); this.onFocus = this.onFocus.bind( this ); - - this.state = { - isFullyBordered: false, - }; } onFocus() { @@ -61,7 +57,6 @@ class BlockListBlock extends Component { ); } @@ -113,84 +102,89 @@ class BlockListBlock extends Component { return blockName; } - applySelectedStyle() { + applySelectedBlockStyle() { const { - isInnerBlock, - isNestedInnerBlock, - hasInnerBlock, + hasChildren, + getStylesFromColorScheme, + isSmallScreen, + selectionIsNested, } = this.props; - if ( isNestedInnerBlock || hasInnerBlock ) { - return styles.nestedFocusedBlock; + const fullSolidBorderStyle = { + ...styles.fullSolidBordered, + ...getStylesFromColorScheme( styles.solidBorderColor, styles.solidBorderColorDark ), + }; + + if ( hasChildren ) { + return { ...styles.selectedParent, ...fullSolidBorderStyle }; } - if ( isInnerBlock ) { - return styles.innerBlockContainerFocused; + if ( isSmallScreen && ! selectionIsNested ) { + return { + ...styles.selectedRootLeaf, + ...styles.semiSolidBordered, + ...{ borderColor: fullSolidBorderStyle.borderColor }, + }; } - return styles.blockContainerFocused; + return { ...styles.selectedLeaf, ...fullSolidBorderStyle }; } - applyUnSelectedStyle() { + applyUnSelectedBlockStyle() { const { - isDashed, - isDimmed, - isNestedInnerBlock, - isInnerBlock, - isChildOfSameRootBlock, - hasInnerBlock, - parentId, - isMediaTextParent, + hasChildren, + isParentSelected, + isAncestorSelected, + hasParent, getStylesFromColorScheme, } = this.props; - if ( ! isDashed && isInnerBlock && ! isChildOfSameRootBlock ) { - return styles.blockContainerInner; + if ( ! hasParent ) { + return hasChildren ? styles.neutral : styles.full; } - const defaultStyle = [ isDashed ? getStylesFromColorScheme( styles.blockHolderDashedBordered, styles.blockHolderDashedBorderedDark ) : styles.blockContainer ]; + if ( isParentSelected ) { + const dashedBorderStyle = { + ...styles.dashedBordered, + ...getStylesFromColorScheme( styles.dashedBorderColor, styles.dashedBorderColorDark ), + }; - if ( isMediaTextParent ) { - return [ ...defaultStyle, styles.horizontalMarginNone, styles.horizontalPaddingNone ]; + return hasChildren ? + { ...styles.childOfSelected, ...dashedBorderStyle } : + { ...styles.childOfSelectedLeaf, ...dashedBorderStyle }; } - if ( isNestedInnerBlock ) { - if ( ! isDimmed || isDashed ) { - return [ ...defaultStyle, styles.blockContainerInner ]; - } - return [ ...defaultStyle, styles.nestedBlockContainerInner ]; + if ( isAncestorSelected ) { + return styles.descendantOfSelectedLeaf; } - if ( hasInnerBlock && ( ! parentId || isChildOfSameRootBlock ) ) { - return [ ...defaultStyle, styles.horizontalMarginNone ]; - } + return hasChildren ? styles.neutral : styles.full; + } + + applyBlockStyle() { + const { + isSelected, + isDimmed, + } = this.props; - return defaultStyle; + return [ + isSelected ? this.applySelectedBlockStyle() : this.applyUnSelectedBlockStyle(), + isDimmed && styles.dimmed, + ]; } render() { const { - borderStyle, clientId, - focusedBorderColor, icon, isSelected, isValid, - showTitle, title, showFloatingToolbar, parentId, - isDashed, - isDimmed, - isInnerBlock, - isNestedInnerBlock, - hasInnerBlock, - isMediaTextParent, isTouchable, } = this.props; - const borderColor = isSelected ? focusedBorderColor : 'transparent'; - const accessibilityLabel = this.getAccessibilityLabel(); return ( @@ -213,31 +207,15 @@ class BlockListBlock extends Component { accessible={ ! isSelected } accessibilityRole={ 'button' } > - - { showTitle && this.renderBlockTitle() } - - { isValid && this.getBlockForType() } - { ! isValid && + + { isValid && this.getBlockForType() } + { ! isValid && - } - + } { isSelected && } @@ -252,7 +230,6 @@ export default compose( [ getBlockIndex, getBlocks, isBlockSelected, - __unstableGetBlockWithoutInnerBlocks, getBlockHierarchyRootClientId, getSelectedBlockClientId, getBlock, @@ -263,18 +240,15 @@ export default compose( [ const order = getBlockIndex( clientId, rootClientId ); const isSelected = isBlockSelected( clientId ); const isLastBlock = order === getBlocks().length - 1; - const block = __unstableGetBlockWithoutInnerBlocks( clientId ); + const block = getBlock( clientId ); const { name, attributes, isValid } = block || {}; const blockType = getBlockType( name || 'core/missing' ); const title = blockType.title; const icon = blockType.icon; const getAccessibilityLabelExtra = blockType.__experimentalGetAccessibilityLabel; - const parentId = getBlockRootClientId( clientId ); - const parentBlock = getBlock( parentId ); - const isGroupParent = parentBlock && parentBlock.name === 'core/group'; - - const isMediaTextParent = parentBlock && parentBlock.name === 'core/media-text'; + const parents = getBlockParents( clientId, true ); + const parentId = parents[ 0 ] || ''; const rootBlockId = getBlockHierarchyRootClientId( clientId ); const rootBlock = getBlock( rootBlockId ); @@ -282,20 +256,20 @@ export default compose( [ const showFloatingToolbar = isSelected && hasRootInnerBlocks; - const firstToSelectId = getLowestCommonAncestorWithSelectedBlock( clientId ); - const selectedBlockClientId = getSelectedBlockClientId(); - const isRootSiblingsSelected = getBlockRootClientId( selectedBlockClientId ) === ''; - const isDashed = selectedBlockClientId === parentId; - const isDimmed = ! isSelected && ! isRootSiblingsSelected && !! selectedBlockClientId && firstToSelectId === clientId && ! isDashed; + const commonAncestor = getLowestCommonAncestorWithSelectedBlock( clientId ); + const clientTree = [ clientId, ...parents ]; + const commonAncestorIndex = clientTree.indexOf( commonAncestor ) - 1; + const firstToSelectId = commonAncestor || selectedBlockClientId ? clientTree[ commonAncestorIndex ] : rootBlockId; + + const hasChildren = block.innerBlocks.length !== 0; + const hasParent = !! parents[ 0 ]; + const isParentSelected = selectedBlockClientId && selectedBlockClientId === parents[ 0 ]; + const isAncestorSelected = selectedBlockClientId && parents.includes( selectedBlockClientId ); + const selectionIsNested = !! getBlockRootClientId( selectedBlockClientId ); + const isDimmed = ! isSelected && ! isAncestorSelected && firstToSelectId === clientId && selectionIsNested; - const isInnerBlock = parentId && firstToSelectId !== parentId; - const isChildOfSameRootBlock = rootBlockId === getBlockHierarchyRootClientId( selectedBlockClientId ); - const isNestedInnerBlock = ! isDashed && selectedBlockClientId === getBlockRootClientId( firstToSelectId ); - const hasInnerBlock = blockType.name === 'core/group' || blockType.name === 'core/media-text'; - const isParentSelected = parentId === selectedBlockClientId; - const isMediaTextChildSelected = blockType.name === 'core/media-text' && getBlockRootClientId( selectedBlockClientId ) === clientId; const isDescendantSelected = selectedBlockClientId && getBlockParents( selectedBlockClientId ).includes( clientId ); const isTouchable = isSelected || isDescendantSelected || selectedBlockClientId === parentId || parentId === ''; @@ -312,18 +286,14 @@ export default compose( [ getAccessibilityLabelExtra, showFloatingToolbar, parentId, - isDashed, - isDimmed, - firstToSelectId, - isInnerBlock, - isChildOfSameRootBlock, - isNestedInnerBlock, - hasInnerBlock, isParentSelected, - isMediaTextParent, - isGroupParent, + firstToSelectId, + hasChildren, + hasParent, + isAncestorSelected, isTouchable, - isMediaTextChildSelected, + isDimmed, + selectionIsNested, }; } ), withDispatch( ( dispatch, ownProps, { select } ) => { diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index 381fde3b6a344e..d42e9f2e1ba26f 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -2,102 +2,92 @@ flex: 1 1 auto; } -.blockContainer { - margin-left: 16px; - margin-right: 16px; - padding-top: 12px; - padding-bottom: 12px; +.semiSolidBordered { + border-top-width: $block-selected-border-width; + border-bottom-width: $block-selected-border-width; + border-left-width: 0; + border-right-width: 0; + border-style: solid; } -.blockContainerInner { - margin-left: 0; - margin-right: 0; - padding-left: 0; - padding-right: 0; - padding-top: 12px; - padding-bottom: 12px; +.fullSolidBordered { + border-width: $block-selected-border-width; + border-radius: 4px; + border-style: solid; } -.nestedBlockContainerInner { - margin-left: 0; - margin-right: 0; - padding-top: 9px; - padding-bottom: 9px; +.dashedBordered { + border-width: $block-selected-child-border-width; + border-radius: 2px; + border-style: dashed; } -.blockContainerDimmed { - opacity: 0.2; +.solidBorderColor { + border-color: $blue-wordpress; } -.blockHolderDashedBordered { - border-width: 1px; - border-radius: 2px; - border-style: dashed; +.solidBorderColorDark { + border-color: $blue-30; +} + +.dashedBorderColor { border-color: $gray; - padding: 5px; - margin-right: 0; - margin-left: 0; - margin-bottom: 6px; - margin-top: 6px; } -.blockHolderDashedBorderedDark { +.dashedBorderColorDark { border-color: $gray-70; } -.horizontalMarginNone { - margin-left: 0; - margin-right: 0; +.dimmed { + opacity: 0.2; } -.verticalPaddingNone { - padding-top: 0; - padding-bottom: 0; +.blockTitle { + background-color: $gray; + padding-left: 8px; + padding-top: 4px; + padding-bottom: 4px; } -.horizontalPaddingNone { - padding-left: 0; - padding-right: 0; +.neutral { + margin: 0; + border: 0; + padding: 0; } -.outlineBorderMargin { - margin-left: 9px; - margin-right: 9px; +.full { + margin: $block-edge-to-content; + border: 0; + padding: 0; } -.dashedBorderMargin { - margin-left: 5px; - margin-right: 5px; +.selectedLeaf { + margin: $block-selected-margin; + padding: $block-selected-to-content; } -.nestedFocusedBlock { - margin-left: 0; - margin-right: 0; - padding-left: 0; - padding-right: 0; - padding-top: 12px; - padding-bottom: 0; // will be flushed into inline toolbar height +.selectedRootLeaf { + margin: 0; + padding: $block-edge-to-content; } -.innerBlockContainerFocused { - padding-left: 12px; - padding-right: 12px; - padding-top: 12px; - padding-bottom: 0; // will be flushed into inline toolbar height +.selectedParent { + margin: $block-selected-margin; + padding: 0; } -.blockContainerFocused { - padding-left: 16px; - padding-right: 16px; - padding-top: 12px; - padding-bottom: 0; // will be flushed into inline toolbar height +.childOfSelected { + margin: $block-selected-child-margin; + padding: 0; } -.blockTitle { - background-color: $gray; - padding-left: 8px; - padding-top: 4px; - padding-bottom: 4px; +.childOfSelectedLeaf { + margin: $block-selected-child-margin; + padding: $block-selected-child-to-content; +} + +.descendantOfSelectedLeaf { + margin: $block-selected-child-to-content; } .aztec_container { diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index faa18991a0dc59..80748396403ccd 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -41,10 +41,6 @@ export class BlockList extends Component { this.props.insertBlock( newBlock, this.props.blockCount ); } - blockHolderBorderStyle() { - return this.props.isFullyBordered || this.props.hasFullBorder ? styles.blockHolderFullBordered : styles.blockHolderSemiBordered; - } - onCaretVerticalPositionChange( targetId, caretY, previousCaretY ) { KeyboardAwareFlatList.handleCaretVerticalPositionChange( this.scrollViewRef, targetId, caretY, previousCaretY ); } @@ -118,7 +114,6 @@ export class BlockList extends Component { } renderItem( { item: clientId, index } ) { - const blockHolderFocusedStyle = this.props.getStylesFromColorScheme( styles.blockHolderFocused, styles.blockHolderFocusedDark ); const { shouldShowBlockAtIndex, shouldShowInsertionPointBefore, shouldShowInsertionPointAfter } = this.props; return ( @@ -130,8 +125,7 @@ export class BlockList extends Component { clientId={ clientId } rootClientId={ this.props.rootClientId } onCaretVerticalPositionChange={ this.onCaretVerticalPositionChange } - borderStyle={ this.blockHolderBorderStyle() } - focusedBorderColor={ blockHolderFocusedStyle.borderColor } + isSmallScreen={ ! ReadableContentView.isContentMaxWidth() } /> ) } { shouldShowInsertionPointAfter( clientId ) && this.renderAddBlockSeparator() } @@ -172,7 +166,6 @@ export default compose( [ getBlockInsertionPoint, isBlockInsertionPointVisible, getSelectedBlock, - getBlockRootClientId, } = select( 'core/block-editor' ); const selectedBlockClientId = getSelectedBlockClientId(); @@ -219,8 +212,6 @@ export default compose( [ return ! shouldHideBlockAtIndex; }; - const hasFullBorder = !! getBlockRootClientId( selectedBlockClientId ) || hasInnerBlocks; - return { blockClientIds, blockCount: getBlockCount( rootClientId ), @@ -229,7 +220,6 @@ export default compose( [ shouldShowInsertionPointBefore, shouldShowInsertionPointAfter, selectedBlockClientId, - hasFullBorder, isRootList: rootClientId === undefined, }; } ), diff --git a/packages/block-editor/src/components/block-list/style.native.scss b/packages/block-editor/src/components/block-list/style.native.scss index efc4ff4cde6a2e..adc601d45810e8 100644 --- a/packages/block-editor/src/components/block-list/style.native.scss +++ b/packages/block-editor/src/components/block-list/style.native.scss @@ -4,13 +4,6 @@ background-color: #fff; } -.list { - flex: 1; - padding-left: 0; - padding-right: 0; - margin: 0; -} - .switch { flex-direction: row; justify-content: flex-start; @@ -49,36 +42,6 @@ flex-direction: row; } -.blockHolderSemiBordered { - border-top-width: 1px; - border-bottom-width: 1px; - border-left-width: 0; - border-right-width: 0; -} - -.blockHolderFullBordered { - border-top-width: 1px; - border-bottom-width: 1px; - border-left-width: 1px; - border-right-width: 1px; - border-radius: 4px; -} - -.blockContainerFocused { - padding-left: 16; - padding-right: 16; - padding-top: 12; - padding-bottom: 0; // will be flushed into inline toolbar height -} - -.blockHolderFocused { - border-color: $blue-wordpress; -} - -.blockHolderFocusedDark { - border-color: $blue-30; -} - .blockListFooter { height: 80px; } diff --git a/packages/block-editor/src/components/button-block-appender/styles.native.scss b/packages/block-editor/src/components/button-block-appender/styles.native.scss index db3aac95d99a8e..05b8adb13c549c 100644 --- a/packages/block-editor/src/components/button-block-appender/styles.native.scss +++ b/packages/block-editor/src/components/button-block-appender/styles.native.scss @@ -2,8 +2,8 @@ align-items: center; justify-content: center; padding: 12px; - margin-left: 9px; - margin-right: 9px; + margin-left: 4px; + margin-right: 4px; background-color: $white; border: $border-width solid $light-gray-500; border-radius: 4px; diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 33216d5215ff38..99fd8d6cd9be69 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -426,7 +426,7 @@ export function getBlockRootClientId( state, clientId ) { * * @param {Object} state Editor state. * @param {string} clientId Block from which to find root client ID. - * @param {boolean} ascending Get parent hierarchy in (top-most hierarchy first). + * @param {boolean} ascending Get parent hierarchy in top-most hierarchy first (false) or reversed (true). * * @return {Array} ClientIDs of the parent blocks. */ @@ -465,45 +465,30 @@ export function getBlockHierarchyRootClientId( state, clientId ) { } /** - * Given a block client ID, returns the next element of the hierarchy from which the block is nested which should be selected onFocus, return the block itself for root level blocks. + * Given a block client ID, returns the common ancestor with selected client ID. * * @param {Object} state Editor state. - * @param {string} clientId Block from which to find first to select client ID. + * @param {string} clientId Block from which to find common ancestor client ID. * - * @return {string} First to select client ID + * @return {string} Common ancestor client ID or undefined */ export function getLowestCommonAncestorWithSelectedBlock( state, clientId ) { const selectedId = getSelectedBlockClientId( state ); - const clientTree = [ clientId, ...getBlockParents( state, clientId, true ) ]; - const rootParent = clientTree[ clientTree.length - 1 ]; + const clientParents = [ ...getBlockParents( state, clientId ), clientId ]; + const selectedParents = [ ...getBlockParents( state, selectedId ), selectedId ]; - let index = 0; - let commonParentFirstChild; - let hasCommonParent = false; + let lowestCommonAncestor; - if ( ! selectedId ) { - return rootParent; - } - - const selectedRoot = getBlockHierarchyRootClientId( state, selectedId ); - const clientRoot = getBlockHierarchyRootClientId( state, clientId ); - - if ( selectedRoot !== clientRoot ) { - return rootParent; - } - - const selectedTree = [ selectedId, ...getBlockParents( state, selectedId, true ) ]; - - do { - const commonParentIndex = clientTree.indexOf( selectedTree[ index ] ); - hasCommonParent = commonParentIndex >= 0; - if ( hasCommonParent ) { - commonParentFirstChild = clientTree[ commonParentIndex - 1 ]; + const maxDepth = Math.min( clientParents.length, selectedParents.length ); + for ( let index = 0; index < maxDepth; index++ ) { + if ( clientParents[ index ] === selectedParents[ index ] ) { + lowestCommonAncestor = clientParents[ index ]; + } else { + break; } - index++; - } while ( index < selectedTree.length && ! hasCommonParent ); + } - return commonParentFirstChild; + return lowestCommonAncestor; } /** diff --git a/packages/block-library/src/group/edit.native.js b/packages/block-library/src/group/edit.native.js index 2b8b37857e72ca..3fa539252d60ac 100644 --- a/packages/block-library/src/group/edit.native.js +++ b/packages/block-library/src/group/edit.native.js @@ -22,20 +22,11 @@ function GroupEdit( { hasInnerBlocks, isSelected, getStylesFromColorScheme, - isInnerBlock, - isDashed, - isChildOfSameRootBlook, - isParentSelected, - isGroupParent, } ) { if ( ! isSelected && ! hasInnerBlocks ) { return ( ); } diff --git a/packages/block-library/src/group/editor.native.scss b/packages/block-library/src/group/editor.native.scss index b93b8e997a024a..a9bd31ed59e63d 100644 --- a/packages/block-library/src/group/editor.native.scss +++ b/packages/block-library/src/group/editor.native.scss @@ -1,27 +1,13 @@ .groupPlaceholder { padding: 12px; margin: 16px; + margin-left: 2px; + margin-right: 2px; background-color: $white; border: $border-width dashed $gray; border-radius: 4px; } -.groupPlaceholderInner { - border: 0; - padding: 4px; -} - -.groupPlaceholderMarginVerticalNone { - margin-top: 0; - margin-bottom: 0; - padding: 12px; -} - -.groupPlaceholderMarginHorizontalNone { - margin-left: 0; - margin-right: 0; -} - .groupPlaceholderDark { background-color: $black; border: $border-width dashed $gray-70; diff --git a/packages/block-library/src/media-text/edit.native.js b/packages/block-library/src/media-text/edit.native.js index 61a8d889f7d7e0..efc4d24618acf5 100644 --- a/packages/block-library/src/media-text/edit.native.js +++ b/packages/block-library/src/media-text/edit.native.js @@ -18,6 +18,7 @@ import { Component } from '@wordpress/element'; import { Toolbar, } from '@wordpress/components'; +import { withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { withViewportMatch } from '@wordpress/viewport'; @@ -121,8 +122,7 @@ class MediaTextEdit extends Component { onWidthChange={ this.onWidthChange } commitWidthChange={ this.commitWidthChange } onFocus={ this.props.onFocus } - isSelected={ isSelected } - { ...{ mediaAlt, mediaId, mediaType, mediaUrl, mediaPosition, mediaWidth, imageFill, focalPoint } } + { ...{ mediaAlt, mediaId, mediaType, mediaUrl, mediaPosition, mediaWidth, imageFill, focalPoint, isSelected } } /> ); } @@ -132,8 +132,9 @@ class MediaTextEdit extends Component { attributes, backgroundColor, setAttributes, - isSelected, isMobile, + isSelected, + isParentSelected, } = this.props; const { isStackedOnMobile, @@ -144,8 +145,10 @@ class MediaTextEdit extends Component { const shouldStack = isStackedOnMobile && isMobile; const temporaryMediaWidth = shouldStack ? 100 : ( this.state.mediaWidth || mediaWidth ); const widthString = `${ temporaryMediaWidth }%`; - const mediaContainerPadding = ( ( this.props.isInnerBlock || this.props.parentId ) && ! this.props.isMediaTextChildSelected ) ? 0 : 16; - const mediaContainerVerticalPadding = isSelected ? 8 : 5; + + // TODO + const parentSelectedStyle = isParentSelected ? { margin: 2 } : { margin: 0, border: 0, padding: 0 }; + const selectedStyle = isSelected ? { margin: 0 } : parentSelectedStyle; const containerStyles = { ...styles[ 'wp-block-media-text' ], ...styles[ `is-vertically-aligned-${ verticalAlignment || 'center' }` ], @@ -153,9 +156,6 @@ class MediaTextEdit extends Component { ...( shouldStack ? styles[ 'is-stacked-on-mobile' ] : {} ), ...( shouldStack && mediaPosition === 'right' ? styles[ 'is-stacked-on-mobile.has-media-on-the-right' ] : {} ), backgroundColor: backgroundColor.color, - ...{ padding: isSelected ? 8 : mediaContainerPadding }, - paddingTop: mediaContainerVerticalPadding, - paddingBottom: mediaContainerVerticalPadding, }; const innerBlockWidth = shouldStack ? 100 : ( 100 - temporaryMediaWidth ); const innerBlockWidthString = `${ innerBlockWidth }%`; @@ -189,23 +189,15 @@ class MediaTextEdit extends Component { /> - + { this.renderMediaArea() } - - - - + + @@ -215,5 +207,18 @@ class MediaTextEdit extends Component { export default compose( withColors( 'backgroundColor' ), - withViewportMatch( { isMobile: '< small' } ) + withViewportMatch( { isMobile: '< small' } ), + withSelect( ( select, { clientId } ) => { + const { + getSelectedBlockClientId, + } = select( 'core/block-editor' ); + + const selectedBlockClientId = getSelectedBlockClientId(); + const isParentSelected = selectedBlockClientId && selectedBlockClientId === clientId; + + return { + isSelected: selectedBlockClientId === clientId, + isParentSelected, + }; + } ), )( MediaTextEdit ); diff --git a/packages/block-library/src/media-text/media-container.js b/packages/block-library/src/media-text/media-container.js index cb45eb27830047..fce35487d6a03b 100644 --- a/packages/block-library/src/media-text/media-container.js +++ b/packages/block-library/src/media-text/media-container.js @@ -93,7 +93,7 @@ class MediaContainer extends Component { } renderPlaceholder() { - const { onSelectMedia, className, noticeUI, style } = this.props; + const { onSelectMedia, className, noticeUI } = this.props; return ( } @@ -106,7 +106,6 @@ class MediaContainer extends Component { allowedTypes={ ALLOWED_MEDIA_TYPES } notices={ noticeUI } onError={ this.onUploadError } - style={ style } /> ); } diff --git a/packages/edit-post/src/components/visual-editor/style.native.scss b/packages/edit-post/src/components/visual-editor/style.native.scss index 4ade220b5dd9e9..59587f891a5e07 100644 --- a/packages/edit-post/src/components/visual-editor/style.native.scss +++ b/packages/edit-post/src/components/visual-editor/style.native.scss @@ -13,9 +13,9 @@ } .blockHolderFocused { - border-color: $gray-lighten-30; + border-color: $blue-wordpress; } .blockHolderFocusedDark { - border-color: $gray-70; + border-color: $blue-30; } diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index 4d456f2b4ee320..b2355b52e15b8f 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -119,20 +119,15 @@ export default compose( isPostTitleSelected, } = select( 'core/editor' ); - const { getSelectedBlockClientId, getBlockRootClientId, getLowestCommonAncestorWithSelectedBlock } = select( 'core/block-editor' ); + const { getSelectedBlockClientId, getBlockRootClientId } = select( 'core/block-editor' ); - const clientId = getSelectedBlockClientId(); - const isAnyBlockSelected = !! clientId; - const parentId = getBlockRootClientId( clientId ); - const firstToSelect = getLowestCommonAncestorWithSelectedBlock( clientId ); - const isInnerBlock = parentId && firstToSelect !== parentId; - - const isDimmed = isAnyBlockSelected && isInnerBlock; + const selectedId = getSelectedBlockClientId(); + const selectionIsNested = !! getBlockRootClientId( selectedId ); return { - isAnyBlockSelected, + isAnyBlockSelected: !! selectedId, isSelected: isPostTitleSelected(), - isDimmed, + isDimmed: selectionIsNested, }; } ), withDispatch( ( dispatch ) => { From 941baefe9463785a013a006d6e48046957f22138 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Fri, 22 Nov 2019 20:52:02 +0100 Subject: [PATCH 052/183] fix dimed style aplied --- packages/block-editor/src/components/block-list/block.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 6945bbc110e809..772ffb8387830a 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -268,10 +268,10 @@ export default compose( [ const isParentSelected = selectedBlockClientId && selectedBlockClientId === parents[ 0 ]; const isAncestorSelected = selectedBlockClientId && parents.includes( selectedBlockClientId ); const selectionIsNested = !! getBlockRootClientId( selectedBlockClientId ); - const isDimmed = ! isSelected && ! isAncestorSelected && firstToSelectId === clientId && selectionIsNested; const isDescendantSelected = selectedBlockClientId && getBlockParents( selectedBlockClientId ).includes( clientId ); const isTouchable = isSelected || isDescendantSelected || selectedBlockClientId === parentId || parentId === ''; + const isDimmed = ! isSelected && selectionIsNested && ! isAncestorSelected && ! isDescendantSelected && ( firstToSelectId === clientId || rootBlockId === clientId ); return { icon, From df0f5a48d674adaaf9dcd8b8fac28ff55acc83f8 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Mon, 25 Nov 2019 11:37:14 +0100 Subject: [PATCH 053/183] add unit test for selector --- .../block-editor/src/store/test/selectors.js | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 6000c76b824f0e..d1be26d50fda04 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -62,6 +62,7 @@ const { INSERTER_UTILITY_HIGH, INSERTER_UTILITY_MEDIUM, INSERTER_UTILITY_LOW, + getLowestCommonAncestorWithSelectedBlock, } = selectors; describe( 'selectors', () => { @@ -2343,4 +2344,83 @@ describe( 'selectors', () => { } ); } ); } ); + + describe( 'getLowestCommonAncestorWithSelectedBlock', () => { + const blocks = { + order: { + '': [ 'a', 'b' ], + a: [ 'c', 'd' ], + d: [ 'e' ], + f: [ 'b' ], + }, + parents: { + a: '', + b: '', + c: 'a', + d: 'a', + e: 'd', + f: 'b', + }, + }; + + it( 'should not be defined if there is no block selected', () => { + const state = { + blocks, + selectionStart: {}, + selectionEnd: {}, + }; + + expect( getLowestCommonAncestorWithSelectedBlock( state, 'd' ) ).not.toBeDefined(); + } ); + + it( 'should not be defined if selected block has no parent', () => { + const state = { + blocks, + selectionStart: { clientId: 'b' }, + selectionEnd: { clientId: 'b' }, + }; + + expect( getLowestCommonAncestorWithSelectedBlock( state, 'b' ) ).toBe( 'b' ); + } ); + + it( 'should not be defined if selected block has no common parent with given block', () => { + const state = { + blocks, + selectionStart: { clientId: 'd' }, + selectionEnd: { clientId: 'd' }, + }; + + expect( getLowestCommonAncestorWithSelectedBlock( state, 'f' ) ).not.toBeDefined(); + } ); + + it( 'should return block id if selected block is ancestor of block', () => { + const state = { + blocks, + selectionStart: { clientId: 'c' }, + selectionEnd: { clientId: 'c' }, + }; + + expect( getLowestCommonAncestorWithSelectedBlock( state, 'a' ) ).toBe( 'a' ); + } ); + + it( 'should return block id if selected block is nested child of given block', () => { + const state = { + blocks, + selectionStart: { clientId: 'e' }, + selectionEnd: { clientId: 'e' }, + }; + + expect( getLowestCommonAncestorWithSelectedBlock( state, 'a' ) ).toBe( 'a' ); + } ); + + it( 'should return block id if selected block has common parent with given block', () => { + const state = { + blocks, + selectionStart: { clientId: 'e' }, + selectionEnd: { clientId: 'e' }, + }; + + expect( getLowestCommonAncestorWithSelectedBlock( state, 'c' ) ).toBe( 'a' ); + } ); + } ); } ); From 8ab7ebe1f04ac3f401aeb37941aae9bd7f6e5b08 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Mon, 25 Nov 2019 12:45:19 +0100 Subject: [PATCH 054/183] use getBlockCount to check if block has children --- .../block-editor/src/components/block-list/block.native.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 772ffb8387830a..7513897fb6fbaa 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -230,17 +230,19 @@ export default compose( [ getBlockIndex, getBlocks, isBlockSelected, + __unstableGetBlockWithoutInnerBlocks, getBlockHierarchyRootClientId, getSelectedBlockClientId, getBlock, getBlockRootClientId, getLowestCommonAncestorWithSelectedBlock, getBlockParents, + getBlockCount, } = select( 'core/block-editor' ); const order = getBlockIndex( clientId, rootClientId ); const isSelected = isBlockSelected( clientId ); const isLastBlock = order === getBlocks().length - 1; - const block = getBlock( clientId ); + const block = __unstableGetBlockWithoutInnerBlocks( clientId ); const { name, attributes, isValid } = block || {}; const blockType = getBlockType( name || 'core/missing' ); const title = blockType.title; @@ -263,7 +265,7 @@ export default compose( [ const commonAncestorIndex = clientTree.indexOf( commonAncestor ) - 1; const firstToSelectId = commonAncestor || selectedBlockClientId ? clientTree[ commonAncestorIndex ] : rootBlockId; - const hasChildren = block.innerBlocks.length !== 0; + const hasChildren = getBlockCount( clientId ) !== 0; const hasParent = !! parents[ 0 ]; const isParentSelected = selectedBlockClientId && selectedBlockClientId === parents[ 0 ]; const isAncestorSelected = selectedBlockClientId && parents.includes( selectedBlockClientId ); From 3ebc50cae041b90c06f0e29b6bb294a7ed56dc25 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Mon, 25 Nov 2019 08:18:31 +0100 Subject: [PATCH 055/183] fix group margins, placeholder and appender --- .../src/components/block-list/block.native.js | 24 +++++- .../components/block-list/block.native.scss | 12 +++ .../src/components/block-list/index.native.js | 13 +-- .../button-block-appender/styles.native.scss | 6 +- .../block-editor/src/store/test/selectors.js | 80 +++++++++++++++++++ .../block-library/src/group/edit.native.js | 1 + .../src/group/editor.native.scss | 9 ++- .../src/media-text/edit.native.js | 7 +- 8 files changed, 133 insertions(+), 19 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 772ffb8387830a..37c39021b99134 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -108,6 +108,8 @@ class BlockListBlock extends Component { getStylesFromColorScheme, isSmallScreen, selectionIsNested, + isGroupType, + isEmptyGroup, } = this.props; const fullSolidBorderStyle = { @@ -119,7 +121,7 @@ class BlockListBlock extends Component { return { ...styles.selectedParent, ...fullSolidBorderStyle }; } - if ( isSmallScreen && ! selectionIsNested ) { + if ( isSmallScreen && ! selectionIsNested && ! isGroupType ) { return { ...styles.selectedRootLeaf, ...styles.semiSolidBordered, @@ -127,6 +129,10 @@ class BlockListBlock extends Component { }; } + if ( isGroupType && isEmptyGroup ) { + return { ...styles.selectedLeaf, ...fullSolidBorderStyle, ... { paddingHorizontal: 1 } }; + } + return { ...styles.selectedLeaf, ...fullSolidBorderStyle }; } @@ -137,6 +143,8 @@ class BlockListBlock extends Component { isAncestorSelected, hasParent, getStylesFromColorScheme, + isEmptyGroup, + isGroupType, } = this.props; if ( ! hasParent ) { @@ -150,12 +158,15 @@ class BlockListBlock extends Component { }; return hasChildren ? - { ...styles.childOfSelected, ...dashedBorderStyle } : - { ...styles.childOfSelectedLeaf, ...dashedBorderStyle }; + { ...styles.childOfSelected, ...( ! isEmptyGroup ? dashedBorderStyle : styles.horizontalSpaceNone ) } : + { ...styles.childOfSelectedLeaf, ...( ! isEmptyGroup ? dashedBorderStyle : styles.horizontalSpaceNone ) }; } if ( isAncestorSelected ) { - return styles.descendantOfSelectedLeaf; + return { + ...styles.descendantOfSelectedLeaf, + ...( isGroupType && ! isEmptyGroup && styles.marginHorizontalNone ), + }; } return hasChildren ? styles.neutral : styles.full; @@ -273,6 +284,9 @@ export default compose( [ const isTouchable = isSelected || isDescendantSelected || selectedBlockClientId === parentId || parentId === ''; const isDimmed = ! isSelected && selectionIsNested && ! isAncestorSelected && ! isDescendantSelected && ( firstToSelectId === clientId || rootBlockId === clientId ); + const isGroupType = name === 'core/group'; + const isEmptyGroup = isGroupType && block.innerBlocks.length === 0; + return { icon, name: name || 'core/missing', @@ -294,6 +308,8 @@ export default compose( [ isTouchable, isDimmed, selectionIsNested, + isGroupType, + isEmptyGroup, }; } ), withDispatch( ( dispatch, ownProps, { select } ) => { diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index d42e9f2e1ba26f..ff9cea835a83c9 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -42,6 +42,18 @@ opacity: 0.2; } +.horizontalSpaceNone { + padding-left: 0; + padding-right: 0; + margin-left: 0; + margin-right: 0; +} + +.marginHorizontalNone { + margin-left: 0; + margin-right: 0; +} + .blockTitle { background-color: $gray; padding-left: 8px; diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 80748396403ccd..c4e7b923ce434f 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -96,11 +96,14 @@ export class BlockList extends Component { ListFooterComponent={ withFooter && this.renderBlockListFooter } /> - { renderAppender && blockClientIds.length > 0 && - + { renderAppender && blockClientIds.length > 0 && ( + + + + ) } ); diff --git a/packages/block-editor/src/components/button-block-appender/styles.native.scss b/packages/block-editor/src/components/button-block-appender/styles.native.scss index 05b8adb13c549c..591fd011a241a6 100644 --- a/packages/block-editor/src/components/button-block-appender/styles.native.scss +++ b/packages/block-editor/src/components/button-block-appender/styles.native.scss @@ -1,9 +1,9 @@ .appender { align-items: center; justify-content: center; - padding: 12px; - margin-left: 4px; - margin-right: 4px; + padding: 9px; + margin-left: 0; + margin-right: 0; background-color: $white; border: $border-width solid $light-gray-500; border-radius: 4px; diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 6000c76b824f0e..d1be26d50fda04 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -62,6 +62,7 @@ const { INSERTER_UTILITY_HIGH, INSERTER_UTILITY_MEDIUM, INSERTER_UTILITY_LOW, + getLowestCommonAncestorWithSelectedBlock, } = selectors; describe( 'selectors', () => { @@ -2343,4 +2344,83 @@ describe( 'selectors', () => { } ); } ); } ); + + describe( 'getLowestCommonAncestorWithSelectedBlock', () => { + const blocks = { + order: { + '': [ 'a', 'b' ], + a: [ 'c', 'd' ], + d: [ 'e' ], + f: [ 'b' ], + }, + parents: { + a: '', + b: '', + c: 'a', + d: 'a', + e: 'd', + f: 'b', + }, + }; + + it( 'should not be defined if there is no block selected', () => { + const state = { + blocks, + selectionStart: {}, + selectionEnd: {}, + }; + + expect( getLowestCommonAncestorWithSelectedBlock( state, 'd' ) ).not.toBeDefined(); + } ); + + it( 'should not be defined if selected block has no parent', () => { + const state = { + blocks, + selectionStart: { clientId: 'b' }, + selectionEnd: { clientId: 'b' }, + }; + + expect( getLowestCommonAncestorWithSelectedBlock( state, 'b' ) ).toBe( 'b' ); + } ); + + it( 'should not be defined if selected block has no common parent with given block', () => { + const state = { + blocks, + selectionStart: { clientId: 'd' }, + selectionEnd: { clientId: 'd' }, + }; + + expect( getLowestCommonAncestorWithSelectedBlock( state, 'f' ) ).not.toBeDefined(); + } ); + + it( 'should return block id if selected block is ancestor of block', () => { + const state = { + blocks, + selectionStart: { clientId: 'c' }, + selectionEnd: { clientId: 'c' }, + }; + + expect( getLowestCommonAncestorWithSelectedBlock( state, 'a' ) ).toBe( 'a' ); + } ); + + it( 'should return block id if selected block is nested child of given block', () => { + const state = { + blocks, + selectionStart: { clientId: 'e' }, + selectionEnd: { clientId: 'e' }, + }; + + expect( getLowestCommonAncestorWithSelectedBlock( state, 'a' ) ).toBe( 'a' ); + } ); + + it( 'should return block id if selected block has common parent with given block', () => { + const state = { + blocks, + selectionStart: { clientId: 'e' }, + selectionEnd: { clientId: 'e' }, + }; + + expect( getLowestCommonAncestorWithSelectedBlock( state, 'c' ) ).toBe( 'a' ); + } ); + } ); } ); diff --git a/packages/block-library/src/group/edit.native.js b/packages/block-library/src/group/edit.native.js index 3fa539252d60ac..f6f9c74d31fb35 100644 --- a/packages/block-library/src/group/edit.native.js +++ b/packages/block-library/src/group/edit.native.js @@ -27,6 +27,7 @@ function GroupEdit( { return ( ); } diff --git a/packages/block-library/src/group/editor.native.scss b/packages/block-library/src/group/editor.native.scss index a9bd31ed59e63d..2c069442b15ac4 100644 --- a/packages/block-library/src/group/editor.native.scss +++ b/packages/block-library/src/group/editor.native.scss @@ -1,8 +1,8 @@ .groupPlaceholder { padding: 12px; margin: 16px; - margin-left: 2px; - margin-right: 2px; + margin-left: 0; + margin-right: 0; background-color: $white; border: $border-width dashed $gray; border-radius: 4px; @@ -12,3 +12,8 @@ background-color: $black; border: $border-width dashed $gray-70; } + +.marginVerticalDense { + margin-top: $block-selected-child-margin; + margin-bottom: $block-selected-child-margin; +} diff --git a/packages/block-library/src/media-text/edit.native.js b/packages/block-library/src/media-text/edit.native.js index efc4d24618acf5..19fb714927f39d 100644 --- a/packages/block-library/src/media-text/edit.native.js +++ b/packages/block-library/src/media-text/edit.native.js @@ -134,7 +134,6 @@ class MediaTextEdit extends Component { setAttributes, isMobile, isSelected, - isParentSelected, } = this.props; const { isStackedOnMobile, @@ -146,9 +145,7 @@ class MediaTextEdit extends Component { const temporaryMediaWidth = shouldStack ? 100 : ( this.state.mediaWidth || mediaWidth ); const widthString = `${ temporaryMediaWidth }%`; - // TODO - const parentSelectedStyle = isParentSelected ? { margin: 2 } : { margin: 0, border: 0, padding: 0 }; - const selectedStyle = isSelected ? { margin: 0 } : parentSelectedStyle; + const selectedStyle = ! shouldStack && { paddingRight: mediaPosition === 'rigth' ? 0 : 12, paddingLeft: mediaPosition === 'left' ? 0 : 12 }; const containerStyles = { ...styles[ 'wp-block-media-text' ], ...styles[ `is-vertically-aligned-${ verticalAlignment || 'center' }` ], @@ -189,7 +186,7 @@ class MediaTextEdit extends Component { /> - + { this.renderMediaArea() } From cd5b6084a780c53a98bf7b26b32cded9f16f5f58 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Mon, 25 Nov 2019 13:56:19 +0100 Subject: [PATCH 056/183] adjustments --- .../src/components/block-list/block.native.js | 14 +++++--------- packages/block-editor/src/store/test/selectors.js | 2 +- .../block-library/src/group/editor.native.scss | 4 ++-- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 3350f72bf12f05..9780ca45eb5190 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -109,7 +109,6 @@ class BlockListBlock extends Component { isSmallScreen, selectionIsNested, isGroupType, - isEmptyGroup, } = this.props; const fullSolidBorderStyle = { @@ -129,7 +128,7 @@ class BlockListBlock extends Component { }; } - if ( isGroupType && isEmptyGroup ) { + if ( isGroupType && ! hasChildren ) { return { ...styles.selectedLeaf, ...fullSolidBorderStyle, ... { paddingHorizontal: 1 } }; } @@ -143,7 +142,6 @@ class BlockListBlock extends Component { isAncestorSelected, hasParent, getStylesFromColorScheme, - isEmptyGroup, isGroupType, } = this.props; @@ -158,14 +156,14 @@ class BlockListBlock extends Component { }; return hasChildren ? - { ...styles.childOfSelected, ...( ! isEmptyGroup ? dashedBorderStyle : styles.horizontalSpaceNone ) } : - { ...styles.childOfSelectedLeaf, ...( ! isEmptyGroup ? dashedBorderStyle : styles.horizontalSpaceNone ) }; + { ...styles.childOfSelected, ...( isGroupType ? dashedBorderStyle : styles.horizontalSpaceNone ) } : + { ...styles.childOfSelectedLeaf, ...( ! isGroupType ? dashedBorderStyle : styles.horizontalSpaceNone ) }; } if ( isAncestorSelected ) { return { ...styles.descendantOfSelectedLeaf, - ...( isGroupType && ! isEmptyGroup && styles.marginHorizontalNone ), + ...( isGroupType && styles.marginHorizontalNone ), }; } @@ -276,7 +274,7 @@ export default compose( [ const commonAncestorIndex = clientTree.indexOf( commonAncestor ) - 1; const firstToSelectId = commonAncestor || selectedBlockClientId ? clientTree[ commonAncestorIndex ] : rootBlockId; - const hasChildren = getBlockCount( clientId ) !== 0; + const hasChildren = !! getBlockCount( clientId ); const hasParent = !! parents[ 0 ]; const isParentSelected = selectedBlockClientId && selectedBlockClientId === parents[ 0 ]; const isAncestorSelected = selectedBlockClientId && parents.includes( selectedBlockClientId ); @@ -287,7 +285,6 @@ export default compose( [ const isDimmed = ! isSelected && selectionIsNested && ! isAncestorSelected && ! isDescendantSelected && ( firstToSelectId === clientId || rootBlockId === clientId ); const isGroupType = name === 'core/group'; - const isEmptyGroup = isGroupType && block.innerBlocks.length === 0; return { icon, @@ -311,7 +308,6 @@ export default compose( [ isDimmed, selectionIsNested, isGroupType, - isEmptyGroup, }; } ), withDispatch( ( dispatch, ownProps, { select } ) => { diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index d1be26d50fda04..18a5894f9ac910 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -2393,7 +2393,7 @@ describe( 'selectors', () => { expect( getLowestCommonAncestorWithSelectedBlock( state, 'f' ) ).not.toBeDefined(); } ); - it( 'should return block id if selected block is ancestor of block', () => { + it( 'should return block id if selected block is ancestor of given block', () => { const state = { blocks, selectionStart: { clientId: 'c' }, diff --git a/packages/block-library/src/group/editor.native.scss b/packages/block-library/src/group/editor.native.scss index 2c069442b15ac4..0404d8897f6e44 100644 --- a/packages/block-library/src/group/editor.native.scss +++ b/packages/block-library/src/group/editor.native.scss @@ -1,8 +1,8 @@ .groupPlaceholder { padding: 12px; margin: 16px; - margin-left: 0; - margin-right: 0; + margin-left: $block-selected-child-margin; + margin-right: $block-selected-child-margin; background-color: $white; border: $border-width dashed $gray; border-radius: 4px; From bd5a1f83f4aacaa042dff1a1d22197301f93dd8a Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Tue, 26 Nov 2019 09:21:00 +0100 Subject: [PATCH 057/183] add parameters to style --- .../src/components/button-block-appender/styles.native.scss | 5 +++++ packages/block-library/src/group/editor.native.scss | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/button-block-appender/styles.native.scss b/packages/block-editor/src/components/button-block-appender/styles.native.scss index 591fd011a241a6..fea218077ceb94 100644 --- a/packages/block-editor/src/components/button-block-appender/styles.native.scss +++ b/packages/block-editor/src/components/button-block-appender/styles.native.scss @@ -26,3 +26,8 @@ color: $background-dark-secondary; background-color: $gray-40; } + +.paddingToContent { + padding-left: $block-selected-to-content; + padding-right: $block-selected-to-content; +} diff --git a/packages/block-library/src/group/editor.native.scss b/packages/block-library/src/group/editor.native.scss index 0404d8897f6e44..a8db71c8c636f6 100644 --- a/packages/block-library/src/group/editor.native.scss +++ b/packages/block-library/src/group/editor.native.scss @@ -1,6 +1,6 @@ .groupPlaceholder { - padding: 12px; - margin: 16px; + padding: $block-selected-to-content; + margin: $block-edge-to-content; margin-left: $block-selected-child-margin; margin-right: $block-selected-child-margin; background-color: $white; From 3219ad0d1f5eae0cf61ed99b74f46c49f1cfad86 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Tue, 26 Nov 2019 15:28:40 +0100 Subject: [PATCH 058/183] refactor for removing isGroupType --- .../src/components/block-list/block.native.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 9780ca45eb5190..ef48898baac5d5 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -108,7 +108,7 @@ class BlockListBlock extends Component { getStylesFromColorScheme, isSmallScreen, selectionIsNested, - isGroupType, + isInnerBlockHolder, } = this.props; const fullSolidBorderStyle = { @@ -120,7 +120,7 @@ class BlockListBlock extends Component { return { ...styles.selectedParent, ...fullSolidBorderStyle }; } - if ( isSmallScreen && ! selectionIsNested && ! isGroupType ) { + if ( isSmallScreen && ! selectionIsNested && ! isInnerBlockHolder ) { return { ...styles.selectedRootLeaf, ...styles.semiSolidBordered, @@ -128,10 +128,6 @@ class BlockListBlock extends Component { }; } - if ( isGroupType && ! hasChildren ) { - return { ...styles.selectedLeaf, ...fullSolidBorderStyle, ... { paddingHorizontal: 1 } }; - } - return { ...styles.selectedLeaf, ...fullSolidBorderStyle }; } @@ -142,7 +138,6 @@ class BlockListBlock extends Component { isAncestorSelected, hasParent, getStylesFromColorScheme, - isGroupType, } = this.props; if ( ! hasParent ) { @@ -156,14 +151,14 @@ class BlockListBlock extends Component { }; return hasChildren ? - { ...styles.childOfSelected, ...( isGroupType ? dashedBorderStyle : styles.horizontalSpaceNone ) } : - { ...styles.childOfSelectedLeaf, ...( ! isGroupType ? dashedBorderStyle : styles.horizontalSpaceNone ) }; + { ...styles.childOfSelected, ...dashedBorderStyle } : + { ...styles.childOfSelectedLeaf, ...dashedBorderStyle }; } if ( isAncestorSelected ) { return { ...styles.descendantOfSelectedLeaf, - ...( isGroupType && styles.marginHorizontalNone ), + ...( hasChildren && styles.marginHorizontalNone ), }; } @@ -248,6 +243,11 @@ export default compose( [ getBlockParents, getBlockCount, } = select( 'core/block-editor' ); + + const { + getGroupingBlockName, + } = select( 'core/blocks' ); + const order = getBlockIndex( clientId, rootClientId ); const isSelected = isBlockSelected( clientId ); const isLastBlock = order === getBlocks().length - 1; @@ -284,7 +284,7 @@ export default compose( [ const isTouchable = isSelected || isDescendantSelected || selectedBlockClientId === parentId || parentId === ''; const isDimmed = ! isSelected && selectionIsNested && ! isAncestorSelected && ! isDescendantSelected && ( firstToSelectId === clientId || rootBlockId === clientId ); - const isGroupType = name === 'core/group'; + const isInnerBlockHolder = name === getGroupingBlockName(); return { icon, @@ -307,7 +307,7 @@ export default compose( [ isTouchable, isDimmed, selectionIsNested, - isGroupType, + isInnerBlockHolder, }; } ), withDispatch( ( dispatch, ownProps, { select } ) => { From e3b540acc8f3daf77427dc644af5dd5302865d7b Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Tue, 26 Nov 2019 23:41:15 +0100 Subject: [PATCH 059/183] refactor group placeholder --- packages/block-library/src/group/edit.native.js | 2 +- packages/block-library/src/group/editor.native.scss | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/group/edit.native.js b/packages/block-library/src/group/edit.native.js index f6f9c74d31fb35..3cfdd34700b81f 100644 --- a/packages/block-library/src/group/edit.native.js +++ b/packages/block-library/src/group/edit.native.js @@ -27,7 +27,7 @@ function GroupEdit( { return ( ); } diff --git a/packages/block-library/src/group/editor.native.scss b/packages/block-library/src/group/editor.native.scss index a8db71c8c636f6..2f426176fe22a2 100644 --- a/packages/block-library/src/group/editor.native.scss +++ b/packages/block-library/src/group/editor.native.scss @@ -17,3 +17,8 @@ margin-top: $block-selected-child-margin; margin-bottom: $block-selected-child-margin; } + +.marginHorizontalNone { + margin-left: 0; + margin-right: 0; +} From f47e6dc8a226f9c30099885d1dbe9951c840de4c Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Wed, 27 Nov 2019 09:55:47 +0100 Subject: [PATCH 060/183] adjust media-text --- packages/block-library/src/media-text/edit.native.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/media-text/edit.native.js b/packages/block-library/src/media-text/edit.native.js index 19fb714927f39d..e98b62ed4fa2a4 100644 --- a/packages/block-library/src/media-text/edit.native.js +++ b/packages/block-library/src/media-text/edit.native.js @@ -134,6 +134,8 @@ class MediaTextEdit extends Component { setAttributes, isMobile, isSelected, + isParentSelected, + isAncestorSelected, } = this.props; const { isStackedOnMobile, @@ -186,7 +188,7 @@ class MediaTextEdit extends Component { /> - + { this.renderMediaArea() } @@ -208,14 +210,20 @@ export default compose( withSelect( ( select, { clientId } ) => { const { getSelectedBlockClientId, + getBlockRootClientId, + getBlockParents, } = select( 'core/block-editor' ); + const parents = getBlockParents( clientId, true ); + const selectedBlockClientId = getSelectedBlockClientId(); - const isParentSelected = selectedBlockClientId && selectedBlockClientId === clientId; + const isParentSelected = selectedBlockClientId && selectedBlockClientId === getBlockRootClientId( clientId ); + const isAncestorSelected = selectedBlockClientId && parents.includes( selectedBlockClientId ); return { isSelected: selectedBlockClientId === clientId, isParentSelected, + isAncestorSelected, }; } ), )( MediaTextEdit ); From ca7e08f11648e9952910a16ac240437b556227d9 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Wed, 27 Nov 2019 12:16:03 +0100 Subject: [PATCH 061/183] finalise group appender logic --- packages/base-styles/_variables.scss | 1 + .../block-editor/src/components/block-list/index.native.js | 2 +- .../block-editor/src/components/block-list/style.native.scss | 5 +++++ .../src/components/button-block-appender/styles.native.scss | 5 ----- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/base-styles/_variables.scss b/packages/base-styles/_variables.scss index 543b1896b94537..9cf33e0d79f0b3 100644 --- a/packages/base-styles/_variables.scss +++ b/packages/base-styles/_variables.scss @@ -72,6 +72,7 @@ $block-selected-child-border-width: 1px; $block-selected-child-padding: 0; $block-selected-to-content: $block-edge-to-content - $block-selected-margin - $block-selected-border-width; $block-selected-child-to-content: $block-selected-to-content - $block-selected-child-margin - $block-selected-child-border-width; +$block-custom-appender-to-content: $block-edge-to-content - $block-selected-margin - $block-selected-child-margin + $block-selected-border-width; // Buttons & UI Widgets $radius-round-rectangle: 4px; diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index c4e7b923ce434f..eb1824494bf497 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -97,7 +97,7 @@ export class BlockList extends Component { /> { renderAppender && blockClientIds.length > 0 && ( - + Date: Wed, 27 Nov 2019 14:01:35 +0100 Subject: [PATCH 062/183] selection and dim adjustments --- .../src/components/block-list/block.native.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index ef48898baac5d5..ca9deb7bf95742 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -270,9 +270,8 @@ export default compose( [ const selectedBlockClientId = getSelectedBlockClientId(); const commonAncestor = getLowestCommonAncestorWithSelectedBlock( clientId ); - const clientTree = [ clientId, ...parents ]; - const commonAncestorIndex = clientTree.indexOf( commonAncestor ) - 1; - const firstToSelectId = commonAncestor || selectedBlockClientId ? clientTree[ commonAncestorIndex ] : rootBlockId; + const commonAncestorIndex = parents.indexOf( commonAncestor ) - 1; + const firstToSelectId = commonAncestor ? parents[ commonAncestorIndex ] : parents[ parents.length - 1 ]; const hasChildren = !! getBlockCount( clientId ); const hasParent = !! parents[ 0 ]; @@ -281,8 +280,9 @@ export default compose( [ const selectionIsNested = !! getBlockRootClientId( selectedBlockClientId ); const isDescendantSelected = selectedBlockClientId && getBlockParents( selectedBlockClientId ).includes( clientId ); - const isTouchable = isSelected || isDescendantSelected || selectedBlockClientId === parentId || parentId === ''; - const isDimmed = ! isSelected && selectionIsNested && ! isAncestorSelected && ! isDescendantSelected && ( firstToSelectId === clientId || rootBlockId === clientId ); + const isDescendantOfParentSelected = selectedBlockClientId && getBlockParents( selectedBlockClientId ).includes( parentId ); + const isTouchable = isSelected || isDescendantSelected || isDescendantOfParentSelected || isParentSelected || parentId === ''; + const isDimmed = ! isSelected && selectionIsNested && ! isAncestorSelected && ! isDescendantSelected && ( isDescendantOfParentSelected || rootBlockId === clientId ); const isInnerBlockHolder = name === getGroupingBlockName(); From 662008e3bbc60a01a953afd940601dc304e9f579 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Thu, 28 Nov 2019 13:59:37 +0100 Subject: [PATCH 063/183] handle semi-bordered --- packages/block-editor/src/components/block-list/index.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index eb1824494bf497..7e87bce6841206 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -128,7 +128,7 @@ export class BlockList extends Component { clientId={ clientId } rootClientId={ this.props.rootClientId } onCaretVerticalPositionChange={ this.onCaretVerticalPositionChange } - isSmallScreen={ ! ReadableContentView.isContentMaxWidth() } + isSmallScreen={ ! this.props.isFullyBordered } /> ) } { shouldShowInsertionPointAfter( clientId ) && this.renderAddBlockSeparator() } From 6414b896c75dc29a8546663c9309a01cb6b500ee Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Fri, 29 Nov 2019 10:51:16 +0100 Subject: [PATCH 064/183] refactor after tug review --- .../src/components/block-list/block.native.js | 38 ++++++++++++------- .../src/media-text/edit.native.js | 6 ++- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index ca9deb7bf95742..2bffa2e373204f 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -107,20 +107,21 @@ class BlockListBlock extends Component { hasChildren, getStylesFromColorScheme, isSmallScreen, - selectionIsNested, - isInnerBlockHolder, + isRootListInnerBlockHolder, } = this.props; - const fullSolidBorderStyle = { + const fullSolidBorderStyle = { // define style for full border ...styles.fullSolidBordered, ...getStylesFromColorScheme( styles.solidBorderColor, styles.solidBorderColorDark ), }; - if ( hasChildren ) { + if ( hasChildren ) { // if block has children apply style for selected parent return { ...styles.selectedParent, ...fullSolidBorderStyle }; } - if ( isSmallScreen && ! selectionIsNested && ! isInnerBlockHolder ) { + // apply semi border selected style when screen is in vertical position + // and selected block does not have InnerBlock inside + if ( isSmallScreen && ! isRootListInnerBlockHolder ) { return { ...styles.selectedRootLeaf, ...styles.semiSolidBordered, @@ -128,6 +129,10 @@ class BlockListBlock extends Component { }; } + /* selected block is one of below: + 1. does not have children + 2. is not on root list level + 3. is an emty group block on root or nested level */ return { ...styles.selectedLeaf, ...fullSolidBorderStyle }; } @@ -140,28 +145,34 @@ class BlockListBlock extends Component { getStylesFromColorScheme, } = this.props; + // if block does not have parent apply neutral or full + // margins depending if block has children or not if ( ! hasParent ) { return hasChildren ? styles.neutral : styles.full; } - if ( isParentSelected ) { - const dashedBorderStyle = { + if ( isParentSelected ) { // parent of a block is selected + const dashedBorderStyle = { // define style for dashed border ...styles.dashedBordered, ...getStylesFromColorScheme( styles.dashedBorderColor, styles.dashedBorderColorDark ), }; + // return apply childOfSelected or childOfSelectedLeaf + // margins depending if block has children or not return hasChildren ? { ...styles.childOfSelected, ...dashedBorderStyle } : { ...styles.childOfSelectedLeaf, ...dashedBorderStyle }; } - if ( isAncestorSelected ) { + if ( isAncestorSelected ) { // ancestor of a block is selected return { ...styles.descendantOfSelectedLeaf, ...( hasChildren && styles.marginHorizontalNone ), }; } + // if none of above condition are met return apply neutral or full + // margins depending if block has children or not return hasChildren ? styles.neutral : styles.full; } @@ -277,14 +288,15 @@ export default compose( [ const hasParent = !! parents[ 0 ]; const isParentSelected = selectedBlockClientId && selectedBlockClientId === parents[ 0 ]; const isAncestorSelected = selectedBlockClientId && parents.includes( selectedBlockClientId ); - const selectionIsNested = !! getBlockRootClientId( selectedBlockClientId ); + const isSelectedBlockNested = !! getBlockRootClientId( selectedBlockClientId ); const isDescendantSelected = selectedBlockClientId && getBlockParents( selectedBlockClientId ).includes( clientId ); const isDescendantOfParentSelected = selectedBlockClientId && getBlockParents( selectedBlockClientId ).includes( parentId ); - const isTouchable = isSelected || isDescendantSelected || isDescendantOfParentSelected || isParentSelected || parentId === ''; - const isDimmed = ! isSelected && selectionIsNested && ! isAncestorSelected && ! isDescendantSelected && ( isDescendantOfParentSelected || rootBlockId === clientId ); + const isTouchable = isSelected || isDescendantOfParentSelected || isParentSelected || parentId === ''; + const isDimmed = ! isSelected && isSelectedBlockNested && ! isAncestorSelected && ! isDescendantSelected && ( isDescendantOfParentSelected || rootBlockId === clientId ); const isInnerBlockHolder = name === getGroupingBlockName(); + const isRootListInnerBlockHolder = ! isSelectedBlockNested && isInnerBlockHolder; return { icon, @@ -306,8 +318,8 @@ export default compose( [ isAncestorSelected, isTouchable, isDimmed, - selectionIsNested, - isInnerBlockHolder, + isSelectedBlockNested, + isRootListInnerBlockHolder, }; } ), withDispatch( ( dispatch, ownProps, { select } ) => { diff --git a/packages/block-library/src/media-text/edit.native.js b/packages/block-library/src/media-text/edit.native.js index e98b62ed4fa2a4..8a72d076254fa9 100644 --- a/packages/block-library/src/media-text/edit.native.js +++ b/packages/block-library/src/media-text/edit.native.js @@ -158,6 +158,10 @@ class MediaTextEdit extends Component { }; const innerBlockWidth = shouldStack ? 100 : ( 100 - temporaryMediaWidth ); const innerBlockWidthString = `${ innerBlockWidth }%`; + const mediaContainerStyle = { + padding: isParentSelected || isAncestorSelected ? 6 : 16, + ...( isSelected && { padding: 12 } ), + }; const toolbarControls = [ { icon: 'align-pull-left', @@ -188,7 +192,7 @@ class MediaTextEdit extends Component { /> - + { this.renderMediaArea() } From 722134da5e5acc936aa3552477c5b0be6e0a6179 Mon Sep 17 00:00:00 2001 From: Jakub Binda Date: Fri, 29 Nov 2019 12:10:27 +0100 Subject: [PATCH 065/183] add isOpen check to conditionally show/hide appender button under group block --- .../src/components/button-block-appender/index.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/button-block-appender/index.native.js b/packages/block-editor/src/components/button-block-appender/index.native.js index b01d3b65109856..3ec783488d257c 100644 --- a/packages/block-editor/src/components/button-block-appender/index.native.js +++ b/packages/block-editor/src/components/button-block-appender/index.native.js @@ -20,7 +20,7 @@ function ButtonBlockAppender( { rootClientId } ) { ( - + ) } + { ! hasSelection && __( 'Document' ) } + + { parents.map( ( parentClientId ) => ( +
  • + +
  • + ) ) } + { !! clientId && ( +
  • + +
  • + ) } + + /* eslint-enable jsx-a11y/no-redundant-roles */ + ); +}; + +export default BlockBreadcrumb; diff --git a/packages/block-editor/src/components/block-breadcrumb/style.scss b/packages/block-editor/src/components/block-breadcrumb/style.scss new file mode 100644 index 00000000000000..b20cdd273b2566 --- /dev/null +++ b/packages/block-editor/src/components/block-breadcrumb/style.scss @@ -0,0 +1,41 @@ +.block-editor-block-breadcrumb { + list-style: none; + padding: 0; + margin: 0; + + li { + display: inline-block; + margin: 0; + + &:not(:last-child)::after { + content: "\2192"; // This becomes →. + } + } +} + +.block-editor-block-breadcrumb__button.components-button { + height: $icon-button-size-small; + line-height: $icon-button-size-small; + padding: 0; + + &:hover { + text-decoration: underline; + } + + &:focus { + @include square-style__focus(); + outline-offset: -2px; + box-shadow: none; + } +} + +.block-editor-block-breadcrumb__current { + cursor: default; +} + +.block-editor-block-breadcrumb__button.components-button, +.block-editor-block-breadcrumb__current { + color: $dark-gray-500; + padding: 0 $grid-size; + font-size: inherit; +} diff --git a/packages/block-editor/src/components/block-draggable/index.js b/packages/block-editor/src/components/block-draggable/index.js index 8818d58be9c2d2..b5851d57a51fbc 100644 --- a/packages/block-editor/src/components/block-draggable/index.js +++ b/packages/block-editor/src/components/block-draggable/index.js @@ -1,23 +1,73 @@ +/** + * External dependencies + */ +import { castArray } from 'lodash'; + /** * WordPress dependencies */ import { Draggable } from '@wordpress/components'; -import { withSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { useEffect, useRef } from '@wordpress/element'; + +const BlockDraggable = ( { children, clientIds } ) => { + const { + srcRootClientId, + index, + isDraggable, + } = useSelect( ( select ) => { + const { + getBlockIndex, + getBlockRootClientId, + getTemplateLock, + } = select( 'core/block-editor' ); + const normalizedClientIds = castArray( clientIds ); + const rootClientId = normalizedClientIds.length === 1 ? getBlockRootClientId( normalizedClientIds[ 0 ] ) : null; + const templateLock = rootClientId ? getTemplateLock( rootClientId ) : null; + + return { + index: getBlockIndex( normalizedClientIds[ 0 ], rootClientId ), + srcRootClientId: rootClientId, + isDraggable: normalizedClientIds.length === 1 && 'all' !== templateLock, + }; + }, [ clientIds ] ); + const isDragging = useRef( false ); + const { startDraggingBlocks, stopDraggingBlocks } = useDispatch( 'core/block-editor' ); -const BlockDraggable = ( { children, clientId, rootClientId, blockElementId, index, onDragStart, onDragEnd } ) => { + // Stop dragging blocks if the block draggable is unmounted + useEffect( () => { + return () => { + if ( isDragging.current ) { + stopDraggingBlocks(); + } + }; + }, [] ); + + if ( ! isDraggable ) { + return null; + } + + const normalizedClientIds = castArray( clientIds ); + const blockElementId = `block-${ normalizedClientIds[ 0 ] }`; const transferData = { type: 'block', srcIndex: index, - srcRootClientId: rootClientId, - srcClientId: clientId, + srcClientId: normalizedClientIds[ 0 ], + srcRootClientId, }; return ( { + startDraggingBlocks(); + isDragging.current = true; + } } + onDragEnd={ () => { + stopDraggingBlocks(); + isDragging.current = false; + } } > { ( { onDraggableStart, onDraggableEnd } ) => { @@ -31,11 +81,4 @@ const BlockDraggable = ( { children, clientId, rootClientId, blockElementId, ind ); }; -export default withSelect( ( select, { clientId } ) => { - const { getBlockIndex, getBlockRootClientId } = select( 'core/block-editor' ); - const rootClientId = getBlockRootClientId( clientId ); - return { - index: getBlockIndex( clientId, rootClientId ), - rootClientId, - }; -} )( BlockDraggable ); +export default BlockDraggable; diff --git a/packages/block-editor/src/components/block-drop-zone/index.js b/packages/block-editor/src/components/block-drop-zone/index.js index adac488e4c3d7f..c4f81b1b4a4c56 100644 --- a/packages/block-editor/src/components/block-drop-zone/index.js +++ b/packages/block-editor/src/components/block-drop-zone/index.js @@ -19,11 +19,6 @@ import { Component } from '@wordpress/element'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; -/** - * Internal dependencies - */ -import MediaUploadCheck from '../media-upload/check'; - const parseDropEvent = ( event ) => { let result = { srcRootClientId: null, @@ -63,6 +58,9 @@ class BlockDropZone extends Component { } onFilesDrop( files, position ) { + if ( ! this.props.hasUploadPermissions ) { + return; + } const transformation = findTransform( getBlockTransforms( 'from' ), ( transform ) => transform.type === 'files' && transform.isMatch( files ) @@ -111,25 +109,21 @@ class BlockDropZone extends Component { } render() { - const { isLockedAll } = this.props; + const { hasUploadPermissions, isLockedAll } = this.props; if ( isLockedAll ) { return null; } - const index = this.getInsertIndex(); const isAppender = index === undefined; - return ( - - - + ); } } @@ -158,11 +152,17 @@ export default compose( }; } ), withSelect( ( select, { rootClientId } ) => { - const { getClientIdsOfDescendants, getTemplateLock, getBlockIndex } = select( 'core/block-editor' ); - return { - isLockedAll: getTemplateLock( rootClientId ) === 'all', + const { + getBlockIndex, getClientIdsOfDescendants, + getSettings, + getTemplateLock, + } = select( 'core/block-editor' ); + return { getBlockIndex, + getClientIdsOfDescendants, + hasUploadPermissions: !! getSettings().mediaUpload, + isLockedAll: getTemplateLock( rootClientId ) === 'all', }; } ), withFilters( 'editor.BlockDropZone' ) diff --git a/packages/block-editor/src/components/block-edit/context.js b/packages/block-editor/src/components/block-edit/context.js index 863cdc3e5d6fa5..08f2ac9f1dd6b1 100644 --- a/packages/block-editor/src/components/block-edit/context.js +++ b/packages/block-editor/src/components/block-edit/context.js @@ -6,19 +6,29 @@ import { noop } from 'lodash'; /** * WordPress dependencies */ -import { createContext } from '@wordpress/element'; +import { createContext, useContext } from '@wordpress/element'; import { createHigherOrderComponent } from '@wordpress/compose'; -const { Consumer, Provider } = createContext( { +const Context = createContext( { name: '', isSelected: false, focusedElement: null, setFocusedElement: noop, clientId: null, } ); +const { Provider, Consumer } = Context; export { Provider as BlockEditContextProvider }; +/** + * A hook that returns the block edit context. + * + * @return {Object} Block edit context + */ +export function useBlockEditContext() { + return useContext( Context ); +} + /** * A Higher Order Component used to inject BlockEdit context to the * wrapped component. @@ -27,7 +37,7 @@ export { Provider as BlockEditContextProvider }; * expected to return object of props to * merge with the component's own props. * - * @return {Component} Enhanced component with injected context as props. + * @return {WPComponent} Enhanced component with injected context as props. */ export const withBlockEditContext = ( mapContextToProps ) => createHigherOrderComponent( ( OriginalComponent ) => { return ( props ) => ( @@ -46,9 +56,9 @@ export const withBlockEditContext = ( mapContextToProps ) => createHigherOrderCo * A Higher Order Component used to render conditionally the wrapped * component only when the BlockEdit has selected state set. * - * @param {Component} OriginalComponent Component to wrap. + * @param {WPComponent} OriginalComponent Component to wrap. * - * @return {Component} Component which renders only when the BlockEdit is selected. + * @return {WPComponent} Component which renders only when the BlockEdit is selected. */ export const ifBlockEditSelected = createHigherOrderComponent( ( OriginalComponent ) => { return ( props ) => ( diff --git a/packages/block-editor/src/components/block-edit/index.js b/packages/block-editor/src/components/block-edit/index.js index 63c475a50692ff..403a5cd87898e3 100644 --- a/packages/block-editor/src/components/block-edit/index.js +++ b/packages/block-editor/src/components/block-edit/index.js @@ -12,7 +12,7 @@ import { Component } from '@wordpress/element'; * Internal dependencies */ import Edit from './edit'; -import { BlockEditContextProvider } from './context'; +import { BlockEditContextProvider, useBlockEditContext } from './context'; class BlockEdit extends Component { constructor() { @@ -44,3 +44,4 @@ class BlockEdit extends Component { } export default BlockEdit; +export { useBlockEditContext }; diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index f395bb24f0f472..379ed9521125be 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -1,14 +1,12 @@ -/** - * External dependencies - */ -import { isEmpty } from 'lodash'; - /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; import { getBlockType, getUnregisteredTypeHandlerName } from '@wordpress/blocks'; -import { PanelBody } from '@wordpress/components'; +import { + PanelBody, + __experimentalSlotFillConsumer, +} from '@wordpress/components'; import { withSelect } from '@wordpress/data'; /** @@ -51,7 +49,7 @@ const BlockInspector = ( { } return ( - <> +
    { hasBlockStyles && (
    @@ -66,22 +64,24 @@ const BlockInspector = ( {
    ) } -
    +
    - - { ( fills ) => ! isEmpty( fills ) && ( - - { fills } - - ) } - + <__experimentalSlotFillConsumer> + { ( { hasFills } ) => + hasFills( InspectorAdvancedControls.slotName ) && ( + + + + ) + } +
    - +
    ); }; diff --git a/packages/block-editor/src/components/block-inspector/style.scss b/packages/block-editor/src/components/block-inspector/style.scss index 6b054c1232b419..bf01c8b87d38d2 100644 --- a/packages/block-editor/src/components/block-inspector/style.scss +++ b/packages/block-editor/src/components/block-inspector/style.scss @@ -1,3 +1,21 @@ +.block-editor-block-inspector { + .components-base-control { + margin-bottom: #{ $grid-size * 3 }; + + &:last-child { + margin-bottom: $grid-size; + } + } + .components-panel__body { + border: none; + border-top: $border-width solid $light-gray-500; + } + + .block-editor-block-card { + padding: $grid-size-large; + } +} + .block-editor-block-inspector__no-blocks { display: block; font-size: $default-font-size; diff --git a/packages/block-editor/src/components/block-list-appender/style.scss b/packages/block-editor/src/components/block-list-appender/style.scss index 5f83d4b44b290b..a219f668134486 100644 --- a/packages/block-editor/src/components/block-list-appender/style.scss +++ b/packages/block-editor/src/components/block-list-appender/style.scss @@ -1,7 +1,7 @@ // These styles are only applied to the appender when it appears inside of a block. // Otherwise the default appender may be improperly positioned in some themes. .block-editor-block-list__block .block-list-appender { - margin: $block-padding; + margin: $block-padding 0; // Add additional margin to the appender when inside a group with a background color. // If changing this, be sure to sync up with group/editor.scss line 13. diff --git a/packages/block-editor/src/components/block-list-footer/index.js b/packages/block-editor/src/components/block-list-footer/index.js new file mode 100644 index 00000000000000..3beba29d90b0ae --- /dev/null +++ b/packages/block-editor/src/components/block-list-footer/index.js @@ -0,0 +1,10 @@ +/** + * WordPress dependencies + */ +import { createSlotFill } from '@wordpress/components'; + +const { Fill: __experimentalBlockListFooter, Slot } = createSlotFill( '__experimentalBlockListFooter' ); + +__experimentalBlockListFooter.Slot = Slot; + +export default __experimentalBlockListFooter; diff --git a/packages/block-editor/src/components/block-list/block-async-mode-provider.js b/packages/block-editor/src/components/block-list/block-async-mode-provider.js index aaa2e709db92c6..6601d428cff3ed 100644 --- a/packages/block-editor/src/components/block-list/block-async-mode-provider.js +++ b/packages/block-editor/src/components/block-list/block-async-mode-provider.js @@ -1,15 +1,12 @@ /** * WordPress dependencies */ -import { - __experimentalAsyncModeProvider as AsyncModeProvider, - useSelect, -} from '@wordpress/data'; +import { AsyncModeProvider, useSelect } from '@wordpress/data'; const BlockAsyncModeProvider = ( { children, clientId, isBlockInSelection } ) => { const isParentOfSelectedBlock = useSelect( ( select ) => { return select( 'core/block-editor' ).hasSelectedInnerBlock( clientId, true ); - } ); + }, [ clientId ] ); const isSyncModeForced = isBlockInSelection || isParentOfSelectedBlock; diff --git a/packages/block-editor/src/components/block-list/block-html.js b/packages/block-editor/src/components/block-list/block-html.js index ce70122701828e..a3b4dc21742341 100644 --- a/packages/block-editor/src/components/block-list/block-html.js +++ b/packages/block-editor/src/components/block-list/block-html.js @@ -3,75 +3,48 @@ * External dependencies */ import TextareaAutosize from 'react-autosize-textarea'; -import { isEqual } from 'lodash'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; -import { compose } from '@wordpress/compose'; +import { useEffect, useState } from '@wordpress/element'; +import { useSelect, useDispatch } from '@wordpress/data'; import { getBlockAttributes, getBlockContent, getBlockType, isValidBlockContent, getSaveContent } from '@wordpress/blocks'; -import { withSelect, withDispatch } from '@wordpress/data'; -export class BlockHTML extends Component { - constructor( props ) { - super( ...arguments ); - this.onChange = this.onChange.bind( this ); - this.onBlur = this.onBlur.bind( this ); - this.state = { - html: props.block.isValid ? getBlockContent( props.block ) : props.block.originalContent, - }; - } - - componentDidUpdate( prevProps ) { - if ( ! isEqual( this.props.block.attributes, prevProps.block.attributes ) ) { - this.setState( { - html: getBlockContent( this.props.block ), - } ); - } - } - - onBlur() { - const { html } = this.state; - const blockType = getBlockType( this.props.block.name ); - const attributes = getBlockAttributes( blockType, html, this.props.block.attributes ); +function BlockHTML( { clientId } ) { + const [ html, setHtml ] = useState( '' ); + const { block } = useSelect( ( select ) => ( { + block: select( 'core/block-editor' ).getBlock( clientId ), + } ), [ clientId ] ); + const { updateBlock } = useDispatch( 'core/block-editor' ); + const onChange = () => { + const blockType = getBlockType( block.name ); + const attributes = getBlockAttributes( blockType, html, block.attributes ); // If html is empty we reset the block to the default HTML and mark it as valid to avoid triggering an error const content = html ? html : getSaveContent( blockType, attributes ); const isValid = html ? isValidBlockContent( blockType, attributes, content ) : true; - this.props.onChange( this.props.clientId, attributes, content, isValid ); + updateBlock( clientId, { attributes, originalContent: content, isValid } ); // Ensure the state is updated if we reset so it displays the default content if ( ! html ) { - this.setState( { html: content } ); + setHtml( { content } ); } - } - - onChange( event ) { - this.setState( { html: event.target.value } ); - } - - render() { - const { html } = this.state; - return ( - - ); - } + }; + + useEffect( () => { + setHtml( getBlockContent( block ) ); + }, [ block ] ); + + return ( + setHtml( event.target.value ) } + /> + ); } -export default compose( [ - withSelect( ( select, ownProps ) => ( { - block: select( 'core/block-editor' ).getBlock( ownProps.clientId ), - } ) ), - withDispatch( ( dispatch ) => ( { - onChange( clientId, attributes, originalContent, isValid ) { - dispatch( 'core/block-editor' ).updateBlock( clientId, { attributes, originalContent, isValid } ); - }, - } ) ), -] )( BlockHTML ); +export default BlockHTML; diff --git a/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.js b/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.js index cdd54ffdcaca1b..08edb75976fba0 100644 --- a/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.js +++ b/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.js @@ -7,34 +7,16 @@ import { View, TouchableWithoutFeedback } from 'react-native'; * Internal dependencies */ import styles from './block-mobile-floating-toolbar.scss'; -/** - * WordPress dependencies - */ -import { createSlotFill } from '@wordpress/components'; - -const { Fill, Slot } = createSlotFill( 'FloatingToolbar' ); -function FloatingToolbar( { children } ) { +const FloatingToolbar = ( { children } ) => { return ( - - { ( { innerFloatingToolbar } ) => { - return ( - - { children } - - - ); - } } - - + + { children } + + ); -} - -FloatingToolbar.Slot = Slot; +}; export default FloatingToolbar; diff --git a/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.scss b/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.scss index 713633516fca23..0f64938c4beffc 100644 --- a/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.scss +++ b/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.scss @@ -1,4 +1,4 @@ -.floatingToolbarFill { +.floatingToolbar { background-color: $dark-gray-500; margin: auto; min-width: 100; @@ -6,10 +6,17 @@ border-radius: 22px; flex-direction: row; z-index: 100; - top: -$floating-toolbar-height; height: $floating-toolbar-height; - position: absolute; align-items: center; justify-content: center; align-self: center; + margin-bottom: 8px; +} + +.floatingToolbarFillColor { + background-color: rgba(#1d2327, 0.85); +} + +.floatingToolbarFillColorDark { + background-color: rgba(#3c434a, 0.85); } diff --git a/packages/block-editor/src/components/block-list/block-mobile-toolbar.js b/packages/block-editor/src/components/block-list/block-mobile-toolbar.js deleted file mode 100644 index f35a7add73641a..00000000000000 --- a/packages/block-editor/src/components/block-list/block-mobile-toolbar.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * WordPress dependencies - */ -import { ifViewportMatches } from '@wordpress/viewport'; - -/** - * Internal dependencies - */ -import BlockMover from '../block-mover'; -import VisualEditorInserter from '../inserter'; - -function BlockMobileToolbar( { clientId } ) { - return ( -
    - - -
    - ); -} - -export default ifViewportMatches( '< small' )( BlockMobileToolbar ); diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index 777cb54933a80b..949369f95b72ed 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -27,6 +27,7 @@ import { __, sprintf } from '@wordpress/i18n'; import { withDispatch, withSelect, + useSelect, } from '@wordpress/data'; import { withViewportMatch } from '@wordpress/viewport'; import { compose, pure, ifCondition } from '@wordpress/compose'; @@ -44,7 +45,6 @@ import BlockHtml from './block-html'; import BlockBreadcrumb from './breadcrumb'; import BlockContextualToolbar from './block-contextual-toolbar'; import BlockMultiControls from './multi-controls'; -import BlockMobileToolbar from './block-mobile-toolbar'; import BlockInsertionPoint from './insertion-point'; import IgnoreNestedEvents from '../ignore-nested-events'; import InserterWithShortcuts from '../inserter-with-shortcuts'; @@ -63,22 +63,21 @@ const preventDrag = ( event ) => { }; function BlockListBlock( { - blockRef, mode, isFocusMode, hasFixedToolbar, + moverDirection, isLocked, clientId, rootClientId, isSelected, + isMultiSelected, isPartOfMultiSelection, isFirstMultiSelected, isTypingWithinBlock, isCaretWithinFormattedText, isEmptyDefaultBlock, - isMovable, isParentOfSelectedBlock, - isDraggable, isSelectionEnabled, className, name, @@ -100,17 +99,24 @@ function BlockListBlock( { animateOnChange, enableAnimation, isNavigationMode, - enableNavigationMode, + setNavigationMode, + isMultiSelecting, + isLargeViewport, } ) { + // In addition to withSelect, we should favor using useSelect in this component going forward + // to avoid leaking new props to the public API (editor.BlockListBlock filter) + const { isDraggingBlocks } = useSelect( ( select ) => { + return { + isDraggingBlocks: select( 'core/block-editor' ).isDraggingBlocks(), + }; + }, [] ); + // Random state used to rerender the component if needed, ideally we don't need this const [ , updateRerenderState ] = useState( {} ); const rerender = () => updateRerenderState( {} ); // Reference of the wrapper const wrapper = useRef( null ); - useEffect( () => { - blockRef( wrapper.current, clientId ); - }, [] ); // Reference to the block edit node const blockNodeRef = useRef(); @@ -170,15 +176,6 @@ function BlockListBlock( { } } ); - // Handling the dragging state - const [ isDragging, setBlockDraggingState ] = useState( false ); - const onDragStart = () => { - setBlockDraggingState( true ); - }; - const onDragEnd = () => { - setBlockDraggingState( false ); - }; - // Handling the error state const [ hasError, setErrorState ] = useState( false ); const onBlockError = () => setErrorState( true ); @@ -206,6 +203,19 @@ function BlockListBlock( { * @param {boolean} ignoreInnerBlocks Should not focus inner blocks. */ const focusTabbable = ( ignoreInnerBlocks ) => { + const selection = window.getSelection(); + + if ( selection.rangeCount && ! selection.isCollapsed ) { + const { startContainer, endContainer } = selection.getRangeAt( 0 ); + + if ( + ! blockNodeRef.current.contains( startContainer ) || + ! blockNodeRef.current.contains( endContainer ) + ) { + selection.removeAllRanges(); + } + } + // Focus is captured by the wrapper node, so while focus transition // should only consider tabbables within editable display, since it // may be the wrapper itself or a side control which triggered the @@ -242,11 +252,11 @@ function BlockListBlock( { // Focus the selected block's wrapper or inner input on mount and update const isMounting = useRef( true ); useEffect( () => { - if ( isSelected ) { + if ( isSelected && ! isMultiSelecting ) { focusTabbable( ! isMounting.current ); } isMounting.current = false; - }, [ isSelected ] ); + }, [ isSelected, isMultiSelecting ] ); // Focus the first multi selected block useEffect( () => { @@ -278,7 +288,7 @@ function BlockListBlock( { * (via `setFocus`), typically if there is no focusable input in the block. */ const onFocus = () => { - if ( ! isSelected && ! isPartOfMultiSelection ) { + if ( ! isSelected && ! isParentOfSelectedBlock && ! isPartOfMultiSelection ) { onSelect(); } }; @@ -325,7 +335,7 @@ function BlockListBlock( { isSelected && isEditMode ) { - enableNavigationMode(); + setNavigationMode( true ); wrapper.current.focus(); } break; @@ -337,33 +347,48 @@ function BlockListBlock( { * * @param {MouseEvent} event A mousedown event. */ - const onPointerDown = ( event ) => { + const onMouseDown = ( event ) => { // Not the main button. // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button if ( event.button !== 0 ) { return; } + if ( + isNavigationMode && + isSelected && + isInsideRootBlock( blockNodeRef.current, event.target ) + ) { + setNavigationMode( false ); + } + if ( event.shiftKey ) { if ( ! isSelected ) { onShiftSelection(); event.preventDefault(); } - // Avoid triggering multi-selection if we click toolbars/inspectors - // and all elements that are outside the Block Edit DOM tree. - } else if ( blockNodeRef.current.contains( event.target ) ) { - onSelectionStart( clientId ); + // Allow user to escape out of a multi-selection to a singular + // selection of a block via click. This is handled here since + // onFocus excludes blocks involved in a multiselection, as + // focus can be incurred by starting a multiselection (focus + // moved to first block's multi-controls). + } else if ( isPartOfMultiSelection ) { + onSelect(); + } + }; - // Allow user to escape out of a multi-selection to a singular - // selection of a block via click. This is handled here since - // onFocus excludes blocks involved in a multiselection, as - // focus can be incurred by starting a multiselection (focus - // moved to first block's multi-controls). - if ( isPartOfMultiSelection ) { - onSelect(); - } + const onMouseLeave = ( { which, buttons } ) => { + // The primary button must be pressed to initiate selection. Fall back + // to `which` if the standard `buttons` property is falsy. There are + // cases where Firefox might always set `buttons` to `0`. + // See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + // See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which + if ( isSelected && ( buttons || which ) === 1 ) { + onSelectionStart( clientId ); } + + hideHoverEffects(); }; const selectOnOpen = ( open ) => { @@ -403,24 +428,27 @@ function BlockListBlock( { ! showEmptyBlockSideInserter && ! isPartOfMultiSelection && ! isTypingWithinBlock; - const shouldShowBreadcrumb = - ( isSelected && isNavigationMode ) || - ( ! isNavigationMode && ! isFocusMode && isHovered && ! isEmptyDefaultBlock ); + const shouldShowBreadcrumb = isNavigationMode && isSelected; const shouldShowContextualToolbar = ! isNavigationMode && ! hasFixedToolbar && + isLargeViewport && ! showEmptyBlockSideInserter && + ! isMultiSelecting && ( ( isSelected && ( ! isTypingWithinBlock || isCaretWithinFormattedText ) ) || isFirstMultiSelected ); - const shouldShowMobileToolbar = ! isNavigationMode && shouldAppearSelected; // Insertion point can only be made visible if the block is at the // the extent of a multi-selection, or not in a multi-selection. - const shouldShowInsertionPoint = + const shouldShowInsertionPoint = ! isMultiSelecting && ( ( isPartOfMultiSelection && isFirstMultiSelected ) || - ! isPartOfMultiSelection; + ! isPartOfMultiSelection + ); + + const shouldRenderDropzone = shouldShowInsertionPoint; + const isDragging = isDraggingBlocks && ( isSelected || isPartOfMultiSelection ); // The wp-block className is important for editor styles. // Generate the wrapper class names handling the different states of the block. @@ -430,7 +458,7 @@ function BlockListBlock( { 'has-warning': ! isValid || !! hasError || isUnregisteredBlock, 'is-selected': shouldAppearSelected, 'is-navigate-mode': isNavigationMode, - 'is-multi-selected': isPartOfMultiSelection, + 'is-multi-selected': isMultiSelected, 'is-hovered': shouldAppearHovered, 'is-reusable': isReusableBlock( blockType ), 'is-dragging': isDragging, @@ -450,6 +478,13 @@ function BlockListBlock( { }; } const blockElementId = `block-${ clientId }`; + const blockMover = ( + + ); // We wrap the BlockEdit component in a div that hides it when editing in // HTML mode. This allows us to render all of the ancillary pieces @@ -506,27 +541,23 @@ function BlockListBlock( { rootClientId={ rootClientId } /> ) } - - { isFirstMultiSelected && ( - - ) } -
    - { shouldRenderMovers && ( - } +
    + { isFirstMultiSelected && ( + ) } + { shouldRenderMovers && ( moverDirection === 'vertical' ) && blockMover } { shouldShowBreadcrumb && ( @@ -566,6 +598,7 @@ function BlockListBlock( { { isValid && mode === 'html' && ( ) } + { shouldRenderMovers && ( moverDirection === 'horizontal' ) && blockMover } { ! isValid && [ { !! hasError && } - { shouldShowMobileToolbar && ( - - ) }
    { showInserterShortcuts && ( @@ -639,6 +669,7 @@ const applyWithSelect = withSelect( const { name, attributes, isValid } = block || {}; return { + isMultiSelected: isBlockMultiSelected( clientId ), isPartOfMultiSelection: isBlockMultiSelected( clientId ) || isAncestorMultiSelected( clientId ), isFirstMultiSelected: isFirstMultiSelectedBlock( clientId ), @@ -652,7 +683,6 @@ const applyWithSelect = withSelect( initialPosition: isSelected ? getSelectedBlocksInitialCaretPosition() : null, isEmptyDefaultBlock: name && isUnmodifiedDefaultBlock( { name, attributes } ), - isMovable: 'all' !== templateLock, isLocked: !! templateLock, isFocusMode: focusMode && isLargeViewport, hasFixedToolbar: hasFixedToolbar && isLargeViewport, @@ -766,9 +796,7 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { toggleSelection( selectionEnabled ) { toggleSelection( selectionEnabled ); }, - enableNavigationMode() { - setNavigationMode( true ); - }, + setNavigationMode, }; } ); diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 456c5fad6ac2ab..314ac65ed6d8c6 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -13,7 +13,7 @@ import { import { Component } from '@wordpress/element'; import { ToolbarButton, Toolbar } from '@wordpress/components'; import { withDispatch, withSelect } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; +import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { getBlockType } from '@wordpress/blocks'; import { __, sprintf } from '@wordpress/i18n'; @@ -23,8 +23,9 @@ import { __, sprintf } from '@wordpress/i18n'; import styles from './block.scss'; import BlockEdit from '../block-edit'; import BlockInvalidWarning from './block-invalid-warning'; -import BlockMobileToolbar from './block-mobile-toolbar'; +import BlockMobileToolbar from '../block-mobile-toolbar'; import FloatingToolbar from './block-mobile-floating-toolbar'; +import Breadcrumbs from './breadcrumb'; import NavigateUpSVG from './nav-up-icon'; class BlockListBlock extends Component { @@ -33,15 +34,12 @@ class BlockListBlock extends Component { this.insertBlocksAfter = this.insertBlocksAfter.bind( this ); this.onFocus = this.onFocus.bind( this ); - - this.state = { - isFullyBordered: false, - }; } onFocus() { - if ( ! this.props.isSelected ) { - this.props.onSelect(); + const { firstToSelectId, isSelected, onSelect } = this.props; + if ( ! isSelected ) { + onSelect( firstToSelectId ); } } @@ -104,28 +102,108 @@ class BlockListBlock extends Component { return blockName; } + applySelectedBlockStyle() { + const { + hasChildren, + getStylesFromColorScheme, + isSmallScreen, + isRootListInnerBlockHolder, + } = this.props; + + const fullSolidBorderStyle = { // define style for full border + ...styles.fullSolidBordered, + ...getStylesFromColorScheme( styles.solidBorderColor, styles.solidBorderColorDark ), + }; + + if ( hasChildren ) { // if block has children apply style for selected parent + return { ...styles.selectedParent, ...fullSolidBorderStyle }; + } + + // apply semi border selected style when screen is in vertical position + // and selected block does not have InnerBlock inside + if ( isSmallScreen && ! isRootListInnerBlockHolder ) { + return { + ...styles.selectedRootLeaf, + ...styles.semiSolidBordered, + ...{ borderColor: fullSolidBorderStyle.borderColor }, + }; + } + + /* selected block is one of below: + 1. does not have children + 2. is not on root list level + 3. is an emty group block on root or nested level */ + return { ...styles.selectedLeaf, ...fullSolidBorderStyle }; + } + + applyUnSelectedBlockStyle() { + const { + hasChildren, + isParentSelected, + isAncestorSelected, + hasParent, + getStylesFromColorScheme, + } = this.props; + + // if block does not have parent apply neutral or full + // margins depending if block has children or not + if ( ! hasParent ) { + return hasChildren ? styles.neutral : styles.full; + } + + if ( isParentSelected ) { // parent of a block is selected + const dashedBorderStyle = { // define style for dashed border + ...styles.dashedBordered, + ...getStylesFromColorScheme( styles.dashedBorderColor, styles.dashedBorderColorDark ), + }; + + // return apply childOfSelected or childOfSelectedLeaf + // margins depending if block has children or not + return hasChildren ? + { ...styles.childOfSelected, ...dashedBorderStyle } : + { ...styles.childOfSelectedLeaf, ...dashedBorderStyle }; + } + + if ( isAncestorSelected ) { // ancestor of a block is selected + return { + ...styles.descendantOfSelectedLeaf, + ...( hasChildren && styles.marginHorizontalNone ), + }; + } + + // if none of above condition are met return apply neutral or full + // margins depending if block has children or not + return hasChildren ? styles.neutral : styles.full; + } + + applyBlockStyle() { + const { + isSelected, + isDimmed, + } = this.props; + + return [ + isSelected ? this.applySelectedBlockStyle() : this.applyUnSelectedBlockStyle(), + isDimmed && styles.dimmed, + ]; + } + render() { const { - borderStyle, clientId, - focusedBorderColor, icon, isSelected, isValid, - showTitle, title, showFloatingToolbar, parentId, - isFirstBlock, + isTouchable, } = this.props; - const borderColor = isSelected ? focusedBorderColor : 'transparent'; - const accessibilityLabel = this.getAccessibilityLabel(); return ( <> - { showFloatingToolbar && ( ! isFirstBlock || parentId === '' ) && } { showFloatingToolbar && ( @@ -136,6 +214,7 @@ class BlockListBlock extends Component { /> + ) } - - { showTitle && this.renderBlockTitle() } - - { isValid && this.getBlockForType() } - { ! isValid && - - } - + + { isValid ? this.getBlockForType() : } { isSelected && } @@ -170,13 +244,20 @@ export default compose( [ isBlockSelected, __unstableGetBlockWithoutInnerBlocks, getBlockHierarchyRootClientId, + getSelectedBlockClientId, getBlock, getBlockRootClientId, - getSelectedBlock, + getLowestCommonAncestorWithSelectedBlock, + getBlockParents, + getBlockCount, } = select( 'core/block-editor' ); + + const { + getGroupingBlockName, + } = select( 'core/blocks' ); + const order = getBlockIndex( clientId, rootClientId ); const isSelected = isBlockSelected( clientId ); - const isFirstBlock = order === 0; const isLastBlock = order === getBlocks().length - 1; const block = __unstableGetBlockWithoutInnerBlocks( clientId ); const { name, attributes, isValid } = block || {}; @@ -185,18 +266,36 @@ export default compose( [ const icon = blockType.icon; const getAccessibilityLabelExtra = blockType.__experimentalGetAccessibilityLabel; - const selectedBlock = getSelectedBlock(); - const parentId = getBlockRootClientId( clientId ); - const parentBlock = getBlock( parentId ); - - const isMediaText = selectedBlock && selectedBlock.name === 'core/media-text'; - const isMediaTextParent = parentBlock && parentBlock.name === 'core/media-text'; + const parents = getBlockParents( clientId, true ); + const parentId = parents[ 0 ] || ''; const rootBlockId = getBlockHierarchyRootClientId( clientId ); const rootBlock = getBlock( rootBlockId ); const hasRootInnerBlocks = rootBlock.innerBlocks.length !== 0; - const showFloatingToolbar = isSelected && hasRootInnerBlocks && ! isMediaText && ! isMediaTextParent; + const showFloatingToolbar = isSelected && hasRootInnerBlocks; + + const selectedBlockClientId = getSelectedBlockClientId(); + + const commonAncestor = getLowestCommonAncestorWithSelectedBlock( clientId ); + const commonAncestorIndex = parents.indexOf( commonAncestor ) - 1; + const firstToSelectId = commonAncestor ? parents[ commonAncestorIndex ] : parents[ parents.length - 1 ]; + + const hasChildren = !! getBlockCount( clientId ); + const hasParent = !! parentId; + const isParentSelected = selectedBlockClientId && selectedBlockClientId === parentId; + const isAncestorSelected = selectedBlockClientId && parents.includes( selectedBlockClientId ); + const isSelectedBlockNested = !! getBlockRootClientId( selectedBlockClientId ); + + const selectedParents = selectedBlockClientId ? getBlockParents( selectedBlockClientId ) : []; + const isDescendantSelected = selectedParents.includes( clientId ); + const isDescendantOfParentSelected = selectedParents.includes( parentId ); + const isTouchable = isSelected || isDescendantOfParentSelected || isParentSelected || parentId === ''; + const isDimmed = ! isSelected && isSelectedBlockNested && ! isAncestorSelected && ! isDescendantSelected && ( isDescendantOfParentSelected || rootBlockId === clientId ); + + // TODO: find better way to handle full border for columns ( maybe return array from getGoupingBlockName ) + const isInnerBlockHolder = name === getGroupingBlockName() || name === 'core/columns'; + const isRootListInnerBlockHolder = ! isSelectedBlockNested && isInnerBlockHolder; return { icon, @@ -205,13 +304,20 @@ export default compose( [ title, attributes, blockType, - isFirstBlock, isLastBlock, isSelected, isValid, getAccessibilityLabelExtra, showFloatingToolbar, parentId, + isParentSelected, + firstToSelectId, + hasChildren, + hasParent, + isAncestorSelected, + isTouchable, + isDimmed, + isRootListInnerBlockHolder, }; } ), withDispatch( ( dispatch, ownProps, { select } ) => { @@ -257,4 +363,5 @@ export default compose( [ }, }; } ), + withPreferredColorScheme, ] )( BlockListBlock ); diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index e3d18ba3f7c640..ff9cea835a83c9 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -2,18 +2,56 @@ flex: 1 1 auto; } -.blockContainer { - padding-left: 16px; - padding-right: 16px; - padding-top: 12px; - padding-bottom: 12px; +.semiSolidBordered { + border-top-width: $block-selected-border-width; + border-bottom-width: $block-selected-border-width; + border-left-width: 0; + border-right-width: 0; + border-style: solid; +} + +.fullSolidBordered { + border-width: $block-selected-border-width; + border-radius: 4px; + border-style: solid; +} + +.dashedBordered { + border-width: $block-selected-child-border-width; + border-radius: 2px; + border-style: dashed; +} + +.solidBorderColor { + border-color: $blue-wordpress; +} + +.solidBorderColorDark { + border-color: $blue-30; +} + +.dashedBorderColor { + border-color: $gray; +} + +.dashedBorderColorDark { + border-color: $gray-70; +} + +.dimmed { + opacity: 0.2; +} + +.horizontalSpaceNone { + padding-left: 0; + padding-right: 0; + margin-left: 0; + margin-right: 0; } -.blockContainerFocused { - padding-left: 16px; - padding-right: 16px; - padding-top: 12px; - padding-bottom: 0; // will be flushed into inline toolbar height +.marginHorizontalNone { + margin-left: 0; + margin-right: 0; } .blockTitle { @@ -23,6 +61,47 @@ padding-bottom: 4px; } +.neutral { + margin: 0; + border: 0; + padding: 0; +} + +.full { + margin: $block-edge-to-content; + border: 0; + padding: 0; +} + +.selectedLeaf { + margin: $block-selected-margin; + padding: $block-selected-to-content; +} + +.selectedRootLeaf { + margin: 0; + padding: $block-edge-to-content; +} + +.selectedParent { + margin: $block-selected-margin; + padding: 0; +} + +.childOfSelected { + margin: $block-selected-child-margin; + padding: 0; +} + +.childOfSelectedLeaf { + margin: $block-selected-child-margin; + padding: $block-selected-child-to-content; +} + +.descendantOfSelectedLeaf { + margin: $block-selected-child-to-content; +} + .aztec_container { flex: 1; } diff --git a/packages/block-editor/src/components/block-list/breadcrumb.js b/packages/block-editor/src/components/block-list/breadcrumb.js index 81f05b40bf7080..25a359c19c1d15 100644 --- a/packages/block-editor/src/components/block-list/breadcrumb.js +++ b/packages/block-editor/src/components/block-list/breadcrumb.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { Toolbar, Button } from '@wordpress/components'; -import { useSelect, useDispatch } from '@wordpress/data'; +import { useDispatch } from '@wordpress/data'; import { forwardRef } from '@wordpress/element'; /** @@ -16,25 +16,14 @@ import BlockTitle from '../block-title'; * the root block. * * @param {string} props.clientId Client ID of block. - * @return {WPElement} Block Breadcrumb. + * @return {WPComponent} The component to be rendered. */ const BlockBreadcrumb = forwardRef( ( { clientId }, ref ) => { const { setNavigationMode } = useDispatch( 'core/block-editor' ); - const { rootClientId } = useSelect( ( select ) => { - return { - rootClientId: select( 'core/block-editor' ).getBlockRootClientId( clientId ), - }; - } ); return (
    - { rootClientId && ( - <> - - - - ) } diff --git a/packages/block-editor/src/components/block-list/breadcrumb.native.js b/packages/block-editor/src/components/block-list/breadcrumb.native.js new file mode 100644 index 00000000000000..1a92bc0bd93cde --- /dev/null +++ b/packages/block-editor/src/components/block-list/breadcrumb.native.js @@ -0,0 +1,76 @@ +/** + * WordPress dependencies + */ +import { Icon } from '@wordpress/components'; +import { withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; +import { getBlockType } from '@wordpress/blocks'; + +/** + * External dependencies + */ +import { View, Text, TouchableOpacity } from 'react-native'; + +/** + * Internal dependencies + */ +import BlockTitle from '../block-title'; +import SubdirectorSVG from './subdirectory-icon'; + +import styles from './breadcrumb.scss'; + +const BlockBreadcrumb = ( { clientId, blockIcon, rootClientId, rootBlockIcon } ) => { + const renderIcon = ( icon, key ) => { + if ( typeof icon.src === 'function' ) { + return ; + } + return ; + }; + + return ( + + {/* Open BottomSheet with markup */} }> + { rootClientId && rootBlockIcon && ( + [ + renderIcon( rootBlockIcon, 'parent-icon' ), + , + ] + ) } + { renderIcon( blockIcon ) } + + + + ); +}; + +export default compose( [ + withSelect( ( select, { clientId } ) => { + const { + getBlockRootClientId, + getBlockName, + } = select( 'core/block-editor' ); + + const blockName = getBlockName( clientId ); + const blockType = getBlockType( blockName ); + const blockIcon = blockType.icon; + + const rootClientId = getBlockRootClientId( clientId ); + + if ( ! rootClientId ) { + return { + clientId, + blockIcon, + }; + } + const rootBlockName = getBlockName( rootClientId ); + const rootBlockType = getBlockType( rootBlockName ); + const rootBlockIcon = rootBlockType.icon; + + return { + clientId, + blockIcon, + rootClientId, + rootBlockIcon, + }; + } ), +] )( BlockBreadcrumb ); diff --git a/packages/block-editor/src/components/block-list/breadcrumb.native.scss b/packages/block-editor/src/components/block-list/breadcrumb.native.scss new file mode 100644 index 00000000000000..8e9b103446291c --- /dev/null +++ b/packages/block-editor/src/components/block-list/breadcrumb.native.scss @@ -0,0 +1,28 @@ +.breadcrumbContainer { + flex-direction: row; + align-items: center; + justify-content: flex-start; + padding-left: 5; + padding-right: 15; +} + +.breadcrumbTitle { + color: $white; + margin-left: 4; +} + +.icon { + color: $white; +} + +.button { + flex-direction: row; + align-items: center; +} + +.arrow { + color: $light-opacity-light-700; + margin-top: -4px; + margin-left: 4; + margin-right: 4; +} diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 25386d5039c12c..d0b6293ec7e7c6 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -1,25 +1,13 @@ /** * External dependencies */ -import { - findLast, - invert, - mapValues, - sortBy, - throttle, -} from 'lodash'; import classnames from 'classnames'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; -import { - withSelect, - withDispatch, - __experimentalAsyncModeProvider as AsyncModeProvider, -} from '@wordpress/data'; -import { compose } from '@wordpress/compose'; +import { useRef } from '@wordpress/element'; +import { AsyncModeProvider, useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -27,7 +15,8 @@ import { compose } from '@wordpress/compose'; import BlockAsyncModeProvider from './block-async-mode-provider'; import BlockListBlock from './block'; import BlockListAppender from '../block-list-appender'; -import { getBlockDOMNode } from '../../utils/dom'; +import __experimentalBlockListFooter from '../block-list-footer'; +import useMultiSelection from './use-multi-selection'; /** * If the block count exceeds the threshold, we disable the reordering animation @@ -43,222 +32,17 @@ const forceSyncUpdates = ( WrappedComponent ) => ( props ) => { ); }; -class BlockList extends Component { - constructor( props ) { - super( props ); - - this.onSelectionStart = this.onSelectionStart.bind( this ); - this.onSelectionEnd = this.onSelectionEnd.bind( this ); - this.setBlockRef = this.setBlockRef.bind( this ); - this.setLastClientY = this.setLastClientY.bind( this ); - this.onPointerMove = throttle( this.onPointerMove.bind( this ), 100 ); - // Browser does not fire `*move` event when the pointer position changes - // relative to the document, so fire it with the last known position. - this.onScroll = () => this.onPointerMove( { clientY: this.lastClientY } ); - - this.lastClientY = 0; - this.nodes = {}; - } - - componentDidMount() { - window.addEventListener( 'mousemove', this.setLastClientY ); - } - - componentWillUnmount() { - window.removeEventListener( 'mousemove', this.setLastClientY ); - } - - setLastClientY( { clientY } ) { - this.lastClientY = clientY; - } - - setBlockRef( node, clientId ) { - if ( node === null ) { - delete this.nodes[ clientId ]; - } else { - this.nodes = { - ...this.nodes, - [ clientId ]: node, - }; - } - } - - /** - * Handles a pointer move event to update the extent of the current cursor - * multi-selection. - * - * @param {MouseEvent} event A mousemove event object. - */ - onPointerMove( { clientY } ) { - // We don't start multi-selection until the mouse starts moving, so as - // to avoid dispatching multi-selection actions on an in-place click. - if ( ! this.props.isMultiSelecting ) { - this.props.onStartMultiSelect(); - } - - const blockContentBoundaries = getBlockDOMNode( this.selectionAtStart ).getBoundingClientRect(); - - // prevent multi-selection from triggering when the selected block is a float - // and the cursor is still between the top and the bottom of the block. - if ( clientY >= blockContentBoundaries.top && clientY <= blockContentBoundaries.bottom ) { - return; - } - - const y = clientY - blockContentBoundaries.top; - const key = findLast( this.coordMapKeys, ( coordY ) => coordY < y ); - - this.onSelectionChange( this.coordMap[ key ] ); - } - - /** - * Binds event handlers to the document for tracking a pending multi-select - * in response to a mousedown event occurring in a rendered block. - * - * @param {string} clientId Client ID of block where mousedown occurred. - */ - onSelectionStart( clientId ) { - if ( ! this.props.isSelectionEnabled ) { - return; - } - - const boundaries = this.nodes[ clientId ].getBoundingClientRect(); - - // Create a clientId to Y coördinate map. - const clientIdToCoordMap = mapValues( this.nodes, ( node ) => - node.getBoundingClientRect().top - boundaries.top ); - - // Cache a Y coördinate to clientId map for use in `onPointerMove`. - this.coordMap = invert( clientIdToCoordMap ); - // Cache an array of the Y coördinates for use in `onPointerMove`. - // Sort the coördinates, as `this.nodes` will not necessarily reflect - // the current block sequence. - this.coordMapKeys = sortBy( Object.values( clientIdToCoordMap ) ); - this.selectionAtStart = clientId; - - window.addEventListener( 'mousemove', this.onPointerMove ); - // Capture scroll on all elements. - window.addEventListener( 'scroll', this.onScroll, true ); - window.addEventListener( 'mouseup', this.onSelectionEnd ); - } - - /** - * Handles multi-selection changes in response to pointer move. - * - * @param {string} clientId Client ID of block under cursor in multi-select - * drag. - */ - onSelectionChange( clientId ) { - const { onMultiSelect, selectionStart, selectionEnd } = this.props; - const { selectionAtStart } = this; - const isAtStart = selectionAtStart === clientId; - - if ( ! selectionAtStart || ! this.props.isSelectionEnabled ) { - return; - } - - // If multi-selecting and cursor extent returns to the start of - // selection, cancel multi-select. - if ( isAtStart && selectionStart ) { - onMultiSelect( null, null ); - } - - // Expand multi-selection to block under cursor. - if ( ! isAtStart && selectionEnd !== clientId ) { - onMultiSelect( selectionAtStart, clientId ); - } - } - - /** - * Handles a mouseup event to end the current cursor multi-selection. - */ - onSelectionEnd() { - // Cancel throttled calls. - this.onPointerMove.cancel(); - - delete this.coordMap; - delete this.coordMapKeys; - delete this.selectionAtStart; - - window.removeEventListener( 'mousemove', this.onPointerMove ); - window.removeEventListener( 'scroll', this.onScroll, true ); - window.removeEventListener( 'mouseup', this.onSelectionEnd ); - - // We may or may not be in a multi-selection when mouseup occurs (e.g. - // an in-place mouse click), so only trigger stop if multi-selecting. - if ( this.props.isMultiSelecting ) { - this.props.onStopMultiSelect(); - } - } - - render() { - const { - className, - blockClientIds, - rootClientId, - isDraggable, - selectedBlockClientId, - multiSelectedBlockClientIds, - hasMultiSelection, - renderAppender, - enableAnimation, - } = this.props; - - return ( -
    - { blockClientIds.map( ( clientId, index ) => { - const isBlockInSelection = hasMultiSelection ? - multiSelectedBlockClientIds.includes( clientId ) : - selectedBlockClientId === clientId; - - return ( - - - - ); - } ) } - - -
    - ); - } -} - -export default compose( [ - // This component needs to always be synchronous - // as it's the one changing the async mode - // depending on the block selection. - forceSyncUpdates, - withSelect( ( select, ownProps ) => { +function BlockList( { + className, + rootClientId, + __experimentalMoverDirection: moverDirection = 'vertical', + isDraggable, + renderAppender, +} ) { + function selector( select ) { const { getBlockOrder, - isSelectionEnabled, isMultiSelecting, - getMultiSelectedBlocksStartClientId, - getMultiSelectedBlocksEndClientId, getSelectedBlockClientId, getMultiSelectedBlockClientIds, hasMultiSelection, @@ -266,13 +50,8 @@ export default compose( [ isTyping, } = select( 'core/block-editor' ); - const { rootClientId } = ownProps; - return { blockClientIds: getBlockOrder( rootClientId ), - selectionStart: getMultiSelectedBlocksStartClientId(), - selectionEnd: getMultiSelectedBlocksEndClientId(), - isSelectionEnabled: isSelectionEnabled(), isMultiSelecting: isMultiSelecting(), selectedBlockClientId: getSelectedBlockClientId(), multiSelectedBlockClientIds: getMultiSelectedBlockClientIds(), @@ -282,18 +61,64 @@ export default compose( [ getGlobalBlockCount() <= BLOCK_ANIMATION_THRESHOLD ), }; - } ), - withDispatch( ( dispatch ) => { - const { - startMultiSelect, - stopMultiSelect, - multiSelect, - } = dispatch( 'core/block-editor' ); + } - return { - onStartMultiSelect: startMultiSelect, - onStopMultiSelect: stopMultiSelect, - onMultiSelect: multiSelect, - }; - } ), -] )( BlockList ); + const { + blockClientIds, + isMultiSelecting, + selectedBlockClientId, + multiSelectedBlockClientIds, + hasMultiSelection, + enableAnimation, + } = useSelect( selector, [ rootClientId ] ); + const ref = useRef(); + const onSelectionStart = useMultiSelection( { ref, rootClientId } ); + + return ( +
    + { blockClientIds.map( ( clientId, index ) => { + const isBlockInSelection = hasMultiSelection ? + multiSelectedBlockClientIds.includes( clientId ) : + selectedBlockClientId === clientId; + + return ( + + + + ); + } ) } + + <__experimentalBlockListFooter.Slot /> +
    + ); +} + +// This component needs to always be synchronous +// as it's the one changing the async mode +// depending on the block selection. +export default forceSyncUpdates( BlockList ); diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 05c2898899ca39..bb0d3cf5964a42 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -20,7 +20,7 @@ import { KeyboardAwareFlatList, ReadableContentView } from '@wordpress/component import styles from './style.scss'; import BlockListBlock from './block'; import BlockListAppender from '../block-list-appender'; -import FloatingToolbar from './block-mobile-floating-toolbar'; +import __experimentalBlockListFooter from '../block-list-footer'; const innerToolbarHeight = 44; @@ -42,10 +42,6 @@ export class BlockList extends Component { this.props.insertBlock( newBlock, this.props.blockCount ); } - blockHolderBorderStyle() { - return this.props.isFullyBordered ? styles.blockHolderFullBordered : styles.blockHolderSemiBordered; - } - onCaretVerticalPositionChange( targetId, caretY, previousCaretY ) { KeyboardAwareFlatList.handleCaretVerticalPositionChange( this.scrollViewRef, targetId, caretY, previousCaretY ); } @@ -59,9 +55,13 @@ export class BlockList extends Component { } renderDefaultBlockAppender() { + const { shouldShowInsertionPointBefore } = this.props; + const willShowInsertionPoint = shouldShowInsertionPointBefore(); // call without the client_id argument since this is the appender return ( - @@ -70,15 +70,22 @@ export class BlockList extends Component { } render() { - const { clearSelectedBlock, blockClientIds, isFullyBordered, title, header, withFooter = true, renderAppender, isFirstBlock, selectedBlockParentId } = this.props; + const { + clearSelectedBlock, + blockClientIds, + isFullyBordered, + title, + header, + withFooter = true, + renderAppender, + isRootList, + } = this.props; - const showFloatingToolbar = isFirstBlock && selectedBlockParentId !== ''; return ( - { showFloatingToolbar && } { - return { length: 0, offset: 0, index }; - } } /> - { renderAppender && blockClientIds.length > 0 && - + { renderAppender && blockClientIds.length > 0 && ( + + + + ) } + ); } @@ -119,11 +127,10 @@ export class BlockList extends Component { } renderItem( { item: clientId, index } ) { - const blockHolderFocusedStyle = this.props.getStylesFromColorScheme( styles.blockHolderFocused, styles.blockHolderFocusedDark ); - const { shouldShowBlockAtIndex, shouldShowInsertionPoint } = this.props; + const { shouldShowBlockAtIndex, shouldShowInsertionPointBefore, shouldShowInsertionPointAfter } = this.props; return ( - { shouldShowInsertionPoint( clientId ) && this.renderAddBlockSeparator() } + { shouldShowInsertionPointBefore( clientId ) && this.renderAddBlockSeparator() } { shouldShowBlockAtIndex( index ) && ( ) } + { shouldShowInsertionPointAfter( clientId ) && this.renderAddBlockSeparator() } ); } @@ -153,11 +160,14 @@ export class BlockList extends Component { renderBlockListFooter() { const paragraphBlock = createBlock( 'core/paragraph' ); return ( - { - this.addBlockToEndOfPost( paragraphBlock ); - } } > - - + <> + { + this.addBlockToEndOfPost( paragraphBlock ); + } } > + + + <__experimentalBlockListFooter.Slot /> + ); } } @@ -172,32 +182,49 @@ export default compose( [ getBlockInsertionPoint, isBlockInsertionPointVisible, getSelectedBlock, - getBlockRootClientId, } = select( 'core/block-editor' ); + const { + getGroupingBlockName, + } = select( 'core/blocks' ); + const selectedBlockClientId = getSelectedBlockClientId(); const blockClientIds = getBlockOrder( rootClientId ); const insertionPoint = getBlockInsertionPoint(); const blockInsertionPointIsVisible = isBlockInsertionPointVisible(); const selectedBlock = getSelectedBlock(); - const isSelectedGroup = selectedBlock && selectedBlock.name === 'core/group'; - const shouldShowInsertionPoint = ( clientId ) => { + const hasInnerBlocks = selectedBlock && ( selectedBlock.name === getGroupingBlockName() || selectedBlock.name === 'core/columns' ); + + const shouldShowInsertionPointBefore = ( clientId ) => { return ( blockInsertionPointIsVisible && insertionPoint.rootClientId === rootClientId && - blockClientIds[ insertionPoint.index ] === clientId + ( + // if list is empty, show the insertion point (via the default appender) + blockClientIds.length === 0 || + // or if the insertion point is right before the denoted block + blockClientIds[ insertionPoint.index ] === clientId + ) ); }; + const shouldShowInsertionPointAfter = ( clientId ) => { + return ( + blockInsertionPointIsVisible && + insertionPoint.rootClientId === rootClientId && - const selectedBlockIndex = getBlockIndex( selectedBlockClientId, rootClientId ); + // if the insertion point is at the end of the list + blockClientIds.length === insertionPoint.index && - const isFirstBlock = selectedBlockIndex === 0; + // and the denoted block is the last one on the list, show the indicator at the end of the block + blockClientIds[ insertionPoint.index - 1 ] === clientId + ); + }; - const selectedBlockParentId = getBlockRootClientId( selectedBlockClientId ); + const selectedBlockIndex = getBlockIndex( selectedBlockClientId, rootClientId ); const shouldShowBlockAtIndex = ( index ) => { const shouldHideBlockAtIndex = ( - ! isSelectedGroup && blockInsertionPointIsVisible && + ! hasInnerBlocks && blockInsertionPointIsVisible && // if `index` === `insertionPoint.index`, then block is replaceable index === insertionPoint.index && // only hide selected block @@ -211,10 +238,10 @@ export default compose( [ blockCount: getBlockCount( rootClientId ), isBlockInsertionPointVisible: isBlockInsertionPointVisible(), shouldShowBlockAtIndex, - shouldShowInsertionPoint, + shouldShowInsertionPointBefore, + shouldShowInsertionPointAfter, selectedBlockClientId, - isFirstBlock, - selectedBlockParentId, + isRootList: rootClientId === undefined, }; } ), withDispatch( ( dispatch ) => { diff --git a/packages/block-editor/src/components/block-list/insertion-point.js b/packages/block-editor/src/components/block-list/insertion-point.js index daa1a0f408f4e9..7f0b07c6911927 100644 --- a/packages/block-editor/src/components/block-list/insertion-point.js +++ b/packages/block-editor/src/components/block-list/insertion-point.js @@ -6,93 +6,69 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; -import { withSelect } from '@wordpress/data'; +import { useState } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ import Inserter from '../inserter'; -class BlockInsertionPoint extends Component { - constructor() { - super( ...arguments ); - this.state = { - isInserterFocused: false, - }; - - this.onBlurInserter = this.onBlurInserter.bind( this ); - this.onFocusInserter = this.onFocusInserter.bind( this ); - } +export default function BlockInsertionPoint( { rootClientId, clientId } ) { + const [ isInserterFocused, setInserterFocused ] = useState( false ); + const showInsertionPoint = useSelect( ( select ) => { + const { + getBlockIndex, + getBlockInsertionPoint, + isBlockInsertionPointVisible, + } = select( 'core/block-editor' ); + const blockIndex = getBlockIndex( clientId, rootClientId ); + const insertionPoint = getBlockInsertionPoint(); + return ( + isBlockInsertionPointVisible() && + insertionPoint.index === blockIndex && + insertionPoint.rootClientId === rootClientId + ); + }, [ clientId, rootClientId ] ); - onFocusInserter( event ) { + function onFocus( event ) { // Stop propagation of the focus event to avoid selecting the current // block while inserting a new block, as it is not relevant to sibling // insertion and conflicts with contextual toolbar placement. event.stopPropagation(); - - this.setState( { - isInserterFocused: true, - } ); + setInserterFocused( true ); } - onBlurInserter() { - this.setState( { - isInserterFocused: false, - } ); + function onBlur() { + setInserterFocused( false ); } - render() { - const { isInserterFocused } = this.state; - const { - showInsertionPoint, - rootClientId, - clientId, - } = this.props; - - return ( -
    - { showInsertionPoint && ( -
    - ) } -
    - -
    + return ( +
    + { showInsertionPoint && ( +
    + ) } +
    +
    - ); - } -} -export default withSelect( ( select, { clientId, rootClientId } ) => { - const { - getBlockIndex, - getBlockInsertionPoint, - isBlockInsertionPointVisible, - } = select( 'core/block-editor' ); - const blockIndex = getBlockIndex( clientId, rootClientId ); - const insertionPoint = getBlockInsertionPoint(); - const showInsertionPoint = ( - isBlockInsertionPointVisible() && - insertionPoint.index === blockIndex && - insertionPoint.rootClientId === rootClientId +
    ); - - return { showInsertionPoint }; -} )( BlockInsertionPoint ); +} diff --git a/packages/block-editor/src/components/block-list/multi-controls.js b/packages/block-editor/src/components/block-list/multi-controls.js index e4dd88aeda030f..2f49ca2ca0a3b4 100644 --- a/packages/block-editor/src/components/block-list/multi-controls.js +++ b/packages/block-editor/src/components/block-list/multi-controls.js @@ -11,6 +11,7 @@ import BlockMover from '../block-mover'; function BlockListMultiControls( { multiSelectedBlockClientIds, isSelecting, + moverDirection, } ) { if ( isSelecting ) { return null; @@ -19,6 +20,7 @@ function BlockListMultiControls( { return ( ); } diff --git a/packages/block-editor/src/components/block-list/style.native.scss b/packages/block-editor/src/components/block-list/style.native.scss index 36be89c4c067cd..c102d1644d838b 100644 --- a/packages/block-editor/src/components/block-list/style.native.scss +++ b/packages/block-editor/src/components/block-list/style.native.scss @@ -4,10 +4,6 @@ background-color: #fff; } -.list { - flex: 1; -} - .switch { flex-direction: row; justify-content: flex-start; @@ -46,35 +42,11 @@ flex-direction: row; } -.blockHolderSemiBordered { - border-top-width: 1px; - border-bottom-width: 1px; - border-left-width: 0; - border-right-width: 0; -} - -.blockHolderFullBordered { - border-top-width: 1px; - border-bottom-width: 1px; - border-left-width: 1px; - border-right-width: 1px; -} - -.blockContainerFocused { - padding-left: 16; - padding-right: 16; - padding-top: 12; - padding-bottom: 0; // will be flushed into inline toolbar height -} - -.blockHolderFocused { - border-color: $gray-lighten-30; -} - -.blockHolderFocusedDark { - border-color: $gray-70; -} - .blockListFooter { height: 80px; } + +.paddingToContent { + padding-left: $block-custom-appender-to-content; + padding-right: $block-custom-appender-to-content; +} diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index e59b6ddc343374..270bb10a285f4b 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -35,43 +35,41 @@ /** - * General layout + * General Post Content Layout */ +// Add side padding for the main block container, currently post_content. +// The purpose of this padding is to ensure that on small viewports, there is +// room for the block border that sits 14px ($block-padding) offset from the +// block footprint, as well as the side-UI. .block-editor-block-list__layout { - // Make room in the main content column for the side UI. - // The side UI uses negative margins to position itself so as to not affect the block width. + padding-left: $block-padding; + padding-right: $block-padding; + + // Beyond the mobile breakpoint, compensate for side UI. @include break-small() { - padding-left: $block-container-side-padding; - padding-right: $block-container-side-padding; + padding-left: $block-padding + $block-side-ui-width + $block-padding + $border-width * 2; + padding-right: $block-padding + $block-side-ui-width + $block-padding + $border-width * 2; } - // Don't add side padding for nested blocks. - .block-editor-block-list__block & { - // Compensate for side UI. + // Don't propogate that padding to nested blocks. + .block-editor-block-list__layout { padding-left: 0; padding-right: 0; - - // Compensate for block padding. - margin-left: -$block-padding; - margin-right: -$block-padding; } } + +/** + * Notices & Block Selected/Hover Styles. + */ + .block-editor-block-list__layout .block-editor-block-list__block { position: relative; - padding-left: $block-padding; - padding-right: $block-padding; // Break long strings of text without spaces so they don't overflow the block. overflow-wrap: break-word; - @include break-small() { - // The block mover needs to stay inside the block to allow clicks when hovering the block-. - padding-left: $block-padding + $block-side-ui-width + $block-side-ui-clearance - $border-width; - padding-right: $block-padding + $block-side-ui-width + $block-side-ui-clearance - $border-width; - } - /** * Notices */ @@ -101,6 +99,9 @@ .block-editor-block-list__block-edit { position: relative; + &.has-mover-inside > [data-block] { + display: flex; + } &::before { z-index: z-index(".block-editor-block-list__block-edit::before"); @@ -126,7 +127,6 @@ // Selected style. &.is-selected { - > .block-editor-block-list__block-edit::before { // Use opacity to work in various editor styles. border-color: $dark-opacity-light-800; @@ -157,15 +157,6 @@ } } - // Hover style. - &.is-hovered:not(.is-navigate-mode) > .block-editor-block-list__block-edit::before { - box-shadow: -$block-left-border-width 0 0 0 $dark-opacity-light-500; - - .is-dark-theme & { - box-shadow: -$block-left-border-width 0 0 0 $light-opacity-light-400; - } - } - // Spotlight mode. &.is-focus-mode:not(.is-multi-selected) { opacity: 0.5; @@ -177,102 +168,48 @@ opacity: 1; } } +} - // Add extra border to parent blocks when their children are selected. - &.has-child-selected { - - > .block-editor-block-list__block-edit::before { - border: $border-width dashed $dark-opacity-light-600; - .is-dark-theme & { - border-color: $light-opacity-light-500; - } - } +/** + * Cross-Block Selection + */ - > .block-editor-block-list__block-edit > [data-block] > div > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected) > .block-editor-block-list__block-edit::before, - > .block-editor-block-list__block-edit > [data-block] > div > .wp-block-cover__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected) > .block-editor-block-list__block-edit::before, - > .block-editor-block-list__block-edit > [data-block] > div > .wp-block-group__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected) > .block-editor-block-list__block-edit::before { - border: $border-width dashed $dark-opacity-light-600; +.block-editor-block-list__layout { - .is-dark-theme & { - border-color: $light-opacity-light-500; - } - } + // The primary indicator of selection is the native selection marker. + // To indicate multiple blocks, we provide an additional selection indicator. + .block-editor-block-list__block.is-multi-selected { - &.is-hovered > .block-editor-block-list__block-edit::before, - > .block-editor-block-list__block-edit > [data-block] > div > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block.is-hovered:not(.is-selected) > .block-editor-block-list__block-edit::before, - > .block-editor-block-list__block-edit > [data-block] > div > .wp-block-cover__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block.is-hovered:not(.is-selected) > .block-editor-block-list__block-edit::before, - > .block-editor-block-list__block-edit > [data-block] > div > .wp-block-group__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block.is-hovered:not(.is-selected) > .block-editor-block-list__block-edit::before { - border-style: solid; - border-color: $dark-opacity-light-500; - border-left-color: transparent; + > .block-editor-block-list__block-edit::before { + border-left-color: $dark-opacity-light-800; + box-shadow: inset $block-left-border-width 0 0 0 $dark-gray-500; .is-dark-theme & { - border-color: $light-opacity-light-400; - border-left-color: transparent; - } - } - } - - // Add extra border to child blocks when they are selected. - &.is-selected { - - > .block-editor-block-list__block-edit > [data-block] > div > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected), - > .block-editor-block-list__block-edit > [data-block] > div > .wp-block-cover__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected), - > .block-editor-block-list__block-edit > [data-block] > div > .wp-block-group__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block:not(.is-selected) { - - > .block-editor-block-list__block-edit::before { - border: $border-width dashed $dark-opacity-light-600; - - .is-dark-theme & { - border-color: $light-opacity-light-500; - } + border-left-color: $light-opacity-light-800; + box-shadow: inset $block-left-border-width 0 0 0 $light-gray-600; } - &.is-hovered > .block-editor-block-list__block-edit::before { - border-style: solid; - border-color: $dark-opacity-light-500; - border-left-color: transparent; + // Switch to outset borders on larger screens. + @include break-small() { + box-shadow: -$block-left-border-width 0 0 0 $dark-gray-500; .is-dark-theme & { - border-color: $light-opacity-light-400; - border-left-color: transparent; + box-shadow: -$block-left-border-width 0 0 0 $light-gray-600; } } } } -} - -/** - * Cross-block selection - */ -.block-editor-block-list__layout .block-editor-block-list__block { - ::-moz-selection { - background-color: $blue-medium-highlight; - } - - ::selection { - background-color: $blue-medium-highlight; - } - - // Selection style for multiple blocks. - &.is-multi-selected *::selection { - background-color: transparent; + // The additional marker, we limit only to top level blocks. + .block-editor-block-list__block.is-multi-selected .block-editor-block-list__block.is-multi-selected > .block-editor-block-list__block-edit::before { + box-shadow: none; } - &.is-multi-selected .block-editor-block-list__block-edit::before { - background: $blue-medium-highlight; - - // Use opacity to work in various editor styles. - mix-blend-mode: multiply; - - // Collapse extra vertical padding on selection. - top: -$block-padding; - bottom: -$block-padding; - - .is-dark-theme & { - mix-blend-mode: soft-light; + // Provide exceptions for placeholders. + .components-placeholder { + ::selection { + background: transparent; } } } @@ -311,7 +248,7 @@ } } - &.has-warning.is-selected .editor-block-list__block-edit::before { + &.has-warning.is-selected .block-editor-block-list__block-edit::before { // Use opacity to work in various editor styles. border-color: $dark-opacity-light-800; border-left-color: transparent; @@ -345,7 +282,7 @@ } } - // Appender + // Appender. &.is-typing .block-editor-block-list__side-inserter { opacity: 0; animation: none; @@ -355,27 +292,14 @@ @include edit-post__fade-in-animation; } - // Reusable blocks - &.is-reusable > .block-editor-block-list__block-edit::before { - border: $border-width dashed $dark-opacity-light-500; - - .is-dark-theme & { - border-color: $light-opacity-light-600; - } - } - + // Reusable blocks. &.is-reusable.is-selected > .block-editor-block-list__block-edit::before { - // Use opacity to work in various editor styles. - border-color: $dark-opacity-light-800; border-left-color: transparent; - - .is-dark-theme & { - border-color: $light-opacity-light-800; - border-left-color: transparent; - } + border-style: dashed; + border-width: $border-width; } - // Reusable Blocks clickthrough overlays + // Reusable Blocks clickthrough overlays. &.is-reusable > .block-editor-block-list__block-edit .block-editor-inner-blocks.has-overlay { // Remove only the top click overlay. &::after { @@ -389,7 +313,7 @@ } - // Alignments + // Alignments. &[data-align="left"], &[data-align="right"] { // Without z-index, won't be clickable as "above" adjacent content. @@ -445,10 +369,10 @@ } } - // Left + // Left. &[data-align="left"] { // This is in the editor only; the image should be floated on the frontend. - .block-editor-block-list__block-edit { + > .block-editor-block-list__block-edit { /*!rtl:begin:ignore*/ float: left; margin-right: 2em; @@ -466,7 +390,7 @@ } } - // Right + // Right. &[data-align="right"] { // Right: This is in the editor only; the image should be floated on the frontend. > .block-editor-block-list__block-edit { @@ -487,7 +411,7 @@ } } - // Wide and full-wide + // Wide and full-wide. &[data-align="full"], &[data-align="wide"] { clear: both; @@ -523,15 +447,10 @@ } } - // If the block movers are visible, push the breadcrumb down to make room for them. - .block-editor-block-mover.is-visible + .block-editor-block-list__breadcrumb { - top: (-$block-padding - $block-left-border-width - ($grid-size-small / 2)); - } - // Align block toolbar to floated content. // Extra specificity is needed to avoid applying this to innerblocks. @include break-small() { - > .editor-block-list__block-edit > .block-editor-block-contextual-toolbar > .block-editor-block-toolbar { + > .block-editor-block-list__block-edit > .block-editor-block-contextual-toolbar > .block-editor-block-toolbar { /*!rtl:begin:ignore*/ left: $block-side-ui-width * 3 + ($grid-size-small * 1.5); /*!rtl:end:ignore*/ @@ -539,7 +458,7 @@ } @include break-xlarge() { - > .editor-block-list__block-edit > .block-editor-block-contextual-toolbar > .block-editor-block-toolbar { + > .block-editor-block-list__block-edit > .block-editor-block-contextual-toolbar > .block-editor-block-toolbar { /*!rtl:begin:ignore*/ left: $block-padding; /*!rtl:end:ignore*/ @@ -547,7 +466,7 @@ } } - // Wide + // Wide. &[data-align="wide"] { // Position mover &.is-multi-selected > .block-editor-block-mover, @@ -556,32 +475,15 @@ } } - // Full-wide + // Full-wide. &[data-align="full"] { - // Position hover label on the left for the top level block. - > .block-editor-block-list__block-edit > .block-editor-block-list__breadcrumb { - left: 0; - } - - // Compensate for main container padding and subtract border. - @include break-small() { - margin-left: -$block-side-ui-width - $block-padding - $block-side-ui-clearance - $border-width; - margin-right: -$block-side-ui-width - $block-padding - $block-side-ui-clearance - $border-width; - } - > .block-editor-block-list__block-edit { margin-left: -$block-padding; margin-right: -$block-padding; @include break-small() { - margin-left: -$block-side-ui-width - $block-side-ui-clearance - $block-padding; - margin-right: -$block-side-ui-width - $block-side-ui-clearance - $block-padding; - } - - // This explicitly sets the width of the block, to override - // `width: fit-content` from the image block. - figure { - width: 100%; + margin-left: -$block-padding - $block-padding - $block-side-ui-width - $border-width - $border-width; + margin-right: -$block-padding - $block-padding - $block-side-ui-width - $border-width - $border-width; } } @@ -592,19 +494,19 @@ border-right-width: 0; } - // Position mover + // Position mover. &.is-multi-selected > .block-editor-block-mover, > .block-editor-block-list__block-edit > .block-editor-block-mover { - left: $border-width; + left: $block-padding; } } - // Clear floats + // Clear floats. &[data-clear="true"] { float: none; } - // Dropzones + // Dropzones. .block-editor-block-drop-zone { top: -4px; bottom: -3px; @@ -629,39 +531,7 @@ /** - * Styles that affect inner-block containers (nested blocks). - */ - -// Hide trailing appender for non-empty blocks, until selected. -.block-editor-inner-blocks { - .block-editor-block-list__block + .block-list-appender { - display: none; - - // When a parent container is selected, show the appender. - .is-selected & { - display: block; - } - } - - // When a child of a parent is selected, show the adjacent appender. - .block-editor-block-list__block.is-selected + .block-list-appender { - display: block; - } - - /* @todo: - The two rules above can be simplified & combined when https://github.com/WordPress/gutenberg/pull/14961 is merged, - into the following: - - .is-selected &, - .has-child-selected & { - display: block; - } - */ -} - - -/** - * Left and right side UI; Unified toolbar on Mobile + * Left and right side UI; Unified toolbar on Mobile. */ .block-editor-block-list__block { @@ -691,10 +561,8 @@ } // Left side UI. - &.is-multi-selected > .block-editor-block-mover, - > .block-editor-block-list__block-edit > .block-editor-block-mover { - padding-right: $block-side-ui-clearance; - + > .block-editor-block-list__block-edit > .block-editor-block-mover, + &.is-multi-selected > .block-editor-block-mover { // Position for top level blocks. left: -$block-side-ui-width - $block-side-ui-clearance - $block-padding - $border-width - $grid-size; @@ -704,41 +572,6 @@ display: block; } } - - &.is-multi-selected > .block-editor-block-mover { - left: -$block-side-ui-width - $block-side-ui-clearance; - } - - // For floats, show block mover when block is selected, and never on hover. - &[data-align="left"], - &[data-align="right"] { - // Show always when the block is selected. - &.is-selected > .block-editor-block-list__block-edit > .block-editor-block-mover { - // Don't show on mobile, allow the special mobile toolbar to work there. - display: none; - @include break-small() { - display: block; - opacity: 1; - animation: none; - - // Make wider and taller to make "safe" hover area bigger. - // The intent is to make it less likely that you hover float-adjacent - // blocks that visually appear below the block. - width: $block-side-ui-width + $block-side-ui-clearance + $block-padding + $border-width; - height: auto; - padding-bottom: $block-padding; - - // Unset the negative top margin, or it might overlap the block toolbar. - margin-top: 0; - } - } - - // Don't show on hover, or on the "ghost" when dragging. - &.is-hovered > .block-editor-block-list__block-edit > .block-editor-block-mover, - &.is-dragging > .block-editor-block-list__block-edit > .block-editor-block-mover { - display: none; - } - } } @@ -909,15 +742,9 @@ height: $block-padding * 2; bottom: auto; - // Go edge to edge on mobile. - left: 0; - right: 0; - - // Beyond mobile, make sure the toolbar overlaps the hover style. - @include break-small() { - left: -$border-width; - right: -$border-width; - } + // Match width of actual content. + left: $block-padding; + right: $block-padding; } &[data-align="full"] > .block-editor-block-list__insertion-point { @@ -996,7 +823,7 @@ box-shadow: none; } - .editor-block-toolbar { + .block-editor-block-toolbar { border-left: none; } } @@ -1104,95 +931,44 @@ /** - * Hover label + * Block Label for Navigation/Selection Mode */ - .block-editor-block-list__breadcrumb { + display: block; position: absolute; line-height: 1; z-index: z-index(".block-editor-block-list__breadcrumb"); // Position in the top left of the border. - left: -$block-padding - $block-left-border-width; - top: (($block-padding * -2) - $block-left-border-width); + left: -$block-padding; + top: -$block-toolbar-height - $block-padding; .components-toolbar { - border: none; - line-height: 1; - font-family: $default-font; - font-size: 11px; - padding: 4px 4px; - background: $light-gray-500; - color: $dark-gray-900; - transition: box-shadow 0.1s linear; - @include reduce-motion("transition"); + display: flex; + background: $white; + border: $border-width solid $blue-medium-focus; + border-left: none; + box-shadow: inset $block-left-border-width 0 0 0 $blue-medium-focus; + height: $block-toolbar-height + $border-width; + font-size: $default-font-size; + line-height: $block-toolbar-height - $grid-size; + padding-left: $grid-size; + padding-right: $grid-size; .components-button { - font-size: inherit; - line-height: inherit; - padding: 0; + box-shadow: none; } .is-dark-theme & { - background: $dark-gray-600; - color: $white; + border-color: $light-opacity-light-800; } - } - - // Remove negative left breadcrumb position for left aligned blocks. - [data-align="left"] & { - left: 0; - } - - // Right-align the breadcrumb for right-aligned blocks. - [data-align="right"] & { - left: auto; - right: 0; - } - - // In navigation mode, this should appear similarly to the block toolbar. - .is-navigate-mode & { - - // Position in the top left of the border. - left: -$block-padding; - top: -$block-toolbar-height - $block-padding; - .components-toolbar { - background: $white; - border: $border-width solid $blue-medium-focus; - border-left: none; - box-shadow: inset $block-left-border-width 0 0 0 $blue-medium-focus; - height: $block-toolbar-height + $border-width; - font-size: $default-font-size; - line-height: $block-toolbar-height - $grid-size; - padding-left: $grid-size; - padding-right: $grid-size; - - .components-button { - box-shadow: none; - } - - .is-dark-theme & { - border-color: $light-opacity-light-800; - } - - @include break-small() { - box-shadow: -$block-left-border-width 0 0 0 $blue-medium-focus; - } + @include break-small() { + box-shadow: -$block-left-border-width 0 0 0 $blue-medium-focus; } } } -.block-editor-block-list__descendant-arrow::before { - content: "→"; - display: inline-block; - padding: 0 4px; - - .rtl & { - content: "←"; - } -} - .block-editor-block-list__block { @include break-small { // Increase the hover and selection area around blocks. diff --git a/packages/block-editor/src/components/block-list/subdirectory-icon.js b/packages/block-editor/src/components/block-list/subdirectory-icon.js new file mode 100644 index 00000000000000..9da2bc4a1c88a9 --- /dev/null +++ b/packages/block-editor/src/components/block-list/subdirectory-icon.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/components'; + +const Subdirectory = ( { ...extraProps } ) => ( + + + ) +; + +export default Subdirectory; diff --git a/packages/block-editor/src/components/block-list/test/block-html.js b/packages/block-editor/src/components/block-list/test/block-html.js deleted file mode 100644 index 442e55de3f5bba..00000000000000 --- a/packages/block-editor/src/components/block-list/test/block-html.js +++ /dev/null @@ -1,110 +0,0 @@ -/** - * External dependencies - */ -import { shallow } from 'enzyme'; - -/** - * WordPress dependencies - */ -import { createBlock } from '@wordpress/blocks'; -import { registerCoreBlocks } from '@wordpress/block-library'; - -/** - * Internal dependencies - */ -import { BlockHTML } from '../block-html'; - -describe( 'BlockHTML', () => { - beforeAll( () => { - registerCoreBlocks(); - } ); - - it( 'renders HTML editor', () => { - const wrapper = shallow( ); - - expect( wrapper.name() ).toBe( 'TextareaAutosize' ); - } ); - - describe( 'block content', () => { - it( 'use originalContent for an invalid block', () => { - const wrapper = shallow( ); - - expect( wrapper.state( 'html' ) ).toBe( 'test' ); - } ); - - it( 'use block content for a valid block', () => { - const block = createBlock( 'core/paragraph', { - content: 'test-block', - isValid: true, - } ); - - const wrapper = shallow( ); - - expect( wrapper.state( 'html' ) ).toBe( '

    test-block

    ' ); - } ); - - it( 'onChange updates block html', () => { - const wrapper = shallow( ); - - wrapper.instance().onChange( { target: { value: 'update' } } ); - - expect( wrapper.state( 'html' ) ).toBe( 'update' ); - } ); - } ); - - describe( 'onBlur', () => { - const onChange = jest.fn(); - - beforeEach( () => { - onChange.mockClear(); - } ); - - it( 'onBlur updates valid HTML string in block', () => { - const block = createBlock( 'core/paragraph', { - content: 'test-block', - isValid: true, - } ); - const wrapper = shallow( ); - - wrapper.instance().onChange( { target: { value: '

    update

    ' } } ); - wrapper.instance().onBlur(); - - expect( onChange ).toHaveBeenCalledTimes( 1 ); - expect( onChange.mock.calls[ 0 ][ 2 ] ).toBe( '

    update

    ' ); - expect( onChange.mock.calls[ 0 ][ 3 ] ).toBe( true ); - } ); - - it( 'onBlur updates invalid HTML string in block', () => { - const block = createBlock( 'core/paragraph', { - content: 'test-block', - isValid: true, - } ); - const wrapper = shallow( ); - - wrapper.instance().onChange( { target: { value: '

    update' } } ); - wrapper.instance().onBlur(); - - expect( console ).toHaveErrored(); - expect( console ).toHaveWarned(); - expect( onChange ).toHaveBeenCalledTimes( 1 ); - expect( onChange.mock.calls[ 0 ][ 2 ] ).toBe( '

    update' ); - expect( onChange.mock.calls[ 0 ][ 3 ] ).toBe( false ); - } ); - - it( 'onBlur resets block to default content if supplied empty string', () => { - const block = createBlock( 'core/paragraph', { - content: 'test-block', - isValid: true, - } ); - const wrapper = shallow( ); - - wrapper.instance().onChange( { target: { value: '' } } ); - wrapper.instance().onBlur(); - - expect( onChange ).toHaveBeenCalledTimes( 1 ); - expect( onChange.mock.calls[ 0 ][ 2 ] ).toBe( '

    ' ); - expect( onChange.mock.calls[ 0 ][ 3 ] ).toBe( true ); - expect( wrapper.state( 'html' ) ).toBe( '

    ' ); - } ); - } ); -} ); diff --git a/packages/block-editor/src/components/block-list/use-multi-selection.js b/packages/block-editor/src/components/block-list/use-multi-selection.js new file mode 100644 index 00000000000000..ca2ccdf13b98a1 --- /dev/null +++ b/packages/block-editor/src/components/block-list/use-multi-selection.js @@ -0,0 +1,200 @@ +/** + * WordPress dependencies + */ +import { useEffect, useRef, useCallback } from '@wordpress/element'; +import { useSelect, useDispatch } from '@wordpress/data'; + +/** + * Returns for the deepest node at the start or end of a container node. Ignores + * any text nodes that only contain HTML formatting whitespace. + * + * @param {Element} node Container to search. + * @param {string} type 'start' or 'end'. + */ +function getDeepestNode( node, type ) { + const child = type === 'start' ? 'firstChild' : 'lastChild'; + const sibling = type === 'start' ? 'nextSibling' : 'previousSibling'; + + while ( node[ child ] ) { + node = node[ child ]; + + while ( + node.nodeType === node.TEXT_NODE && + /^[ \t\n]*$/.test( node.data ) && + node[ sibling ] + ) { + node = node[ sibling ]; + } + } + + return node; +} + +export default function useMultiSelection( { ref, rootClientId } ) { + function selector( select ) { + const { + getBlockOrder, + isSelectionEnabled, + isMultiSelecting, + getMultiSelectedBlockClientIds, + hasMultiSelection, + getBlockParents, + } = select( 'core/block-editor' ); + + return { + blockClientIds: getBlockOrder( rootClientId ), + isSelectionEnabled: isSelectionEnabled(), + isMultiSelecting: isMultiSelecting(), + multiSelectedBlockClientIds: getMultiSelectedBlockClientIds(), + hasMultiSelection: hasMultiSelection(), + getBlockParents, + }; + } + + const { + blockClientIds, + isSelectionEnabled, + isMultiSelecting, + multiSelectedBlockClientIds, + hasMultiSelection, + getBlockParents, + } = useSelect( selector, [ rootClientId ] ); + const { + startMultiSelect, + stopMultiSelect, + multiSelect, + selectBlock, + } = useDispatch( 'core/block-editor' ); + const rafId = useRef(); + const startClientId = useRef(); + + /** + * When the component updates, and there is multi selection, we need to + * select the entire block contents. + */ + useEffect( () => { + if ( ! hasMultiSelection || isMultiSelecting ) { + return; + } + + const { length } = multiSelectedBlockClientIds; + // These must be in the right DOM order. + const start = multiSelectedBlockClientIds[ 0 ]; + const end = multiSelectedBlockClientIds[ length - 1 ]; + const startIndex = blockClientIds.indexOf( start ); + + // The selected block is not in this block list. + if ( startIndex === -1 ) { + return; + } + + let startNode = ref.current.querySelector( + `[data-block="${ start }"]` + ); + let endNode = ref.current.querySelector( + `[data-block="${ end }"]` + ); + + const selection = window.getSelection(); + const range = document.createRange(); + + // The most stable way to select the whole block contents is to start + // and end at the deepest points. + startNode = getDeepestNode( startNode, 'start' ); + endNode = getDeepestNode( endNode, 'end' ); + + range.setStartBefore( startNode ); + range.setEndAfter( endNode ); + + selection.removeAllRanges(); + selection.addRange( range ); + }, [ + hasMultiSelection, + isMultiSelecting, + multiSelectedBlockClientIds, + blockClientIds, + selectBlock, + ] ); + + const onSelectionChange = useCallback( () => { + const selection = window.getSelection(); + + // If no selection is found, end multi selection. + if ( ! selection.rangeCount || selection.isCollapsed ) { + return; + } + + let { focusNode } = selection; + let clientId; + + // Find the client ID of the block where the selection ends. + do { + focusNode = focusNode.parentElement; + } while ( + focusNode && + ! ( clientId = focusNode.getAttribute( 'data-block' ) ) + ); + + if ( startClientId.current === clientId ) { + selectBlock( clientId ); + } else { + const startPath = [ ...getBlockParents( startClientId.current ), startClientId.current ]; + const endPath = [ ...getBlockParents( clientId ), clientId ]; + const depth = Math.min( startPath.length, endPath.length ) - 1; + + multiSelect( startPath[ depth ], endPath[ depth ] ); + } + }, [ selectBlock, getBlockParents, multiSelect ] ); + + /** + * Handles a mouseup event to end the current mouse multi-selection. + */ + const onSelectionEnd = useCallback( () => { + document.removeEventListener( 'selectionchange', onSelectionChange ); + // Equivalent to attaching the listener once. + window.removeEventListener( 'mouseup', onSelectionEnd ); + // The browser selection won't have updated yet at this point, so wait + // until the next animation frame to get the browser selection. + rafId.current = window.requestAnimationFrame( () => { + onSelectionChange(); + stopMultiSelect(); + } ); + }, [ onSelectionChange, stopMultiSelect ] ); + + // Only clean up when unmounting, these are added and cleaned up elsewhere. + useEffect( () => () => { + document.removeEventListener( 'selectionchange', onSelectionChange ); + window.removeEventListener( 'mouseup', onSelectionEnd ); + window.cancelAnimationFrame( rafId.current ); + }, [ onSelectionChange, onSelectionEnd ] ); + + /** + * Binds event handlers to the document for tracking a pending multi-select + * in response to a mousedown event occurring in a rendered block. + */ + return useCallback( ( clientId ) => { + if ( ! isSelectionEnabled ) { + return; + } + + startClientId.current = clientId; + startMultiSelect(); + + // `onSelectionStart` is called after `mousedown` and `mouseleave` + // (from a block). The selection ends when `mouseup` happens anywhere + // in the window. + document.addEventListener( 'selectionchange', onSelectionChange ); + window.addEventListener( 'mouseup', onSelectionEnd ); + + // Removing the contenteditable attributes within the block editor is + // essential for selection to work across editable areas. The edible + // hosts are removed, allowing selection to be extended outside the + // DOM element. `startMultiSelect` sets a flag in the store so the rich + // text components are updated, but the rerender may happen very slowly, + // especially in Safari for the blocks that are asynchonously rendered. + // To ensure the browser instantly removes the selection boundaries, we + // remove the contenteditable attributes manually. + Array.from( ref.current.querySelectorAll( '.rich-text' ) ) + .forEach( ( node ) => node.removeAttribute( 'contenteditable' ) ); + }, [ isSelectionEnabled, startMultiSelect, onSelectionEnd ] ); +} diff --git a/packages/block-editor/src/components/block-mobile-toolbar/index.js b/packages/block-editor/src/components/block-mobile-toolbar/index.js new file mode 100644 index 00000000000000..689e455d833885 --- /dev/null +++ b/packages/block-editor/src/components/block-mobile-toolbar/index.js @@ -0,0 +1,24 @@ +/** + * WordPress dependencies + */ +import { useViewportMatch } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import BlockMover from '../block-mover'; + +function BlockMobileToolbar( { clientId, moverDirection } ) { + const isMobile = useViewportMatch( 'small', '<' ); + if ( ! isMobile ) { + return null; + } + + return ( +
    + +
    + ); +} + +export default BlockMobileToolbar; diff --git a/packages/block-editor/src/components/block-list/block-mobile-toolbar.native.js b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js similarity index 96% rename from packages/block-editor/src/components/block-list/block-mobile-toolbar.native.js rename to packages/block-editor/src/components/block-mobile-toolbar/index.native.js index b6fbfd0e49febb..46580878ff6843 100644 --- a/packages/block-editor/src/components/block-list/block-mobile-toolbar.native.js +++ b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js @@ -14,7 +14,7 @@ import { compose } from '@wordpress/compose'; /** * Internal dependencies */ -import styles from './block-mobile-toolbar.scss'; +import styles from './style.scss'; import BlockMover from '../block-mover'; import { BlockSettingsButton } from '../block-settings'; diff --git a/packages/block-editor/src/components/block-list/block-mobile-toolbar.native.scss b/packages/block-editor/src/components/block-mobile-toolbar/style.native.scss similarity index 100% rename from packages/block-editor/src/components/block-list/block-mobile-toolbar.native.scss rename to packages/block-editor/src/components/block-mobile-toolbar/style.native.scss diff --git a/packages/block-editor/src/components/block-mobile-toolbar/style.scss b/packages/block-editor/src/components/block-mobile-toolbar/style.scss new file mode 100644 index 00000000000000..a57cd99fdd77f6 --- /dev/null +++ b/packages/block-editor/src/components/block-mobile-toolbar/style.scss @@ -0,0 +1,29 @@ +.block-editor-block-mobile-toolbar { + display: flex; + flex-direction: row; + border-right: $border-width solid $light-gray-500; + + .block-editor-block-mover__control { + width: $icon-button-size; + height: $icon-button-size; + border-radius: $radius-round-rectangle; + padding: 3px; + margin: 0; + justify-content: center; + align-items: center; + + .dashicon { + margin: auto; + } + } + + // Movers + .block-editor-block-mover { + display: flex; + margin-right: auto; + + .block-editor-block-mover__control { + float: left; + } + } +} diff --git a/packages/block-editor/src/components/block-mover/drag-handle.js b/packages/block-editor/src/components/block-mover/drag-handle.js deleted file mode 100644 index 86551bf4be0e30..00000000000000 --- a/packages/block-editor/src/components/block-mover/drag-handle.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - -/** - * Internal dependencies - */ -import BlockDraggable from '../block-draggable'; - -export const IconDragHandle = ( { isVisible, className, icon, onDragStart, onDragEnd, blockElementId, clientId } ) => { - if ( ! isVisible ) { - return null; - } - - const dragHandleClassNames = classnames( 'editor-block-mover__control-drag-handle block-editor-block-mover__control-drag-handle', className ); - - return ( - - { - ( { onDraggableStart, onDraggableEnd } ) => ( - - ) } - - ); -}; diff --git a/packages/block-editor/src/components/block-mover/icons.js b/packages/block-editor/src/components/block-mover/icons.js index 06bf5601f439db..2a15b8a6159d5d 100644 --- a/packages/block-editor/src/components/block-mover/icons.js +++ b/packages/block-editor/src/components/block-mover/icons.js @@ -9,12 +9,24 @@ export const upArrow = ( ); +export const leftArrow = ( + + + +); + export const downArrow = ( ); +export const rightArrow = ( + + + +); + export const dragHandle = ( - + + + { ( { onDraggableStart, onDraggableEnd } ) => ( + + ) } + + @@ -107,6 +146,8 @@ export class BlockMover extends Component { isFirst, isLast, 1, + orientation, + isRTL, ) } @@ -125,12 +166,17 @@ export default compose( const blockOrder = getBlockOrder( rootClientId ); const firstIndex = getBlockIndex( firstClientId, rootClientId ); const lastIndex = getBlockIndex( last( normalizedClientIds ), rootClientId ); + const { getSettings } = select( 'core/block-editor' ); + const { + isRTL, + } = getSettings(); return { blockType: block ? getBlockType( block.name ) : null, isLocked: getTemplateLock( rootClientId ) === 'all', rootClientId, firstIndex, + isRTL, isFirst: firstIndex === 0, isLast: lastIndex === blockOrder.length - 1, }; diff --git a/packages/block-editor/src/components/block-mover/mover-description.js b/packages/block-editor/src/components/block-mover/mover-description.js index 44174ce85534ca..d9a62de176d821 100644 --- a/packages/block-editor/src/components/block-mover/mover-description.js +++ b/packages/block-editor/src/components/block-mover/mover-description.js @@ -14,12 +14,30 @@ import { __, _n, sprintf } from '@wordpress/i18n'; * @param {boolean} isLast This is the last block. * @param {number} dir Direction of movement (> 0 is considered to be going * down, < 0 is up). + * @param {string} orientation The orientation of the block movers, vertical or + * horizontal. + * @param {boolean} isRTL True if current writing system is right to left. * * @return {string} Label for the block movement controls. */ -export function getBlockMoverDescription( selectedCount, type, firstIndex, isFirst, isLast, dir ) { +export function getBlockMoverDescription( selectedCount, type, firstIndex, isFirst, isLast, dir, orientation, isRTL ) { const position = ( firstIndex + 1 ); + const getMovementDirection = ( moveDirection ) => { + if ( moveDirection === 'up' ) { + if ( orientation === 'horizontal' ) { + return isRTL ? 'right' : 'left'; + } + return 'up'; + } else if ( moveDirection === 'down' ) { + if ( orientation === 'horizontal' ) { + return isRTL ? 'left' : 'right'; + } + return 'down'; + } + return null; + }; + if ( selectedCount > 1 ) { return getMultiBlockMoverDescription( selectedCount, firstIndex, isFirst, isLast, dir ); } @@ -32,35 +50,46 @@ export function getBlockMoverDescription( selectedCount, type, firstIndex, isFir if ( dir > 0 && ! isLast ) { // moving down return sprintf( - // translators: 1: Type of block (i.e. Text, Image etc), 2: Position of selected block, 3: New position - __( 'Move %1$s block from position %2$d down to position %3$d' ), + // translators: 1: Type of block (i.e. Text, Image etc), 2: Position of selected block, 3: Direction of movement ( up, down, left, right ), 4: New position + __( 'Move %1$s block from position %2$d %3$s to position %4$d' ), type, position, - ( position + 1 ) + getMovementDirection( 'down' ), + ( position + 1 ), ); } if ( dir > 0 && isLast ) { // moving down, and is the last item - // translators: %s: Type of block (i.e. Text, Image etc) - return sprintf( __( 'Block %s is at the end of the content and can’t be moved down' ), type ); + // translators: 1: Type of block (i.e. Text, Image etc), 2: Direction of movement ( up, down, left, right ) + return sprintf( + __( 'Block %1$s is at the end of the content and can’t be moved %2$s' ), + type, + getMovementDirection( 'down' ), + + ); } if ( dir < 0 && ! isFirst ) { // moving up return sprintf( - // translators: 1: Type of block (i.e. Text, Image etc), 2: Position of selected block, 3: New position - __( 'Move %1$s block from position %2$d up to position %3$d' ), + // translators: 1: Type of block (i.e. Text, Image etc), 2: Position of selected block, 3: Direction of movement ( up, down, left, right ), 4: New position + __( 'Move %1$s block from position %2$d %3$s to position %4$d' ), type, position, - ( position - 1 ) + getMovementDirection( 'up' ), + ( position - 1 ), ); } if ( dir < 0 && isFirst ) { // moving up, and is the first item - // translators: %s: Type of block (i.e. Text, Image etc) - return sprintf( __( 'Block %s is at the beginning of the content and can’t be moved up' ), type ); + // translators: 1: Type of block (i.e. Text, Image etc), 2: Direction of movement ( up, down, left, right ) + return sprintf( + __( 'Block %1$s is at the beginning of the content and can’t be moved %2$s' ), + type, + getMovementDirection( 'up' ), + ); } } diff --git a/packages/block-editor/src/components/block-mover/style.scss b/packages/block-editor/src/components/block-mover/style.scss index a506b3282fad42..61493ee98b4f03 100644 --- a/packages/block-editor/src/components/block-mover/style.scss +++ b/packages/block-editor/src/components/block-mover/style.scss @@ -1,7 +1,5 @@ .block-editor-block-mover { - @include break-small() { - min-height: $empty-paragraph-height; opacity: 0; background: $white; border: 1px solid $dark-opacity-light-800; @@ -20,13 +18,38 @@ // 24px is the smallest size of a good pressable button. // With 3 pieces of side UI, that comes to a total of 72px. // To vertically center against a 56px paragraph, move upwards 72px - 56px / 2. - // Don't do this for wide, fullwide, or mobile. - .block-editor-block-list__block:not([data-align="wide"]):not([data-align="full"]) & { - margin-top: -$grid-size; + margin-top: -$grid-size; + } + + &.is-horizontal { + margin-top: 5px; // The height of the appender is 36px. This pushes down the mover to be centered according to that. + margin-right: $grid-size; + padding-right: 0; + min-height: auto; + width: ($icon-button-size-small * 2) + ($border-width * 2); + height: $icon-button-size-small + ($border-width * 2); + display: flex; + + .block-editor-block-mover__control { + width: $icon-button-size-small; + height: $icon-button-size-small; + + svg { + width: $icon-button-size-small; + padding: 3px; + } } } } +// Don't add negative vertical margin for wide, fullwide, or mobile. +// @todo: simplify this selector. +@include break-small() { + .block-editor-block-list__block:not([data-align="wide"]):not([data-align="full"]) .editor-block-mover:not(.is-horizontal) { + margin-top: 0; + } +} + // Mover icon buttons. .block-editor-block-mover__control { display: flex; diff --git a/packages/block-editor/src/components/block-mover/test/index.js b/packages/block-editor/src/components/block-mover/test/index.js index 48809ae7905669..386baaa55554ad 100644 --- a/packages/block-editor/src/components/block-mover/test/index.js +++ b/packages/block-editor/src/components/block-mover/test/index.js @@ -7,7 +7,7 @@ import { shallow } from 'enzyme'; * Internal dependencies */ import { BlockMover } from '../'; -import { upArrow, downArrow, dragHandle } from '../icons'; +import { upArrow, downArrow } from '../icons'; describe( 'BlockMover', () => { describe( 'basic rendering', () => { @@ -28,7 +28,8 @@ describe( 'BlockMover', () => { clientIds={ selectedClientIds } blockType={ blockType } firstIndex={ 0 } - instanceId={ 1 } /> + instanceId={ 1 } + /> ); expect( blockMover.hasClass( 'block-editor-block-mover' ) ).toBe( true ); @@ -38,7 +39,7 @@ describe( 'BlockMover', () => { const moveUpDesc = blockMover.childAt( 3 ); const moveDownDesc = blockMover.childAt( 4 ); expect( moveUp.name() ).toBe( 'ForwardRef(IconButton)' ); - expect( drag.type().name ).toBe( 'IconDragHandle' ); + expect( drag.type().name ).toBe( 'BlockDraggable' ); expect( moveDown.name() ).toBe( 'ForwardRef(IconButton)' ); expect( moveUp.props() ).toMatchObject( { className: 'editor-block-mover__control block-editor-block-mover__control', @@ -48,10 +49,6 @@ describe( 'BlockMover', () => { 'aria-disabled': undefined, 'aria-describedby': 'block-editor-block-mover__up-description-1', } ); - expect( drag.props() ).toMatchObject( { - className: 'editor-block-mover__control block-editor-block-mover__control', - icon: dragHandle, - } ); expect( moveDown.props() ).toMatchObject( { className: 'editor-block-mover__control block-editor-block-mover__control', onClick: undefined, @@ -77,24 +74,6 @@ describe( 'BlockMover', () => { const moveUp = blockMover.childAt( 0 ); expect( moveUp.prop( 'onClick' ) ).toBe( onMoveUp ); } ); - - it( 'should render the drag handle with onDragStart and onDragEnd callback', () => { - const onDragStart = ( event ) => event; - const onDragEnd = ( event ) => event; - const blockMover = shallow( - - ); - const dragHandler = blockMover.childAt( 1 ); - expect( dragHandler.prop( 'onDragStart' ) ).toBe( onDragStart ); - expect( dragHandler.prop( 'onDragEnd' ) ).toBe( onDragEnd ); - } ); - it( 'should render the down arrow with a onMoveDown callback', () => { const onMoveDown = ( event ) => event; const blockMover = shallow( @@ -109,19 +88,6 @@ describe( 'BlockMover', () => { expect( moveDown.prop( 'onClick' ) ).toBe( onMoveDown ); } ); - it( 'should not render the drag handle if block is not draggable', () => { - const blockMover = shallow( - - ); - const dragHandler = blockMover.childAt( 1 ); - expect( dragHandler.type().name ).toBe( 'IconDragHandle' ); - expect( dragHandler.prop( 'isVisible' ) ).toBe( false ); - } ); - it( 'should render with a disabled down arrow when the block isLast', () => { const onMoveDown = ( event ) => event; const blockMover = shallow( diff --git a/packages/block-editor/src/components/block-navigation/list.js b/packages/block-editor/src/components/block-navigation/list.js index 0a34d24ff4e4b6..85cd6837356123 100644 --- a/packages/block-editor/src/components/block-navigation/list.js +++ b/packages/block-editor/src/components/block-navigation/list.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { map } from 'lodash'; +import { isNil, map, omitBy } from 'lodash'; import classnames from 'classnames'; /** @@ -10,18 +10,48 @@ import classnames from 'classnames'; import { Button } from '@wordpress/components'; import { getBlockType } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; +import { create, getTextContent } from '@wordpress/rich-text'; /** * Internal dependencies */ import BlockIcon from '../block-icon'; +import ButtonBlockAppender from '../button-block-appender'; + +/** + * Get the block display name, if it has one, or the block title if it doesn't. + * + * @param {Object} blockType The block type. + * @param {Object} attributes The values of the block's attributes + * + * @return {string} The display name value. + */ +function getBlockDisplayName( blockType, attributes ) { + const displayNameAttribute = blockType.__experimentalDisplayName; + + if ( ! displayNameAttribute || ! attributes[ displayNameAttribute ] ) { + return blockType.title; + } + + // Strip any formatting. + const richTextValue = create( { html: attributes[ displayNameAttribute ] } ); + const formatlessDisplayName = getTextContent( richTextValue ); + + return formatlessDisplayName; +} export default function BlockNavigationList( { blocks, selectedBlockClientId, selectBlock, + showAppender, + + // Internal use only. showNestedBlocks, + parentBlockClientId, } ) { + const shouldShowAppender = showAppender && !! parentBlockClientId; + return ( /* * Disable reason: The `list` ARIA role is redundant but @@ -29,7 +59,7 @@ export default function BlockNavigationList( { */ /* eslint-disable jsx-a11y/no-redundant-roles */
      - { map( blocks, ( block ) => { + { map( omitBy( blocks, isNil ), ( block ) => { const blockType = getBlockType( block.name ); const isSelected = block.clientId === selectedBlockClientId; @@ -43,7 +73,7 @@ export default function BlockNavigationList( { onClick={ () => selectBlock( block.clientId ) } > - { blockType.title } + { getBlockDisplayName( blockType, block.attributes ) } { isSelected && { __( '(selected block)' ) } }
    @@ -52,12 +82,24 @@ export default function BlockNavigationList( { blocks={ block.innerBlocks } selectedBlockClientId={ selectedBlockClientId } selectBlock={ selectBlock } + parentBlockClientId={ block.clientId } + showAppender={ showAppender } showNestedBlocks /> ) } ); } ) } + { shouldShowAppender && ( +
  • +
    + +
    +
  • + ) } /* eslint-enable jsx-a11y/no-redundant-roles */ ); diff --git a/packages/block-editor/src/components/block-navigation/style.scss b/packages/block-editor/src/components/block-navigation/style.scss index fbd5a71c989bdb..737353fedf67bc 100644 --- a/packages/block-editor/src/components/block-navigation/style.scss +++ b/packages/block-editor/src/components/block-navigation/style.scss @@ -16,6 +16,24 @@ $tree-item-height: 36px; margin: 0; } +.block-editor-block-navigation__list .block-editor-button-block-appender { + outline: none; + background: none; + padding: $grid-size; + margin-left: 0.8em; + width: $icon-button-size; + border-radius: 4px; + + &:hover:not(:disabled):not([aria-disabled="true"]) { + @include menu-style__hover; + outline: none; + } + + &:focus:not(:disabled):not([aria-disabled="true"]) { + @include menu-style__focus; + } +} + .block-editor-block-navigation__list .block-editor-block-navigation__list { margin-top: 2px; border-left: $tree-border-width solid $light-gray-900; diff --git a/packages/block-editor/src/components/block-pattern-picker/index.js b/packages/block-editor/src/components/block-pattern-picker/index.js new file mode 100644 index 00000000000000..3b2b0a1c05383f --- /dev/null +++ b/packages/block-editor/src/components/block-pattern-picker/index.js @@ -0,0 +1,67 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Button, IconButton, Placeholder } from '@wordpress/components'; + +function BlockPatternPicker( { + icon = 'layout', + label = __( 'Choose pattern' ), + instructions = __( 'Select a pattern to start with.' ), + patterns, + onSelect, + allowSkip, +} ) { + const classes = classnames( 'block-editor-block-pattern-picker', { + 'has-many-patterns': patterns.length > 4, + } ); + + return ( + + { + /* + * Disable reason: The `list` ARIA role is redundant but + * Safari+VoiceOver won't announce the list otherwise. + */ + /* eslint-disable jsx-a11y/no-redundant-roles */ + } +
      + { patterns.map( ( pattern ) => ( +
    • + onSelect( pattern ) } + className="block-editor-block-pattern-picker__pattern" + label={ pattern.label } + /> +
    • + ) ) } +
    + { /* eslint-enable jsx-a11y/no-redundant-roles */ } + { allowSkip && ( +
    + +
    + ) } +
    + ); +} + +export default BlockPatternPicker; diff --git a/packages/block-editor/src/components/block-pattern-picker/style.scss b/packages/block-editor/src/components/block-pattern-picker/style.scss new file mode 100644 index 00000000000000..ac92faea0848aa --- /dev/null +++ b/packages/block-editor/src/components/block-pattern-picker/style.scss @@ -0,0 +1,74 @@ +.block-editor-block-pattern-picker { + .components-placeholder__instructions { + // Defer to vertical margins applied by template picker options. + margin-bottom: 0; + } + + .components-placeholder__fieldset { + // Options will render horizontally, but the immediate children of the + // fieldset are the options and the skip button, oriented vertically. + flex-direction: column; + } + + &.has-many-patterns .components-placeholder__fieldset { + // Allow options to occupy a greater amount of the available space if + // many options exist. + max-width: 90%; + } +} + +.block-editor-block-pattern-picker__patterns.block-editor-block-pattern-picker__patterns { + display: flex; + justify-content: center; + flex-direction: row; + flex-wrap: wrap; + width: 100%; + margin: $grid-size-small 0; + list-style: none; + + > li { + list-style: none; + margin: $grid-size; + flex-shrink: 1; + max-width: 100px; + } + + .block-editor-block-pattern-picker__pattern { + padding: $grid-size; + } +} + +.block-editor-block-pattern-picker__pattern { + width: 100%; + + &.components-icon-button { + // Override default styles inherited from to center + // icon horizontally. + justify-content: center; + + &.is-default { + background-color: $white; + } + } + + &.components-button { + // Override default styles inherited from - ) } + __experimentalSelectBlockOnInsert={ selectBlockOnInsert } + renderToggle={ ( { onToggle, disabled, isOpen, blockTitle, hasSingleBlockType } ) => { + let label; + if ( hasSingleBlockType ) { + // translators: %s: the name of the block when there is only one + label = sprintf( _x( 'Add %s', 'directly add the only allowed block' ), blockTitle ); + } else { + label = _x( 'Add block', 'Generic label for block inserter button' ); + } + const isToggleButton = ! hasSingleBlockType; + return ( + + + + ); + } } isAppender /> diff --git a/packages/block-editor/src/components/button-block-appender/index.native.js b/packages/block-editor/src/components/button-block-appender/index.native.js index b01d3b65109856..6e612c4b62a790 100644 --- a/packages/block-editor/src/components/button-block-appender/index.native.js +++ b/packages/block-editor/src/components/button-block-appender/index.native.js @@ -6,6 +6,7 @@ import { View } from 'react-native'; /** * WordPress dependencies */ +import { withPreferredColorScheme } from '@wordpress/compose'; import { Button, Dashicon } from '@wordpress/components'; /** @@ -14,24 +15,27 @@ import { Button, Dashicon } from '@wordpress/components'; import Inserter from '../inserter'; import styles from './styles.scss'; -function ButtonBlockAppender( { rootClientId } ) { +function ButtonBlockAppender( { rootClientId, getStylesFromColorScheme } ) { + const appenderStyle = { ...styles.appender, ...getStylesFromColorScheme( styles.appenderLight, styles.appenderDark ) }; + const addBlockButtonStyle = getStylesFromColorScheme( styles.addBlockButton, styles.addBlockButtonDark ); + return ( <> ( - @@ -45,4 +49,4 @@ function ButtonBlockAppender( { rootClientId } ) { /** * @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/button-block-appender/README.md */ -export default ButtonBlockAppender; +export default withPreferredColorScheme( ButtonBlockAppender ); diff --git a/packages/block-editor/src/components/button-block-appender/styles.native.scss b/packages/block-editor/src/components/button-block-appender/styles.native.scss index 3a6549980af87d..98035ede34c74a 100644 --- a/packages/block-editor/src/components/button-block-appender/styles.native.scss +++ b/packages/block-editor/src/components/button-block-appender/styles.native.scss @@ -1,10 +1,21 @@ .appender { + flex: 1; align-items: center; justify-content: center; - padding: 12px; + padding: 9px; + margin-left: 0; + margin-right: 0; + border-radius: 4px; +} + +.appenderLight { background-color: $white; border: $border-width solid $light-gray-500; - border-radius: 4px; +} + +.appenderDark { + background-color: $background-dark-secondary; + border: $border-width solid $gray-70; } .addBlockButton { @@ -14,3 +25,8 @@ overflow: hidden; size: $icon-button-size-small; } + +.addBlockButtonDark { + color: $background-dark-secondary; + background-color: $gray-40; +} diff --git a/packages/block-editor/src/components/caption/index.native.js b/packages/block-editor/src/components/caption/index.native.js index 68e965524c8b87..664ec75da94119 100644 --- a/packages/block-editor/src/components/caption/index.native.js +++ b/packages/block-editor/src/components/caption/index.native.js @@ -29,7 +29,7 @@ const Caption = ( { accessible, accessibilityLabel, onBlur, onChange, onFocus, i fontSize={ 14 } underlineColorAndroid="transparent" textAlign={ 'center' } - tagName={ '' } + tagName={ 'p' } /> ); diff --git a/packages/block-editor/src/components/color-palette/control.scss b/packages/block-editor/src/components/color-palette/control.scss deleted file mode 100644 index 249fc32607be17..00000000000000 --- a/packages/block-editor/src/components/color-palette/control.scss +++ /dev/null @@ -1,4 +0,0 @@ -.block-editor-color-palette-control__color-palette { - display: inline-block; - margin-top: 0.6rem; -} diff --git a/packages/block-editor/src/components/colors/index.js b/packages/block-editor/src/components/colors/index.js index f6b6fac984db89..cadb34a8c9aa37 100644 --- a/packages/block-editor/src/components/colors/index.js +++ b/packages/block-editor/src/components/colors/index.js @@ -7,3 +7,4 @@ export { createCustomColorsHOC, default as withColors, } from './with-colors'; +export { default as __experimentalUseColors } from './use-colors'; diff --git a/packages/block-editor/src/components/colors/use-colors.js b/packages/block-editor/src/components/colors/use-colors.js new file mode 100644 index 00000000000000..a2b378f447353e --- /dev/null +++ b/packages/block-editor/src/components/colors/use-colors.js @@ -0,0 +1,350 @@ +/** + * External dependencies + */ +import memoize from 'memize'; +import classnames from 'classnames'; +import { map, kebabCase, camelCase, castArray, startCase } from 'lodash'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { + useCallback, + useMemo, + Children, + cloneElement, + useRef, +} from '@wordpress/element'; +import { withFallbackStyles } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import PanelColorSettings from '../panel-color-settings'; +import ContrastChecker from '../contrast-checker'; +import InspectorControls from '../inspector-controls'; +import { useBlockEditContext } from '../block-edit'; + +/** + * Browser dependencies + */ +const { getComputedStyle, Node } = window; + +const DEFAULT_COLORS = []; + +const resolveContrastCheckerColor = ( color, colorSettings, detectedColor ) => { + if ( typeof color === 'function' ) { + return color( colorSettings ); + } else if ( color === true ) { + return detectedColor; + } + return color; +}; + +const ColorPanel = ( { + title, + colorSettings, + colorPanelProps, + contrastCheckers, + detectedBackgroundColorRef, + detectedColorRef, + panelChildren, +} ) => ( + + { contrastCheckers && + ( Array.isArray( contrastCheckers ) ? + contrastCheckers.map( ( { backgroundColor, textColor, ...rest } ) => { + backgroundColor = resolveContrastCheckerColor( + backgroundColor, + colorSettings, + detectedBackgroundColorRef.current + ); + textColor = resolveContrastCheckerColor( + textColor, + colorSettings, + detectedColorRef.current + ); + return ( + + ); + } ) : + map( colorSettings, ( { value } ) => { + let { backgroundColor, textColor } = contrastCheckers; + backgroundColor = resolveContrastCheckerColor( + backgroundColor || value, + colorSettings, + detectedBackgroundColorRef.current + ); + textColor = resolveContrastCheckerColor( + textColor || value, + colorSettings, + detectedColorRef.current + ); + return ( + + ); + } ) ) } + { typeof panelChildren === 'function' ? + panelChildren( colorSettings ) : + panelChildren } + +); +const InspectorControlsColorPanel = ( props ) => ( + + + +); + +export default function __experimentalUseColors( + colorConfigs, + { + panelTitle = __( 'Color Settings' ), + colorPanelProps, + contrastCheckers, + panelChildren, + } = { + panelTitle: __( 'Color Settings' ), + }, + deps = [] +) { + const { clientId } = useBlockEditContext(); + const { attributes, settingsColors } = useSelect( + ( select ) => { + const { getBlockAttributes, getSettings } = select( 'core/block-editor' ); + const colors = getSettings().colors; + return { + attributes: getBlockAttributes( clientId ), + settingsColors: ! colors || colors === true ? DEFAULT_COLORS : colors, + }; + }, + [ clientId ] + ); + const { updateBlockAttributes } = useDispatch( 'core/block-editor' ); + const setAttributes = useCallback( + ( newAttributes ) => updateBlockAttributes( clientId, newAttributes ), + [ updateBlockAttributes, clientId ] + ); + + const createComponent = useMemo( + () => + memoize( + ( name, property, className, color, colorValue, customColor ) => ( { + children, + className: componentClassName = '', + style: componentStyle = {}, + } ) => + // Clone children, setting the style property from the color configuration, + // if not already set explicitly through props. + Children.map( children, ( child ) => { + let colorStyle = {}; + if ( color ) { + colorStyle = { [ property ]: colorValue }; + } else if ( customColor ) { + colorStyle = { [ property ]: customColor }; + } + + return cloneElement( child, { + className: classnames( componentClassName, child.props.className, { + [ `has-${ kebabCase( color ) }-${ kebabCase( property ) }` ]: color, + [ className || `has-${ kebabCase( name ) }` ]: color || customColor, + } ), + style: { + ...colorStyle, + ...componentStyle, + ...( child.props.style || {} ), + }, + } ); + } ), + { maxSize: colorConfigs.length } + ), + [ colorConfigs.length ] + ); + const createSetColor = useMemo( + () => + memoize( + ( name, colors ) => ( newColor ) => { + const color = colors.find( ( _color ) => _color.color === newColor ); + setAttributes( { + [ color ? camelCase( `custom ${ name }` ) : name ]: undefined, + } ); + setAttributes( { + [ color ? name : camelCase( `custom ${ name }` ) ]: color ? + color.slug : + newColor, + } ); + }, + { + maxSize: colorConfigs.length, + } + ), + [ setAttributes, colorConfigs.length ] + ); + + const detectedBackgroundColorRef = useRef(); + const detectedColorRef = useRef(); + const ColorDetector = useMemo( () => { + if ( ! contrastCheckers ) { + return undefined; + } + let needsBackgroundColor = false; + let needsColor = false; + for ( const { backgroundColor, textColor } of castArray( contrastCheckers ) ) { + if ( ! needsBackgroundColor ) { + needsBackgroundColor = backgroundColor === true; + } + if ( ! needsColor ) { + needsColor = textColor === true; + } + if ( needsBackgroundColor && needsColor ) { + break; + } + } + return ( + ( needsBackgroundColor || needsColor ) && + withFallbackStyles( + ( + node, + { + querySelector, + backgroundColorSelector = querySelector, + textColorSelector = querySelector, + } + ) => { + let backgroundColorNode = node; + let textColorNode = node; + if ( backgroundColorSelector ) { + backgroundColorNode = node.parentNode.querySelector( + backgroundColorSelector + ); + } + if ( textColorSelector ) { + textColorNode = node.parentNode.querySelector( textColorSelector ); + } + let backgroundColor; + const color = getComputedStyle( textColorNode ).color; + if ( needsBackgroundColor ) { + backgroundColor = getComputedStyle( backgroundColorNode ) + .backgroundColor; + while ( + backgroundColor === 'rgba(0, 0, 0, 0)' && + backgroundColorNode.parentNode && + backgroundColorNode.parentNode === Node.ELEMENT_NODE + ) { + backgroundColorNode = backgroundColorNode.parentNode; + backgroundColor = getComputedStyle( backgroundColorNode ) + .backgroundColor; + } + } + detectedBackgroundColorRef.current = backgroundColor; + detectedColorRef.current = color; + return { backgroundColor, color }; + } + )( () => <> ) + ); + }, [ + colorConfigs.reduce( + ( acc, colorConfig ) => + `${ acc } | ${ attributes[ colorConfig.name ] } | ${ + attributes[ camelCase( `custom ${ colorConfig.name }` ) ] + }`, + '' + ), + ...deps, + ] ); + + return useMemo( () => { + const colorSettings = {}; + + const components = colorConfigs.reduce( ( acc, colorConfig ) => { + if ( typeof colorConfig === 'string' ) { + colorConfig = { name: colorConfig }; + } + const { + name, // E.g. 'backgroundColor'. + property = name, // E.g. 'backgroundColor'. + className, + + panelLabel = startCase( name ), // E.g. 'Background Color'. + componentName = panelLabel.replace( /\s/g, '' ), // E.g. 'BackgroundColor'. + + color = colorConfig.color, + colors = settingsColors, + } = { + ...colorConfig, + color: attributes[ colorConfig.name ], + }; + + const customColor = attributes[ camelCase( `custom ${ name }` ) ]; + // We memoize the non-primitives to avoid unnecessary updates + // when they are used as props for other components. + const _color = customColor ? + undefined : + colors.find( ( __color ) => __color.slug === color ); + acc[ componentName ] = createComponent( + name, + property, + className, + color, + _color && _color.color, + customColor + ); + acc[ componentName ].displayName = componentName; + acc[ componentName ].color = customColor ? + customColor : + _color && _color.color; + acc[ componentName ].slug = color; + acc[ componentName ].setColor = createSetColor( name, colors ); + + colorSettings[ componentName ] = { + value: _color ? _color.color : attributes[ camelCase( `custom ${ name }` ) ], + onChange: acc[ componentName ].setColor, + label: panelLabel, + colors, + }; + // These settings will be spread over the `colors` in + // `colorPanelProps`, so we need to unset the key here, + // if not set to an actual value, to avoid overwriting + // an actual value in `colorPanelProps`. + if ( ! colors ) { + delete colorSettings[ componentName ].colors; + } + + return acc; + }, {} ); + + const wrappedColorPanelProps = { + title: panelTitle, + colorSettings, + colorPanelProps, + contrastCheckers, + detectedBackgroundColorRef, + detectedColorRef, + panelChildren, + }; + return { + ...components, + ColorPanel: , + InspectorControlsColorPanel: ( + + ), + ColorDetector, + }; + }, [ attributes, setAttributes, ...deps ] ); +} diff --git a/packages/block-editor/src/components/colors/with-colors.js b/packages/block-editor/src/components/colors/with-colors.js index b6f33b1b8590f7..4544c6c8c49e1e 100644 --- a/packages/block-editor/src/components/colors/with-colors.js +++ b/packages/block-editor/src/components/colors/with-colors.js @@ -49,7 +49,7 @@ const withEditorColorPalette = () => withSelect( ( select ) => { * @param {Array} colorTypes An array of color types (e.g. 'backgroundColor, borderColor). * @param {Function} withColorPalette A HOC for injecting the 'colors' prop into the WrappedComponent. * - * @return {Component} The component that can be used as a HOC. + * @return {WPComponent} The component that can be used as a HOC. */ function createColorHOC( colorTypes, withColorPalette ) { const colorMap = reduce( colorTypes, ( colorObject, colorType ) => { diff --git a/packages/block-editor/src/components/contrast-checker/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/contrast-checker/test/__snapshots__/index.js.snap index 49f7c916e7151f..a61121de6e0b47 100644 --- a/packages/block-editor/src/components/contrast-checker/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/contrast-checker/test/__snapshots__/index.js.snap @@ -22,7 +22,7 @@ exports[`ContrastChecker should render component when the colors do not meet AA "_originalInput": "#666", "_r": 102, "_roundA": 1, - "_tc_id": 2, + "_tc_id": 31, } } tinyTextColor={ @@ -36,7 +36,7 @@ exports[`ContrastChecker should render component when the colors do not meet AA "_originalInput": "#666", "_r": 102, "_roundA": 1, - "_tc_id": 3, + "_tc_id": 32, } } > @@ -84,7 +84,7 @@ exports[`ContrastChecker should render different message matching snapshot when "_originalInput": "#555", "_r": 85, "_roundA": 1, - "_tc_id": 8, + "_tc_id": 37, } } tinyTextColor={ @@ -98,7 +98,7 @@ exports[`ContrastChecker should render different message matching snapshot when "_originalInput": "#666", "_r": 102, "_roundA": 1, - "_tc_id": 9, + "_tc_id": 38, } } > @@ -142,7 +142,7 @@ exports[`ContrastChecker should render messages when the textColor is valid, but "_originalInput": "#000000", "_r": 0, "_roundA": 1, - "_tc_id": 24, + "_tc_id": 53, } } tinyTextColor={ @@ -156,7 +156,7 @@ exports[`ContrastChecker should render messages when the textColor is valid, but "_originalInput": "#000000", "_r": 0, "_roundA": 1, - "_tc_id": 25, + "_tc_id": 54, } } > @@ -202,7 +202,7 @@ exports[`ContrastChecker should take into consideration the font size passed 1`] "_originalInput": "#C44B4B", "_r": 196, "_roundA": 1, - "_tc_id": 14, + "_tc_id": 43, } } tinyTextColor={ @@ -216,7 +216,7 @@ exports[`ContrastChecker should take into consideration the font size passed 1`] "_originalInput": "#000000", "_r": 0, "_roundA": 1, - "_tc_id": 15, + "_tc_id": 44, } } > @@ -262,7 +262,7 @@ exports[`ContrastChecker should take into consideration wherever text is large o "_originalInput": "#C44B4B", "_r": 196, "_roundA": 1, - "_tc_id": 10, + "_tc_id": 39, } } tinyTextColor={ @@ -276,7 +276,7 @@ exports[`ContrastChecker should take into consideration wherever text is large o "_originalInput": "#000000", "_r": 0, "_roundA": 1, - "_tc_id": 11, + "_tc_id": 40, } } > @@ -323,7 +323,7 @@ exports[`ContrastChecker should use isLargeText to make decisions if both isLarg "_originalInput": "#C44B4B", "_r": 196, "_roundA": 1, - "_tc_id": 20, + "_tc_id": 49, } } tinyTextColor={ @@ -337,7 +337,7 @@ exports[`ContrastChecker should use isLargeText to make decisions if both isLarg "_originalInput": "#000000", "_r": 0, "_roundA": 1, - "_tc_id": 21, + "_tc_id": 50, } } > diff --git a/packages/block-editor/src/components/copy-handler/index.js b/packages/block-editor/src/components/copy-handler/index.js index 75b2db4098858e..a369144b2c6a90 100644 --- a/packages/block-editor/src/components/copy-handler/index.js +++ b/packages/block-editor/src/components/copy-handler/index.js @@ -1,14 +1,19 @@ /** * WordPress dependencies */ -import { serialize } from '@wordpress/blocks'; +import { serialize, pasteHandler } from '@wordpress/blocks'; import { documentHasSelection } from '@wordpress/dom'; import { withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; -function CopyHandler( { children, onCopy, onCut } ) { +/** + * Internal dependencies + */ +import { getPasteEventData } from '../../utils/get-paste-event-data'; + +function CopyHandler( { children, handler } ) { return ( -
    +
    { children }
    ); @@ -20,38 +25,49 @@ export default compose( [ getBlocksByClientId, getSelectedBlockClientIds, hasMultiSelection, + getSettings, } = select( 'core/block-editor' ); - const { removeBlocks } = dispatch( 'core/block-editor' ); - - const onCopy = ( event ) => { - const selectedBlockClientIds = getSelectedBlockClientIds(); - - if ( selectedBlockClientIds.length === 0 ) { - return; - } + const { removeBlocks, replaceBlocks } = dispatch( 'core/block-editor' ); + const { + __experimentalCanUserUseUnfilteredHTML: canUserUseUnfilteredHTML, + } = getSettings(); - // Let native copy behaviour take over in input fields. - if ( ! hasMultiSelection() && documentHasSelection() ) { - return; - } + return { + handler( event ) { + const selectedBlockClientIds = getSelectedBlockClientIds(); - const serialized = serialize( getBlocksByClientId( selectedBlockClientIds ) ); + if ( selectedBlockClientIds.length === 0 ) { + return; + } - event.clipboardData.setData( 'text/plain', serialized ); - event.clipboardData.setData( 'text/html', serialized ); + // Always handle multiple selected blocks. + // Let native copy behaviour take over in input fields. + if ( ! hasMultiSelection() && documentHasSelection() ) { + return; + } - event.preventDefault(); - }; + event.preventDefault(); - return { - onCopy, - onCut( event ) { - onCopy( event ); + if ( event.type === 'copy' || event.type === 'cut' ) { + const blocks = getBlocksByClientId( selectedBlockClientIds ); + const serialized = serialize( blocks ); - if ( hasMultiSelection() ) { - const selectedBlockClientIds = getSelectedBlockClientIds(); + event.clipboardData.setData( 'text/plain', serialized ); + event.clipboardData.setData( 'text/html', serialized ); + } + if ( event.type === 'cut' ) { removeBlocks( selectedBlockClientIds ); + } else if ( event.type === 'paste' ) { + const { plainText, html } = getPasteEventData( event ); + const blocks = pasteHandler( { + HTML: html, + plainText, + mode: 'BLOCKS', + canUserUseUnfilteredHTML, + } ); + + replaceBlocks( selectedBlockClientIds, blocks ); } }, }; diff --git a/packages/block-editor/src/components/default-block-appender/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/default-block-appender/test/__snapshots__/index.js.snap index 9a09dae0d47674..08a0f7df9efe74 100644 --- a/packages/block-editor/src/components/default-block-appender/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/default-block-appender/test/__snapshots__/index.js.snap @@ -29,7 +29,7 @@ exports[`DefaultBlockAppender should append a default block when input focused 1 rows={1} value="Start writing or type / to choose a block" /> - @@ -53,7 +53,7 @@ exports[`DefaultBlockAppender should match snapshot 1`] = ` rows={1} value="Start writing or type / to choose a block" /> - @@ -77,7 +77,7 @@ exports[`DefaultBlockAppender should optionally show without prompt 1`] = ` rows={1} value="" /> - diff --git a/packages/block-editor/src/components/gradient-picker/control.js b/packages/block-editor/src/components/gradient-picker/control.js index 73609ffdf17e27..16179ff0bb9be6 100644 --- a/packages/block-editor/src/components/gradient-picker/control.js +++ b/packages/block-editor/src/components/gradient-picker/control.js @@ -3,19 +3,30 @@ * External dependencies */ import classnames from 'classnames'; +import { isEmpty, pick } from 'lodash'; /** * WordPress dependencies */ import { BaseControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ import GradientPicker from './'; -export default function( { className, ...props } ) { +export default function( { className, value, onChange, label = __( 'Gradient Presets' ), ...props } ) { + const { gradients = [], disableCustomGradients } = useSelect( ( select ) => ( + pick( + select( 'core/block-editor' ).getSettings(), + [ 'gradients', 'disableCustomGradients' ] + ) + ), [] ); + if ( isEmpty( gradients ) && disableCustomGradients ) { + return null; + } return ( - { __( 'Gradient Presets' ) } + { label } diff --git a/packages/block-editor/src/components/gradient-picker/control.scss b/packages/block-editor/src/components/gradient-picker/control.scss deleted file mode 100644 index e73745321e6c93..00000000000000 --- a/packages/block-editor/src/components/gradient-picker/control.scss +++ /dev/null @@ -1,4 +0,0 @@ -.block-editor-gradient-picker-control__gradient-picker-presets { - display: inline-block; - margin-top: 0.6rem; -} diff --git a/packages/block-editor/src/components/gradient-picker/index.js b/packages/block-editor/src/components/gradient-picker/index.js index a4b5fac2b98f7c..972e1aa6c763fd 100644 --- a/packages/block-editor/src/components/gradient-picker/index.js +++ b/packages/block-editor/src/components/gradient-picker/index.js @@ -1,3 +1,9 @@ + +/** + * External dependencies + */ +import { pick } from 'lodash'; + /** * WordPress dependencies */ @@ -5,19 +11,23 @@ import { __experimentalGradientPicker } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; function GradientPickerWithGradients( props ) { - const gradients = useSelect( ( select ) => ( - select( 'core/block-editor' ).getSettings().gradients - ) ); + const { gradients, disableCustomGradients } = useSelect( ( select ) => ( + pick( + select( 'core/block-editor' ).getSettings(), + [ 'gradients', 'disableCustomGradients' ] + ) + ), [] ); return ( <__experimentalGradientPicker + gradients={ props.gradients !== undefined ? props.gradient : gradients } + disableCustomGradients={ props.disableCustomGradients !== undefined ? props.disableCustomGradients : disableCustomGradients } { ...props } - gradients={ gradients } /> ); } export default function( props ) { - const ComponentToUse = props.gradients ? + const ComponentToUse = props.gradients !== undefined && props.disableCustomGradients !== undefined ? __experimentalGradientPicker : GradientPickerWithGradients; return ( ); diff --git a/packages/block-editor/src/components/gradient-picker/panel.js b/packages/block-editor/src/components/gradient-picker/panel.js new file mode 100644 index 00000000000000..25d07234e9cf50 --- /dev/null +++ b/packages/block-editor/src/components/gradient-picker/panel.js @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import { isEmpty } from 'lodash'; + +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; +import { PanelBody } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import GradientPicker from './control'; + +export default function GradientPanel( props ) { + const gradients = useSelect( ( select ) => ( + select( 'core/block-editor' ).getSettings().gradients + ), [] ); + if ( isEmpty( gradients ) ) { + return null; + } + return ( + + + + ); +} diff --git a/packages/block-editor/src/components/gradients/index.js b/packages/block-editor/src/components/gradients/index.js new file mode 100644 index 00000000000000..e0fc950294173c --- /dev/null +++ b/packages/block-editor/src/components/gradients/index.js @@ -0,0 +1,77 @@ +/** + * External dependencies + */ +import { find } from 'lodash'; + +/** + * WordPress dependencies + */ +import { useCallback } from '@wordpress/element'; +import { useSelect, useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { useBlockEditContext } from '../block-edit'; + +export function __experimentalGetGradientClass( gradientSlug ) { + if ( ! gradientSlug ) { + return undefined; + } + return `has-${ gradientSlug }-gradient-background`; +} + +function getGradientValueBySlug( gradients, slug ) { + const gradient = find( gradients, [ 'slug', slug ] ); + return gradient && gradient.gradient; +} + +function getGradientSlugByValue( gradients, value ) { + const gradient = find( gradients, [ 'gradient', value ] ); + return gradient && gradient.slug; +} + +export function __experimentalUseGradient( { + gradientAttribute = 'gradient', + customGradientAttribute = 'customGradient', +} = {} ) { + const { clientId } = useBlockEditContext(); + + const { gradients, gradient, customGradient } = useSelect( ( select ) => { + const { getBlockAttributes, getSettings } = select( 'core/block-editor' ); + const attributes = getBlockAttributes( clientId ); + return { + gradient: attributes[ gradientAttribute ], + customGradient: attributes[ customGradientAttribute ], + gradients: getSettings().gradients, + }; + }, [ clientId, gradientAttribute, customGradientAttribute ] ); + + const { updateBlockAttributes } = useDispatch( 'core/block-editor' ); + const setGradient = useCallback( + ( newGradientValue ) => { + const slug = getGradientSlugByValue( gradients, newGradientValue ); + if ( slug ) { + updateBlockAttributes( clientId, { + [ gradientAttribute ]: slug, + [ customGradientAttribute ]: undefined, + } ); + return; + } + updateBlockAttributes( clientId, { + [ gradientAttribute ]: undefined, + [ customGradientAttribute ]: newGradientValue, + } ); + }, + [ gradients, clientId, updateBlockAttributes ] + ); + + const gradientClass = __experimentalGetGradientClass( gradient ); + let gradientValue; + if ( gradient ) { + gradientValue = getGradientValueBySlug( gradients, gradient ); + } else { + gradientValue = customGradient; + } + return { gradientClass, gradientValue, setGradient }; +} diff --git a/packages/block-editor/src/components/ignore-nested-events/index.js b/packages/block-editor/src/components/ignore-nested-events/index.js index 2592ba3abd0527..92bbe5ed44c639 100644 --- a/packages/block-editor/src/components/ignore-nested-events/index.js +++ b/packages/block-editor/src/components/ignore-nested-events/index.js @@ -20,7 +20,7 @@ import { Component, forwardRef, createElement } from '@wordpress/element'; * element should stop propagation but not invoke a callback handler, since it * would be assumed these are invoked by the child element. * - * @type {Component} + * @type {WPComponent} */ export class IgnoreNestedEvents extends Component { constructor() { diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index 6e9bb592378678..1fb22e42008057 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -3,36 +3,45 @@ */ export * from './colors'; +export * from './gradients'; export * from './font-sizes'; export { default as AlignmentToolbar } from './alignment-toolbar'; export { default as Autocomplete } from './autocomplete'; export { default as BlockAlignmentToolbar } from './block-alignment-toolbar'; +export { default as BlockBreadcrumb } from './block-breadcrumb'; export { default as BlockControls } from './block-controls'; -export { default as BlockEdit } from './block-edit'; +export { default as BlockEdit, useBlockEditContext } from './block-edit'; export { default as BlockFormatControls } from './block-format-controls'; export { default as BlockIcon } from './block-icon'; export { default as BlockNavigationDropdown } from './block-navigation/dropdown'; export { default as __experimentalBlockNavigationList } from './block-navigation/list'; +export { default as __experimentalBlockPatternPicker } from './block-pattern-picker'; export { default as BlockVerticalAlignmentToolbar } from './block-vertical-alignment-toolbar'; export { default as ButtonBlockerAppender } from './button-block-appender'; export { default as ColorPalette } from './color-palette'; +export { default as ColorPaletteControl } from './color-palette/control'; export { default as ContrastChecker } from './contrast-checker'; export { default as __experimentalGradientPicker } from './gradient-picker'; export { default as __experimentalGradientPickerControl } from './gradient-picker/control'; +export { default as __experimentalGradientPickerPanel } from './gradient-picker/panel'; export { default as InnerBlocks } from './inner-blocks'; export { default as InspectorAdvancedControls } from './inspector-advanced-controls'; export { default as InspectorControls } from './inspector-controls'; +export { default as __experimentalLinkControl } from './link-control'; +export { default as MediaReplaceFlow } from './media-replace-flow'; export { default as MediaPlaceholder } from './media-placeholder'; export { default as MediaUpload } from './media-upload'; export { default as MediaUploadCheck } from './media-upload/check'; export { default as PanelColorSettings } from './panel-color-settings'; export { default as PlainText } from './plain-text'; +export { default as __experimentalResponsiveBlockControl } from './responsive-block-control'; export { default as RichText, RichTextShortcut, RichTextToolbarButton, __unstableRichTextInputEvent, } from './rich-text'; +export { default as ToolSelector } from './tool-selector'; export { default as URLInput } from './url-input'; export { default as URLInputButton } from './url-input/button'; export { default as URLPopover } from './url-popover'; @@ -42,9 +51,15 @@ export { default as withColorContext } from './color-palette/with-color-context' * Content Related Components */ +export { default as __experimentalBlockListFooter } from './block-list-footer'; export { default as __experimentalBlockSettingsMenuFirstItem } from './block-settings-menu/block-settings-menu-first-item'; export { default as __experimentalBlockSettingsMenuPluginsExtension } from './block-settings-menu/block-settings-menu-plugins-extension'; export { default as __experimentalInserterMenuExtension } from './inserter-menu-extension'; +export { + __experimentalPageTemplatePicker, + __experimentalWithPageTemplatePickerVisible, + __experimentalUsePageTemplatePickerVisible, +} from './page-template-picker'; export { default as BlockEditorKeyboardShortcuts } from './block-editor-keyboard-shortcuts'; export { default as BlockInspector } from './block-inspector'; export { default as BlockList } from './block-list'; diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js index 7fd6966211c20b..5f88d2b81a501f 100644 --- a/packages/block-editor/src/components/index.native.js +++ b/packages/block-editor/src/components/index.native.js @@ -1,6 +1,7 @@ // Block Creation Components +export { default as BlockAlignmentToolbar } from './block-alignment-toolbar'; export { default as BlockControls } from './block-controls'; -export { default as BlockEdit } from './block-edit'; +export { default as BlockEdit, useBlockEditContext } from './block-edit'; export { default as BlockFormatControls } from './block-format-controls'; export { default as BlockIcon } from './block-icon'; export { default as BlockVerticalAlignmentToolbar } from './block-vertical-alignment-toolbar'; @@ -23,8 +24,10 @@ export { default as URLInput } from './url-input'; export { default as BlockInvalidWarning } from './block-list/block-invalid-warning'; export { default as Caption } from './caption'; export { BottomSheetSettings, BlockSettingsButton } from './block-settings'; +export { default as VideoPlayer, VIDEO_ASPECT_RATIO } from './video-player'; // Content Related Components +export { __experimentalPageTemplatePicker, __experimentalWithPageTemplatePickerVisible } from './page-template-picker'; export { default as BlockList } from './block-list'; export { default as BlockMover } from './block-mover'; export { default as BlockToolbar } from './block-toolbar'; diff --git a/packages/block-editor/src/components/inner-blocks/README.md b/packages/block-editor/src/components/inner-blocks/README.md index 5bed06f7a80473..676eba071437ec 100644 --- a/packages/block-editor/src/components/inner-blocks/README.md +++ b/packages/block-editor/src/components/inner-blocks/README.md @@ -90,69 +90,6 @@ const TEMPLATE = [ [ 'core/columns', {}, [ The previous example creates an InnerBlocks area containing two columns one with an image and the other with a paragraph. -### `__experimentalTemplateOptions` - -* **Type:** `Array` - -To present the user with a set of template choices for the inner blocks, you may provide an array of template options. - -A template option is an object consisting of the following properties: - -- `title` (`string`): A human-readable label which describes the template. -- `icon` (`WPElement|string`): An element or [Dashicon](https://developer.wordpress.org/resource/dashicons/) slug to show as a visual approximation of the template. -- `template` (`Array`): The template to apply when the option has been selected. See [`template` documentation](#template) for more information. - -For the template placeholder selection to be displayed, you must render `InnerBlocks` with a `template` prop value of `null`. You may track this as state of your component, updating its value when receiving the selected template via `__experimentalOnSelectTemplateOption`. - -```jsx -import { useState } from '@wordpress/element'; - -const TEMPLATE_OPTIONS = [ - { - title: 'Two Columns', - icon: , - template: [ - [ 'core/column', { width: 50 } ], - [ 'core/column', { width: 50 } ], - ], - }, - { - title: 'Three Columns', - icon: , - template: [ - [ 'core/column', { width: 33.33 } ], - [ 'core/column', { width: 33.33 } ], - [ 'core/column', { width: 33.33 } ], - ], - }, -]; - -function edit() { - const [ template, setTemplate ] = useState( null ); - - return ( - - ); -} -``` - -### `__experimentalOnSelectTemplateOption` - -* **Type:** `Function` - -Callback function invoked when the user makes a template selection, used in combination with the `__experimentalTemplateOptions` props. The selected template is passed as the first and only argument. The value of the template may be `undefined` if the `__experimentalAllowTemplateOptionSkip` prop is passed to `InnerBlocks` and the user opts to skip template selection. - -### `__experimentalAllowTemplateOptionSkip` - -* **Type:** `Boolean` -* **Default:** `false` - -Whether to include a button in the template selection placeholder to allow the user to skip selection, used in combination with the `__experimentalTemplateOptions` prop. When clicked, the `__experimentalOnSelectTemplateOption` callback will be passed an `undefined` value as the template. - ### `templateInsertUpdatesSelection` * **Type:** `Boolean` * **Default:** `true` diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js index 35e1bf10b47ca2..19301cf37d7e8a 100644 --- a/packages/block-editor/src/components/inner-blocks/index.js +++ b/packages/block-editor/src/components/inner-blocks/index.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { withViewportMatch } from '@wordpress/viewport'; // Temporary click-through disable on desktop. +import { withViewportMatch } from '@wordpress/viewport'; import { Component } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; import { synchronizeBlocksWithTemplate, withBlockContentContext } from '@wordpress/blocks'; @@ -25,7 +25,6 @@ import DefaultBlockAppender from './default-block-appender'; */ import BlockList from '../block-list'; import { withBlockEditContext } from '../block-edit/context'; -import TemplatePicker from './template-picker'; class InnerBlocks extends Component { constructor() { @@ -102,36 +101,26 @@ class InnerBlocks extends Component { render() { const { - isSmallScreen, // Temporary click-through disable on desktop. + enableClickThrough, clientId, hasOverlay, renderAppender, - template, - __experimentalTemplateOptions: templateOptions, - __experimentalOnSelectTemplateOption: onSelectTemplateOption, - __experimentalAllowTemplateOptionSkip: allowTemplateOptionSkip, + __experimentalMoverDirection: moverDirection, } = this.props; const { templateInProcess } = this.state; - const isPlaceholder = template === null && !! templateOptions; - const classes = classnames( 'editor-inner-blocks block-editor-inner-blocks', { - 'has-overlay': isSmallScreen && ( hasOverlay && ! isPlaceholder ), // Temporary click-through disable on desktop. + 'has-overlay': enableClickThrough && hasOverlay, } ); return (
    { ! templateInProcess && ( - isPlaceholder ? - : - + ) }
    ); @@ -139,7 +128,7 @@ class InnerBlocks extends Component { } InnerBlocks = compose( [ - withViewportMatch( { isSmallScreen: '< medium' } ), // Temporary click-through disable on desktop. + withViewportMatch( { isSmallScreen: '< medium' } ), withBlockEditContext( ( context ) => pick( context, [ 'clientId' ] ) ), withSelect( ( select, ownProps ) => { const { @@ -149,8 +138,9 @@ InnerBlocks = compose( [ getBlockListSettings, getBlockRootClientId, getTemplateLock, + isNavigationMode, } = select( 'core/block-editor' ); - const { clientId } = ownProps; + const { clientId, isSmallScreen } = ownProps; const block = getBlock( clientId ); const rootClientId = getBlockRootClientId( clientId ); @@ -159,6 +149,7 @@ InnerBlocks = compose( [ blockListSettings: getBlockListSettings( clientId ), hasOverlay: block.name !== 'core/template' && ! isBlockSelected( clientId ) && ! hasSelectedInnerBlock( clientId, true ), parentLock: getTemplateLock( rootClientId ), + enableClickThrough: isNavigationMode() || isSmallScreen, }; } ), withDispatch( ( dispatch, ownProps ) => { diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index 2b6a6c9320c923..145c961f08233b 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -106,23 +106,18 @@ class InnerBlocks extends Component { const { clientId, renderAppender, - template, - __experimentalTemplateOptions: templateOptions, } = this.props; const { templateInProcess } = this.state; - const isPlaceholder = template === null && !! templateOptions; - return ( <> { ! templateInProcess && ( - isPlaceholder ? - null : - + ) } ); diff --git a/packages/block-editor/src/components/inner-blocks/style.scss b/packages/block-editor/src/components/inner-blocks/style.scss index e7bd479e04e02e..ff8e4b9adbe96a 100644 --- a/packages/block-editor/src/components/inner-blocks/style.scss +++ b/packages/block-editor/src/components/inner-blocks/style.scss @@ -17,78 +17,3 @@ right: 0; left: 0; } - -.block-editor-inner-blocks__template-picker { - .components-placeholder__instructions { - // Defer to vertical margins applied by template picker options. - margin-bottom: 0; - } - - .components-placeholder__fieldset { - // Options will render horizontally, but the immediate children of the - // fieldset are the options and the skip button, oriented vertically. - flex-direction: column; - } - - &.has-many-options .components-placeholder__fieldset { - // Allow options to occupy a greater amount of the available space if - // many options exist. - max-width: 90%; - } -} - -.block-editor-inner-blocks__template-picker-options.block-editor-inner-blocks__template-picker-options { - display: flex; - justify-content: center; - flex-direction: row; - flex-wrap: wrap; - width: 100%; - margin: $grid-size-small 0; - list-style: none; - - > li { - list-style: none; - margin: $grid-size; - flex-shrink: 1; - max-width: 100px; - } - - .block-editor-inner-blocks__template-picker-option { - padding: $grid-size; - } -} - -.block-editor-inner-blocks__template-picker-option { - width: 100%; - - &.components-icon-button { - // Override default styles inherited from to center - // icon horizontally. - justify-content: center; - - &.is-default { - background-color: $white; - } - } - - &.components-button { - // Override default styles inherited from - - ) } - - ); -} - -export default InnerBlocksTemplatePicker; diff --git a/packages/block-editor/src/components/inserter-list-item/index.js b/packages/block-editor/src/components/inserter-list-item/index.js index a4af815ac36446..5aeed7b0bd00f0 100644 --- a/packages/block-editor/src/components/inserter-list-item/index.js +++ b/packages/block-editor/src/components/inserter-list-item/index.js @@ -3,6 +3,11 @@ */ import classnames from 'classnames'; +/** + * WordPress dependencies + */ +import { Button } from '@wordpress/components'; + /** * Internal dependencies */ @@ -23,7 +28,7 @@ function InserterListItem( { return (
  • - +
  • ); } diff --git a/packages/block-editor/src/components/inserter-list-item/style.scss b/packages/block-editor/src/components/inserter-list-item/style.scss index 8dc8b8084ad4f9..4f384b1c775660 100644 --- a/packages/block-editor/src/components/inserter-list-item/style.scss +++ b/packages/block-editor/src/components/inserter-list-item/style.scss @@ -5,7 +5,7 @@ margin: 0 0 12px; } -.block-editor-block-types-list__item { +.components-button.block-editor-block-types-list__item { display: flex; flex-direction: column; width: 100%; @@ -54,6 +54,7 @@ &:focus { position: relative; @include block-style__focus(); + background: transparent; .block-editor-block-types-list__item-icon, .block-editor-block-types-list__item-title { diff --git a/packages/block-editor/src/components/inserter/index.js b/packages/block-editor/src/components/inserter/index.js index 5abb1ae4ef9230..fc153b21ea303b 100644 --- a/packages/block-editor/src/components/inserter/index.js +++ b/packages/block-editor/src/components/inserter/index.js @@ -1,29 +1,50 @@ +/** + * External dependencies + */ +import { get } from 'lodash'; /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { speak } from '@wordpress/a11y'; +import { __, _x, sprintf } from '@wordpress/i18n'; import { Dropdown, IconButton } from '@wordpress/components'; import { Component } from '@wordpress/element'; -import { withSelect } from '@wordpress/data'; +import { withDispatch, withSelect } from '@wordpress/data'; import { compose, ifCondition } from '@wordpress/compose'; +import { + createBlock, +} from '@wordpress/blocks'; /** * Internal dependencies */ import InserterMenu from './menu'; -const defaultRenderToggle = ( { onToggle, disabled, isOpen } ) => ( - -); +const defaultRenderToggle = ( { onToggle, disabled, isOpen, blockTitle, hasSingleBlockType } ) => { + let label; + if ( hasSingleBlockType ) { + // translators: %s: the name of the block when there is only one + label = sprintf( _x( 'Add %s', 'directly add the only allowed block' ), blockTitle ); + } else { + label = _x( 'Add block', 'Generic label for block inserter button' ); + } + return ( + { + event.preventDefault(); + event.currentTarget.focus(); + } } + onClick={ onToggle } + className="editor-inserter__toggle block-editor-inserter__toggle" + aria-haspopup={ ! hasSingleBlockType ? 'true' : false } + aria-expanded={ ! hasSingleBlockType ? isOpen : false } + disabled={ disabled } + /> + ); +}; class Inserter extends Component { constructor() { @@ -56,10 +77,12 @@ class Inserter extends Component { renderToggle( { onToggle, isOpen } ) { const { disabled, + blockTitle, + hasSingleBlockType, renderToggle = defaultRenderToggle, } = this.props; - return renderToggle( { onToggle, isOpen, disabled } ); + return renderToggle( { onToggle, isOpen, disabled, blockTitle, hasSingleBlockType } ); } /** @@ -72,7 +95,13 @@ class Inserter extends Component { * @return {WPElement} Dropdown content element. */ renderContent( { onClose } ) { - const { rootClientId, clientId, isAppender, showInserterHelpPanel } = this.props; + const { + rootClientId, + clientId, + isAppender, + showInserterHelpPanel, + __experimentalSelectBlockOnInsert: selectBlockOnInsert, + } = this.props; return ( ); } render() { - const { position } = this.props; + const { position, hasSingleBlockType, insertOnlyAllowedBlock } = this.props; + + if ( hasSingleBlockType ) { + return this.renderToggle( { onToggle: insertOnlyAllowedBlock } ); + } return ( { - const { hasInserterItems } = select( 'core/block-editor' ); + const { + hasInserterItems, + __experimentalGetAllowedBlocks, + } = select( 'core/block-editor' ); + + const allowedBlocks = __experimentalGetAllowedBlocks( rootClientId ); + + const hasSingleBlockType = allowedBlocks && ( get( allowedBlocks, [ 'length' ], 0 ) === 1 ); + + let allowedBlockType = false; + if ( hasSingleBlockType ) { + allowedBlockType = allowedBlocks[ 0 ]; + } return { hasItems: hasInserterItems( rootClientId ), + hasSingleBlockType, + blockTitle: allowedBlockType ? allowedBlockType.title : '', + allowedBlockType, + }; + } ), + withDispatch( ( dispatch, ownProps, { select } ) => { + return { + insertOnlyAllowedBlock() { + const { rootClientId, clientId, isAppender } = ownProps; + const { + hasSingleBlockType, + allowedBlockType, + __experimentalSelectBlockOnInsert: selectBlockOnInsert, + } = ownProps; + + if ( ! hasSingleBlockType ) { + return; + } + + function getInsertionIndex() { + const { + getBlockIndex, + getBlockSelectionEnd, + getBlockOrder, + } = select( 'core/block-editor' ); + + // If the clientId is defined, we insert at the position of the block. + if ( clientId ) { + return getBlockIndex( clientId, rootClientId ); + } + + // If there a selected block, we insert after the selected block. + const end = getBlockSelectionEnd(); + if ( ! isAppender && end ) { + return getBlockIndex( end, rootClientId ) + 1; + } + + // Otherwise, we insert at the end of the current rootClientId + return getBlockOrder( rootClientId ).length; + } + + const { + insertBlock, + } = dispatch( 'core/block-editor' ); + + const blockToInsert = createBlock( allowedBlockType.name ); + + insertBlock( + blockToInsert, + getInsertionIndex(), + rootClientId, + selectBlockOnInsert + ); + + if ( ! selectBlockOnInsert ) { + // translators: %s: the name of the block that has been added + const message = sprintf( __( '%s block added' ), allowedBlockType.title ); + speak( message ); + } + }, }; } ), ifCondition( ( { hasItems } ) => hasItems ), diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 6b2fee7ad06c39..084ddb92c5e1ff 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -21,8 +21,13 @@ import classnames from 'classnames'; /** * WordPress dependencies */ +import { speak } from '@wordpress/a11y'; import { __, _n, _x, sprintf } from '@wordpress/i18n'; -import { Component, createRef } from '@wordpress/element'; +import { + Component, + __experimentalCreateInterpolateElement, + createRef, +} from '@wordpress/element'; import { PanelBody, withSpokenMessages, @@ -119,8 +124,9 @@ export class InserterMenu extends Component { } componentDidMount() { - // This could be replaced by a resolver. - this.props.fetchReusableBlocks(); + if ( this.props.fetchReusableBlocks ) { + this.props.fetchReusableBlocks(); + } this.filter(); } @@ -392,10 +398,11 @@ export class InserterMenu extends Component { { ! isReusableBlock( hoveredItem ) && ( ) } - { ( isReusableBlock( hoveredItem ) || hoveredItemBlockType.example ) && ( -
    +
    + { ( isReusableBlock( hoveredItem ) || hoveredItemBlockType.example ) ? (
    -
    - ) } + ) : ( +
    + { __( 'No Preview Available.' ) } +
    + ) } +
    ) } { ! hoveredItem && ( @@ -429,8 +440,9 @@ export class InserterMenu extends Component {

    - { __( - 'While writing, you can press "/" to quickly insert new blocks.' + { __experimentalCreateInterpolateElement( + __( 'While writing, you can press / to quickly insert new blocks.' ), + { kbd: } ) } @@ -465,11 +477,17 @@ export default compose( } const destinationRootBlockName = getBlockName( destinationRootClientId ); + const { + showInserterHelpPanel: showInserterHelpPanelSetting, + __experimentalFetchReusableBlocks: fetchReusableBlocks, + } = getSettings(); + return { rootChildBlocks: getChildBlockNames( destinationRootBlockName ), items: getInserterItems( destinationRootClientId ), - showInserterHelpPanel: showInserterHelpPanel && getSettings().showInserterHelpPanel, + showInserterHelpPanel: showInserterHelpPanel && showInserterHelpPanelSetting, destinationRootClientId, + fetchReusableBlocks, }; } ), withDispatch( ( dispatch, ownProps, { select } ) => { @@ -478,11 +496,6 @@ export default compose( hideInsertionPoint, } = dispatch( 'core/block-editor' ); - // This should be an external action provided in the editor settings. - const { - __experimentalFetchReusableBlocks: fetchReusableBlocks, - } = dispatch( 'core/editor' ); - // To avoid duplication, getInsertionIndex is extracted and used in two event handlers // This breaks the withDispatch not containing any logic rule. // Since it's a function only called when the event handlers are called, @@ -512,7 +525,6 @@ export default compose( } return { - fetchReusableBlocks, showInsertionPoint() { const index = getInsertionIndex(); showInsertionPoint( ownProps.destinationRootClientId, index ); @@ -526,21 +538,33 @@ export default compose( const { getSelectedBlock, } = select( 'core/block-editor' ); - const { isAppender } = ownProps; - const { name, initialAttributes } = item; + const { + isAppender, + onSelect, + __experimentalSelectBlockOnInsert: selectBlockOnInsert, + } = ownProps; + const { name, title, initialAttributes } = item; const selectedBlock = getSelectedBlock(); const insertedBlock = createBlock( name, initialAttributes ); + if ( ! isAppender && selectedBlock && isUnmodifiedDefaultBlock( selectedBlock ) ) { replaceBlocks( selectedBlock.clientId, insertedBlock ); } else { insertBlock( insertedBlock, getInsertionIndex(), - ownProps.destinationRootClientId + ownProps.destinationRootClientId, + selectBlockOnInsert ); + + if ( ! selectBlockOnInsert ) { + // translators: %s: the name of the block that has been added + const message = sprintf( __( '%s block added' ), title ); + speak( message ); + } } - ownProps.onSelect(); + onSelect(); return insertedBlock; }, }; diff --git a/packages/block-editor/src/components/inserter/menu.native.js b/packages/block-editor/src/components/inserter/menu.native.js index a03e5f818540c6..591932dde8ab62 100644 --- a/packages/block-editor/src/components/inserter/menu.native.js +++ b/packages/block-editor/src/components/inserter/menu.native.js @@ -21,6 +21,15 @@ import { BottomSheet, Icon } from '@wordpress/components'; import styles from './style.scss'; export class InserterMenu extends Component { + constructor() { + super( ...arguments ); + + this.onLayout = this.onLayout.bind( this ); + this.state = { + numberOfColumns: this.calculateNumberOfColumns(), + }; + } + componentDidMount() { this.onOpen(); } @@ -47,9 +56,13 @@ export class InserterMenu extends Component { this.props.hideInsertionPoint(); } + onLayout() { + const numberOfColumns = this.calculateNumberOfColumns(); + this.setState( { numberOfColumns } ); + } + render() { const { getStylesFromColorScheme } = this.props; - const numberOfColumns = this.calculateNumberOfColumns(); const bottomPadding = styles.contentBottomPadding; const modalIconWrapperStyle = getStylesFromColorScheme( styles.modalIconWrapper, styles.modalIconWrapperDark ); const modalIconStyle = getStylesFromColorScheme( styles.modalIcon, styles.modalIconDark ); @@ -63,10 +76,11 @@ export class InserterMenu extends Component { hideHeader > diff --git a/packages/block-editor/src/components/inserter/style.scss b/packages/block-editor/src/components/inserter/style.scss index a460cbbb10e05b..528c46adb87d75 100644 --- a/packages/block-editor/src/components/inserter/style.scss +++ b/packages/block-editor/src/components/inserter/style.scss @@ -16,7 +16,7 @@ $block-inserter-search-height: 38px; } } -.block-editor-inserter__popover:not(.is-mobile) > .components-popover__content { +.block-editor-inserter__popover > .components-popover__content { @include break-medium { overflow-y: visible; height: $block-inserter-content-height + $block-inserter-tabs-height + $block-inserter-search-height; @@ -218,12 +218,22 @@ $block-inserter-search-height: 38px; border: $border-width solid $light-gray-500; border-radius: $radius-round-rectangle; min-height: 150px; - padding: 10px; display: grid; flex-grow: 2; .block-editor-block-preview__container { margin-right: 0; margin-left: 0; + padding: 10px; } } + +.block-editor-inserter__preview-content-missing { + flex: 1; + display: flex; + justify-content: center; + color: $dark-gray-400; + border: $border-width solid $light-gray-500; + border-radius: $radius-round-rectangle; + align-items: center; +} diff --git a/packages/block-editor/src/components/inspector-advanced-controls/index.js b/packages/block-editor/src/components/inspector-advanced-controls/index.js index 663e95bb491e50..ac840b52e7df64 100644 --- a/packages/block-editor/src/components/inspector-advanced-controls/index.js +++ b/packages/block-editor/src/components/inspector-advanced-controls/index.js @@ -8,10 +8,12 @@ import { createSlotFill } from '@wordpress/components'; */ import { ifBlockEditSelected } from '../block-edit/context'; -const { Fill, Slot } = createSlotFill( 'InspectorAdvancedControls' ); +const name = 'InspectorAdvancedControls'; +const { Fill, Slot } = createSlotFill( name ); const InspectorAdvancedControls = ifBlockEditSelected( Fill ); +InspectorAdvancedControls.slotName = name; InspectorAdvancedControls.Slot = Slot; export default InspectorAdvancedControls; diff --git a/packages/block-editor/src/components/link-control/README.md b/packages/block-editor/src/components/link-control/README.md new file mode 100644 index 00000000000000..af25dfc2f42fe3 --- /dev/null +++ b/packages/block-editor/src/components/link-control/README.md @@ -0,0 +1,109 @@ +# Link Control + +## Props + +### className + +- Type: `String` +- Required: Yes + +### currentLink + +- Type: `Object` +- Required: Yes + +### currentSettings + +- Type: `Array` +- Required: No +- Default: +``` +[ + { + id: 'newTab', + title: 'Open in New Tab', + checked: false, + }, +]; +``` + +An array of settings objects. Each object will used to render a `ToggleControl` for that setting. See also `onSettingsChange`. + +### fetchSearchSuggestions + +- Type: `Function` +- Required: No + +## Event handlers + +### onChangeMode + +- Type: `Function` +- Required: No + +Use this callback to know when the LinkControl component changes its mode to `edit` or `show` +through of its function parameter. + +```es6 + { console.log( `Mode change to ${ mode } mode.` ) } +/> +``` + +### onClose + +- Type: `Function` +- Required: No + +### onKeyDown + +- Type: `Function` +- Required: No + +### onKeyPress + +- Type: `Function` +- Required: No + +### onLinkChange + +- Type: `Function` +- Required: No + +Use this callback to take an action after a user set or updated a link. +The function callback will receive the selected item, or Null. + +```es6 + { + item + ? console.log( `The item selected has the ${ item.id } id.` ) + : console.warn( 'No Item selected.' ); + } +/> +``` + +### onSettingsChange + +- Type: `Function` +- Required: No +- Args: + - `id` - the `id` property of the setting that changed (eg: `newTab`). + - `value` - the `checked` value of the control. + - `settings` - the current settings object. + +Called when any of the settings supplied as `currentSettings` are changed/toggled. May be used to attribute a Block's `attributes` with the current state of the control. + +``` + setAttributes( { [ setting ]: value } ) } +/> +``` + diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js new file mode 100644 index 00000000000000..e1ad86afc773d5 --- /dev/null +++ b/packages/block-editor/src/components/link-control/index.js @@ -0,0 +1,269 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { isFunction, noop, startsWith } from 'lodash'; + +/** + * WordPress dependencies + */ +import { + Button, + ExternalLink, + Popover, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +import { + useCallback, + useState, + useEffect, + Fragment, +} from '@wordpress/element'; + +import { + safeDecodeURI, + filterURLForDisplay, + isURL, + prependHTTP, + getProtocol, +} from '@wordpress/url'; + +import { withInstanceId, compose } from '@wordpress/compose'; +import { withSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import LinkControlSettingsDrawer from './settings-drawer'; +import LinkControlSearchItem from './search-item'; +import LinkControlSearchInput from './search-input'; + +const MODE_EDIT = 'edit'; +// const MODE_SHOW = 'show'; + +function LinkControl( { + className, + currentLink, + currentSettings, + fetchSearchSuggestions, + instanceId, + onClose = noop, + onChangeMode = noop, + onKeyDown = noop, + onKeyPress = noop, + onLinkChange = noop, + onSettingsChange = noop, +} ) { + // State + const [ inputValue, setInputValue ] = useState( '' ); + const [ isEditingLink, setIsEditingLink ] = useState( false ); + + // Effects + useEffect( () => { + // If we have a link then stop editing mode + if ( currentLink ) { + setIsEditingLink( false ); + } else { + setIsEditingLink( true ); + } + }, [ currentLink ] ); + + // Handlers + + /** + * onChange LinkControlSearchInput event handler + * + * @param {string} value Current value returned by the search. + */ + const onInputChange = ( value = '' ) => { + setInputValue( value ); + }; + + // Utils + + /** + * Handler function which switches the mode of the component, + * between `edit` and `show` mode. + * Also, it calls `onChangeMode` callback function. + * + * @param {string} mode Component mode: `show` or `edit`. + */ + const setMode = ( mode = 'show' ) => () => { + setIsEditingLink( MODE_EDIT === mode ); + + // Populate input searcher whether + // the current link has a title. + if ( currentLink && currentLink.title ) { + setInputValue( currentLink.title ); + } + + if ( isFunction( onChangeMode ) ) { + onChangeMode( mode ); + } + }; + + const closeLinkUI = () => { + resetInput(); + onClose(); + }; + + const resetInput = () => { + setInputValue( '' ); + }; + + const handleDirectEntry = ( value ) => { + let type = 'URL'; + + const protocol = getProtocol( value ) || ''; + + if ( protocol.includes( 'mailto' ) ) { + type = 'mailto'; + } + + if ( protocol.includes( 'tel' ) ) { + type = 'tel'; + } + + if ( startsWith( value, '#' ) ) { + type = 'internal'; + } + + return Promise.resolve( + [ { + id: '-1', + title: value, + url: type === 'URL' ? prependHTTP( value ) : value, + type, + } ] + ); + }; + + const handleEntitySearch = async ( value ) => { + const results = await Promise.all( [ + fetchSearchSuggestions( value ), + handleDirectEntry( value ), + ] ); + + const couldBeURL = ! value.includes( ' ' ); + + // If it's potentially a URL search then concat on a URL search suggestion + // just for good measure. That way once the actual results run out we always + // have a URL option to fallback on. + return couldBeURL ? results[ 0 ].concat( results[ 1 ] ) : results[ 0 ]; + }; + + // Effects + const getSearchHandler = useCallback( ( value ) => { + const protocol = getProtocol( value ) || ''; + const isMailto = protocol.includes( 'mailto' ); + const isInternal = startsWith( value, '#' ); + const isTel = protocol.includes( 'tel' ); + + const handleManualEntry = isInternal || isMailto || isTel || isURL( value ) || ( value && value.includes( 'www.' ) ); + + return ( handleManualEntry ) ? handleDirectEntry( value ) : handleEntitySearch( value ); + }, [ handleDirectEntry, fetchSearchSuggestions ] ); + + // Render Components + const renderSearchResults = ( { suggestionsListProps, buildSuggestionItemProps, suggestions, selectedSuggestion, isLoading } ) => { + const resultsListClasses = classnames( 'block-editor-link-control__search-results', { + 'is-loading': isLoading, + } ); + + const manualLinkEntryTypes = [ 'url', 'mailto', 'tel', 'internal' ]; + + return ( +
    +
    + { suggestions.map( ( suggestion, index ) => ( + onLinkChange( suggestion ) } + isSelected={ index === selectedSuggestion } + isURL={ manualLinkEntryTypes.includes( suggestion.type.toLowerCase() ) } + searchTerm={ inputValue } + /> + ) ) } +
    +
    + ); + }; + + return ( + +
    +
    + + { ( ! isEditingLink && currentLink ) && ( + +

    + { __( 'Currently selected' ) }: +

    +
    + + + + { currentLink.title } + + { filterURLForDisplay( safeDecodeURI( currentLink.url ) ) || '' } + + + +
    +
    + ) } + + { isEditingLink && ( + + ) } + + { ! isEditingLink && ( + + ) } +
    +
    +
    + ); +} + +export default compose( + withInstanceId, + withSelect( ( select, ownProps ) => { + if ( ownProps.fetchSearchSuggestions && isFunction( ownProps.fetchSearchSuggestions ) ) { + return; + } + + const { getSettings } = select( 'core/block-editor' ); + return { + fetchSearchSuggestions: getSettings().__experimentalFetchLinkSuggestions, + }; + } ) +)( LinkControl ); diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js new file mode 100644 index 00000000000000..84fd5db8359363 --- /dev/null +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -0,0 +1,69 @@ + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { IconButton } from '@wordpress/components'; +import { ENTER } from '@wordpress/keycodes'; + +/** + * Internal dependencies + */ +import { URLInput } from '../'; + +const LinkControlSearchInput = ( { + value, + onChange, + onSelect, + renderSuggestions, + fetchSuggestions, + onReset, + onKeyDown, + onKeyPress, +} ) => { + const selectItemHandler = ( selection, suggestion ) => { + onChange( selection ); + + if ( suggestion ) { + onSelect( suggestion ); + } + }; + + const stopFormEventsPropagation = ( event ) => { + event.preventDefault(); + event.stopPropagation(); + }; + + return ( +
    + { + if ( event.keyCode === ENTER ) { + return; + } + onKeyDown( event ); + } } + onKeyPress={ onKeyPress } + placeholder={ __( 'Search or type url' ) } + __experimentalRenderSuggestions={ renderSuggestions } + __experimentalFetchLinkSuggestions={ fetchSuggestions } + __experimentalHandleURLSuggestions={ true } + /> + + + + + ); +}; + +export default LinkControlSearchInput; diff --git a/packages/block-editor/src/components/link-control/search-item.js b/packages/block-editor/src/components/link-control/search-item.js new file mode 100644 index 00000000000000..7b086860d30560 --- /dev/null +++ b/packages/block-editor/src/components/link-control/search-item.js @@ -0,0 +1,51 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { safeDecodeURI } from '@wordpress/url'; +import { __ } from '@wordpress/i18n'; +import { + Button, + Icon, + TextHighlight, +} from '@wordpress/components'; + +export const LinkControlSearchItem = ( { itemProps, suggestion, isSelected = false, onClick, isURL = false, searchTerm = '' } ) => { + return ( + + ); +}; + +export default LinkControlSearchItem; + diff --git a/packages/block-editor/src/components/link-control/settings-drawer.js b/packages/block-editor/src/components/link-control/settings-drawer.js new file mode 100644 index 00000000000000..3d9f957e207a83 --- /dev/null +++ b/packages/block-editor/src/components/link-control/settings-drawer.js @@ -0,0 +1,50 @@ +/** + * External dependencies + */ +import { noop } from 'lodash'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + ToggleControl, +} from '@wordpress/components'; + +const defaultSettings = [ + { + id: 'newTab', + title: __( 'Open in New Tab' ), + checked: false, + }, +]; + +const LinkControlSettingsDrawer = ( { settings = defaultSettings, onSettingChange = noop } ) => { + if ( ! settings || ! settings.length ) { + return null; + } + + const handleSettingChange = ( setting ) => ( value ) => { + onSettingChange( setting.id, value, settings ); + }; + + const theSettings = settings.map( ( setting ) => ( + + ) ); + + return ( +
    + + { __( 'Currently selected link settings' ) } + + { theSettings } +
    + ); +}; + +export default LinkControlSettingsDrawer; diff --git a/packages/block-editor/src/components/link-control/style.scss b/packages/block-editor/src/components/link-control/style.scss new file mode 100644 index 00000000000000..30cbc78526b3d0 --- /dev/null +++ b/packages/block-editor/src/components/link-control/style.scss @@ -0,0 +1,207 @@ +.block-editor-link-control__search { + position: relative; + min-width: $modal-min-width; +} + +// LinkControl popover. +.block-editor-link-control__search .block-editor-link-control__search-input { + // Specificity override. + &.block-editor-link-control__search-input input[type="text"] { + width: calc(100% - #{$grid-size-large*2}); + display: block; + padding: 11px $grid-size-large; + margin: $grid-size-large; + padding-right: 38px; // width of reset button + position: relative; + z-index: 1; + border: 1px solid #e1e1e1; + border-radius: $radius-round-rectangle; + + /* Fonts smaller than 16px causes mobile safari to zoom. */ + font-size: $mobile-text-min-font-size; + + @include break-small { + font-size: $default-font-size; + } + + &:focus { + @include input-style__focus(); + } + } +} + +.block-editor-link-control__search-reset { + position: absolute; + top: 19px; // has to be hard coded as form expands with search suggestions + right: 19px; // push away to avoid focus style obscuring input border + z-index: 10; +} + +.block-editor-link-control__search-results-wrapper { + position: relative; + margin-top: -$grid-size-large + 1px; + + &::before, + &::after { + content: ""; + position: absolute; + left: -1px; + right: $grid-size-large; // avoid overlaying scrollbars + display: block; + pointer-events: none; + z-index: 100; + } + + &::before { + height: $grid-size-large/2; + top: 0; + bottom: auto; + background: linear-gradient(to bottom, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%); + } + + &::after { + height: $grid-size-large; + bottom: 0; + top: auto; + background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%); + } +} + +.block-editor-link-control__search-results { + margin: 0; + padding: $grid-size-large/2 $grid-size-large $grid-size-large; + max-height: 200px; + overflow-y: scroll; // allow results list to scroll + + &.is-loading { + opacity: 0.2; + } +} + +.block-editor-link-control__search-item { + position: relative; + display: flex; + align-items: center; + font-size: $default-font-size; + cursor: pointer; + background: $white; + width: 100%; + border: none; + text-align: left; + padding: 10px 15px; + border-radius: 5px; + + &:hover, + &:focus { + background-color: #e9e9e9; + } + + &.is-selected { + background: #f2f2f2; + + .block-editor-link-control__search-item-type { + background: #fff; + } + } + + &.is-current { + background: transparent; + border: 0; + width: 100%; + cursor: default; + padding: $grid-size-large; + padding-left: $grid-size-xlarge; + } + + .block-editor-link-control__search-item-header { + display: block; + margin-right: $grid-size-xlarge; + } + + .block-editor-link-control__search-item-icon { + margin-right: 1em; + min-width: 24px; + } + + .block-editor-link-control__search-item-info, + .block-editor-link-control__search-item-title { + text-overflow: ellipsis; + max-width: 230px; + overflow: hidden; + white-space: nowrap; + } + + .block-editor-link-control__search-item-title { + display: block; + margin-bottom: 0.2em; + font-weight: 500; + + mark { + font-weight: 700; + color: #000; + background-color: transparent; + } + + span { + font-weight: normal; + } + } + + .block-editor-link-control__search-item-info { + display: block; + color: #999; + font-size: 0.9em; + line-height: 1.3; + } + + .block-editor-link-control__search-item-type { + display: block; + padding: 3px 8px; + margin-left: auto; + font-size: 0.9em; + background-color: #f3f4f5; + border-radius: 2px; + } +} + +// Specificity overide +.block-editor-link-control__search-results div[role="menu"] > .block-editor-link-control__search-item.block-editor-link-control__search-item { + padding: 10px; +} + +.block-editor-link-control__settings { + border-top: 1px solid #e1e1e1; + margin: 0; + padding: $grid-size-large $grid-size-xlarge; + + :last-child { + margin-bottom: 0; + } +} + +.block-editor-link-control__setting { + margin-bottom: $grid-size-large; + + :last-child { + margin-bottom: 0; + } +} + +.block-editor-link-control .block-editor-link-control__search-input .components-spinner { + display: block; + z-index: 100; + float: none; + + &.components-spinner { // Specificity override. + position: absolute; + top: 27px; + left: auto; + right: 60px; + bottom: 0; + } +} + +.block-editor-link-control__search-item-action { + margin-left: auto; // push to far right hand side + flex-shrink: 0; +} diff --git a/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap new file mode 100644 index 00000000000000..e1dba581caf897 --- /dev/null +++ b/packages/block-editor/src/components/link-control/test/__snapshots__/index.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Basic rendering should display with required props 1`] = `"
    "`; diff --git a/packages/block-editor/src/components/link-control/test/fixtures/index.js b/packages/block-editor/src/components/link-control/test/fixtures/index.js new file mode 100644 index 00000000000000..fc974749c982b1 --- /dev/null +++ b/packages/block-editor/src/components/link-control/test/fixtures/index.js @@ -0,0 +1,41 @@ +/** + * External dependencies + */ +import { uniqueId } from 'lodash'; + +export const fauxEntitySuggestions = [ + { + id: uniqueId(), + title: 'Hello Page', + type: 'Page', + info: '2 days ago', + url: `?p=${ uniqueId() }`, + }, + { + id: uniqueId(), + title: 'Hello Post', + type: 'Post', + info: '19 days ago', + url: `?p=${ uniqueId() }`, + }, + { + id: uniqueId(), + title: 'Hello Another One', + type: 'Page', + info: '19 days ago', + url: `?p=${ uniqueId() }`, + }, + { + id: uniqueId(), + title: 'This is another Post with a much longer title just to be really annoying and to try and break the UI', + type: 'Post', + info: '1 month ago', + url: `?p=${ uniqueId() }`, + }, +]; + +// export const fetchFauxEntitySuggestions = async () => fauxEntitySuggestions; + +export const fetchFauxEntitySuggestions = () => { + return Promise.resolve( fauxEntitySuggestions ); +}; diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js new file mode 100644 index 00000000000000..5a69142b5f495e --- /dev/null +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -0,0 +1,629 @@ +/** + * External dependencies + */ +import { render, unmountComponentAtNode } from 'react-dom'; +import { act, Simulate } from 'react-dom/test-utils'; +import { first, last, nth } from 'lodash'; +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; +import { UP, DOWN, ENTER } from '@wordpress/keycodes'; +/** + * Internal dependencies + */ +import LinkControl from '../index'; +import { fauxEntitySuggestions, fetchFauxEntitySuggestions } from './fixtures'; + +/** + * Wait for next tick of event loop. This is required + * because the `fetchSearchSuggestions` Promise will + * resolve on the next tick of the event loop (this is + * inline with the Promise spec). As a result we need to + * wait on this loop to "tick" before we can expect the UI + * to have updated. + */ +function eventLoopTick() { + return new Promise( ( resolve ) => setImmediate( resolve ) ); +} + +let container = null; + +beforeEach( () => { + // setup a DOM element as a render target + container = document.createElement( 'div' ); + document.body.appendChild( container ); +} ); + +afterEach( () => { + // cleanup on exiting + unmountComponentAtNode( container ); + container.remove(); + container = null; +} ); + +describe( 'Basic rendering', () => { + it( 'should display with required props', () => { + act( () => { + render( + , container + ); + } ); + + // Search Input UI + const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + + // expect( searchInputLabel ).not.toBeNull(); + expect( searchInput ).not.toBeNull(); + + expect( container.innerHTML ).toMatchSnapshot(); + } ); +} ); + +describe( 'Searching for a link', () => { + it( 'should display loading UI when input is valid but search results have yet to be returned', async () => { + const searchTerm = 'Hello'; + + let resolver; + + const fauxRequest = () => new Promise( ( resolve ) => { + resolver = resolve; + } ); + + act( () => { + render( + , container + ); + } ); + + // Search Input UI + const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { target: { value: searchTerm } } ); + } ); + + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( '[role="menu"] button[role="menuitem"]' ); + + let loadingUI = container.querySelector( '.components-spinner' ); + + expect( searchResultElements ).toHaveLength( 0 ); + + expect( loadingUI ).not.toBeNull(); + + act( () => { + resolver( fauxEntitySuggestions ); + } ); + + await eventLoopTick(); + + loadingUI = container.querySelector( '.components-spinner' ); + + expect( loadingUI ).toBeNull(); + } ); + + it( 'should display only search suggestions when current input value is not URL-like', async ( ) => { + const searchTerm = 'Hello world'; + const firstFauxSuggestion = first( fauxEntitySuggestions ); + + act( () => { + render( + , container + ); + } ); + + // Search Input UI + const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { target: { value: searchTerm } } ); + } ); + + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + const firstSearchResultItemHTML = first( searchResultElements ).innerHTML; + const lastSearchResultItemHTML = last( searchResultElements ).innerHTML; + + expect( searchResultElements ).toHaveLength( fauxEntitySuggestions.length ); + + // Sanity check that a search suggestion shows up corresponding to the data + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( firstFauxSuggestion.title ) ); + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( firstFauxSuggestion.type ) ); + + // The fallback URL suggestion should not be shown when input is not URL-like + expect( lastSearchResultItemHTML ).not.toEqual( expect.stringContaining( 'URL' ) ); + } ); + + it.each( [ + [ 'couldbeurlorentitysearchterm' ], + [ 'ThisCouldAlsoBeAValidURL' ], + ] )( 'should display a URL suggestion as a default fallback for the search term "%s" which could potentially be a valid url.', async ( searchTerm ) => { + act( () => { + render( + , container + ); + } ); + + // Search Input UI + const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { target: { value: searchTerm } } ); + } ); + + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + const lastSearchResultItemHTML = last( searchResultElements ).innerHTML; + const additionalDefaultFallbackURLSuggestionLength = 1; + + // We should see a search result for each of the expect search suggestions + // plus 1 additional one for the fallback URL suggestion + expect( searchResultElements ).toHaveLength( fauxEntitySuggestions.length + additionalDefaultFallbackURLSuggestionLength ); + + // The last item should be a URL search suggestion + expect( lastSearchResultItemHTML ).toEqual( expect.stringContaining( searchTerm ) ); + expect( lastSearchResultItemHTML ).toEqual( expect.stringContaining( 'URL' ) ); + expect( lastSearchResultItemHTML ).toEqual( expect.stringContaining( 'Press ENTER to add this link' ) ); + } ); + + it( 'should reset the input field and the search results when search term is cleared or reset', async ( ) => { + const searchTerm = 'Hello world'; + + act( () => { + render( + , container + ); + } ); + + let searchResultElements; + let searchInput; + + // Search Input UI + searchInput = container.querySelector( 'input[aria-label="URL"]' ); + + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { target: { value: searchTerm } } ); + } ); + + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + + // Check we have definitely rendered some suggestions + expect( searchResultElements ).toHaveLength( fauxEntitySuggestions.length ); + + // Grab the reset button now it's available + const resetUI = container.querySelector( '[aria-label="Reset"]' ); + + act( () => { + Simulate.click( resetUI ); + } ); + + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + searchInput = container.querySelector( 'input[aria-label="URL"]' ); + + expect( searchInput.value ).toBe( '' ); + expect( searchResultElements ).toHaveLength( 0 ); + } ); +} ); + +describe( 'Manual link entry', () => { + it.each( [ + [ 'https://make.wordpress.org' ], // explicit https + [ 'http://make.wordpress.org' ], // explicit http + [ 'www.wordpress.org' ], // usage of "www" + ] )( 'should display a single suggestion result when the current input value is URL-like (eg: %s)', async ( searchTerm ) => { + act( () => { + render( + , container + ); + } ); + + // Search Input UI + const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { target: { value: searchTerm } } ); + } ); + + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + const firstSearchResultItemHTML = searchResultElements[ 0 ].innerHTML; + const expectedResultsLength = 1; + + expect( searchResultElements ).toHaveLength( expectedResultsLength ); + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( searchTerm ) ); + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( 'URL' ) ); + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( 'Press ENTER to add this link' ) ); + } ); + + describe( 'Alternative link protocols and formats', () => { + it.each( [ + [ 'mailto:example123456@wordpress.org', 'mailto' ], + [ 'tel:example123456@wordpress.org', 'tel' ], + [ '#internal-anchor', 'internal' ], + ] )( 'should recognise "%s" as a %s link and handle as manual entry by displaying a single suggestion', async ( searchTerm, searchType ) => { + act( () => { + render( + , container + ); + } ); + + // Search Input UI + const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { target: { value: searchTerm } } ); + } ); + + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + const firstSearchResultItemHTML = searchResultElements[ 0 ].innerHTML; + const expectedResultsLength = 1; + + expect( searchResultElements ).toHaveLength( expectedResultsLength ); + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( searchTerm ) ); + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( searchType ) ); + expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( 'Press ENTER to add this link' ) ); + } ); + } ); +} ); + +describe( 'Selecting links', () => { + it( 'should display a selected link corresponding to the provided "currentLink" prop', () => { + const selectedLink = first( fauxEntitySuggestions ); + + const LinkControlConsumer = () => { + const [ link ] = useState( selectedLink ); + + return ( + + ); + }; + + act( () => { + render( + , container + ); + } ); + + // TODO: select by aria role or visible text + const currentLink = container.querySelector( '.block-editor-link-control__search-item.is-current' ); + const currentLinkHTML = currentLink.innerHTML; + const currentLinkAnchor = currentLink.querySelector( `[href="${ selectedLink.url }"]` ); + + expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.title ) ); + expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.type ) ); + expect( currentLinkHTML ).toEqual( expect.stringContaining( 'Change' ) ); + expect( currentLinkAnchor ).not.toBeNull(); + } ); + + it( 'should hide "selected" link UI and display search UI prepopulated with previously selected link title when "Change" button is clicked', () => { + const selectedLink = first( fauxEntitySuggestions ); + const spyOnEditMode = jest.fn(); + + const LinkControlConsumer = () => { + const [ link, setLink ] = useState( selectedLink ); + + return ( + setLink( suggestion ) } + fetchSearchSuggestions={ fetchFauxEntitySuggestions } + onChangeMode={ spyOnEditMode( 'edit' ) } + /> + ); + }; + + act( () => { + render( + , container + ); + } ); + + // Required in order to select the button below + let currentLinkUI = container.querySelector( '.block-editor-link-control__search-item.is-current' ); + const currentLinkBtn = currentLinkUI.querySelector( 'button' ); + + // Simulate searching for a term + act( () => { + Simulate.click( currentLinkBtn ); + } ); + + const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + currentLinkUI = container.querySelector( '.block-editor-link-control__search-item.is-current' ); + + // We should be back to showing the search input + expect( searchInput ).not.toBeNull(); + expect( searchInput.value ).toBe( selectedLink.title ); // prepopulated with previous link's title + expect( currentLinkUI ).toBeNull(); + expect( spyOnEditMode ).toHaveBeenCalled(); + } ); + + describe( 'Selection using mouse click', () => { + it.each( [ + [ 'entity', 'hello world', first( fauxEntitySuggestions ) ], // entity search + [ 'url', 'https://www.wordpress.org', { + id: '1', + title: 'https://www.wordpress.org', + url: 'https://www.wordpress.org', + type: 'URL', + } ], // url + ] )( 'should display a current selected link UI when a %s suggestion for the search "%s" is clicked', async ( type, searchTerm, selectedLink ) => { + const LinkControlConsumer = () => { + const [ link, setLink ] = useState( null ); + + return ( + setLink( suggestion ) } + fetchSearchSuggestions={ fetchFauxEntitySuggestions } + /> + ); + }; + + act( () => { + render( + , container + ); + } ); + + // Search Input UI + const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { target: { value: searchTerm } } ); + } ); + + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + + const firstSearchSuggestion = first( searchResultElements ); + + // Simulate selecting the first of the search suggestions + act( () => { + Simulate.click( firstSearchSuggestion ); + } ); + + const currentLink = container.querySelector( '.block-editor-link-control__search-item.is-current' ); + const currentLinkHTML = currentLink.innerHTML; + const currentLinkAnchor = currentLink.querySelector( `[href="${ selectedLink.url }"]` ); + + // Check that this suggestion is now shown as selected + expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.title ) ); + expect( currentLinkHTML ).toEqual( expect.stringContaining( 'Change' ) ); + expect( currentLinkAnchor ).not.toBeNull(); + } ); + } ); + + describe( 'Selection using keyboard', () => { + it.each( [ + [ 'entity', 'hello world', first( fauxEntitySuggestions ) ], // entity search + [ 'url', 'https://www.wordpress.org', { + id: '1', + title: 'https://www.wordpress.org', + url: 'https://www.wordpress.org', + type: 'URL', + } ], // url + ] )( 'should display a current selected link UI when an %s suggestion for the search "%s" is selected using the keyboard', async ( type, searchTerm, selectedLink ) => { + const LinkControlConsumer = () => { + const [ link, setLink ] = useState( null ); + + return ( + setLink( suggestion ) } + fetchSearchSuggestions={ fetchFauxEntitySuggestions } + /> + ); + }; + + act( () => { + render( + , container + ); + } ); + + // Search Input UI + const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { target: { value: searchTerm } } ); + } ); + + //fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); + + // Step down into the search results, highlighting the first result item + act( () => { + Simulate.keyDown( searchInput, { keyCode: DOWN } ); + } ); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + const firstSearchSuggestion = first( searchResultElements ); + const secondSearchSuggestion = nth( searchResultElements, 1 ); + + let selectedSearchResultElement = container.querySelector( '[role="option"][aria-selected="true"]' ); + + // We should have highlighted the first item using the keyboard + expect( selectedSearchResultElement ).toEqual( firstSearchSuggestion ); + + // Only entity searches contain more than 1 suggestion + if ( type === 'entity' ) { + // Check we can go down again using the down arrow + act( () => { + Simulate.keyDown( searchInput, { keyCode: DOWN } ); + } ); + + selectedSearchResultElement = container.querySelector( '[role="option"][aria-selected="true"]' ); + + // We should have highlighted the first item using the keyboard + expect( selectedSearchResultElement ).toEqual( secondSearchSuggestion ); + + // Check we can go back up via up arrow + act( () => { + Simulate.keyDown( searchInput, { keyCode: UP } ); + } ); + + selectedSearchResultElement = container.querySelector( '[role="option"][aria-selected="true"]' ); + + // We should be back to highlighting the first search result again + expect( selectedSearchResultElement ).toEqual( firstSearchSuggestion ); + } + + // Commit the selected item as the current link + act( () => { + Simulate.keyDown( searchInput, { keyCode: ENTER } ); + } ); + + // Check that the suggestion selected via is now shown as selected + const currentLink = container.querySelector( '.block-editor-link-control__search-item.is-current' ); + const currentLinkHTML = currentLink.innerHTML; + const currentLinkAnchor = currentLink.querySelector( `[href="${ selectedLink.url }"]` ); + + expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.title ) ); + expect( currentLinkHTML ).toEqual( expect.stringContaining( 'Change' ) ); + expect( currentLinkAnchor ).not.toBeNull(); + } ); + } ); +} ); + +describe( 'Addition Settings UI', () => { + it( 'should display "New Tab" setting (in "off" mode) by default when a link is selected', async () => { + const selectedLink = first( fauxEntitySuggestions ); + const expectedSettingText = 'Open in New Tab'; + + const LinkControlConsumer = () => { + const [ link ] = useState( selectedLink ); + + return ( + + ); + }; + + act( () => { + render( + , container + ); + } ); + + // console.log( container.innerHTML ); + + const newTabSettingLabel = Array.from( container.querySelectorAll( 'label' ) ).find( ( label ) => label.innerHTML && label.innerHTML.includes( expectedSettingText ) ); + expect( newTabSettingLabel ).not.toBeUndefined(); // find() returns "undefined" if not found + + const newTabSettingLabelForAttr = newTabSettingLabel.getAttribute( 'for' ); + const newTabSettingInput = container.querySelector( `#${ newTabSettingLabelForAttr }` ); + expect( newTabSettingInput ).not.toBeNull(); + expect( newTabSettingInput.checked ).toBe( false ); + } ); + + it( 'should display a setting control with correct default state for each of the custom settings provided', async () => { + const selectedLink = first( fauxEntitySuggestions ); + + const customSettings = [ + { + id: 'newTab', + title: 'Open in New Tab', + checked: false, + }, + { + id: 'noFollow', + title: 'No follow', + checked: true, + }, + ]; + + const customSettingsLabelsText = customSettings.map( ( setting ) => setting.title ); + + const LinkControlConsumer = () => { + const [ link ] = useState( selectedLink ); + + return ( + + ); + }; + + act( () => { + render( + , container + ); + } ); + + // Grab the elements using user perceivable DOM queries + const settingsLegend = Array.from( container.querySelectorAll( 'legend' ) ).find( ( legend ) => legend.innerHTML && legend.innerHTML.includes( 'Currently selected link settings' ) ); + const settingsFieldset = settingsLegend.closest( 'fieldset' ); + const settingControlsLabels = Array.from( settingsFieldset.querySelectorAll( 'label' ) ); + const settingControlsInputs = settingControlsLabels.map( ( label ) => { + return settingsFieldset.querySelector( `#${ label.getAttribute( 'for' ) }` ); + } ); + + const settingControlLabelsText = Array.from( settingControlsLabels ).map( ( label ) => label.innerHTML ); + + // Check we have the correct number of controls + expect( settingControlsLabels ).toHaveLength( 2 ); + + // Check the labels match + expect( settingControlLabelsText ).toEqual( expect.arrayContaining( customSettingsLabelsText ) ); + + // Assert the default "checked" states match the expected + expect( settingControlsInputs[ 0 ].checked ).toEqual( false ); + expect( settingControlsInputs[ 1 ].checked ).toEqual( true ); + } ); +} ); diff --git a/packages/block-editor/src/components/media-placeholder/README.md b/packages/block-editor/src/components/media-placeholder/README.md index b730b9af5cbc9c..6fee3970c843b4 100644 --- a/packages/block-editor/src/components/media-placeholder/README.md +++ b/packages/block-editor/src/components/media-placeholder/README.md @@ -8,7 +8,7 @@ MediaPlaceholder An example usage which sets the URL of the selected image to `theImage` attributes. ``` -const { MediaPlaceholder } = wp.editor; +const { MediaPlaceholder } = wp.blockEditor; ... @@ -155,9 +155,9 @@ The argument of the callback is an object containing the following properties: ### value -Media ID (or media IDs if multiple is true) to be selected by default when opening the media library. +An object or an array of objects that contain media ID (`id` property) to be selected by default when opening the media library. -- Type: `Number|Array` +- Type: `Object|Array` - Required: No - Platform: Web diff --git a/packages/block-editor/src/components/media-placeholder/index.js b/packages/block-editor/src/components/media-placeholder/index.js index d142d6d6c70008..cb5fc3806f0301 100644 --- a/packages/block-editor/src/components/media-placeholder/index.js +++ b/packages/block-editor/src/components/media-placeholder/index.js @@ -272,7 +272,7 @@ export class MediaPlaceholder extends Component { + { showMediaReplaceOptions && + + + + { __( 'Open Media Library' ) } + + + { + return ( + { + openFileDialog(); + } } + > + { __( 'Upload' ) } + + ); + } } + /> + + ( setShowURLInput( ! showURLInput ) ) } + aria-expanded={ showURLInput } + > +
    { __( 'Insert from URL' ) }
    +
    +
    + { showURLInput &&
    + { urlInputUIContent } +
    } +
    + } + + ) } + /> + ); +}; + +export default compose( + withNotices, +)( MediaReplaceFlow ); diff --git a/packages/block-editor/src/components/media-replace-flow/style.scss b/packages/block-editor/src/components/media-replace-flow/style.scss new file mode 100644 index 00000000000000..e5f8a559860d2c --- /dev/null +++ b/packages/block-editor/src/components/media-replace-flow/style.scss @@ -0,0 +1,76 @@ +.media-replace-flow .components-dropdown-menu__indicator { + margin-left: 4px; + + .components-dropdown-menu.media-flow_toolbar { + .components-dropdown-menu__label { + margin-right: 6px; + margin-left: 2px; + } + } +} + +.media-replace-flow__options.components-popover:not(.is-mobile) { + + .components-popover__content { + // this is a safari problem that shows scrollbars + // when display:flex and max-width are used together + overflow-x: hidden; + } + + .block-editor-media-flow__url-input { + + padding: 0 15px 10px 25px; + + .components-external-link__icon { + position: absolute; + right: -4px; + bottom: 5px; + margin-right: 2px; + } + + input { + max-width: 169px; + border: 1px solid $dark-gray-500; + border-radius: 4px; + } + + .block-editor-url-popover__link-viewer-url { + padding-right: 15px; + padding-top: 3px; + max-width: 179px; + position: relative; + margin-right: 0; + } + + // Mimic toolbar component styles for the icons in this popover. + .components-icon-button { + padding: 5px; + width: 40px; + height: 40px; + padding-left: 0; + + > svg { + padding: 5px; + border-radius: $radius-round-rectangle; + height: 30px; + width: 30px; + } + + &:not(:disabled):not([aria-disabled="true"]):not(.is-default):hover { + box-shadow: none; + + > svg { + @include formatting-button-style__hover; + } + } + + &:not(:disabled):focus { + box-shadow: none; + + > svg { + @include formatting-button-style__focus; + } + } + } + } +} diff --git a/packages/block-editor/src/components/media-upload-progress/index.native.js b/packages/block-editor/src/components/media-upload-progress/index.native.js index 18856fb596606f..ca13038a2795ed 100644 --- a/packages/block-editor/src/components/media-upload-progress/index.native.js +++ b/packages/block-editor/src/components/media-upload-progress/index.native.js @@ -123,7 +123,11 @@ export class MediaUploadProgress extends React.Component { return ( - { showSpinner && } + { showSpinner && + + + + } { coverUrl && { ( sizes ) => { diff --git a/packages/block-editor/src/components/media-upload-progress/styles.native.scss b/packages/block-editor/src/components/media-upload-progress/styles.native.scss index 5dea1caaf5aeff..9924af03a33c42 100644 --- a/packages/block-editor/src/components/media-upload-progress/styles.native.scss +++ b/packages/block-editor/src/components/media-upload-progress/styles.native.scss @@ -1,4 +1,7 @@ .mediaUploadProgress { flex: 1; +} + +.progressBar { background-color: $gray-lighten-30; } diff --git a/packages/block-editor/src/components/media-upload/README.md b/packages/block-editor/src/components/media-upload/README.md index d445348fa0b15d..8053e4080ab907 100644 --- a/packages/block-editor/src/components/media-upload/README.md +++ b/packages/block-editor/src/components/media-upload/README.md @@ -20,7 +20,7 @@ addFilter( ); ``` -You can check how this component is implemented for the edit post page using `wp.media` module in [edit-post](https://github.com/WordPress/gutenberg/tree/master/packages/edit-post/src/hooks/components/media-upload/index.js). +You can check how this component is implemented for the edit post page using `wp.media` module in [edit-post](https://github.com/WordPress/gutenberg/blob/master/packages/media-utils/src/components/media-upload/index.js). ## Usage diff --git a/packages/block-editor/src/components/media-upload/check.js b/packages/block-editor/src/components/media-upload/check.js index 6f2903be5b1e10..c1fb3398020f4c 100644 --- a/packages/block-editor/src/components/media-upload/check.js +++ b/packages/block-editor/src/components/media-upload/check.js @@ -14,6 +14,6 @@ export default withSelect( ( select ) => { const { getSettings } = select( 'core/block-editor' ); return { - hasUploadPermissions: !! getSettings().__experimentalMediaUpload, + hasUploadPermissions: !! getSettings().mediaUpload, }; } )( MediaUploadCheck ); diff --git a/packages/block-editor/src/components/media-upload/index.js b/packages/block-editor/src/components/media-upload/index.js index 1cb3e511097276..2318ef93d5e4f4 100644 --- a/packages/block-editor/src/components/media-upload/index.js +++ b/packages/block-editor/src/components/media-upload/index.js @@ -8,7 +8,7 @@ import { withFilters } from '@wordpress/components'; * an integration with the core blocks that handle media files. By default it renders nothing but * it provides a way to have it overridden with the `editor.MediaUpload` filter. * - * @return {WPElement} Media upload element. + * @return {WPComponent} The component to be rendered. */ const MediaUpload = () => null; diff --git a/packages/block-editor/src/components/media-upload/index.native.js b/packages/block-editor/src/components/media-upload/index.native.js index 8bed24e0928909..72a337cc921bd1 100644 --- a/packages/block-editor/src/components/media-upload/index.native.js +++ b/packages/block-editor/src/components/media-upload/index.native.js @@ -3,9 +3,9 @@ */ import React from 'react'; import { - requestMediaPickFromMediaLibrary, - requestMediaPickFromDeviceLibrary, - requestMediaPickFromDeviceCamera, + getOtherMediaOptions, + requestMediaPicker, + mediaSources, } from 'react-native-gutenberg-bridge'; /** @@ -17,41 +17,93 @@ import { Picker } from '@wordpress/components'; export const MEDIA_TYPE_IMAGE = 'image'; export const MEDIA_TYPE_VIDEO = 'video'; -export const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE = 'choose_from_device'; -export const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_MEDIA = 'take_media'; -export const MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY = 'wordpress_media_library'; - export const OPTION_TAKE_VIDEO = __( 'Take a Video' ); export const OPTION_TAKE_PHOTO = __( 'Take a Photo' ); export const OPTION_TAKE_PHOTO_OR_VIDEO = __( 'Take a Photo or Video' ); +const cameraImageSource = { + id: mediaSources.deviceCamera, // ID is the value sent to native + value: mediaSources.deviceCamera + '-IMAGE', // This is needed to diferenciate image-camera from video-camera sources. + label: __( 'Take a Photo' ), + types: [ MEDIA_TYPE_IMAGE ], + icon: 'camera', +}; + +const cameraVideoSource = { + id: mediaSources.deviceCamera, + value: mediaSources.deviceCamera, + label: __( 'Take a Video' ), + types: [ MEDIA_TYPE_VIDEO ], + icon: 'camera', +}; + +const deviceLibrarySource = { + id: mediaSources.deviceLibrary, + value: mediaSources.deviceLibrary, + label: __( 'Choose from device' ), + types: [ MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO ], +}; + +const siteLibrarySource = { + id: mediaSources.siteMediaLibrary, + value: mediaSources.siteMediaLibrary, + label: __( 'WordPress Media Library' ), + types: [ MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO ], + icon: 'wordpress-alt', +}; + +const internalSources = [ deviceLibrarySource, cameraImageSource, cameraVideoSource, siteLibrarySource ]; + export class MediaUpload extends React.Component { constructor( props ) { super( props ); this.onPickerPresent = this.onPickerPresent.bind( this ); - this.onPickerChange = this.onPickerChange.bind( this ); this.onPickerSelect = this.onPickerSelect.bind( this ); + this.getAllSources = this.getAllSources.bind( this ); + + this.state = { + otherMediaOptions: [], + }; } - getTakeMediaLabel() { - const { allowedTypes = [] } = this.props; - const isOneType = allowedTypes.length === 1; - const isImage = isOneType && allowedTypes.includes( MEDIA_TYPE_IMAGE ); - const isVideo = isOneType && allowedTypes.includes( MEDIA_TYPE_VIDEO ); + componentDidMount() { + const { allowedTypes = [] } = this.props; + getOtherMediaOptions( allowedTypes, ( otherMediaOptions ) => { + const otherMediaOptionsWithIcons = otherMediaOptions.map( ( option ) => { + return { + ...option, + types: allowedTypes, + id: option.value, + }; + } ); + + this.setState( { otherMediaOptions: otherMediaOptionsWithIcons } ); + } ); + } - if ( isImage ) { - return OPTION_TAKE_PHOTO; - } else if ( isVideo ) { - return OPTION_TAKE_VIDEO; - } return OPTION_TAKE_PHOTO_OR_VIDEO; + getAllSources() { + return internalSources.concat( this.state.otherMediaOptions ); } getMediaOptionsItems() { - return [ - { icon: this.getChooseFromDeviceIcon(), value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE, label: __( 'Choose from device' ) }, - { icon: this.getTakeMediaIcon(), value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_MEDIA, label: this.getTakeMediaLabel() }, - { icon: this.getWordPressLibraryIcon(), value: MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY, label: __( 'WordPress Media Library' ) }, - ]; + const { allowedTypes = [], multiple = false } = this.props; + + // disable upload sources for now when multiple flag is set + // eslint-disable-next-line no-undef + if ( ! __DEV__ ) { + if ( allowedTypes.includes( MEDIA_TYPE_IMAGE ) && multiple ) { + return [ siteLibrarySource ]; + } + } + + return this.getAllSources().filter( ( source ) => { + return allowedTypes.filter( ( allowedType ) => source.types.includes( allowedType ) ).length > 0; + } ).map( ( source ) => { + return { + ...source, + icon: source.icon || this.getChooseFromDeviceIcon(), + }; + } ); } getChooseFromDeviceIcon() { @@ -68,48 +120,30 @@ export class MediaUpload extends React.Component { } } - getTakeMediaIcon() { - return 'camera'; - } - - getWordPressLibraryIcon() { - return 'wordpress-alt'; - } - onPickerPresent() { if ( this.picker ) { this.picker.presentPicker(); } } - onPickerSelect( requestFunction ) { + onPickerSelect( value ) { const { allowedTypes = [], onSelect, multiple = false } = this.props; - requestFunction( allowedTypes, multiple, ( media ) => { - if ( ( multiple && media ) || media.id ) { + const mediaSource = this.getAllSources().filter( ( source ) => source.value === value ).shift(); + const types = allowedTypes.filter( ( type ) => mediaSource.types.includes( type ) ); + requestMediaPicker( mediaSource.id, types, multiple, ( media ) => { + if ( ( multiple && media ) || ( media && media.id ) ) { onSelect( media ); } } ); } - onPickerChange( value ) { - if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE ) { - this.onPickerSelect( requestMediaPickFromDeviceLibrary ); - } else if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_MEDIA ) { - this.onPickerSelect( requestMediaPickFromDeviceCamera ); - } else if ( value === MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY ) { - this.onPickerSelect( requestMediaPickFromMediaLibrary ); - } - } - render() { - const mediaOptions = this.getMediaOptionsItems(); - const getMediaOptions = () => ( this.picker = instance } - options={ mediaOptions } - onChange={ this.onPickerChange } + options={ this.getMediaOptionsItems() } + onChange={ this.onPickerSelect } /> ); diff --git a/packages/block-editor/src/components/media-upload/test/index.native.js b/packages/block-editor/src/components/media-upload/test/index.native.js index 85b63b098ca927..337b038cab9499 100644 --- a/packages/block-editor/src/components/media-upload/test/index.native.js +++ b/packages/block-editor/src/components/media-upload/test/index.native.js @@ -4,9 +4,8 @@ import { shallow } from 'enzyme'; import { TouchableWithoutFeedback } from 'react-native'; import { - requestMediaPickFromMediaLibrary, - requestMediaPickFromDeviceLibrary, - requestMediaPickFromDeviceCamera, + requestMediaPicker, + mediaSources, } from 'react-native-gutenberg-bridge'; /** @@ -14,9 +13,6 @@ import { */ import { MediaUpload, - MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE, - MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_MEDIA, - MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY, MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO, OPTION_TAKE_VIDEO, @@ -67,7 +63,7 @@ describe( 'MediaUpload component', () => { } ); const expectMediaPickerForOption = ( option, allowMultiple, requestFunction ) => { - requestFunction.mockImplementation( ( mediaTypes, multiple, callback ) => { + requestFunction.mockImplementation( ( source, mediaTypes, multiple, callback ) => { expect( mediaTypes[ 0 ] ).toEqual( MEDIA_TYPE_VIDEO ); if ( multiple ) { callback( [ { id: MEDIA_ID, url: MEDIA_URL } ] ); @@ -101,22 +97,22 @@ describe( 'MediaUpload component', () => { }; it( 'can select media from device library', () => { - expectMediaPickerForOption( MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE, false, requestMediaPickFromDeviceLibrary ); + expectMediaPickerForOption( mediaSources.deviceLibrary, false, requestMediaPicker ); } ); it( 'can select media from WP media library', () => { - expectMediaPickerForOption( MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY, false, requestMediaPickFromMediaLibrary ); + expectMediaPickerForOption( mediaSources.siteMediaLibrary, false, requestMediaPicker ); } ); it( 'can select media by capturig', () => { - expectMediaPickerForOption( MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_TAKE_MEDIA, false, requestMediaPickFromDeviceCamera ); + expectMediaPickerForOption( mediaSources.deviceCamera, false, requestMediaPicker ); } ); it( 'can select multiple media from device library', () => { - expectMediaPickerForOption( MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_CHOOSE_FROM_DEVICE, true, requestMediaPickFromDeviceLibrary ); + expectMediaPickerForOption( mediaSources.deviceLibrary, true, requestMediaPicker ); } ); it( 'can select multiple media from WP media library', () => { - expectMediaPickerForOption( MEDIA_UPLOAD_BOTTOM_SHEET_VALUE_WORD_PRESS_LIBRARY, true, requestMediaPickFromMediaLibrary ); + expectMediaPickerForOption( mediaSources.siteMediaLibrary, true, requestMediaPicker ); } ); } ); diff --git a/packages/block-editor/src/components/multi-select-scroll-into-view/index.js b/packages/block-editor/src/components/multi-select-scroll-into-view/index.js index 240a6dfaadc3ef..23a917ca676e2b 100644 --- a/packages/block-editor/src/components/multi-select-scroll-into-view/index.js +++ b/packages/block-editor/src/components/multi-select-scroll-into-view/index.js @@ -6,8 +6,8 @@ import scrollIntoView from 'dom-scroll-into-view'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; -import { withSelect } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; import { getScrollContainer } from '@wordpress/dom'; /** @@ -15,24 +15,31 @@ import { getScrollContainer } from '@wordpress/dom'; */ import { getBlockDOMNode } from '../../utils/dom'; -class MultiSelectScrollIntoView extends Component { - componentDidUpdate() { - // Relies on expectation that `componentDidUpdate` will only be called - // if value of `extentClientId` changes. - this.scrollIntoView(); - } +/** + * Scrolls the multi block selection end into view if not in view already. This + * is important to do after selection by keyboard. + */ +export default function MultiSelectScrollIntoView() { + const selector = ( select ) => { + const { + getBlockSelectionEnd, + isMultiSelecting, + } = select( 'core/block-editor' ); + + return { + selectionEnd: getBlockSelectionEnd(), + isMultiSelecting: isMultiSelecting(), + }; + }; + const { selectionEnd, isMultiSelecting } = useSelect( selector, [] ); - /** - * Ensures that if a multi-selection exists, the extent of the selection is - * visible within the nearest scrollable container. - */ - scrollIntoView() { - const { extentClientId } = this.props; - if ( ! extentClientId ) { + useEffect( () => { + if ( ! selectionEnd || isMultiSelecting ) { return; } - const extentNode = getBlockDOMNode( extentClientId ); + const extentNode = getBlockDOMNode( selectionEnd ); + if ( ! extentNode ) { return; } @@ -48,17 +55,7 @@ class MultiSelectScrollIntoView extends Component { scrollIntoView( extentNode, scrollContainer, { onlyScrollIfNeeded: true, } ); - } + }, [ selectionEnd, isMultiSelecting ] ); - render() { - return null; - } + return null; } - -export default withSelect( ( select ) => { - const { getLastMultiSelectedBlockClientId } = select( 'core/block-editor' ); - - return { - extentClientId: getLastMultiSelectedBlockClientId(), - }; -} )( MultiSelectScrollIntoView ); diff --git a/packages/block-editor/src/components/multi-selection-inspector/style.scss b/packages/block-editor/src/components/multi-selection-inspector/style.scss index bd81710ebfcaef..0723ada11378ae 100644 --- a/packages/block-editor/src/components/multi-selection-inspector/style.scss +++ b/packages/block-editor/src/components/multi-selection-inspector/style.scss @@ -1,8 +1,7 @@ .block-editor-multi-selection-inspector__card { display: flex; align-items: flex-start; - margin: -16px; - padding: 16px; + padding: $grid-size-large; } .block-editor-multi-selection-inspector__card-content { diff --git a/packages/block-editor/src/components/page-template-picker/button.js b/packages/block-editor/src/components/page-template-picker/button.js new file mode 100644 index 00000000000000..6677fb87af5bcd --- /dev/null +++ b/packages/block-editor/src/components/page-template-picker/button.js @@ -0,0 +1,19 @@ +/** + * WordPress dependencies + */ +import { Button } from '@wordpress/components'; + +const PickerButton = ( props ) => { + const { + label, + onPress, + } = props; + + return ( + + ); +}; + +export default PickerButton; diff --git a/packages/block-editor/src/components/page-template-picker/button.native.js b/packages/block-editor/src/components/page-template-picker/button.native.js new file mode 100644 index 00000000000000..36f9a97eb6c432 --- /dev/null +++ b/packages/block-editor/src/components/page-template-picker/button.native.js @@ -0,0 +1,20 @@ +/** + * External dependencies + */ +import { Button } from 'react-native'; + +const PickerButton = ( props ) => { + const { + label, + onPress, + } = props; + + return ( + ); diff --git a/packages/block-editor/src/components/tool-selector/index.js b/packages/block-editor/src/components/tool-selector/index.js new file mode 100644 index 00000000000000..3dab7d858db0b3 --- /dev/null +++ b/packages/block-editor/src/components/tool-selector/index.js @@ -0,0 +1,81 @@ +/** + * WordPress dependencies + */ +import { + Dropdown, + IconButton, + MenuItemsChoice, + SVG, + Path, + NavigableMenu, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { useViewportMatch } from '@wordpress/compose'; + +const editIcon = ; +const selectIcon = ; + +function ToolSelector() { + const isNavigationTool = useSelect( ( select ) => select( 'core/block-editor' ).isNavigationMode(), [] ); + const { setNavigationMode } = useDispatch( 'core/block-editor' ); + const isMediumViewport = useViewportMatch( 'medium' ); + if ( ! isMediumViewport ) { + return null; + } + + const onSwitchMode = ( mode ) => { + setNavigationMode( mode === 'edit' ? false : true ); + }; + + return ( + ( + + ) } + renderContent={ () => ( + <> + + + { editIcon } + { __( 'Edit' ) } + + ), + }, + { + value: 'select', + label: ( + <> + { selectIcon } + { __( 'Select' ) } + + ), + }, + ] } + /> + +
    + { __( 'Tools offer different interactions for block selection & editing. To select, press Escape, to go back to editing, press Enter.' ) } +
    + + ) } + /> + ); +} + +export default ToolSelector; diff --git a/packages/block-editor/src/components/tool-selector/style.scss b/packages/block-editor/src/components/tool-selector/style.scss new file mode 100644 index 00000000000000..1f61a871dce896 --- /dev/null +++ b/packages/block-editor/src/components/tool-selector/style.scss @@ -0,0 +1,5 @@ +.block-editor-tool-selector__help { + padding: $grid-size-large; + border-top: 1px solid $light-gray-500; + color: $dark-gray-300; +} diff --git a/packages/block-editor/src/components/typewriter/index.js b/packages/block-editor/src/components/typewriter/index.js index 7600b2cd8747cd..99487f7a31cf22 100644 --- a/packages/block-editor/src/components/typewriter/index.js +++ b/packages/block-editor/src/components/typewriter/index.js @@ -6,6 +6,8 @@ import { computeCaretRect, getScrollContainer } from '@wordpress/dom'; import { withSelect } from '@wordpress/data'; import { UP, DOWN, LEFT, RIGHT } from '@wordpress/keycodes'; +/** @typedef {import('@wordpress/element').WPSyntheticEvent} WPSyntheticEvent */ + const isIE = window.navigator.userAgent.indexOf( 'Trident' ) !== -1; const arrowKeyCodes = new Set( [ UP, DOWN, LEFT, RIGHT ] ); const initialTriggerPercentage = 0.75; @@ -100,7 +102,7 @@ class Typewriter extends Component { * Maintains the scroll position after a selection change caused by a * keyboard event. * - * @param {SyntheticEvent} event Synthetic keyboard event. + * @param {WPSyntheticEvent} event Synthetic keyboard event. */ maintainCaretPosition( { keyCode } ) { if ( ! this.isSelectionEligibleForScroll() ) { @@ -235,6 +237,7 @@ class Typewriter extends Component { onKeyUp={ this.maintainCaretPosition } onMouseDown={ this.addSelectionChangeListener } onTouchStart={ this.addSelectionChangeListener } + className="block-editor__typewriter" > { this.props.children } diff --git a/packages/block-editor/src/components/ungroup-button/icon.js b/packages/block-editor/src/components/ungroup-button/icon.js index e730041a12d8de..4ef9e2694bd6ef 100644 --- a/packages/block-editor/src/components/ungroup-button/icon.js +++ b/packages/block-editor/src/components/ungroup-button/icon.js @@ -1,8 +1,8 @@ /** * WordPress dependencies */ -import { Icon, SVG, Path } from '@wordpress/components'; +import { SVG, Path } from '@wordpress/components'; const UngroupSVG = ; -export const Ungroup = ; +export default UngroupSVG; diff --git a/packages/block-editor/src/components/ungroup-button/index.native.js b/packages/block-editor/src/components/ungroup-button/index.native.js index d5a193ff02020b..df006897210f97 100644 --- a/packages/block-editor/src/components/ungroup-button/index.native.js +++ b/packages/block-editor/src/components/ungroup-button/index.native.js @@ -17,7 +17,7 @@ import { compose } from '@wordpress/compose'; /** * Internal dependencies */ -import { UngroupIcon } from './icon'; +import UngroupIcon from './icon'; export function UngroupButton( { onConvertFromGroup, @@ -44,10 +44,16 @@ export default compose( [ getBlock, } = select( 'core/block-editor' ); + const { + getGroupingBlockName, + } = select( 'core/blocks' ); + const selectedId = getSelectedBlockClientId(); const selectedBlock = getBlock( selectedId ); - const isUngroupable = selectedBlock && selectedBlock.innerBlocks && !! selectedBlock.innerBlocks.length; + const groupingBlockName = getGroupingBlockName(); + + const isUngroupable = selectedBlock && selectedBlock.innerBlocks && !! selectedBlock.innerBlocks.length && selectedBlock.name === groupingBlockName; const innerBlocks = isUngroupable ? selectedBlock.innerBlocks : []; return { diff --git a/packages/block-editor/src/components/url-input/README.md b/packages/block-editor/src/components/url-input/README.md index d2d11f7573b47b..1adbd5b97084f5 100644 --- a/packages/block-editor/src/components/url-input/README.md +++ b/packages/block-editor/src/components/url-input/README.md @@ -125,6 +125,10 @@ Renders the URL input field used by the `URLInputButton` component. It can be us } ``` +### `label: String` + +*Optional.* If this property is added, a label will be generated using label property as the content. + ### `autoFocus: Boolean` *Optional.* By default, the input will gain focus when it is rendered, as typically it is displayed conditionally. For example when clicking on `URLInputButton` or editing a block. diff --git a/packages/block-editor/src/components/url-input/button.js b/packages/block-editor/src/components/url-input/button.js index 077decf42d6313..a1b57e5d3d6548 100644 --- a/packages/block-editor/src/components/url-input/button.js +++ b/packages/block-editor/src/components/url-input/button.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - /** * WordPress dependencies */ @@ -45,9 +40,8 @@ class URLInputButton extends Component { icon="admin-links" label={ buttonLabel } onClick={ this.toggle } - className={ classnames( 'components-toolbar__control', { - 'is-active': url, - } ) } + className="components-toolbar__control" + isPressed={ !! url } /> { expanded &&
    event.stopPropagation(); class URLInput extends Component { - constructor( { autocompleteRef } ) { - super( ...arguments ); + constructor( props ) { + super( props ); this.onChange = this.onChange.bind( this ); this.onKeyDown = this.onKeyDown.bind( this ); - this.autocompleteRef = autocompleteRef || createRef(); + this.selectLink = this.selectLink.bind( this ); + this.handleOnClick = this.handleOnClick.bind( this ); + this.bindSuggestionNode = this.bindSuggestionNode.bind( this ); + this.autocompleteRef = props.autocompleteRef || createRef(); this.inputRef = createRef(); this.updateSuggestions = throttle( this.updateSuggestions.bind( this ), 200 ); @@ -45,6 +49,7 @@ class URLInput extends Component { // when already expanded if ( showSuggestions && selectedSuggestion !== null && ! this.scrollingIntoView ) { this.scrollingIntoView = true; + scrollIntoView( this.suggestionNodes[ selectedSuggestion ], this.autocompleteRef.current, { onlyScrollIfNeeded: true, } ); @@ -66,14 +71,17 @@ class URLInput extends Component { } updateSuggestions( value ) { - const { fetchLinkSuggestions } = this.props; + const { + __experimentalFetchLinkSuggestions: fetchLinkSuggestions, + __experimentalHandleURLSuggestions: handleURLSuggestions, + } = this.props; if ( ! fetchLinkSuggestions ) { return; } // Show the suggestions after typing at least 2 characters // and also for URLs - if ( value.length < 2 || /^https?:/.test( value ) ) { + if ( value.length < 2 || ( ! handleURLSuggestions && isURL( value ) ) ) { this.setState( { showSuggestions: false, selectedSuggestion: null, @@ -127,14 +135,20 @@ class URLInput extends Component { onChange( event ) { const inputValue = event.target.value; this.props.onChange( inputValue ); - this.updateSuggestions( inputValue ); + if ( ! this.props.disableSuggestions ) { + this.updateSuggestions( inputValue ); + } } onKeyDown( event ) { const { showSuggestions, selectedSuggestion, suggestions, loading } = this.state; + // If the suggestions are not shown or loading, we shouldn't handle the arrow keys // We shouldn't preventDefault to allow block arrow keys navigation - if ( ! showSuggestions || ! suggestions.length || loading ) { + if ( + ( ! showSuggestions || ! suggestions.length || loading ) && + this.props.value + ) { // In the Windows version of Firefox the up and down arrows don't move the caret // within an input field like they do for Mac Firefox/Chrome/Safari. This causes // a form of focus trapping that is disruptive to the user experience. This disruption @@ -223,27 +237,76 @@ class URLInput extends Component { this.inputRef.current.focus(); } - static getDerivedStateFromProps( { disableSuggestions }, { showSuggestions } ) { + static getDerivedStateFromProps( { value, disableSuggestions }, { showSuggestions, selectedSuggestion } ) { + let shouldShowSuggestions = showSuggestions; + + const hasValue = value && value.length; + + if ( ! hasValue ) { + shouldShowSuggestions = false; + } + + if ( disableSuggestions === true ) { + shouldShowSuggestions = false; + } + return { - showSuggestions: disableSuggestions === true ? false : showSuggestions, + selectedSuggestion: hasValue ? selectedSuggestion : null, + showSuggestions: shouldShowSuggestions, }; } render() { - const { value = '', autoFocus = true, instanceId, className, id, isFullWidth, hasBorder } = this.props; - const { showSuggestions, suggestions, selectedSuggestion, loading } = this.state; + const { + label, + instanceId, + className, + isFullWidth, + hasBorder, + __experimentalRenderSuggestions: renderSuggestions, + placeholder = __( 'Paste URL or type to search' ), + value = '', + autoFocus = true, + } = this.props; + + const { + showSuggestions, + suggestions, + selectedSuggestion, + loading, + } = this.state; + const id = `url-input-control-${ instanceId }`; const suggestionsListboxId = `block-editor-url-input-suggestions-${ instanceId }`; const suggestionOptionIdPrefix = `block-editor-url-input-suggestion-${ instanceId }`; + const suggestionsListProps = { + id: suggestionsListboxId, + ref: this.autocompleteRef, + role: 'listbox', + }; + + const buildSuggestionItemProps = ( suggestion, index ) => { + return { + role: 'option', + tabIndex: '-1', + id: `${ suggestionOptionIdPrefix }-${ index }`, + ref: this.bindSuggestionNode( index ), + 'aria-selected': index === selectedSuggestion, + }; + }; + /* eslint-disable jsx-a11y/no-autofocus */ return ( -
    + } - { showSuggestions && !! suggestions.length && + { isFunction( renderSuggestions ) && showSuggestions && !! suggestions.length && renderSuggestions( { + suggestions, + selectedSuggestion, + suggestionsListProps, + buildSuggestionItemProps, + isLoading: loading, + handleSuggestionClick: this.handleOnClick, + } ) } + + { ! isFunction( renderSuggestions ) && showSuggestions && !! suggestions.length &&
    { suggestions.map( ( suggestion, index ) => ( - + ) ) }
    } -
    + ); /* eslint-enable jsx-a11y/no-autofocus */ } @@ -311,10 +377,15 @@ export default compose( withSafeTimeout, withSpokenMessages, withInstanceId, - withSelect( ( select ) => { + withSelect( ( select, props ) => { + // If a link suggestions handler is already provided then + // bail + if ( isFunction( props.__experimentalFetchLinkSuggestions ) ) { + return; + } const { getSettings } = select( 'core/block-editor' ); return { - fetchLinkSuggestions: getSettings().__experimentalFetchLinkSuggestions, + __experimentalFetchLinkSuggestions: getSettings().__experimentalFetchLinkSuggestions, }; } ) )( URLInput ); diff --git a/packages/block-editor/src/components/url-input/style.scss b/packages/block-editor/src/components/url-input/style.scss index 5e6f34762a4605..82633eed772ccd 100644 --- a/packages/block-editor/src/components/url-input/style.scss +++ b/packages/block-editor/src/components/url-input/style.scss @@ -51,7 +51,7 @@ $input-size: 300px; .components-spinner { position: absolute; right: $input-padding; - top: $input-padding + 1; + bottom: $input-padding + $grid-size + 1; margin: 0; } } diff --git a/packages/block-editor/src/components/url-input/test/button.js b/packages/block-editor/src/components/url-input/test/button.js index 8088d61ea5016e..78a1e5f9fa562a 100644 --- a/packages/block-editor/src/components/url-input/test/button.js +++ b/packages/block-editor/src/components/url-input/test/button.js @@ -20,13 +20,13 @@ describe( 'URLInputButton', () => { const wrapper = shallow( ); expect( wrapper.hasClass( 'block-editor-url-input__button' ) ).toBe( true ); } ); - it( 'should not have is-active class when url prop not defined', () => { + it( 'should have isPressed props set to false when url prop not defined', () => { const wrapper = shallow( ); - expect( wrapper.find( 'ForwardRef(IconButton)' ).hasClass( 'is-active' ) ).toBe( false ); + expect( wrapper.find( 'ForwardRef(IconButton)' ).prop( 'isPressed' ) ).toBe( false ); } ); - it( 'should have is-active class name if url prop defined', () => { + it( 'should have isPressed prop set to true if url prop defined', () => { const wrapper = shallow( ); - expect( wrapper.find( 'ForwardRef(IconButton)' ).hasClass( 'is-active' ) ).toBe( true ); + expect( wrapper.find( 'ForwardRef(IconButton)' ).prop( 'isPressed' ) ).toBe( true ); } ); it( 'should have hidden form by default', () => { const wrapper = shallow( ); diff --git a/packages/block-editor/src/components/url-popover/style.scss b/packages/block-editor/src/components/url-popover/style.scss index 268908dd01478f..e844e4b8f29423 100644 --- a/packages/block-editor/src/components/url-popover/style.scss +++ b/packages/block-editor/src/components/url-popover/style.scss @@ -60,21 +60,29 @@ transform: rotate(180deg); } } +.block-editor-url-popover__input-container { + .components-base-control:last-child, + .components-base-control:last-child .components-base-control__field { + margin-bottom: 0; + } +} .block-editor-url-popover__settings { display: block; padding: $panel-padding; border-top: $border-width solid $light-gray-500; - - .components-base-control:last-child, - .components-base-control:last-child .components-base-control__field { - margin-bottom: 0; - } } .block-editor-url-popover__link-editor, .block-editor-url-popover__link-viewer { display: flex; + + .block-editor-url-input .components-base-control__field { + margin-bottom: 0; + } + .block-editor-url-input .components-spinner { + bottom: $input-padding + 1; + } } .block-editor-url-popover__link-viewer-url { diff --git a/packages/block-library/src/video/gridicon-play.native.js b/packages/block-editor/src/components/video-player/gridicon-play.native.js similarity index 100% rename from packages/block-library/src/video/gridicon-play.native.js rename to packages/block-editor/src/components/video-player/gridicon-play.native.js diff --git a/packages/block-library/src/video/video-player.native.js b/packages/block-editor/src/components/video-player/index.native.js similarity index 96% rename from packages/block-library/src/video/video-player.native.js rename to packages/block-editor/src/components/video-player/index.native.js index 31c2cdb7b64baa..73c2038a0b457c 100644 --- a/packages/block-library/src/video/video-player.native.js +++ b/packages/block-editor/src/components/video-player/index.native.js @@ -14,9 +14,12 @@ import { default as VideoPlayer } from 'react-native-video'; /** * Internal dependencies */ -import styles from './video-player.scss'; +import styles from './styles.scss'; import PlayIcon from './gridicon-play'; +// Default Video ratio 16:9 +export const VIDEO_ASPECT_RATIO = 16 / 9; + class Video extends Component { constructor() { super( ...arguments ); diff --git a/packages/block-library/src/video/video-player.native.scss b/packages/block-editor/src/components/video-player/styles.native.scss similarity index 100% rename from packages/block-library/src/video/video-player.native.scss rename to packages/block-editor/src/components/video-player/styles.native.scss diff --git a/packages/block-editor/src/components/warning/style.scss b/packages/block-editor/src/components/warning/style.scss index 3f6c78825c68b2..da6306932af853 100644 --- a/packages/block-editor/src/components/warning/style.scss +++ b/packages/block-editor/src/components/warning/style.scss @@ -8,11 +8,6 @@ text-align: left; padding: 10px $block-padding $block-padding; - // Avoid conflict with the multi-selection highlight color. - .has-warning.is-multi-selected & { - background-color: transparent; - } - .is-selected & { // Use opacity to work in various editor styles. border-color: $dark-opacity-light-800; diff --git a/packages/block-editor/src/components/writing-flow/index.js b/packages/block-editor/src/components/writing-flow/index.js index 54e1a7ee77fe08..cd9355bcf2a36e 100644 --- a/packages/block-editor/src/components/writing-flow/index.js +++ b/packages/block-editor/src/components/writing-flow/index.js @@ -104,13 +104,6 @@ class WritingFlow extends Component { onMouseDown() { this.verticalRect = null; - this.disableNavigationMode(); - } - - disableNavigationMode() { - if ( this.props.isNavigationMode ) { - this.props.disableNavigationMode(); - } } /** @@ -444,11 +437,10 @@ export default compose( [ }; } ), withDispatch( ( dispatch ) => { - const { multiSelect, selectBlock, setNavigationMode, clearSelectedBlock } = dispatch( 'core/block-editor' ); + const { multiSelect, selectBlock, clearSelectedBlock } = dispatch( 'core/block-editor' ); return { onMultiSelect: multiSelect, onSelectBlock: selectBlock, - disableNavigationMode: () => setNavigationMode( false ), clearSelectedBlock, }; } ), diff --git a/packages/block-editor/src/hooks/align.js b/packages/block-editor/src/hooks/align.js index 635db6b65f68e3..bd04ecd554a36e 100644 --- a/packages/block-editor/src/hooks/align.js +++ b/packages/block-editor/src/hooks/align.js @@ -7,10 +7,10 @@ import { assign, get, has, includes, without } from 'lodash'; /** * WordPress dependencies */ -import { compose, createHigherOrderComponent } from '@wordpress/compose'; +import { createHigherOrderComponent } from '@wordpress/compose'; import { addFilter } from '@wordpress/hooks'; import { getBlockSupport, getBlockType, hasBlockSupport } from '@wordpress/blocks'; -import { withSelect } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -138,45 +138,39 @@ export const withToolbarControls = createHigherOrderComponent( 'withToolbarControls' ); -// Exported just for testing purposes, not exported outside the module. -export const insideSelectWithDataAlign = ( BlockListBlock ) => ( - ( props ) => { - const { name, attributes, hasWideEnabled } = props; - const { align } = attributes; - const validAlignments = getValidAlignments( - getBlockSupport( name, 'align' ), - hasBlockSupport( name, 'alignWide', true ), - hasWideEnabled - ); - - let wrapperProps = props.wrapperProps; - if ( includes( validAlignments, align ) ) { - wrapperProps = { ...wrapperProps, 'data-align': align }; - } - - return ; - } -); - /** * Override the default block element to add alignment wrapper props. * * @param {Function} BlockListBlock Original component * @return {Function} Wrapped component */ -export const withDataAlign = createHigherOrderComponent( - compose( [ - withSelect( - ( select ) => { - const { getSettings } = select( 'core/block-editor' ); - return { - hasWideEnabled: !! getSettings().alignWide, - }; - } - ), - insideSelectWithDataAlign, - ] ) -); +export const withDataAlign = createHigherOrderComponent( ( BlockListBlock ) => ( props ) => { + const { name, attributes } = props; + const { align } = attributes; + const hasWideEnabled = useSelect( + ( select ) => !! select( 'core/block-editor' ).getSettings().alignWide, + [] + ); + + // If an alignment is not assigned, there's no need to go through the + // effort to validate or assign its value. + if ( align === undefined ) { + return ; + } + + const validAlignments = getValidAlignments( + getBlockSupport( name, 'align' ), + hasBlockSupport( name, 'alignWide', true ), + hasWideEnabled + ); + + let wrapperProps = props.wrapperProps; + if ( includes( validAlignments, align ) ) { + wrapperProps = { ...wrapperProps, 'data-align': align }; + } + + return ; +} ); /** * Override props assigned to save component to inject alignment class name if diff --git a/packages/block-editor/src/hooks/anchor.js b/packages/block-editor/src/hooks/anchor.js index 9b78c39bbfeb02..5f8f1ca25879f5 100644 --- a/packages/block-editor/src/hooks/anchor.js +++ b/packages/block-editor/src/hooks/anchor.js @@ -56,9 +56,9 @@ export function addAttribute( settings ) { * Override the default edit UI to include a new block inspector control for * assigning the anchor ID, if block supports anchor. * - * @param {Function|Component} BlockEdit Original component. + * @param {WPComponent} BlockEdit Original component. * - * @return {string} Wrapped component. + * @return {WPComponent} Wrapped component. */ export const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => { return ( props ) => { diff --git a/packages/block-editor/src/hooks/custom-class-name.js b/packages/block-editor/src/hooks/custom-class-name.js index bac77a37ea42a2..dfbceb627389cc 100644 --- a/packages/block-editor/src/hooks/custom-class-name.js +++ b/packages/block-editor/src/hooks/custom-class-name.js @@ -47,9 +47,9 @@ export function addAttribute( settings ) { * Override the default edit UI to include a new block inspector control for * assigning the custom class name, if block supports custom class name. * - * @param {Function|Component} BlockEdit Original component. + * @param {WPComponent} BlockEdit Original component. * - * @return {string} Wrapped component. + * @return {WPComponent} Wrapped component. */ export const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => { return ( props ) => { diff --git a/packages/block-editor/src/hooks/test/align.js b/packages/block-editor/src/hooks/test/align.js index 995d3a9a59c4c3..1e348229ad675c 100644 --- a/packages/block-editor/src/hooks/test/align.js +++ b/packages/block-editor/src/hooks/test/align.js @@ -2,7 +2,7 @@ * External dependencies */ import { noop } from 'lodash'; -import renderer from 'react-test-renderer'; +import renderer, { act } from 'react-test-renderer'; /** * WordPress dependencies @@ -17,10 +17,11 @@ import { /** * Internal dependencies */ +import BlockEditorProvider from '../../components/provider'; import { getValidAlignments, withToolbarControls, - insideSelectWithDataAlign, + withDataAlign, addAssignedAlign, } from '../align'; @@ -169,19 +170,24 @@ describe( 'align', () => { }, } ); - const EnhancedComponent = insideSelectWithDataAlign( ( { wrapperProps } ) => ( + const EnhancedComponent = withDataAlign( ( { wrapperProps } ) => (
    ) ); - const wrapper = renderer.create( - - ); - expect( wrapper.toTree().rendered.props.wrapperProps ).toEqual( { + let wrapper; + act( () => { + wrapper = renderer.create( + + + + ); + } ); + expect( wrapper.root.findByType( 'div' ).props ).toEqual( { 'data-align': 'wide', } ); } ); @@ -195,20 +201,24 @@ describe( 'align', () => { }, } ); - const EnhancedComponent = insideSelectWithDataAlign( ( { wrapperProps } ) => ( + const EnhancedComponent = withDataAlign( ( { wrapperProps } ) => (
    ) ); - const wrapper = renderer.create( - - ); - expect( wrapper.toTree().rendered.props.wrapperProps ).toEqual( undefined ); + let wrapper; + act( () => { + wrapper = renderer.create( + + + + ); + } ); + expect( wrapper.root.findByType( 'div' ).props ).toEqual( {} ); } ); it( 'should not render invalid align', () => { @@ -220,20 +230,24 @@ describe( 'align', () => { }, } ); - const EnhancedComponent = insideSelectWithDataAlign( ( { wrapperProps } ) => ( + const EnhancedComponent = withDataAlign( ( { wrapperProps } ) => (
    ) ); - const wrapper = renderer.create( - - ); - - expect( wrapper.toTree().props.wrapperProps ).toBeUndefined(); + let wrapper; + act( () => { + wrapper = renderer.create( + + + + ); + } ); + expect( wrapper.root.findByType( 'div' ).props ).toEqual( {} ); } ); } ); diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 3c4c69b91d4b54..933a5e80064b33 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -7,6 +7,8 @@ import { castArray, first, get, includes } from 'lodash'; * WordPress dependencies */ import { getDefaultBlockName, createBlock } from '@wordpress/blocks'; +import { speak } from '@wordpress/a11y'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -48,6 +50,34 @@ export function resetBlocks( blocks ) { }; } +/** + * A block selection object. + * + * @typedef {Object} WPBlockSelection + * + * @property {string} clientId A block client ID. + * @property {string} attributeKey A block attribute key. + * @property {number} offset An attribute value offset, based on the rich + * text value. See `wp.richText.create`. + */ + +/** + * Returns an action object used in signalling that selection state should be + * reset to the specified selection. + * + * @param {WPBlockSelection} selectionStart The selection start. + * @param {WPBlockSelection} selectionEnd The selection end. + * + * @return {Object} Action object. + */ +export function resetSelection( selectionStart, selectionEnd ) { + return { + type: 'RESET_SELECTION', + selectionStart, + selectionEnd, + }; +} + /** * Returns an action object used in signalling that blocks have been received. * Unlike resetBlocks, these should be appended to the existing known set, not @@ -627,6 +657,28 @@ export function stopTyping() { }; } +/** + * Returns an action object used in signalling that the user has begun to drag blocks. + * + * @return {Object} Action object. + */ +export function startDraggingBlocks() { + return { + type: 'START_DRAGGING_BLOCKS', + }; +} + +/** + * Returns an action object used in signalling that the user has stopped dragging blocks. + * + * @return {Object} Action object. + */ +export function stopDraggingBlocks() { + return { + type: 'STOP_DRAGGING_BLOCKS', + }; +} + /** * Returns an action object used in signalling that the caret has entered formatted text. * @@ -765,15 +817,19 @@ export function __unstableMarkAutomaticChange() { } /** - * Returns an action object used to enable or disable the navigation mode. + * Generators that triggers an action used to enable or disable the navigation mode. * * @param {string} isNavigationMode Enable/Disable navigation mode. - * - * @return {Object} Action object */ -export function setNavigationMode( isNavigationMode = true ) { - return { +export function * setNavigationMode( isNavigationMode = true ) { + yield { type: 'SET_NAVIGATION_MODE', isNavigationMode, }; + + if ( isNavigationMode ) { + speak( __( 'You are currently in navigation mode. Navigate blocks using the Tab key. To exit navigation mode and edit the selected block, press Enter.' ) ); + } else { + speak( __( 'You are currently in edit mode. To return to the navigation mode, press Escape.' ) ); + } } diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js index 06eabc9f6f11f4..dca689127ad93d 100644 --- a/packages/block-editor/src/store/defaults.js +++ b/packages/block-editor/src/store/defaults.js @@ -10,28 +10,31 @@ export const PREFERENCES_DEFAULTS = { /** * The default editor settings * - * alignWide boolean Enable/Disable Wide/Full Alignments - * availableLegacyWidgets Array Array of objects representing the legacy widgets available. - * colors Array Palette colors - * disableCustomColors boolean Whether or not the custom colors are disabled - * fontSizes Array Available font sizes - * disableCustomFontSizes boolean Whether or not the custom font sizes are disabled - * imageSizes Array Available image sizes - * maxWidth number Max width to constraint resizing - * allowedBlockTypes boolean|Array Allowed block types - * hasFixedToolbar boolean Whether or not the editor toolbar is fixed - * hasPermissionsToManageWidgets boolean Whether or not the user is able to manage widgets. - * focusMode boolean Whether the focus mode is enabled or not - * styles Array Editor Styles - * isRTL boolean Whether the editor is in RTL mode - * bodyPlaceholder string Empty post placeholder - * titlePlaceholder string Empty title placeholder - * codeEditingEnabled string Whether or not the user can switch to the code editor - * showInserterHelpPanel boolean Whether or not the inserter help panel is shown - * __experimentalCanUserUseUnfilteredHTML string Whether the user should be able to use unfiltered HTML or the HTML should be filtered e.g., to remove elements considered insecure like iframes. - * __experimentalEnableLegacyWidgetBlock boolean Whether the user has enabled the Legacy Widget Block - * __experimentalEnableMenuBlock boolean Whether the user has enabled the Menu Block - * __experimentalBlockDirectory boolean Whether the user has enabled the Block Directory + * @typedef {Object} SETTINGS_DEFAULT + * @property {boolean} alignWide Enable/Disable Wide/Full Alignments + * @property {Array} availableLegacyWidgets Array of objects representing the legacy widgets available. + * @property {Array} colors Palette colors + * @property {boolean} disableCustomColors Whether or not the custom colors are disabled + * @property {Array} fontSizes Available font sizes + * @property {boolean} disableCustomFontSizes Whether or not the custom font sizes are disabled + * @property {Array} imageSizes Available image sizes + * @property {number} maxWidth Max width to constraint resizing + * @property {boolean|Array} allowedBlockTypes Allowed block types + * @property {boolean} hasFixedToolbar Whether or not the editor toolbar is fixed + * @property {boolean} hasPermissionsToManageWidgets Whether or not the user is able to manage widgets. + * @property {boolean} focusMode Whether the focus mode is enabled or not + * @property {Array} styles Editor Styles + * @property {boolean} isRTL Whether the editor is in RTL mode + * @property {string} bodyPlaceholder Empty post placeholder + * @property {string} titlePlaceholder Empty title placeholder + * @property {boolean} codeEditingEnabled Whether or not the user can switch to the code editor + * @property {boolean} showInserterHelpPanel Whether or not the inserter help panel is shown + * @property {boolean} __experimentalCanUserUseUnfilteredHTML Whether the user should be able to use unfiltered HTML or the HTML should be filtered e.g., to remove elements considered insecure like iframes. + * @property {boolean} __experimentalEnableLegacyWidgetBlock Whether the user has enabled the Legacy Widget Block + * @property {boolean} __experimentalBlockDirectory Whether the user has enabled the Block Directory + * @property {boolean} __experimentalEnableFullSiteEditing Whether the user has enabled Full Site Editing + * @property {boolean} __experimentalEnableFullSiteEditingDemo Whether the user has enabled Full Site Editing Demo Templates + * @property {boolean} __experimentalEnablePageTemplates Whether the user has enabled the Page Templates */ export const SETTINGS_DEFAULTS = { alignWide: false, @@ -150,82 +153,70 @@ export const SETTINGS_DEFAULTS = { showInserterHelpPanel: true, __experimentalCanUserUseUnfilteredHTML: false, __experimentalEnableLegacyWidgetBlock: false, - __experimentalEnableMenuBlock: false, __experimentalBlockDirectory: false, + __experimentalEnableFullSiteEditing: false, + __experimentalEnableFullSiteEditingDemo: false, + __experimentalEnablePageTemplates: false, gradients: [ { name: __( 'Vivid cyan blue to vivid purple' ), - gradient: 'linear-gradient(135deg, rgba(6, 147, 227, 1) 0%, rgb(155, 81, 224) 100%)', - }, - { - name: __( 'Vivid green cyan to vivid cyan blue' ), - gradient: 'linear-gradient(135deg, rgba(0, 208, 132, 1) 0%, rgba(6, 147, 227, 1) 100%)', + gradient: 'linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)', + slug: 'vivid-cyan-blue-to-vivid-purple', }, { name: __( 'Light green cyan to vivid green cyan' ), - gradient: 'linear-gradient(135deg, rgb(122, 220, 180) 0%, rgb(0, 208, 130) 100%)', + gradient: 'linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%)', + slug: 'light-green-cyan-to-vivid-green-cyan', }, { name: __( 'Luminous vivid amber to luminous vivid orange' ), - gradient: 'linear-gradient(135deg, rgba(252, 185, 0, 1) 0%, rgba(255, 105, 0, 1) 100%)', + gradient: 'linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%)', + slug: 'luminous-vivid-amber-to-luminous-vivid-orange', }, { name: __( 'Luminous vivid orange to vivid red' ), - gradient: 'linear-gradient(135deg, rgba(255, 105, 0, 1) 0%, rgb(207, 46, 46) 100%)', + gradient: 'linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%)', + slug: 'luminous-vivid-orange-to-vivid-red', }, { name: __( 'Very light gray to cyan bluish gray' ), - gradient: 'linear-gradient(135deg, rgb(238, 238, 238) 0%, rgb(169, 184, 195)', + gradient: 'linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%)', + slug: 'very-light-gray-to-cyan-bluish-gray', }, - // The following use new, customized colors. { name: __( 'Cool to warm spectrum' ), - gradient: 'linear-gradient(135deg, rgb(74, 234, 220), rgb(151, 120, 209), rgb(207, 42, 186), rgb(238, 44, 130), rgb(251, 105, 98),rgb(254, 248, 76)', + gradient: 'linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%)', + slug: 'cool-to-warm-spectrum', }, { name: __( 'Blush light purple' ), - gradient: 'linear-gradient(135deg, rgb(255, 206, 236), rgb(152, 150, 240)', + gradient: 'linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%)', + slug: 'blush-light-purple', }, { name: __( 'Blush bordeaux' ), - gradient: 'linear-gradient(135deg, rgb(254, 205, 165), rgb(254, 45, 45), rgb(107, 0, 62)', - }, - { - name: __( 'Purple crush' ), - gradient: 'linear-gradient(135deg, rgb(52, 226, 228), rgb(71, 33, 251), rgb(171, 29, 254)', + gradient: 'linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%)', + slug: 'blush-bordeaux', }, { name: __( 'Luminous dusk' ), - gradient: 'linear-gradient(135deg, rgb(255, 203, 112), rgb(199, 81, 192), rgb(65, 88, 208)', - }, - { - name: __( 'Hazy dawn' ), - gradient: 'linear-gradient(135deg, rgb(250, 172, 168), rgb(218, 208, 236)', + gradient: 'linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)', + slug: 'luminous-dusk', }, { name: __( 'Pale ocean' ), - gradient: 'linear-gradient(135deg, rgb(255, 245, 203), rgb(182, 227, 212), rgb(51, 167, 181)', + gradient: 'linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%)', + slug: 'pale-ocean', }, { name: __( 'Electric grass' ), - gradient: 'linear-gradient(135deg, rgb(202, 248, 128), rgb(113, 206, 126)', - }, - { - name: __( 'Subdued olive' ), - gradient: 'linear-gradient(135deg, rgb(250, 250, 225), rgb(103, 166, 113)', - }, - { - name: __( 'Atomic cream' ), - gradient: 'linear-gradient(135deg, rgb(253, 215, 154), rgb(0, 74, 89)', - }, - { - name: __( 'Nightshade' ), - gradient: 'linear-gradient(135deg, rgb(51, 9, 104), rgb(49, 205, 207)', + gradient: 'linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%)', + slug: 'electric-grass', }, { name: __( 'Midnight' ), - gradient: 'linear-gradient(135deg, rgb(2, 3, 129), rgb(40, 116, 252)', + gradient: 'linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%)', + slug: 'midnight', }, ], }; - diff --git a/packages/block-editor/src/store/effects.js b/packages/block-editor/src/store/effects.js index 3e9b2b9b7e613a..fbfaee23cc715d 100644 --- a/packages/block-editor/src/store/effects.js +++ b/packages/block-editor/src/store/effects.js @@ -84,12 +84,31 @@ export default { const blockB = getBlock( state, clientIdB ); const blockBType = getBlockType( blockB.name ); const { clientId, attributeKey, offset } = getSelectionStart( state ); - const hasTextSelection = ( + const selectedBlockType = clientId === clientIdA ? blockAType : blockBType; + const attributeDefinition = selectedBlockType.attributes[ attributeKey ]; + const canRestoreTextSelection = ( ( clientId === clientIdA || clientId === clientIdB ) && attributeKey !== undefined && - offset !== undefined + offset !== undefined && + // We cannot restore text selection if the RichText identifier + // is not a defined block attribute key. This can be the case if the + // fallback intance ID is used to store selection (and no RichText + // identifier is set), or when the identifier is wrong. + !! attributeDefinition ); + if ( ! attributeDefinition ) { + if ( typeof attributeKey === 'number' ) { + window.console.error( + `RichText needs an identifier prop that is the block attribute key of the attribute it controls. Its type is expected to be a string, but was ${ typeof attributeKey }` + ); + } else { + window.console.error( + 'The RichText identifier prop does not match any attributes defined by the block.' + ); + } + } + // A robust way to retain selection position through various transforms // is to insert a special character at the position and then recover it. const START_OF_SELECTED_AREA = '\u0086'; @@ -98,19 +117,25 @@ export default { const cloneA = cloneBlock( blockA ); const cloneB = cloneBlock( blockB ); - if ( hasTextSelection ) { + if ( canRestoreTextSelection ) { const selectedBlock = clientId === clientIdA ? cloneA : cloneB; const html = selectedBlock.attributes[ attributeKey ]; - const selectedBlockType = clientId === clientIdA ? blockAType : blockBType; - const multilineTag = selectedBlockType.attributes[ attributeKey ].multiline; + const { + multiline: multilineTag, + __unstableMultilineWrapperTags: multilineWrapperTags, + __unstablePreserveWhiteSpace: preserveWhiteSpace, + } = attributeDefinition; const value = insert( create( { html, multilineTag, + multilineWrapperTags, + preserveWhiteSpace, } ), START_OF_SELECTED_AREA, offset, offset ); selectedBlock.attributes[ attributeKey ] = toHTMLString( { value, multilineTag, + preserveWhiteSpace, } ); } @@ -131,16 +156,29 @@ export default { blocksWithTheSameType[ 0 ].attributes ); - if ( hasTextSelection ) { + if ( canRestoreTextSelection ) { const newAttributeKey = findKey( updatedAttributes, ( v ) => typeof v === 'string' && v.indexOf( START_OF_SELECTED_AREA ) !== -1 ); const convertedHtml = updatedAttributes[ newAttributeKey ]; - const multilineTag = blockAType.attributes[ newAttributeKey ].multiline; - const convertedValue = create( { html: convertedHtml, multilineTag } ); + const { + multiline: multilineTag, + __unstableMultilineWrapperTags: multilineWrapperTags, + __unstablePreserveWhiteSpace: preserveWhiteSpace, + } = blockAType.attributes[ newAttributeKey ]; + const convertedValue = create( { + html: convertedHtml, + multilineTag, + multilineWrapperTags, + preserveWhiteSpace, + } ); const newOffset = convertedValue.text.indexOf( START_OF_SELECTED_AREA ); const newValue = remove( convertedValue, newOffset, newOffset + 1 ); - const newHtml = toHTMLString( { value: newValue, multilineTag } ); + const newHtml = toHTMLString( { + value: newValue, + multilineTag, + preserveWhiteSpace, + } ); updatedAttributes[ newAttributeKey ] = newHtml; diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index fa0f849eacecc2..b7a94813779fb9 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -902,6 +902,26 @@ export function isTyping( state = false, action ) { return state; } +/** + * Reducer returning dragging state. + * + * @param {boolean} state Current state. + * @param {Object} action Dispatched action. + * + * @return {boolean} Updated state. + */ +export function isDraggingBlocks( state = false, action ) { + switch ( action.type ) { + case 'START_DRAGGING_BLOCKS': + return true; + + case 'STOP_DRAGGING_BLOCKS': + return false; + } + + return state; +} + /** * Reducer returning whether the caret is within formatted text. * @@ -1256,7 +1276,7 @@ export const blockListSettings = ( state = {}, action ) => { * * @return {string} Updated state. */ -export function isNavigationMode( state = true, action ) { +export function isNavigationMode( state = false, action ) { if ( action.type === 'SET_NAVIGATION_MODE' ) { return action.isNavigationMode; } @@ -1315,6 +1335,12 @@ export function automaticChangeStatus( state, action ) { if ( state !== 'final' ) { return state; } + + return; + // Undoing an automatic change should still be possible after mouse + // move. + case 'STOP_TYPING': + return state; } // Reset the state by default (for any action not handled). @@ -1323,6 +1349,7 @@ export function automaticChangeStatus( state, action ) { export default combineReducers( { blocks, isTyping, + isDraggingBlocks, isCaretWithinFormattedText, selectionStart, selectionEnd, diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index d0dd7a69a15973..2e5cc4c359eb6a 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -15,6 +15,7 @@ import { reduce, some, find, + filter, } from 'lodash'; import createSelector from 'rememo'; @@ -29,6 +30,17 @@ import { } from '@wordpress/blocks'; import { SVG, Rect, G, Path } from '@wordpress/components'; +/** + * A block selection object. + * + * @typedef {Object} WPBlockSelection + * + * @property {string} clientId A block client ID. + * @property {string} attributeKey A block attribute key. + * @property {number} offset An attribute value offset, based on the rich + * text value. See `wp.richText.create`. + */ + // Module constants /** @@ -237,9 +249,9 @@ export const getGlobalBlockCount = createSelector( if ( ! blockName ) { return clientIds.length; } - return reduce( clientIds, ( count, clientId ) => { + return reduce( clientIds, ( accumulator, clientId ) => { const block = state.blocks.byClientId[ clientId ]; - return block.name === blockName ? count + 1 : count; + return block.name === blockName ? accumulator + 1 : accumulator; }, 0 ); }, ( state ) => [ @@ -281,14 +293,6 @@ export function getBlockCount( state, rootClientId ) { return getBlockOrder( state, rootClientId ).length; } -/** - * @typedef {WPBlockSelection} A block selection object. - * - * @property {string} clientId A block client ID. - * @property {string} attributeKey A block attribute key. - * @property {number} offset A block attribute offset. - */ - /** * Returns the current selection start block client ID, attribute key and text * offset. @@ -418,6 +422,31 @@ export function getBlockRootClientId( state, clientId ) { null; } +/** + * Given a block client ID, returns the list of all its parents from top to bottom. + * + * @param {Object} state Editor state. + * @param {string} clientId Block from which to find root client ID. + * @param {boolean} ascending Order results from bottom to top (true) or top to bottom (false). + * + * @return {Array} ClientIDs of the parent blocks. + */ +export const getBlockParents = createSelector( + ( state, clientId, ascending = false ) => { + const parents = []; + let current = clientId; + while ( !! state.blocks.parents[ current ] ) { + current = state.blocks.parents[ current ]; + parents.push( current ); + } + + return ascending ? parents : parents.reverse(); + }, + ( state ) => [ + state.blocks.parents, + ] +); + /** * Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. * @@ -436,6 +465,33 @@ export function getBlockHierarchyRootClientId( state, clientId ) { return parent; } +/** + * Given a block client ID, returns the lowest common ancestor with selected client ID. + * + * @param {Object} state Editor state. + * @param {string} clientId Block from which to find common ancestor client ID. + * + * @return {string} Common ancestor client ID or undefined + */ +export function getLowestCommonAncestorWithSelectedBlock( state, clientId ) { + const selectedId = getSelectedBlockClientId( state ); + const clientParents = [ ...getBlockParents( state, clientId ), clientId ]; + const selectedParents = [ ...getBlockParents( state, selectedId ), selectedId ]; + + let lowestCommonAncestor; + + const maxDepth = Math.min( clientParents.length, selectedParents.length ); + for ( let index = 0; index < maxDepth; index++ ) { + if ( clientParents[ index ] === selectedParents[ index ] ) { + lowestCommonAncestor = clientParents[ index ]; + } else { + break; + } + } + + return lowestCommonAncestor; +} + /** * Returns the client ID of the block adjacent one at the given reference * startClientId and modifier directionality. Defaults start startClientId to @@ -900,6 +956,17 @@ export function isTyping( state ) { return state.isTyping; } +/** + * Returns true if the user is dragging blocks, or false otherwise. + * + * @param {Object} state Global application state. + * + * @return {boolean} Whether user is dragging blocks. + */ +export function isDraggingBlocks( state ) { + return state.isDraggingBlocks; +} + /** * Returns true if the caret is within formatted text, or false otherwise. * @@ -1009,6 +1076,12 @@ const canInsertBlockTypeUnmemoized = ( state, blockName, rootClientId = null ) = return list; } if ( isArray( list ) ) { + // TODO: when there is a canonical way to detect that we are editing a post + // the following check should be changed to something like: + // if ( includes( list, 'core/post-content' ) && getEditorMode() === 'post-content' && item === null ) + if ( includes( list, 'core/post-content' ) && item === null ) { + return true; + } return includes( list, item ); } return defaultResult; @@ -1123,9 +1196,9 @@ const canIncludeBlockTypeInInserter = ( state, blockType, rootClientId ) => { * @param {Object} state Editor state. * @param {?string} rootClientId Optional root client ID of block list. * - * @return {Editor.InserterItem[]} Items that appear in inserter. + * @return {WPEditorInserterItem[]} Items that appear in inserter. * - * @typedef {Object} Editor.InserterItem + * @typedef {Object} WPEditorInserterItem * @property {string} id Unique identifier for the item. * @property {string} name The type of block to create. * @property {Object} initialAttributes Attributes to pass to the newly created block. @@ -1284,6 +1357,34 @@ export const hasInserterItems = createSelector( ], ); +/** + * Returns the list of allowed inserter blocks for inner blocks children + * + * @param {Object} state Editor state. + * @param {?string} rootClientId Optional root client ID of block list. + * + * @return {Array?} The list of allowed block types. + */ +export const __experimentalGetAllowedBlocks = createSelector( + ( state, rootClientId = null ) => { + if ( ! rootClientId ) { + return; + } + + return filter( + getBlockTypes(), + ( blockType ) => canIncludeBlockTypeInInserter( state, blockType, rootClientId ) + ); + }, + ( state, rootClientId ) => [ + state.blockListSettings[ rootClientId ], + state.blocks.byClientId, + state.settings.allowedBlockTypes, + state.settings.templateLock, + getBlockTypes(), + ] +); + /** * Returns the Block List settings of a block, if any exist. * diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 6000c76b824f0e..5018d255d37fa4 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -62,6 +62,7 @@ const { INSERTER_UTILITY_HIGH, INSERTER_UTILITY_MEDIUM, INSERTER_UTILITY_LOW, + getLowestCommonAncestorWithSelectedBlock, } = selectors; describe( 'selectors', () => { @@ -121,6 +122,15 @@ describe( 'selectors', () => { }, } ); + registerBlockType( 'core/post-content-child', { + save: () => null, + category: 'common', + title: 'Test Block Post Content Child', + icon: 'test', + keywords: [ 'testing' ], + parent: [ 'core/post-content' ], + } ); + setFreeformContentHandlerName( 'core/test-freeform' ); cachedSelectors.forEach( ( { clear } ) => clear() ); @@ -132,6 +142,7 @@ describe( 'selectors', () => { unregisterBlockType( 'core/test-block-b' ); unregisterBlockType( 'core/test-block-c' ); unregisterBlockType( 'core/test-freeform' ); + unregisterBlockType( 'core/post-content-child' ); setFreeformContentHandlerName( undefined ); } ); @@ -1933,6 +1944,34 @@ describe( 'selectors', () => { }; expect( canInsertBlockType( state, 'core/test-block-c', 'block1' ) ).toBe( true ); } ); + + it( 'should deny blocks that restrict parent to core/post-content when not in editor root', () => { + const state = { + blocks: { + byClientId: { + block1: { name: 'core/test-block-c' }, + }, + attributes: { + block1: {}, + }, + }, + blockListSettings: {}, + settings: {}, + }; + expect( canInsertBlockType( state, 'core/post-content-child', 'block1' ) ).toBe( false ); + } ); + + it( 'should allow blocks that restrict parent to core/post-content when in editor root', () => { + const state = { + blocks: { + byClientId: {}, + attributes: {}, + }, + blockListSettings: {}, + settings: {}, + }; + expect( canInsertBlockType( state, 'core/post-content-child' ) ).toBe( true ); + } ); } ); describe( 'getInserterItems', () => { @@ -2033,6 +2072,7 @@ describe( 'selectors', () => { }; const itemIDs = getInserterItems( state ).map( ( item ) => item.id ); expect( itemIDs ).toEqual( [ + 'core/post-content-child', 'core/block/2', 'core/block/1', 'core/test-block-b', @@ -2343,4 +2383,83 @@ describe( 'selectors', () => { } ); } ); } ); + + describe( 'getLowestCommonAncestorWithSelectedBlock', () => { + const blocks = { + order: { + '': [ 'a', 'b' ], + a: [ 'c', 'd' ], + d: [ 'e' ], + b: [ 'f' ], + }, + parents: { + a: '', + b: '', + c: 'a', + d: 'a', + e: 'd', + f: 'b', + }, + }; + + it( 'should not be defined if there is no block selected', () => { + const state = { + blocks, + selectionStart: {}, + selectionEnd: {}, + }; + + expect( getLowestCommonAncestorWithSelectedBlock( state, 'd' ) ).not.toBeDefined(); + } ); + + it( 'should not be defined if selected block has no parent', () => { + const state = { + blocks, + selectionStart: { clientId: 'b' }, + selectionEnd: { clientId: 'b' }, + }; + + expect( getLowestCommonAncestorWithSelectedBlock( state, 'b' ) ).toBe( 'b' ); + } ); + + it( 'should not be defined if selected block has no common parent with given block', () => { + const state = { + blocks, + selectionStart: { clientId: 'd' }, + selectionEnd: { clientId: 'd' }, + }; + + expect( getLowestCommonAncestorWithSelectedBlock( state, 'f' ) ).not.toBeDefined(); + } ); + + it( 'should return block id if selected block is ancestor of given block', () => { + const state = { + blocks, + selectionStart: { clientId: 'c' }, + selectionEnd: { clientId: 'c' }, + }; + + expect( getLowestCommonAncestorWithSelectedBlock( state, 'a' ) ).toBe( 'a' ); + } ); + + it( 'should return block id if selected block is nested child of given block', () => { + const state = { + blocks, + selectionStart: { clientId: 'e' }, + selectionEnd: { clientId: 'e' }, + }; + + expect( getLowestCommonAncestorWithSelectedBlock( state, 'a' ) ).toBe( 'a' ); + } ); + + it( 'should return block id if selected block has common parent with given block', () => { + const state = { + blocks, + selectionStart: { clientId: 'e' }, + selectionEnd: { clientId: 'e' }, + }; + + expect( getLowestCommonAncestorWithSelectedBlock( state, 'c' ) ).toBe( 'a' ); + } ); + } ); } ); diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index 8ebb2e487a4f29..f5cf26e054ac17 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -3,10 +3,13 @@ @import "./components/block-inspector/style.scss"; @import "./components/block-list/style.scss"; @import "./components/block-list-appender/style.scss"; +@import "./components/block-breadcrumb/style.scss"; @import "./components/block-card/style.scss"; @import "./components/block-compare/style.scss"; +@import "./components/block-mobile-toolbar/style.scss"; @import "./components/block-mover/style.scss"; @import "./components/block-navigation/style.scss"; +@import "./components/block-pattern-picker/style.scss"; @import "./components/block-preview/style.scss"; @import "./components/block-settings-menu/style.scss"; @import "./components/block-styles/style.scss"; @@ -14,21 +17,23 @@ @import "./components/block-toolbar/style.scss"; @import "./components/block-types-list/style.scss"; @import "./components/button-block-appender/style.scss"; -@import "./components/color-palette/control.scss"; @import "./components/contrast-checker/style.scss"; @import "./components/default-block-appender/style.scss"; -@import "./components/gradient-picker/control.scss"; +@import "./components/link-control/style.scss"; @import "./components/inner-blocks/style.scss"; @import "./components/inserter-with-shortcuts/style.scss"; @import "./components/inserter/style.scss"; @import "./components/inserter-list-item/style.scss"; +@import "./components/media-replace-flow/style.scss"; @import "./components/media-placeholder/style.scss"; @import "./components/multi-selection-inspector/style.scss"; @import "./components/panel-color-settings/style.scss"; @import "./components/plain-text/style.scss"; +@import "./components/responsive-block-control/style.scss"; @import "./components/rich-text/format-toolbar/style.scss"; @import "./components/rich-text/style.scss"; @import "./components/skip-to-selected-block/style.scss"; +@import "./components/tool-selector/style.scss"; @import "./components/url-input/style.scss"; @import "./components/url-popover/style.scss"; @import "./components/warning/style.scss"; diff --git a/packages/block-editor/src/utils/get-paste-event-data.js b/packages/block-editor/src/utils/get-paste-event-data.js new file mode 100644 index 00000000000000..20211ec336d601 --- /dev/null +++ b/packages/block-editor/src/utils/get-paste-event-data.js @@ -0,0 +1,71 @@ +/** + * External dependencies + */ +import { find, isNil } from 'lodash'; + +/** + * WordPress dependencies + */ +import { createBlobURL } from '@wordpress/blob'; + +export function getPasteEventData( { clipboardData } ) { + let { items, files } = clipboardData; + + // In Edge these properties can be null instead of undefined, so a more + // rigorous test is required over using default values. + items = isNil( items ) ? [] : items; + files = isNil( files ) ? [] : files; + + let plainText = ''; + let html = ''; + + // IE11 only supports `Text` as an argument for `getData` and will + // otherwise throw an invalid argument error, so we try the standard + // arguments first, then fallback to `Text` if they fail. + try { + plainText = clipboardData.getData( 'text/plain' ); + html = clipboardData.getData( 'text/html' ); + } catch ( error1 ) { + try { + html = clipboardData.getData( 'Text' ); + } catch ( error2 ) { + // Some browsers like UC Browser paste plain text by default and + // don't support clipboardData at all, so allow default + // behaviour. + return; + } + } + + files = Array.from( files ); + + Array.from( items ).forEach( ( item ) => { + if ( ! item.getAsFile ) { + return; + } + + const file = item.getAsFile(); + + if ( ! file ) { + return; + } + + const { name, type, size } = file; + + if ( ! find( files, { name, type, size } ) ) { + files.push( file ); + } + } ); + + files = files.filter( ( { type } ) => /^image\/(?:jpe?g|png|gif)$/.test( type ) ); + + // Only process files if no HTML is present. + // A pasted file may have the URL as plain text. + if ( files.length && ! html ) { + html = files + .map( ( file ) => `` ) + .join( '' ); + plainText = ''; + } + + return { html, plainText }; +} diff --git a/packages/block-library/package.json b/packages/block-library/package.json index b0c71f9eafaae1..36ce38c1d9c860 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-library", - "version": "2.9.0", + "version": "2.10.0", "description": "Block library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -20,6 +20,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "sideEffects": false, "dependencies": { "@babel/runtime": "^7.4.4", "@wordpress/a11y": "file:../a11y", @@ -36,6 +37,7 @@ "@wordpress/deprecated": "file:../deprecated", "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", "@wordpress/i18n": "file:../i18n", "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/keycodes": "file:../keycodes", diff --git a/packages/block-library/src/audio/edit.js b/packages/block-library/src/audio/edit.js index 15387acbd90c7a..7c582faa63ad30 100644 --- a/packages/block-library/src/audio/edit.js +++ b/packages/block-library/src/audio/edit.js @@ -9,7 +9,7 @@ import { PanelBody, SelectControl, ToggleControl, - Toolbar, + ToolbarGroup, withNotices, } from '@wordpress/components'; import { @@ -154,14 +154,14 @@ class AudioEdit extends Component { return ( <> - + - + @@ -214,10 +214,8 @@ class AudioEdit extends Component { export default compose( [ withSelect( ( select ) => { const { getSettings } = select( 'core/block-editor' ); - const { __experimentalMediaUpload } = getSettings(); - return { - mediaUpload: __experimentalMediaUpload, - }; + const { mediaUpload } = getSettings(); + return { mediaUpload }; } ), withNotices, ] )( AudioEdit ); diff --git a/packages/block-library/src/audio/index.js b/packages/block-library/src/audio/index.js index c6ce546e37f6e0..abeca49dc36e50 100644 --- a/packages/block-library/src/audio/index.js +++ b/packages/block-library/src/audio/index.js @@ -19,6 +19,7 @@ export { metadata, name }; export const settings = { title: __( 'Audio' ), description: __( 'Embed a simple audio player.' ), + keywords: [ __( 'music' ), __( 'sound' ), __( 'podcast' ), __( 'recording' ) ], icon, transforms, supports: { diff --git a/packages/block-library/src/audio/style.scss b/packages/block-library/src/audio/style.scss index 4bec2b85225387..64d79cafd85dc9 100644 --- a/packages/block-library/src/audio/style.scss +++ b/packages/block-library/src/audio/style.scss @@ -1,4 +1,11 @@ .wp-block-audio { + // Supply caption styles to audio blocks, even if the theme hasn't opted in. + // Reason being: the new markup, , are not likely to be styled in the majority of existing themes, + // so we supply the styles so as to not appear broken or unstyled in those themes. + figcaption { + @include caption-style(); + } + // Show full-width when not aligned. audio { width: 100%; diff --git a/packages/block-library/src/block/edit-panel/editor.scss b/packages/block-library/src/block/edit-panel/editor.scss index 72ad4bb3feac5c..6c86bb9221af0b 100644 --- a/packages/block-library/src/block/edit-panel/editor.scss +++ b/packages/block-library/src/block/edit-panel/editor.scss @@ -74,3 +74,13 @@ border-left-color: transparent; } } + +.is-selected.is-navigate-mode .reusable-block-edit-panel { + border-color: $blue-medium-focus; + border-left-color: transparent; + + .is-dark-theme & { + border-color: $blue-medium-focus; + border-left-color: transparent; + } +} diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 7e450327085df7..76f6e47cf13624 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -25,7 +25,6 @@ import { parse, serialize } from '@wordpress/blocks'; * Internal dependencies */ import ReusableBlockEditPanel from './edit-panel'; -import ReusableBlockIndicator from './indicator'; class ReusableBlockEdit extends Component { constructor( { reusableBlock } ) { @@ -147,7 +146,6 @@ class ReusableBlockEdit extends Component { onCancel={ this.stopEditing } /> ) } - { ! isSelected && ! isEditing && } { element }
    ); diff --git a/packages/block-library/src/block/editor.scss b/packages/block-library/src/block/editor.scss index cdeaf9ad0ef6a5..516b41feb52776 100644 --- a/packages/block-library/src/block/editor.scss +++ b/packages/block-library/src/block/editor.scss @@ -1,3 +1,3 @@ .edit-post-visual-editor .block-library-block__reusable-block-container .block-editor-writing-flow__click-redirect { - height: auto; + min-height: auto; } diff --git a/packages/block-library/src/block/indicator/editor.scss b/packages/block-library/src/block/indicator/editor.scss deleted file mode 100644 index 08ebbce4674faf..00000000000000 --- a/packages/block-library/src/block/indicator/editor.scss +++ /dev/null @@ -1,12 +0,0 @@ -.block-editor-block-list__layout .reusable-block-indicator { - background: $white; - border: $border-width dashed $light-gray-500; - color: $dark-gray-500; - top: -$block-padding; - height: 30px; - padding: $grid-size-small; - position: absolute; - z-index: z-index(".block-editor-block-list__layout .reusable-block-indicator"); - width: 30px; - right: -$block-padding; -} diff --git a/packages/block-library/src/block/indicator/index.js b/packages/block-library/src/block/indicator/index.js deleted file mode 100644 index 7e5310feceaddc..00000000000000 --- a/packages/block-library/src/block/indicator/index.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * WordPress dependencies - */ -import { Tooltip, Dashicon } from '@wordpress/components'; -import { __, sprintf } from '@wordpress/i18n'; - -function ReusableBlockIndicator( { title } ) { - // translators: %s: title/name of the reusable block - const tooltipText = sprintf( __( 'Reusable Block: %s' ), title ); - return ( - - - - - - ); -} - -export default ReusableBlockIndicator; diff --git a/packages/block-library/src/button/block.json b/packages/block-library/src/button/block.json index ff43c67db5e834..bc35b9ff5ee32e 100644 --- a/packages/block-library/src/button/block.json +++ b/packages/block-library/src/button/block.json @@ -49,6 +49,9 @@ "borderRadius": { "type": "number" }, + "gradient": { + "type": "string" + }, "customGradient": { "type": "string" } diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 17908484218091..7a8c835256a191 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -8,7 +8,6 @@ import classnames from 'classnames'; */ import { __ } from '@wordpress/i18n'; import { - Component, useCallback, } from '@wordpress/element'; import { @@ -16,7 +15,6 @@ import { withInstanceId, } from '@wordpress/compose'; import { - BaseControl, PanelBody, RangeControl, TextControl, @@ -24,13 +22,14 @@ import { withFallbackStyles, } from '@wordpress/components'; import { - URLInput, - RichText, + __experimentalGradientPickerPanel, + __experimentalUseGradient, ContrastChecker, InspectorControls, - withColors, PanelColorSettings, - __experimentalGradientPickerControl, + RichText, + URLInput, + withColors, } from '@wordpress/block-editor'; const { getComputedStyle } = window; @@ -74,177 +73,156 @@ function BorderPanel( { borderRadius = '', setAttributes } ) { ); } -class ButtonEdit extends Component { - constructor() { - super( ...arguments ); - this.nodeRef = null; - this.bindRef = this.bindRef.bind( this ); - this.onSetLinkRel = this.onSetLinkRel.bind( this ); - this.onToggleOpenInNewTab = this.onToggleOpenInNewTab.bind( this ); - } - - bindRef( node ) { - if ( ! node ) { - return; - } - this.nodeRef = node; - } - - onSetLinkRel( value ) { - this.props.setAttributes( { rel: value } ); - } - - onToggleOpenInNewTab( value ) { - const { rel } = this.props.attributes; - const linkTarget = value ? '_blank' : undefined; - - let updatedRel = rel; - if ( linkTarget && ! rel ) { - updatedRel = NEW_TAB_REL; - } else if ( ! linkTarget && rel === NEW_TAB_REL ) { - updatedRel = undefined; - } - - this.props.setAttributes( { - linkTarget, - rel: updatedRel, - } ); - } - - render() { - const { - attributes, - backgroundColor, - textColor, - setBackgroundColor, - setTextColor, - fallbackBackgroundColor, - fallbackTextColor, - setAttributes, - className, - instanceId, - isSelected, - } = this.props; - - const { - borderRadius, - linkTarget, - placeholder, - rel, - text, - title, - url, - customGradient, - } = attributes; +function ButtonEdit( { + attributes, + backgroundColor, + textColor, + setBackgroundColor, + setTextColor, + fallbackBackgroundColor, + fallbackTextColor, + setAttributes, + className, + isSelected, +} ) { + const { + borderRadius, + linkTarget, + placeholder, + rel, + text, + title, + url, + } = attributes; + const onSetLinkRel = useCallback( + ( value ) => { + setAttributes( { rel: value } ); + }, + [ setAttributes ] + ); - const linkId = `wp-block-button__inline-link-${ instanceId }`; + const onToggleOpenInNewTab = useCallback( + ( value ) => { + const newLinkTarget = value ? '_blank' : undefined; + + let updatedRel = rel; + if ( newLinkTarget && ! rel ) { + updatedRel = NEW_TAB_REL; + } else if ( ! newLinkTarget && rel === NEW_TAB_REL ) { + updatedRel = undefined; + } + + setAttributes( { + linkTarget: newLinkTarget, + rel: updatedRel, + } ); + }, + [ rel, setAttributes ] + ); + const { + gradientClass, + gradientValue, + setGradient, + } = __experimentalUseGradient(); - return ( -
    - setAttributes( { text: value } ) } - withoutInteractiveFormatting - className={ classnames( - 'wp-block-button__link', { - 'has-background': backgroundColor.color || customGradient, - [ backgroundColor.class ]: ! customGradient && backgroundColor.class, - 'has-text-color': textColor.color, - [ textColor.class ]: textColor.class, - 'no-border-radius': borderRadius === 0, + return ( +
    + setAttributes( { text: value } ) } + withoutInteractiveFormatting + className={ classnames( + 'wp-block-button__link', { + 'has-background': backgroundColor.color || gradientValue, + [ backgroundColor.class ]: ! gradientValue && backgroundColor.class, + 'has-text-color': textColor.color, + [ textColor.class ]: textColor.class, + [ gradientClass ]: gradientClass, + 'no-border-radius': borderRadius === 0, + } + ) } + style={ { + ...( ! backgroundColor.color && gradientValue ? + { background: gradientValue } : + { backgroundColor: backgroundColor.color } + ), + color: textColor.color, + borderRadius: borderRadius ? borderRadius + 'px' : undefined, + } } + /> + setAttributes( { url: value } ) } + disableSuggestions={ ! isSelected } + isFullWidth + hasBorder + /> + + { + setAttributes( { customGradient: undefined } ); + setBackgroundColor( newColor ); + }, + label: __( 'Background Color' ), + }, + { + value: textColor.color, + onChange: setTextColor, + label: __( 'Text Color' ), + }, + ] } + > + + + <__experimentalGradientPickerPanel + onChange={ + ( newGradient ) => { + setGradient( newGradient ); + setBackgroundColor(); } - ) } - style={ { - backgroundColor: ! customGradient && backgroundColor.color, - background: customGradient, - color: textColor.color, - borderRadius: borderRadius ? borderRadius + 'px' : undefined, - } } + } + value={ gradientValue } + /> + - - setAttributes( { url: value } ) } - disableSuggestions={ ! isSelected } - id={ linkId } - isFullWidth - hasBorder + + - - - { - setAttributes( { customGradient: undefined } ); - setBackgroundColor( newColor ); - }, - label: __( 'Background Color' ), - }, - { - value: textColor.color, - onChange: setTextColor, - label: __( 'Text Color' ), - }, - ] } - > - - - - <__experimentalGradientPickerControl - onChange={ - ( newGradient ) => { - setAttributes( { - customGradient: newGradient, - backgroundColor: undefined, - customBackgroundColor: undefined, - } ); - } - } - value={ customGradient } - /> - - - - - - - -
    - ); - } + + +
    + ); } export default compose( [ diff --git a/packages/block-library/src/button/save.js b/packages/block-library/src/button/save.js index bcf18ffc293242..59eaa9f201217e 100644 --- a/packages/block-library/src/button/save.js +++ b/packages/block-library/src/button/save.js @@ -9,6 +9,7 @@ import classnames from 'classnames'; import { RichText, getColorClassName, + __experimentalGetGradientClass, } from '@wordpress/block-editor'; export default function save( { attributes } ) { @@ -19,6 +20,7 @@ export default function save( { attributes } ) { customTextColor, customGradient, linkTarget, + gradient, rel, text, textColor, @@ -28,18 +30,20 @@ export default function save( { attributes } ) { const textClass = getColorClassName( 'color', textColor ); const backgroundClass = ! customGradient && getColorClassName( 'background-color', backgroundColor ); + const gradientClass = __experimentalGetGradientClass( gradient ); const buttonClasses = classnames( 'wp-block-button__link', { 'has-text-color': textColor || customTextColor, [ textClass ]: textClass, - 'has-background': backgroundColor || customBackgroundColor || customGradient, + 'has-background': backgroundColor || customBackgroundColor || customGradient || gradient, [ backgroundClass ]: backgroundClass, 'no-border-radius': borderRadius === 0, + [ gradientClass ]: gradientClass, } ); const buttonStyle = { - backgroundColor: backgroundClass || customGradient ? undefined : customBackgroundColor, background: customGradient ? customGradient : undefined, + backgroundColor: backgroundClass || customGradient || gradient ? undefined : customBackgroundColor, color: textClass ? undefined : customTextColor, borderRadius: borderRadius ? borderRadius + 'px' : undefined, }; diff --git a/packages/block-library/src/classic/editor.scss b/packages/block-library/src/classic/editor.scss index 186523cc549b35..c36f1b96b055a3 100644 --- a/packages/block-library/src/classic/editor.scss +++ b/packages/block-library/src/classic/editor.scss @@ -254,11 +254,6 @@ div[data-type="core/freeform"] { border-left-color: transparent; } - // Don't show block type label for classic block - &.is-hovered .block-editor-block-list__breadcrumb { - display: none; - } - .editor-block-contextual-toolbar + div { margin-top: 0; padding-top: 0; diff --git a/packages/block-library/src/code/edit.js b/packages/block-library/src/code/edit.js index 2926774f46ff22..5aa4b337bd3f12 100644 --- a/packages/block-library/src/code/edit.js +++ b/packages/block-library/src/code/edit.js @@ -7,14 +7,13 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import { PlainText } from '@wordpress/block-editor'; -import { escape, unescape } from './utils'; export default function CodeEdit( { attributes, setAttributes, className } ) { return (
    setAttributes( { content: escape( content ) } ) } + value={ attributes.content } + onChange={ ( content ) => setAttributes( { content } ) } placeholder={ __( 'Write code…' ) } aria-label={ __( 'Code' ) } /> diff --git a/packages/block-library/src/code/edit.native.js b/packages/block-library/src/code/edit.native.js index d8641b2270d20a..319ce46d9762ce 100644 --- a/packages/block-library/src/code/edit.native.js +++ b/packages/block-library/src/code/edit.native.js @@ -13,7 +13,6 @@ import { withPreferredColorScheme } from '@wordpress/compose'; /** * Internal dependencies */ -import { escape, unescape } from './utils'; /** * Block code style @@ -23,24 +22,23 @@ import styles from './theme.scss'; // Note: styling is applied directly to the (nested) PlainText component. Web-side components // apply it to the container 'div' but we don't have a proper proposal for cascading styling yet. export function CodeEdit( props ) { - const { attributes, setAttributes, style, onFocus, onBlur, getStylesFromColorScheme } = props; + const { attributes, setAttributes, onFocus, onBlur, getStylesFromColorScheme } = props; const codeStyle = getStylesFromColorScheme( styles.blockCode, styles.blockCodeDark ); const placeholderStyle = getStylesFromColorScheme( styles.placeholder, styles.placeholderDark ); return ( <View> <PlainText - value={ unescape( attributes.content ) } - style={ [ style, codeStyle ] } + value={ attributes.content } + style={ codeStyle } multiline={ true } underlineColorAndroid="transparent" - onChange={ ( content ) => setAttributes( { content: escape( content ) } ) } + onChange={ ( content ) => setAttributes( { content } ) } placeholder={ __( 'Write code…' ) } aria-label={ __( 'Code' ) } isSelected={ props.isSelected } onFocus={ onFocus } onBlur={ onBlur } - fontFamily={ ( styles.blockCode.fontFamily ) } placeholderTextColor={ placeholderStyle.color } /> </View> diff --git a/packages/block-library/src/code/editor.scss b/packages/block-library/src/code/editor.scss index 5fb4e6e4682058..5a4d5ee76e1acc 100644 --- a/packages/block-library/src/code/editor.scss +++ b/packages/block-library/src/code/editor.scss @@ -12,57 +12,3 @@ box-shadow: none; } } - -// We should extract this to a separate components-toolbar -.components-tab-button { - display: inline-flex; - align-items: flex-end; - margin: 0; - padding: 3px; - background: none; - outline: none; - color: $dark-gray-500; - cursor: pointer; - position: relative; - height: $icon-button-size; - font-family: $default-font; - font-size: $default-font-size; - font-weight: 500; - border: 0; - - &.is-active, - &.is-active:hover { - color: $white; - } - - &:disabled { - cursor: default; - } - - & > span { - border: $border-width solid transparent; - padding: 0 6px; - box-sizing: content-box; - height: 28px; - line-height: 28px; - } - - &:hover > span, - &:focus > span { - color: $dark-gray-500; - } - - &:not(:disabled) { - &.is-active > span, - &:hover > span, - &:focus > span { - border: $border-width solid $dark-gray-500; - } - } - - &.is-active > span, - &.is-active:hover > span { - background-color: $dark-gray-500; - color: $white; - } -} diff --git a/packages/block-library/src/code/index.js b/packages/block-library/src/code/index.js index e23a740b9d5e66..f258d7b972dd9f 100644 --- a/packages/block-library/src/code/index.js +++ b/packages/block-library/src/code/index.js @@ -22,11 +22,8 @@ export const settings = { icon, example: { attributes: { - content: __( '// A "block" is the abstract term used' ) + '\n' + - __( '// to describe units of markup that,' ) + '\n' + - __( '// when composed together, form the' ) + '\n' + - __( '// content or layout of a page.' ) + '\n' + - __( 'registerBlockType( name, settings );' ), + // translators: Preserve \n markers for line breaks + content: __( '// A "block" is the abstract term used\n// to describe units of markup that\n// when composed together, form the\n// content or layout of a page.\nregisterBlockType( name, settings );' ), }, }, supports: { diff --git a/packages/block-library/src/code/save.js b/packages/block-library/src/code/save.js index 21d74669c854b9..ece9eef96dae3d 100644 --- a/packages/block-library/src/code/save.js +++ b/packages/block-library/src/code/save.js @@ -1,3 +1,8 @@ +/** + * Internal dependencies + */ +import { escape } from './utils'; + export default function save( { attributes } ) { - return <pre><code>{ attributes.content }</code></pre>; + return <pre><code>{ escape( attributes.content ) }</code></pre>; } diff --git a/packages/block-library/src/code/test/utils.js b/packages/block-library/src/code/test/utils.js index 4926eef16d5282..d3e9de573cf77f 100644 --- a/packages/block-library/src/code/test/utils.js +++ b/packages/block-library/src/code/test/utils.js @@ -1,15 +1,10 @@ /** * Internal dependencies */ -import { escape, unescape } from '../utils'; +import { escape } from '../utils'; describe( 'core/code', () => { describe( 'escape()', () => { - it( 'should escape ampersands', () => { - const text = escape( '&' ); - expect( text ).toBe( '&amp;' ); - } ); - it( 'should escape opening square brackets', () => { const text = escape( '[shortcode][/shortcode]' ); expect( text ).toBe( '&#91;shortcode]&#91;/shortcode]' ); @@ -24,39 +19,5 @@ describe( 'core/code', () => { const text = escape( 'Text https://example.com/test/' ); expect( text ).toBe( 'Text https://example.com/test/' ); } ); - - it( 'should escape ampersands last', () => { - const text = escape( '[shortcode][/shortcode]' ); - expect( text ).toBe( '&#91;shortcode]&#91;/shortcode]' ); - expect( text ).not.toBe( '&amp;#91;shortcode]&amp;#91;/shortcode]' ); - } ); - } ); - - describe( 'unescape()', () => { - it( 'should unescape escaped ampersands', () => { - const text = unescape( '&amp;' ); - expect( text ).toBe( '&' ); - } ); - - it( 'should unescape escaped opening square brackets', () => { - const text = unescape( '&#91;shortcode]&#91;/shortcode]' ); - expect( text ).toBe( '[shortcode][/shortcode]' ); - } ); - - it( 'should unescape the escaped protocol of an isolated url', () => { - const text = unescape( 'https:&#47;&#47;example.com/test/' ); - expect( text ).toBe( 'https://example.com/test/' ); - } ); - - it( 'should revert the result of escape()', () => { - const ampersand = unescape( escape( '&' ) ); - expect( ampersand ).toBe( '&' ); - - const squareBracket = unescape( escape( '[shortcode][/shortcode]' ) ); - expect( squareBracket ).toBe( '[shortcode][/shortcode]' ); - - const url = unescape( escape( 'https://example.com/test/' ) ); - expect( url ).toBe( 'https://example.com/test/' ); - } ); } ); } ); diff --git a/packages/block-library/src/code/theme.native.scss b/packages/block-library/src/code/theme.native.scss index 40a4ba9bfcbfdf..ee432598dd1c22 100644 --- a/packages/block-library/src/code/theme.native.scss +++ b/packages/block-library/src/code/theme.native.scss @@ -2,10 +2,15 @@ .blockCode { font-family: $default-monospace-font; + font-size: 14; + padding: 12px 16px; + border-radius: 4px; + background-color: $gray-light; } .blockCodeDark { color: $white; + background-color: $gray-100; } .placeholder { diff --git a/packages/block-library/src/code/utils.js b/packages/block-library/src/code/utils.js index fb8557a95b18ee..5178e3d0135fca 100644 --- a/packages/block-library/src/code/utils.js +++ b/packages/block-library/src/code/utils.js @@ -3,6 +3,11 @@ */ import { flow } from 'lodash'; +/** + * WordPress dependencies + */ +import { escapeEditableHTML } from '@wordpress/escape-html'; + /** * Escapes ampersands, shortcodes, and links. * @@ -11,49 +16,12 @@ import { flow } from 'lodash'; */ export function escape( content ) { return flow( - escapeAmpersands, + escapeEditableHTML, escapeOpeningSquareBrackets, escapeProtocolInIsolatedUrls )( content || '' ); } -/** - * Unescapes escaped ampersands, shortcodes, and links. - * - * @param {string} content Content with (maybe) escaped ampersands, shortcodes, and links. - * @return {string} The given content with escaped characters unescaped. - */ -export function unescape( content ) { - return flow( - unescapeProtocolInIsolatedUrls, - unescapeOpeningSquareBrackets, - unescapeAmpersands - )( content || '' ); -} - -/** - * Returns the given content with all its ampersand characters converted - * into their HTML entity counterpart (i.e. & => &amp;) - * - * @param {string} content The content of a code block. - * @return {string} The given content with its ampersands converted into - * their HTML entity counterpart (i.e. & => &amp;) - */ -function escapeAmpersands( content ) { - return content.replace( /&/g, '&amp;' ); -} - -/** - * Returns the given content with all &amp; HTML entities converted into &. - * - * @param {string} content The content of a code block. - * @return {string} The given content with all &amp; HTML entities - * converted into &. - */ -function unescapeAmpersands( content ) { - return content.replace( /&amp;/g, '&' ); -} - /** * Returns the given content with all opening shortcode characters converted * into their HTML entity counterpart (i.e. [ => &#91;). For instance, a @@ -71,16 +39,6 @@ function escapeOpeningSquareBrackets( content ) { return content.replace( /\[/g, '&#91;' ); } -/** - * Returns the given content translating all &#91; into [. - * - * @param {string} content The content of a code block. - * @return {string} The given content with all &#91; into [. - */ -function unescapeOpeningSquareBrackets( content ) { - return content.replace( /&#91;/g, '[' ); -} - /** * Converts the first two forward slashes of any isolated URL into their HTML * counterparts (i.e. // => &#47;&#47;). For instance, https://youtube.com/watch?x @@ -98,20 +56,3 @@ function unescapeOpeningSquareBrackets( content ) { function escapeProtocolInIsolatedUrls( content ) { return content.replace( /^(\s*https?:)\/\/([^\s<>"]+\s*)$/m, '$1&#47;&#47;$2' ); } - -/** - * Converts the first two forward slashes of any isolated URL from the HTML entity - * &#73; into /. - * - * An isolated URL is a URL that sits in its own line, surrounded only by spacing - * characters. - * - * See https://github.com/WordPress/wordpress-develop/blob/5.1.1/src/wp-includes/class-wp-embed.php#L403 - * - * @param {string} content The content of a code block. - * @return {string} The given content with the first two forward slashes of any - * isolated URL from the HTML entity &#73; into /. - */ -function unescapeProtocolInIsolatedUrls( content ) { - return content.replace( /^(\s*https?:)&#47;&#47;([^\s<>"]+\s*)$/m, '$1//$2' ); -} diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js new file mode 100644 index 00000000000000..b7ece7b095cc34 --- /dev/null +++ b/packages/block-library/src/column/edit.native.js @@ -0,0 +1,58 @@ + +/** + * External dependencies + */ +import { View } from 'react-native'; + +/** + * WordPress dependencies + */ +import { withSelect } from '@wordpress/data'; +import { compose, withPreferredColorScheme } from '@wordpress/compose'; +import { + InnerBlocks, + withColors, +} from '@wordpress/block-editor'; +/** + * Internal dependencies + */ +import styles from './editor.scss'; + +function ColumnEdit( { + hasInnerBlocks, + isSelected, + getStylesFromColorScheme, +} ) { + // TODO: make sure if column should reder placeholder if whole parent + // columns block is not selected + if ( ! isSelected && ! hasInnerBlocks ) { + return ( + <View style={ [ + getStylesFromColorScheme( styles.columnPlaceholder, styles.columnPlaceholderDark ), + ! hasInnerBlocks && { ...styles.marginVerticalDense, ...styles.marginHorizontalNone }, + ] } /> + ); + } + + return ( + <InnerBlocks + renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } + /> + ); +} + +export default compose( [ + withColors( 'backgroundColor' ), + withSelect( ( select, { clientId } ) => { + const { + getBlock, + } = select( 'core/block-editor' ); + + const block = getBlock( clientId ); + + return { + hasInnerBlocks: !! ( block && block.innerBlocks.length ), + }; + } ), + withPreferredColorScheme, +] )( ColumnEdit ); diff --git a/packages/block-library/src/column/editor.native.scss b/packages/block-library/src/column/editor.native.scss new file mode 100644 index 00000000000000..fe84f0d5689214 --- /dev/null +++ b/packages/block-library/src/column/editor.native.scss @@ -0,0 +1,24 @@ +.columnPlaceholder { + padding: $block-selected-to-content; + margin: $block-edge-to-content; + margin-left: $block-selected-child-margin; + margin-right: $block-selected-child-margin; + background-color: $white; + border: $border-width dashed $gray; + border-radius: 4px; +} + +.columnPlaceholderDark { + background-color: $black; + border: $border-width dashed $gray-70; +} + +.marginVerticalDense { + margin-top: $block-selected-child-margin; + margin-bottom: $block-selected-child-margin; +} + +.marginHorizontalNone { + margin-left: 0; + margin-right: 0; +} diff --git a/packages/block-library/src/columns/deprecated.js b/packages/block-library/src/columns/deprecated.js index 56ffe5d90bc6a1..a344f72eaf69f3 100644 --- a/packages/block-library/src/columns/deprecated.js +++ b/packages/block-library/src/columns/deprecated.js @@ -66,7 +66,7 @@ export default [ ) ); }, migrate( attributes, innerBlocks ) { - const columns = innerBlocks.reduce( ( result, innerBlock ) => { + const columns = innerBlocks.reduce( ( accumulator, innerBlock ) => { const { originalContent } = innerBlock; let columnIndex = getDeprecatedLayoutColumn( originalContent ); @@ -74,13 +74,13 @@ export default [ columnIndex = 0; } - if ( ! result[ columnIndex ] ) { - result[ columnIndex ] = []; + if ( ! accumulator[ columnIndex ] ) { + accumulator[ columnIndex ] = []; } - result[ columnIndex ].push( innerBlock ); + accumulator[ columnIndex ].push( innerBlock ); - return result; + return accumulator; }, [] ); const migratedInnerBlocks = columns.map( ( columnBlocks ) => ( diff --git a/packages/block-library/src/columns/edit.js b/packages/block-library/src/columns/edit.js index 48f35e90b2d9ce..31fa98dda1a100 100644 --- a/packages/block-library/src/columns/edit.js +++ b/packages/block-library/src/columns/edit.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { dropRight, times } from 'lodash'; +import { dropRight, get, map, times } from 'lodash'; /** * WordPress dependencies @@ -11,24 +11,25 @@ import { __ } from '@wordpress/i18n'; import { PanelBody, RangeControl, - SVG, - Path, } from '@wordpress/components'; import { InspectorControls, InnerBlocks, BlockControls, + __experimentalBlockPatternPicker, BlockVerticalAlignmentToolbar, } from '@wordpress/block-editor'; -import { withDispatch, useSelect } from '@wordpress/data'; +import { + withDispatch, + useDispatch, + useSelect, +} from '@wordpress/data'; import { createBlock } from '@wordpress/blocks'; -import { useState, useEffect } from '@wordpress/element'; /** * Internal dependencies */ import { - getColumnsTemplate, hasExplicitColumnWidths, getMappedColumnWidths, getRedistributedColumnWidths, @@ -46,66 +47,7 @@ import { */ const ALLOWED_BLOCKS = [ 'core/column' ]; -/** - * Template option choices for predefined columns layouts. - * - * @constant - * @type {Array} - */ -const TEMPLATE_OPTIONS = [ - { - title: __( 'Two columns; equal split' ), - icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M39 12C40.1046 12 41 12.8954 41 14V34C41 35.1046 40.1046 36 39 36H9C7.89543 36 7 35.1046 7 34V14C7 12.8954 7.89543 12 9 12H39ZM39 34V14H25V34H39ZM23 34H9V14H23V34Z" /></SVG>, - template: [ - [ 'core/column' ], - [ 'core/column' ], - ], - }, - { - title: __( 'Two columns; one-third, two-thirds split' ), - icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M39 12C40.1046 12 41 12.8954 41 14V34C41 35.1046 40.1046 36 39 36H9C7.89543 36 7 35.1046 7 34V14C7 12.8954 7.89543 12 9 12H39ZM39 34V14H20V34H39ZM18 34H9V14H18V34Z" /></SVG>, - template: [ - [ 'core/column', { width: 33.33 } ], - [ 'core/column', { width: 66.66 } ], - ], - }, - { - title: __( 'Two columns; two-thirds, one-third split' ), - icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M39 12C40.1046 12 41 12.8954 41 14V34C41 35.1046 40.1046 36 39 36H9C7.89543 36 7 35.1046 7 34V14C7 12.8954 7.89543 12 9 12H39ZM39 34V14H30V34H39ZM28 34H9V14H28V34Z" /></SVG>, - template: [ - [ 'core/column', { width: 66.66 } ], - [ 'core/column', { width: 33.33 } ], - ], - }, - { - title: __( 'Three columns; equal split' ), - icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" d="M41 14a2 2 0 0 0-2-2H9a2 2 0 0 0-2 2v20a2 2 0 0 0 2 2h30a2 2 0 0 0 2-2V14zM28.5 34h-9V14h9v20zm2 0V14H39v20h-8.5zm-13 0H9V14h8.5v20z" /></SVG>, - template: [ - [ 'core/column' ], - [ 'core/column' ], - [ 'core/column' ], - ], - }, - { - title: __( 'Three columns; wide center column' ), - icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" d="M41 14a2 2 0 0 0-2-2H9a2 2 0 0 0-2 2v20a2 2 0 0 0 2 2h30a2 2 0 0 0 2-2V14zM31 34H17V14h14v20zm2 0V14h6v20h-6zm-18 0H9V14h6v20z" /></SVG>, - template: [ - [ 'core/column', { width: 25 } ], - [ 'core/column', { width: 50 } ], - [ 'core/column', { width: 25 } ], - ], - }, -]; - -/** - * Number of columns to assume for template in case the user opts to skip - * template option selection. - * - * @type {number} - */ -const DEFAULT_COLUMNS = 2; - -export function ColumnsEdit( { +function ColumnsEditContainer( { attributes, className, updateAlignment, @@ -118,64 +60,33 @@ export function ColumnsEdit( { return { count: select( 'core/block-editor' ).getBlockCount( clientId ), }; - } ); - const [ template, setTemplate ] = useState( getColumnsTemplate( count ) ); - const [ forceUseTemplate, setForceUseTemplate ] = useState( false ); - - // This is used to force the usage of the template even if the count doesn't match the template - // The count doesn't match the template once you use undo/redo (this is used to reset to the placeholder state). - useEffect( () => { - // Once the template is applied, reset it. - if ( forceUseTemplate ) { - setForceUseTemplate( false ); - } - }, [ forceUseTemplate ] ); + }, [ clientId ] ); const classes = classnames( className, { [ `are-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, } ); - // The template selector is shown when we first insert the columns block (count === 0). - // or if there's no template available. - // The count === 0 trick is useful when you use undo/redo. - const showTemplateSelector = ( count === 0 && ! forceUseTemplate ) || ! template; - return ( <> - { ! showTemplateSelector && ( - <> - <InspectorControls> - <PanelBody> - <RangeControl - label={ __( 'Columns' ) } - value={ count } - onChange={ ( value ) => updateColumns( count, value ) } - min={ 2 } - max={ 6 } - /> - </PanelBody> - </InspectorControls> - <BlockControls> - <BlockVerticalAlignmentToolbar - onChange={ updateAlignment } - value={ verticalAlignment } - /> - </BlockControls> - </> - ) } + <InspectorControls> + <PanelBody> + <RangeControl + label={ __( 'Columns' ) } + value={ count } + onChange={ ( value ) => updateColumns( count, value ) } + min={ 2 } + max={ 6 } + /> + </PanelBody> + </InspectorControls> + <BlockControls> + <BlockVerticalAlignmentToolbar + onChange={ updateAlignment } + value={ verticalAlignment } + /> + </BlockControls> <div className={ classes }> <InnerBlocks - __experimentalTemplateOptions={ TEMPLATE_OPTIONS } - __experimentalOnSelectTemplateOption={ ( nextTemplate ) => { - if ( nextTemplate === undefined ) { - nextTemplate = getColumnsTemplate( DEFAULT_COLUMNS ); - } - - setTemplate( nextTemplate ); - setForceUseTemplate( true ); - } } - __experimentalAllowTemplateOptionSkip - template={ showTemplateSelector ? null : template } templateLock="all" allowedBlocks={ ALLOWED_BLOCKS } /> </div> @@ -183,7 +94,7 @@ export function ColumnsEdit( { ); } -export default withDispatch( ( dispatch, ownProps, registry ) => ( { +const ColumnsEditContainerWrapper = withDispatch( ( dispatch, ownProps, registry ) => ( { /** * Update all child Column blocks with a new vertical alignment setting * based on whatever alignment is passed in. This allows change to parent @@ -264,4 +175,59 @@ export default withDispatch( ( dispatch, ownProps, registry ) => ( { replaceInnerBlocks( clientId, innerBlocks, false ); }, -} ) )( ColumnsEdit ); +} ) )( ColumnsEditContainer ); + +const createBlocksFromInnerBlocksTemplate = ( innerBlocksTemplate ) => { + return map( + innerBlocksTemplate, + ( [ name, attributes, innerBlocks = [] ] ) => + createBlock( name, attributes, createBlocksFromInnerBlocksTemplate( innerBlocks ) ) + ); +}; + +const ColumnsEdit = ( props ) => { + const { clientId, name } = props; + const { blockType, defaultPattern, hasInnerBlocks, patterns } = useSelect( ( select ) => { + const { + __experimentalGetBlockPatterns, + getBlockType, + __experimentalGetDefaultBlockPattern, + } = select( 'core/blocks' ); + + return { + blockType: getBlockType( name ), + defaultPattern: __experimentalGetDefaultBlockPattern( name ), + hasInnerBlocks: select( 'core/block-editor' ).getBlocks( clientId ).length > 0, + patterns: __experimentalGetBlockPatterns( name ), + }; + }, [ clientId, name ] ); + + const { replaceInnerBlocks } = useDispatch( 'core/block-editor' ); + + if ( hasInnerBlocks ) { + return ( + <ColumnsEditContainerWrapper { ...props } /> + ); + } + + return ( + <__experimentalBlockPatternPicker + icon={ get( blockType, [ 'icon', 'src' ] ) } + label={ get( blockType, [ 'title' ] ) } + patterns={ patterns } + onSelect={ ( nextPattern = defaultPattern ) => { + if ( nextPattern.attributes ) { + props.setAttributes( nextPattern.attributes ); + } + if ( nextPattern.innerBlocks ) { + replaceInnerBlocks( + props.clientId, + createBlocksFromInnerBlocksTemplate( nextPattern.innerBlocks ) + ); + } + } } + allowSkip + /> ); +}; + +export default ColumnsEdit; diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js new file mode 100644 index 00000000000000..54d945755f60bc --- /dev/null +++ b/packages/block-library/src/columns/edit.native.js @@ -0,0 +1,275 @@ +/** + * External dependencies + */ +import { View } from 'react-native'; +import classnames from 'classnames'; +import { dropRight, times } from 'lodash'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + PanelBody, + RangeControl, + Toolbar, + ToolbarButton, +} from '@wordpress/components'; +import { + InspectorControls, + InnerBlocks, + BlockControls, + // __experimentalBlockPatternPicker, + BlockVerticalAlignmentToolbar, +} from '@wordpress/block-editor'; +import { + withDispatch, + // useDispatch, + useSelect, +} from '@wordpress/data'; +import { compose, withPreferredColorScheme } from '@wordpress/compose'; +import { createBlock } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { + hasExplicitColumnWidths, + getMappedColumnWidths, + getRedistributedColumnWidths, + toWidthPrecision, +} from './utils'; + +import styles from './editor.scss'; +import Icon from './icon'; + +/** + * Allowed blocks constant is passed to InnerBlocks precisely as specified here. + * The contents of the array should never change. + * The array should contain the name of each block that is allowed. + * In columns block, the only block we allow is 'core/column'. + * + * @constant + * @type {string[]} + */ +const ALLOWED_BLOCKS = [ 'core/column' ]; + +/** + * Number of columns to assume for template in case the user opts to skip + * template option selection. + * + * @type {number} + */ +const DEFAULT_COLUMNS = 2; + +function ColumnsEditContainer( { + attributes, + className, + updateAlignment, + updateColumns, + clientId, + // isSelected, +} ) { + const { verticalAlignment } = attributes; + + const { count } = useSelect( ( select ) => { + return { + count: select( 'core/block-editor' ).getBlockCount( clientId ), + }; + } ); + + const classes = classnames( className, { + [ `are-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, + } ); + + return ( + <> + <InspectorControls> + <PanelBody title={ __( 'Columns Settings' ) }> + <RangeControl + label={ __( 'Number of columns' ) } + value={ count } + defaultValue={ DEFAULT_COLUMNS } + onChange={ ( value ) => updateColumns( count, value ) } + min={ 2 } + max={ 6 } + /> + </PanelBody> + </InspectorControls> + <BlockControls> + <Toolbar> + <ToolbarButton + title={ __( 'ColumnsButton' ) } + icon={ <Icon width={ 20 } height={ 20 } /> } + onClick={ () => {} } + /> + </Toolbar> + <BlockVerticalAlignmentToolbar + onChange={ updateAlignment } + value={ verticalAlignment } + /> + </BlockControls> + <View className={ classes }> + <InnerBlocks + // templateLock="all" + allowedBlocks={ ALLOWED_BLOCKS } + // renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } + /> + </View> + </> + ); +} + +const ColumnsEditContainerWrapper = withDispatch( ( dispatch, ownProps, registry ) => ( { + /** + * Update all child Column blocks with a new vertical alignment setting + * based on whatever alignment is passed in. This allows change to parent + * to overide anything set on a individual column basis. + * + * @param {string} verticalAlignment the vertical alignment setting + */ + updateAlignment( verticalAlignment ) { + const { clientId, setAttributes } = ownProps; + const { updateBlockAttributes } = dispatch( 'core/block-editor' ); + const { getBlockOrder } = registry.select( 'core/block-editor' ); + + // Update own alignment. + setAttributes( { verticalAlignment } ); + + // Update all child Column Blocks to match + const innerBlockClientIds = getBlockOrder( clientId ); + innerBlockClientIds.forEach( ( innerBlockClientId ) => { + updateBlockAttributes( innerBlockClientId, { + verticalAlignment, + } ); + } ); + }, + + /** + * Updates the column count, including necessary revisions to child Column + * blocks to grant required or redistribute available space. + * + * @param {number} previousColumns Previous column count. + * @param {number} newColumns New column count. + */ + updateColumns( previousColumns, newColumns ) { + const { clientId } = ownProps; + const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); + const { getBlocks } = registry.select( 'core/block-editor' ); + + let innerBlocks = getBlocks( clientId ); + const hasExplicitWidths = hasExplicitColumnWidths( innerBlocks ); + + // Redistribute available width for existing inner blocks. + const isAddingColumn = newColumns > previousColumns; + + if ( isAddingColumn && hasExplicitWidths ) { + // If adding a new column, assign width to the new column equal to + // as if it were `1 / columns` of the total available space. + const newColumnWidth = toWidthPrecision( 100 / newColumns ); + + // Redistribute in consideration of pending block insertion as + // constraining the available working width. + const widths = getRedistributedColumnWidths( innerBlocks, 100 - newColumnWidth ); + + innerBlocks = [ + ...getMappedColumnWidths( innerBlocks, widths ), + ...times( newColumns - previousColumns, () => { + return createBlock( 'core/column', { + width: newColumnWidth, + } ); + } ), + ]; + } else if ( isAddingColumn ) { + innerBlocks = [ + ...innerBlocks, + ...times( newColumns - previousColumns, () => { + return createBlock( 'core/column' ); + } ), + ]; + } else { + // The removed column will be the last of the inner blocks. + innerBlocks = dropRight( innerBlocks, previousColumns - newColumns ); + + if ( hasExplicitWidths ) { + // Redistribute as if block is already removed. + const widths = getRedistributedColumnWidths( innerBlocks, 100 ); + + innerBlocks = getMappedColumnWidths( innerBlocks, widths ); + } + } + + replaceInnerBlocks( clientId, innerBlocks, false ); + }, +} ) )( ColumnsEditContainer ); + +// TODO: implement "templates" to allow select column layout + +// const createBlocksFromInnerBlocksTemplate = ( innerBlocksTemplate ) => { +// return map( +// innerBlocksTemplate, +// ( [ name, attributes, innerBlocks = [] ] ) => +// createBlock( name, attributes, createBlocksFromInnerBlocksTemplate( innerBlocks ) ) +// ); +// }; + +const ColumnsEdit = ( props ) => { + const { clientId, name, isSelected, getStylesFromColorScheme } = props; + // const { blockType, defaultPattern, hasInnerBlocks, patterns } = useSelect( ( select ) => { + const { hasInnerBlocks } = useSelect( ( select ) => { + const { + __experimentalGetBlockPatterns, + getBlockType, + __experimentalGetDefaultBlockPattern, + } = select( 'core/blocks' ); + + return { + blockType: getBlockType( name ), + defaultPattern: __experimentalGetDefaultBlockPattern( name ), + hasInnerBlocks: select( 'core/block-editor' ).getBlocks( clientId ).length > 0, + patterns: __experimentalGetBlockPatterns( name ), + }; + }, [ clientId, name ] ); + + // const { replaceInnerBlocks } = useDispatch( 'core/block-editor' ); + + // TODO: make sure if columns should reder placeholder if block is not selected + if ( ! isSelected && ! hasInnerBlocks ) { + return ( + <View style={ [ + getStylesFromColorScheme( styles.columnsPlaceholder, styles.columnsPlaceholderDark ), + ! hasInnerBlocks && { ...styles.marginVerticalDense, ...styles.marginHorizontalNone }, + ] } /> + ); + } + + return ( + <ColumnsEditContainerWrapper { ...props } /> + ); + + // TODO: implement "templates" to allow select column layout + + // return ( + // <__experimentalBlockPatternPicker + // icon={ get( blockType, [ 'icon', 'src' ] ) } + // label={ get( blockType, [ 'title' ] ) } + // patterns={ patterns } + // onSelect={ ( nextPattern = defaultPattern ) => { + // if ( nextPattern.attributes ) { + // props.setAttributes( nextPattern.attributes ); + // } + // if ( nextPattern.innerBlocks ) { + // replaceInnerBlocks( + // props.clientId, + // createBlocksFromInnerBlocksTemplate( nextPattern.innerBlocks ) + // ); + // } + // } } + // allowSkip + // /> + // ); +}; + +export default compose( [ + withPreferredColorScheme, +] )( ColumnsEdit ); diff --git a/packages/block-library/src/columns/editor.native.scss b/packages/block-library/src/columns/editor.native.scss new file mode 100644 index 00000000000000..ff92d0885ba3dc --- /dev/null +++ b/packages/block-library/src/columns/editor.native.scss @@ -0,0 +1,22 @@ +.columnsPlaceholder { + padding: 12px; + margin-bottom: 12px; + background-color: $white; + border: $border-width dashed $gray; + border-radius: 4px; +} + +.columnsPlaceholderDark { + background-color: $black; + border: $border-width dashed $gray-70; +} + +.marginVerticalDense { + margin-top: $block-selected-child-margin; + margin-bottom: $block-selected-child-margin; +} + +.marginHorizontalNone { + margin-left: 0; + margin-right: 0; +} diff --git a/packages/block-library/src/columns/editor.scss b/packages/block-library/src/columns/editor.scss index 1dc626a9e84e06..17d9a5a7b12afe 100644 --- a/packages/block-library/src/columns/editor.scss +++ b/packages/block-library/src/columns/editor.scss @@ -1,36 +1,14 @@ -@mixin flex-full-height() { - display: flex; - flex-direction: column; - flex: 1; -} - - // These margins make sure that nested blocks stack/overlay with the parent block chrome // This is sort of an experiment at making sure the editor looks as much like the end result as possible // Potentially the rules here can apply to all nested blocks and enable stacking, in which case it should be moved elsewhere // When using CSS grid, margins do not collapse on the container. .wp-block-columns .editor-block-list__layout { - margin-left: 0; - margin-right: 0; - // This max-width is used to constrain the main editor column, it should not cascade into columns .editor-block-list__block { max-width: none; } } -// Fullwide: show margin left/right to ensure there's room for the side UI. -// This is not a 1:1 preview with the front-end where these margins would presumably be zero. -[data-type="core/columns"][data-align="full"] .wp-block-columns > .editor-inner-blocks { - padding-left: $block-padding; - padding-right: $block-padding; - - @include break-small() { - padding-left: $block-container-side-padding; - padding-right: $block-container-side-padding; - } -} - .wp-block-columns { display: block; @@ -48,8 +26,18 @@ > [data-type="core/column"] > .editor-block-list__block-edit, > [data-type="core/column"] > .editor-block-list__block-edit > div[data-block], > [data-type="core/column"] > .editor-block-list__block-edit .block-core-columns { - @include flex-full-height(); + display: flex; + flex-direction: column; + + // This flex rule fixes an issue in IE11. + flex: 1 1 auto; + + // IE11 does not support `position: sticky`, so we use it here to serve correct Flex rules to modern browsers. + @supports (position: sticky) { + flex: 1; + } } + // Adjust the individual column block. > [data-type="core/column"] { @@ -71,10 +59,10 @@ // Beyond mobile, allow 2 columns. @include break-small() { - flex-basis: calc(50% - (#{$grid-size-large} + #{$block-padding * 2})); + flex-basis: calc(50% - (#{$grid-size-large})); flex-grow: 0; - margin-left: $block-padding; - margin-right: $block-padding; + margin-left: 0; + margin-right: 0; } // Add space between columns. Themes can customize this if they wish to work differently. @@ -82,14 +70,14 @@ // Only apply this beyond the mobile breakpoint, as there's only a single column on mobile. @include break-small() { &:nth-child(even) { - margin-left: calc(#{$grid-size-large * 2} + #{$block-padding}); + margin-left: calc(#{$grid-size-large * 2}); } } // When columns are in a single row, add space before all except the first. @include break-medium() { &:not(:first-child) { - margin-left: calc(#{$grid-size-large * 2} + #{$block-padding}); + margin-left: calc(#{$grid-size-large * 2}); } } @@ -138,12 +126,6 @@ margin-top: $block-padding*2; margin-bottom: $block-padding*2; } - - // When the individual column block is selected, the nested padding overrules - // some of this margin. We need to adjust the appender spacing again as a result. - [data-type="core/column"].is-selected .block-list-appender { - margin: $block-padding 0; - } } /** @@ -182,18 +164,14 @@ div.block-core-columns.is-vertically-aligned-bottom { right: 0; } -/** - * Add extra padding when the parent block is selected, for easier interaction. - */ -.block-editor-block-list__layout .block-editor-block-list__block[data-type="core/columns"].is-selected > .block-editor-block-list__block-edit > [data-block] > div > .block-editor-inner-blocks, -.block-editor-block-list__layout .block-editor-block-list__block[data-type="core/columns"].has-child-selected > .block-editor-block-list__block-edit > [data-block] > div > .block-editor-inner-blocks, -.block-editor-block-list__layout .block-editor-block-list__block[data-type="core/column"].is-selected > .block-editor-block-list__block-edit > [data-block] > div > .block-editor-inner-blocks, -.block-editor-block-list__layout .block-editor-block-list__block[data-type="core/column"].has-child-selected > .block-editor-block-list__block-edit > [data-block] > div > .block-editor-inner-blocks { - padding: $block-padding; - - // Negate this padding for the placeholder. - > .components-placeholder { - margin: -$block-padding; - width: calc(100% + #{$block-padding * 2}); +// Fullwide: show margin left/right to ensure there's room for the side UI. +// This is not a 1:1 preview with the front-end where these margins would presumably be zero. +[data-type="core/columns"][data-align="full"] .wp-block-columns { + padding-left: $block-padding; + padding-right: $block-padding; + + @include break-small() { + padding-left: $block-container-side-padding; + padding-right: $block-container-side-padding; } } diff --git a/packages/block-library/src/columns/icon.js b/packages/block-library/src/columns/icon.js index f45aba71242c40..594a80b5d78e86 100644 --- a/packages/block-library/src/columns/icon.js +++ b/packages/block-library/src/columns/icon.js @@ -3,6 +3,6 @@ */ import { G, Path, SVG } from '@wordpress/components'; -export default ( - <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M4,4H20a2,2,0,0,1,2,2V18a2,2,0,0,1-2,2H4a2,2,0,0,1-2-2V6A2,2,0,0,1,4,4ZM4 6V18H8V6Zm6 0V18h4V6Zm6 0V18h4V6Z" /></G></SVG> -); +const Icon = ( props ) => <SVG { ...props } viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M4,4H20a2,2,0,0,1,2,2V18a2,2,0,0,1-2,2H4a2,2,0,0,1-2-2V6A2,2,0,0,1,4,4ZM4 6V18H8V6Zm6 0V18h4V6Zm6 0V18h4V6Z" /></G></SVG>; + +export default Icon; diff --git a/packages/block-library/src/columns/index.js b/packages/block-library/src/columns/index.js index 71f9ff081ec1b8..f151a89fc53732 100644 --- a/packages/block-library/src/columns/index.js +++ b/packages/block-library/src/columns/index.js @@ -10,6 +10,7 @@ import deprecated from './deprecated'; import edit from './edit'; import icon from './icon'; import metadata from './block.json'; +import patterns from './patterns'; import save from './save'; const { name } = metadata; @@ -24,6 +25,7 @@ export const settings = { align: [ 'wide', 'full' ], html: false, }, + patterns, example: { innerBlocks: [ { @@ -32,19 +34,19 @@ export const settings = { { name: 'core/paragraph', attributes: { - content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent et eros eu felis pellentesque efficitur. Nam dapibus felis malesuada tincidunt rhoncus. Integer non malesuada tortor.', + content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent et eros eu felis.', }, }, { name: 'core/image', attributes: { - url: 'https://upload.wikimedia.org/wikipedia/commons/9/95/Windbuchencom.jpg', + url: 'https://s.w.org/images/core/5.3/Windbuchencom.jpg', }, }, { name: 'core/paragraph', attributes: { - content: 'Suspendisse commodo neque lacus, a dictum orci interdum et. Ut vel mi ut leo fringilla rutrum.', + content: 'Suspendisse commodo neque lacus, a dictum orci interdum et.', }, }, ], @@ -55,13 +57,13 @@ export const settings = { { name: 'core/paragraph', attributes: { - content: __( 'Etiam et egestas lorem. Vivamus sagittis sit amet dolor quis lobortis. Integer sed fermentum arcu, id vulputate lacus. Etiam fermentum sem eu quam hendrerit, eget faucibus urna pulvinar.' ), + content: __( 'Etiam et egestas lorem. Vivamus sagittis sit amet dolor quis lobortis. Integer sed fermentum arcu, id vulputate lacus. Etiam fermentum sem eu quam hendrerit.' ), }, }, { name: 'core/paragraph', attributes: { - content: __( 'Nam risus massa, ullamcorper consectetur eros fermentum, porta aliquet ligula. Sed vel mauris nec enim ultricies commodo.' ), + content: __( 'Nam risus massa, ullamcorper consectetur eros fermentum, porta aliquet ligula. Sed vel mauris nec enim.' ), }, }, ], diff --git a/packages/block-library/src/columns/patterns.js b/packages/block-library/src/columns/patterns.js new file mode 100644 index 00000000000000..90241750c031fe --- /dev/null +++ b/packages/block-library/src/columns/patterns.js @@ -0,0 +1,63 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Template option choices for predefined columns layouts. + * + * @type {WPBlockPattern[]} + */ +const patterns = [ + { + name: 'two-columns-equal', + label: __( 'Two columns; equal split' ), + icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M39 12C40.1046 12 41 12.8954 41 14V34C41 35.1046 40.1046 36 39 36H9C7.89543 36 7 35.1046 7 34V14C7 12.8954 7.89543 12 9 12H39ZM39 34V14H25V34H39ZM23 34H9V14H23V34Z" /></SVG>, + isDefault: true, + innerBlocks: [ + [ 'core/column' ], + [ 'core/column' ], + ], + }, + { + name: 'two-columns-one-third-two-thirds', + label: __( 'Two columns; one-third, two-thirds split' ), + icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M39 12C40.1046 12 41 12.8954 41 14V34C41 35.1046 40.1046 36 39 36H9C7.89543 36 7 35.1046 7 34V14C7 12.8954 7.89543 12 9 12H39ZM39 34V14H20V34H39ZM18 34H9V14H18V34Z" /></SVG>, + innerBlocks: [ + [ 'core/column', { width: 33.33 } ], + [ 'core/column', { width: 66.66 } ], + ], + }, + { + name: 'two-columns-two-thirds-one-third', + label: __( 'Two columns; two-thirds, one-third split' ), + icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M39 12C40.1046 12 41 12.8954 41 14V34C41 35.1046 40.1046 36 39 36H9C7.89543 36 7 35.1046 7 34V14C7 12.8954 7.89543 12 9 12H39ZM39 34V14H30V34H39ZM28 34H9V14H28V34Z" /></SVG>, + innerBlocks: [ + [ 'core/column', { width: 66.66 } ], + [ 'core/column', { width: 33.33 } ], + ], + }, + { + name: 'three-columns-equal', + label: __( 'Three columns; equal split' ), + icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" d="M41 14a2 2 0 0 0-2-2H9a2 2 0 0 0-2 2v20a2 2 0 0 0 2 2h30a2 2 0 0 0 2-2V14zM28.5 34h-9V14h9v20zm2 0V14H39v20h-8.5zm-13 0H9V14h8.5v20z" /></SVG>, + innerBlocks: [ + [ 'core/column' ], + [ 'core/column' ], + [ 'core/column' ], + ], + }, + { + name: 'three-columns-wider-center', + label: __( 'Three columns; wide center column' ), + icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" d="M41 14a2 2 0 0 0-2-2H9a2 2 0 0 0-2 2v20a2 2 0 0 0 2 2h30a2 2 0 0 0 2-2V14zM31 34H17V14h14v20zm2 0V14h6v20h-6zm-18 0H9V14h6v20z" /></SVG>, + innerBlocks: [ + [ 'core/column', { width: 25 } ], + [ 'core/column', { width: 50 } ], + [ 'core/column', { width: 25 } ], + ], + }, +]; + +export default patterns; diff --git a/packages/block-library/src/columns/style.scss b/packages/block-library/src/columns/style.scss index 7a2c0877889332..0c641d5060baa7 100644 --- a/packages/block-library/src/columns/style.scss +++ b/packages/block-library/src/columns/style.scss @@ -11,7 +11,6 @@ } .wp-block-column { - margin-bottom: 1em; flex-grow: 1; @media (max-width: #{ ($break-small - 1) }) { diff --git a/packages/block-library/src/columns/test/utils.js b/packages/block-library/src/columns/test/utils.js index b89a63e2e01533..67ef6dd0332727 100644 --- a/packages/block-library/src/columns/test/utils.js +++ b/packages/block-library/src/columns/test/utils.js @@ -2,7 +2,6 @@ * Internal dependencies */ import { - getColumnsTemplate, toWidthPrecision, getAdjacentBlocks, getEffectiveColumnWidth, @@ -13,25 +12,6 @@ import { getMappedColumnWidths, } from '../utils'; -describe( 'getColumnsTemplate', () => { - it( 'should return a template corresponding to columns count', () => { - const template = getColumnsTemplate( 4 ); - - expect( template ).toEqual( [ - [ 'core/column' ], - [ 'core/column' ], - [ 'core/column' ], - [ 'core/column' ], - ] ); - } ); - - it( 'should return null if columns count is not defined', () => { - const template = getColumnsTemplate( undefined ); - - expect( template ).toBe( null ); - } ); -} ); - describe( 'toWidthPrecision', () => { it( 'should round value to standard precision', () => { const value = toWidthPrecision( 50.108 ); diff --git a/packages/block-library/src/columns/utils.js b/packages/block-library/src/columns/utils.js index 77a0b7cf4375a8..61b423f8ca761e 100644 --- a/packages/block-library/src/columns/utils.js +++ b/packages/block-library/src/columns/utils.js @@ -1,23 +1,7 @@ /** * External dependencies */ -import memoize from 'memize'; -import { times, findIndex, sumBy, merge, mapValues } from 'lodash'; - -/** - * Returns the layouts configuration for a given number of columns. - * - * @param {number} columns Number of columns. - * - * @return {Object[]} Columns layout configuration. - */ -export const getColumnsTemplate = memoize( ( columns ) => { - if ( columns === undefined ) { - return null; - } - - return times( columns, () => [ 'core/column' ] ); -} ); +import { findIndex, sumBy, merge, mapValues } from 'lodash'; /** * Returns a column width attribute value rounded to standard precision. @@ -87,9 +71,9 @@ export function getTotalColumnsWidth( blocks, totalBlockCount = blocks.length ) * @return {Object<string,number>} Column widths. */ export function getColumnWidths( blocks, totalBlockCount = blocks.length ) { - return blocks.reduce( ( result, block ) => { + return blocks.reduce( ( accumulator, block ) => { const width = getEffectiveColumnWidth( block, totalBlockCount ); - return Object.assign( result, { [ block.clientId ]: width } ); + return Object.assign( accumulator, { [ block.clientId ]: width } ); }, {} ); } diff --git a/packages/block-library/src/cover/block.json b/packages/block-library/src/cover/block.json index 957a8264ef6e6e..cb6a0a1c330e64 100644 --- a/packages/block-library/src/cover/block.json +++ b/packages/block-library/src/cover/block.json @@ -31,6 +31,12 @@ }, "minHeight": { "type": "number" + }, + "gradient": { + "type": "string" + }, + "customGradient": { + "type": "string" } } } diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index 1994f4a57e0713..bdb6aa9a7be917 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -9,23 +9,22 @@ import tinycolor from 'tinycolor2'; * WordPress dependencies */ import { - Component, - createRef, - useCallback, + useEffect, + useRef, useState, } from '@wordpress/element'; import { + BaseControl, + Button, FocalPointPicker, IconButton, PanelBody, PanelRow, RangeControl, + ResizableBox, ToggleControl, - Toolbar, + ToolbarGroup, withNotices, - ResizableBox, - BaseControl, - Button, } from '@wordpress/components'; import { compose, withInstanceId } from '@wordpress/compose'; import { @@ -39,6 +38,9 @@ import { PanelColorSettings, withColors, ColorPalette, + __experimentalUseGradient, + __experimentalGradientPickerControl, + __experimentalGradientPicker, } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { withDispatch } from '@wordpress/data'; @@ -77,40 +79,32 @@ function retrieveFastAverageColor() { const CoverHeightInput = withInstanceId( function( { value = '', instanceId, onChange } ) { const [ temporaryInput, setTemporaryInput ] = useState( null ); - const onChangeEvent = useCallback( - ( event ) => { - const unprocessedValue = event.target.value; - const inputValue = unprocessedValue !== '' ? - parseInt( event.target.value, 10 ) : - undefined; - if ( ( isNaN( inputValue ) || inputValue < COVER_MIN_HEIGHT ) && inputValue !== undefined ) { - setTemporaryInput( event.target.value ); - return; - } - setTemporaryInput( null ); - onChange( inputValue ); - }, - [ onChange, setTemporaryInput ] - ); - const onBlurEvent = useCallback( - () => { - if ( temporaryInput !== null ) { - setTemporaryInput( null ); - } - }, - [ temporaryInput, setTemporaryInput ] - ); const inputId = `block-cover-height-input-${ instanceId }`; return ( <BaseControl label={ __( 'Minimum height in pixels' ) } id={ inputId }> <input type="number" id={ inputId } - onChange={ onChangeEvent } - onBlur={ onBlurEvent } + onChange={ ( event ) => { + const unprocessedValue = event.target.value; + const inputValue = unprocessedValue !== '' ? + parseInt( event.target.value, 10 ) : + undefined; + if ( ( isNaN( inputValue ) || inputValue < COVER_MIN_HEIGHT ) && inputValue !== undefined ) { + setTemporaryInput( event.target.value ); + return; + } + setTemporaryInput( null ); + onChange( inputValue ); + } } + onBlur={ () => { + if ( temporaryInput !== null ) { + setTemporaryInput( null ); + } + } } value={ temporaryInput !== null ? temporaryInput : value } min={ COVER_MIN_HEIGHT } - step="10" + step="1" /> </BaseControl> ); @@ -136,29 +130,6 @@ function ResizableCover( { onResizeStop, } ) { const [ isResizing, setIsResizing ] = useState( false ); - const onResizeEvent = useCallback( - ( event, direction, elt ) => { - onResize( elt.clientHeight ); - if ( ! isResizing ) { - setIsResizing( true ); - } - }, - [ onResize, setIsResizing ], - ); - const onResizeStartEvent = useCallback( - ( event, direction, elt ) => { - onResizeStart( elt.clientHeight ); - onResize( elt.clientHeight ); - }, - [ onResizeStart, onResize ] - ); - const onResizeStopEvent = useCallback( - ( event, direction, elt ) => { - onResizeStop( elt.clientHeight ); - setIsResizing( false ); - }, - [ onResizeStop, setIsResizing ] - ); return ( <ResizableBox @@ -169,9 +140,20 @@ function ResizableCover( { } ) } enable={ RESIZABLE_BOX_ENABLE_OPTION } - onResizeStart={ onResizeStartEvent } - onResize={ onResizeEvent } - onResizeStop={ onResizeStopEvent } + onResizeStart={ ( event, direction, elt ) => { + onResizeStart( elt.clientHeight ); + onResize( elt.clientHeight ); + } } + onResize={ ( event, direction, elt ) => { + onResize( elt.clientHeight ); + if ( ! isResizing ) { + setIsResizing( true ); + } + } } + onResizeStop={ ( event, direction, elt ) => { + onResizeStop( elt.clientHeight ); + setIsResizing( false ); + } } minHeight={ COVER_MIN_HEIGHT } > { children } @@ -179,386 +161,402 @@ function ResizableCover( { ); } -class CoverEdit extends Component { - constructor() { - super( ...arguments ); - this.state = { - isDark: false, - temporaryMinHeight: null, - }; - this.imageRef = createRef(); - this.videoRef = createRef(); - this.changeIsDarkIfRequired = this.changeIsDarkIfRequired.bind( this ); - this.onUploadError = this.onUploadError.bind( this ); - } - - componentDidMount() { - this.handleBackgroundMode(); - } - - componentDidUpdate( prevProps ) { - this.handleBackgroundMode( prevProps ); - } - - onUploadError( message ) { - const { noticeOperations } = this.props; - noticeOperations.removeAllNotices(); - noticeOperations.createErrorNotice( message ); - } - - render() { - const { - attributes, - setAttributes, - isSelected, - className, - noticeUI, - overlayColor, - setOverlayColor, - toggleSelection, - } = this.props; - const { - backgroundType, - dimRatio, - focalPoint, - hasParallax, - id, - url, - minHeight, - } = attributes; - const onSelectMedia = ( media ) => { - if ( ! media || ! media.url ) { - setAttributes( { url: undefined, id: undefined } ); - return; +function onCoverSelectMedia( setAttributes ) { + return ( media ) => { + if ( ! media || ! media.url ) { + setAttributes( { url: undefined, id: undefined } ); + return; + } + let mediaType; + // for media selections originated from a file upload. + if ( media.media_type ) { + if ( media.media_type === IMAGE_BACKGROUND_TYPE ) { + mediaType = IMAGE_BACKGROUND_TYPE; + } else { + // only images and videos are accepted so if the media_type is not an image we can assume it is a video. + // Videos contain the media type of 'file' in the object returned from the rest api. + mediaType = VIDEO_BACKGROUND_TYPE; } - let mediaType; - // for media selections originated from a file upload. - if ( media.media_type ) { - if ( media.media_type === IMAGE_BACKGROUND_TYPE ) { - mediaType = IMAGE_BACKGROUND_TYPE; - } else { - // only images and videos are accepted so if the media_type is not an image we can assume it is a video. - // Videos contain the media type of 'file' in the object returned from the rest api. - mediaType = VIDEO_BACKGROUND_TYPE; - } - } else { // for media selections originated from existing files in the media library. - if ( - media.type !== IMAGE_BACKGROUND_TYPE && - media.type !== VIDEO_BACKGROUND_TYPE - ) { - return; - } - mediaType = media.type; + } else { // for media selections originated from existing files in the media library. + if ( + media.type !== IMAGE_BACKGROUND_TYPE && + media.type !== VIDEO_BACKGROUND_TYPE + ) { + return; } + mediaType = media.type; + } - setAttributes( { - url: media.url, - id: media.id, - backgroundType: mediaType, - ...( mediaType === VIDEO_BACKGROUND_TYPE ? - { focalPoint: undefined, hasParallax: undefined } : - {} - ), - } ); - }; + setAttributes( { + url: media.url, + id: media.id, + backgroundType: mediaType, + ...( mediaType === VIDEO_BACKGROUND_TYPE ? + { focalPoint: undefined, hasParallax: undefined } : + {} + ), + } ); + }; +} - const toggleParallax = () => { - setAttributes( { - hasParallax: ! hasParallax, - ...( ! hasParallax ? { focalPoint: undefined } : {} ), +/** + * useCoverIsDark is a hook that returns a boolean variable specifying if the cover + * background is dark or not. + * + * @param {?string} url Url of the media background. + * @param {?number} dimRatio Transparency of the overlay color. If an image and + * color are set, dimRatio is used to decide what is used + * for background darkness checking purposes. + * @param {?string} overlayColor String containing the overlay color value if one exists. + * @param {?Object} elementRef If a media background is set, elementRef should contain a reference to a + * dom element that renders that media. + * + * @return {boolean} True if the cover background is considered "dark" and false otherwise. + */ +function useCoverIsDark( url, dimRatio = 50, overlayColor, elementRef ) { + const [ isDark, setIsDark ] = useState( false ); + useEffect( () => { + // If opacity is lower than 50 the dominant color is the image or video color, + // so use that color for the dark mode computation. + if ( url && dimRatio <= 50 && elementRef.current ) { + retrieveFastAverageColor().getColorAsync( elementRef.current, ( color ) => { + setIsDark( color.isDark ); } ); - }; - const setDimRatio = ( ratio ) => setAttributes( { dimRatio: ratio } ); + } + }, [ url, url && dimRatio <= 50 && elementRef.current, setIsDark ] ); + useEffect( () => { + // If opacity is greater than 50 the dominant color is the overlay color, + // so use that color for the dark mode computation. + if ( dimRatio > 50 || ! url ) { + if ( ! overlayColor ) { + // If no overlay color exists the overlay color is black (isDark ) + setIsDark( true ); + return; + } + setIsDark( tinycolor( overlayColor ).isDark() ); + } + }, [ overlayColor, dimRatio > 50 || ! url, setIsDark ] ); + useEffect( () => { + if ( ! url && ! overlayColor ) { + // Reset isDark + setIsDark( false ); + } + }, [ ! url && ! overlayColor, setIsDark ] ); + return isDark; +} - const { temporaryMinHeight } = this.state; +function CoverEdit( { + attributes, + setAttributes, + isSelected, + className, + noticeUI, + overlayColor, + setOverlayColor, + toggleSelection, + noticeOperations, +} ) { + const { + backgroundType, + dimRatio, + focalPoint, + hasParallax, + id, + minHeight, + url, + } = attributes; + const { + gradientClass, + gradientValue, + setGradient, + } = __experimentalUseGradient(); + const onSelectMedia = onCoverSelectMedia( setAttributes ); - const style = { - ...( - backgroundType === IMAGE_BACKGROUND_TYPE ? - backgroundImageStyles( url ) : - {} - ), - backgroundColor: overlayColor.color, - minHeight: ( temporaryMinHeight || minHeight ), - }; + const toggleParallax = () => { + setAttributes( { + hasParallax: ! hasParallax, + ...( ! hasParallax ? { focalPoint: undefined } : {} ), + } ); + }; - if ( focalPoint ) { - style.backgroundPosition = `${ focalPoint.x * 100 }% ${ focalPoint.y * 100 }%`; - } + const isDarkElement = useRef(); + const isDark = useCoverIsDark( url, dimRatio, overlayColor.color, isDarkElement ); - const controls = ( - <> - <BlockControls> - { !! ( url || overlayColor.color ) && ( - <> - <MediaUploadCheck> - <Toolbar> - <MediaUpload - onSelect={ onSelectMedia } - allowedTypes={ ALLOWED_MEDIA_TYPES } - value={ id } - render={ ( { open } ) => ( - <IconButton - className="components-toolbar__control" - label={ __( 'Edit media' ) } - icon="edit" - onClick={ open } - /> - ) } - /> - </Toolbar> - </MediaUploadCheck> - </> - ) } - </BlockControls> - <InspectorControls> - { !! url && ( - <PanelBody title={ __( 'Media Settings' ) }> - { IMAGE_BACKGROUND_TYPE === backgroundType && ( - <ToggleControl - label={ __( 'Fixed Background' ) } - checked={ hasParallax } - onChange={ toggleParallax } - /> - ) } - { IMAGE_BACKGROUND_TYPE === backgroundType && ! hasParallax && ( - <FocalPointPicker - label={ __( 'Focal Point Picker' ) } - url={ url } - value={ focalPoint } - onChange={ ( value ) => setAttributes( { focalPoint: value } ) } - /> - ) } - <PanelRow> - <Button - isDefault - isSmall - className="block-library-cover__reset-button" - onClick={ () => setAttributes( { - url: undefined, - id: undefined, - backgroundType: undefined, - dimRatio: undefined, - focalPoint: undefined, - hasParallax: undefined, - } ) } - > - { __( 'Clear Media' ) } - </Button> - </PanelRow> - </PanelBody> - ) } - { ( url || overlayColor.color ) && ( - <> - <PanelBody title={ __( 'Dimensions' ) }> - <CoverHeightInput - value={ temporaryMinHeight || minHeight } - onChange={ - ( value ) => { - setAttributes( { - minHeight: value, - } ); - } - } - /> - </PanelBody> - <PanelColorSettings - title={ __( 'Overlay' ) } - initialOpen={ true } - colorSettings={ [ { - value: overlayColor.color, - onChange: setOverlayColor, - label: __( 'Overlay Color' ), - } ] } - > - { !! url && ( - <RangeControl - label={ __( 'Background Opacity' ) } - value={ dimRatio } - onChange={ setDimRatio } - min={ 0 } - max={ 100 } - step={ 10 } - required - /> - ) } - </PanelColorSettings> - </> - ) } - </InspectorControls> - </> - ); + const [ temporaryMinHeight, setTemporaryMinHeight ] = useState( null ); - if ( ! ( url || overlayColor.color ) ) { - const placeholderIcon = <BlockIcon icon={ icon } />; - const label = __( 'Cover' ); + const { removeAllNotices, createErrorNotice } = noticeOperations; - return ( - <> - { controls } - <MediaPlaceholder - icon={ placeholderIcon } - className={ className } - labels={ { - title: label, - instructions: __( 'Upload an image or video file, or pick one from your media library.' ), - } } - onSelect={ onSelectMedia } - accept="image/*,video/*" - allowedTypes={ ALLOWED_MEDIA_TYPES } - notices={ noticeUI } - onError={ this.onUploadError } - > - <ColorPalette - disableCustomColors={ true } - value={ overlayColor.color } - onChange={ setOverlayColor } - clearable={ false } - className="wp-block-cover__placeholder-color-palette" - /> - </MediaPlaceholder> - </> - ); - } + const style = { + ...( + backgroundType === IMAGE_BACKGROUND_TYPE ? + backgroundImageStyles( url ) : + {} + ), + backgroundColor: overlayColor.color, + minHeight: ( temporaryMinHeight || minHeight ), + }; - const classes = classnames( - className, - dimRatioToClass( dimRatio ), - { - 'is-dark-theme': this.state.isDark, - 'has-background-dim': dimRatio !== 0, - 'has-parallax': hasParallax, - [ overlayColor.class ]: overlayColor.class, - } - ); + if ( gradientValue && ! url ) { + style.background = gradientValue; + } - return ( - <> - { controls } - <ResizableCover - className={ classnames( - 'block-library-cover__resize-container', - { 'is-selected': isSelected }, - ) } - onResizeStart={ () => toggleSelection( false ) } - onResize={ ( newMinHeight ) => { - this.setState( { - temporaryMinHeight: newMinHeight, - } ); - } } - onResizeStop={ - ( newMinHeight ) => { - toggleSelection( true ); - setAttributes( { - minHeight: newMinHeight, - } ); - this.setState( { - temporaryMinHeight: null, - } ); - } - } - > + if ( focalPoint ) { + style.backgroundPosition = `${ focalPoint.x * 100 }% ${ focalPoint.y * 100 }%`; + } - <div - data-url={ url } - style={ style } - className={ classes } - > + const hasBackground = !! ( url || overlayColor.color || gradientValue ); + + const controls = ( + <> + <BlockControls> + { hasBackground && ( + <> + <MediaUploadCheck> + <ToolbarGroup> + <MediaUpload + onSelect={ onSelectMedia } + allowedTypes={ ALLOWED_MEDIA_TYPES } + value={ id } + render={ ( { open } ) => ( + <IconButton + className="components-toolbar__control" + label={ __( 'Edit media' ) } + icon="edit" + onClick={ open } + /> + ) } + /> + </ToolbarGroup> + </MediaUploadCheck> + </> + ) } + </BlockControls> + <InspectorControls> + { !! url && ( + <PanelBody title={ __( 'Media Settings' ) }> { IMAGE_BACKGROUND_TYPE === backgroundType && ( - // Used only to programmatically check if the image is dark or not - <img - ref={ this.imageRef } - aria-hidden - alt="" - style={ { - display: 'none', - } } - src={ url } + <ToggleControl + label={ __( 'Fixed Background' ) } + checked={ hasParallax } + onChange={ toggleParallax } + /> + ) } + { IMAGE_BACKGROUND_TYPE === backgroundType && ! hasParallax && ( + <FocalPointPicker + label={ __( 'Focal Point Picker' ) } + url={ url } + value={ focalPoint } + onChange={ ( newFocalPoint ) => setAttributes( { focalPoint: newFocalPoint } ) } /> ) } { VIDEO_BACKGROUND_TYPE === backgroundType && ( <video - ref={ this.videoRef } - className="wp-block-cover__video-background" autoPlay muted loop src={ url } /> ) } - <div className="wp-block-cover__inner-container"> - <InnerBlocks - template={ INNER_BLOCKS_TEMPLATE } + <PanelRow> + <Button + isDefault + isSmall + className="block-library-cover__reset-button" + onClick={ () => setAttributes( { + url: undefined, + id: undefined, + backgroundType: undefined, + dimRatio: undefined, + focalPoint: undefined, + hasParallax: undefined, + } ) } + > + { __( 'Clear Media' ) } + </Button> + </PanelRow> + </PanelBody> + ) } + { hasBackground && ( + <> + <PanelBody title={ __( 'Dimensions' ) }> + <CoverHeightInput + value={ temporaryMinHeight || minHeight } + onChange={ ( newMinHeight ) => setAttributes( { minHeight: newMinHeight } ) } /> - </div> + </PanelBody> + <PanelColorSettings + title={ __( 'Overlay' ) } + initialOpen={ true } + colorSettings={ [ { + value: overlayColor.color, + onChange: ( ...args ) => { + setAttributes( { + customGradient: undefined, + } ); + setOverlayColor( ...args ); + }, + label: __( 'Overlay Color' ), + } ] } + > + <__experimentalGradientPickerControl + label={ __( 'Overlay Gradient' ) } + onChange={ + ( newGradient ) => { + setGradient( newGradient ); + setAttributes( { + overlayColor: undefined, + } ); + } + } + value={ gradientValue } + /> + { !! url && ( + <RangeControl + label={ __( 'Background Opacity' ) } + value={ dimRatio } + onChange={ ( newDimRation ) => setAttributes( { dimRatio: newDimRation } ) } + min={ 0 } + max={ 100 } + step={ 10 } + required + /> + ) } + </PanelColorSettings> + </> + ) } + </InspectorControls> + </> + ); + + if ( ! hasBackground ) { + const placeholderIcon = <BlockIcon icon={ icon } />; + const label = __( 'Cover' ); + + return ( + <> + { controls } + <MediaPlaceholder + icon={ placeholderIcon } + className={ className } + labels={ { + title: label, + instructions: __( 'Upload an image or video file, or pick one from your media library.' ), + } } + onSelect={ onSelectMedia } + accept="image/*,video/*" + allowedTypes={ ALLOWED_MEDIA_TYPES } + notices={ noticeUI } + onError={ ( message ) => { + removeAllNotices(); + createErrorNotice( message ); + } } + > + <div + className="wp-block-cover__placeholder-background-options" + > + <ColorPalette + disableCustomColors={ true } + value={ overlayColor.color } + onChange={ setOverlayColor } + clearable={ false } + /> + <__experimentalGradientPicker + disableCustomGradients + onChange={ + ( newGradient ) => { + setGradient( newGradient ); + setAttributes( { + overlayColor: undefined, + } ); + } + } + value={ gradientValue } + clearable={ false } + /> </div> - </ResizableCover> + </MediaPlaceholder> </> ); } - handleBackgroundMode( prevProps ) { - const { attributes, overlayColor } = this.props; - const { dimRatio, url } = attributes; - // If opacity is greater than 50 the dominant color is the overlay color, - // so use that color for the dark mode computation. - if ( dimRatio > 50 ) { - if ( - prevProps && - prevProps.attributes.dimRatio > 50 && - prevProps.overlayColor.color === overlayColor.color - ) { - // No relevant prop changes happened there is no need to apply any change. - return; - } - if ( ! overlayColor.color ) { - // If no overlay color exists the overlay color is black (isDark ) - this.changeIsDarkIfRequired( true ); - return; - } - this.changeIsDarkIfRequired( - tinycolor( overlayColor.color ).isDark() - ); - return; - } - // If opacity is lower than 50 the dominant color is the image or video color, - // so use that color for the dark mode computation. - - if ( - prevProps && - prevProps.attributes.dimRatio <= 50 && - prevProps.attributes.url === url - ) { - // No relevant prop changes happened there is no need to apply any change. - return; + const classes = classnames( + className, + dimRatioToClass( dimRatio ), + { + 'is-dark-theme': isDark, + 'has-background-dim': dimRatio !== 0, + 'has-parallax': hasParallax, + [ overlayColor.class ]: overlayColor.class, + 'has-background-gradient': gradientValue, + [ gradientClass ]: ! url && gradientClass, } - const { backgroundType } = attributes; - - let element; + ); - switch ( backgroundType ) { - case IMAGE_BACKGROUND_TYPE: - element = this.imageRef.current; - break; - case VIDEO_BACKGROUND_TYPE: - element = this.videoRef.current; - break; - } - if ( ! element ) { - return; - } - retrieveFastAverageColor().getColorAsync( element, ( color ) => { - this.changeIsDarkIfRequired( color.isDark ); - } ); - } + return ( + <> + { controls } + <ResizableCover + className={ classnames( + 'block-library-cover__resize-container', + { 'is-selected': isSelected }, + ) } + onResizeStart={ () => toggleSelection( false ) } + onResize={ setTemporaryMinHeight } + onResizeStop={ + ( newMinHeight ) => { + toggleSelection( true ); + setAttributes( { minHeight: newMinHeight } ); + setTemporaryMinHeight( null ); + } + } + > - changeIsDarkIfRequired( newIsDark ) { - if ( this.state.isDark !== newIsDark ) { - this.setState( { - isDark: newIsDark, - } ); - } - } + <div + data-url={ url } + style={ style } + className={ classes } + > + { IMAGE_BACKGROUND_TYPE === backgroundType && ( + // Used only to programmatically check if the image is dark or not + <img + ref={ isDarkElement } + aria-hidden + alt="" + style={ { + display: 'none', + } } + src={ url } + /> + ) } + { url && gradientValue && dimRatio !== 0 && ( + <span + aria-hidden="true" + className={ classnames( + 'wp-block-cover__gradient-background', + gradientClass, + ) } + style={ { background: gradientValue } } + /> + ) } + { VIDEO_BACKGROUND_TYPE === backgroundType && ( + <video + ref={ isDarkElement } + className="wp-block-cover__video-background" + autoPlay + muted + loop + src={ url } + /> + ) } + <div className="wp-block-cover__inner-container"> + <InnerBlocks + template={ INNER_BLOCKS_TEMPLATE } + /> + </div> + </div> + </ResizableCover> + </> + ); } export default compose( [ diff --git a/packages/block-library/src/cover/editor.scss b/packages/block-library/src/cover/editor.scss index b40aed0a472b30..a824f33c85b2f2 100644 --- a/packages/block-library/src/cover/editor.scss +++ b/packages/block-library/src/cover/editor.scss @@ -34,10 +34,11 @@ } } - .wp-block-cover__placeholder-color-palette { + .wp-block-cover__placeholder-background-options { // wraps about 6 color swatches - max-width: 290px; + max-width: 260px; margin-top: 1em; + width: 100%; } // Apply max-width to floated items that have no intrinsic width diff --git a/packages/block-library/src/cover/index.js b/packages/block-library/src/cover/index.js index 57e568ded8e68f..e3b6519466d8af 100644 --- a/packages/block-library/src/cover/index.js +++ b/packages/block-library/src/cover/index.js @@ -23,12 +23,13 @@ export const settings = { icon, supports: { align: true, + html: false, }, example: { attributes: { customOverlayColor: '#065174', dimRatio: 40, - url: 'https://upload.wikimedia.org/wikipedia/commons/9/95/Windbuchencom.jpg', + url: 'https://s.w.org/images/core/5.3/Windbuchencom.jpg', }, innerBlocks: [ { diff --git a/packages/block-library/src/cover/save.js b/packages/block-library/src/cover/save.js index de057122a0f7b7..106b340fd2a61f 100644 --- a/packages/block-library/src/cover/save.js +++ b/packages/block-library/src/cover/save.js @@ -9,6 +9,7 @@ import classnames from 'classnames'; import { InnerBlocks, getColorClassName, + __experimentalGetGradientClass, } from '@wordpress/block-editor'; /** @@ -24,6 +25,8 @@ import { export default function save( { attributes } ) { const { backgroundType, + gradient, + customGradient, customOverlayColor, dimRatio, focalPoint, @@ -33,6 +36,8 @@ export default function save( { attributes } ) { minHeight, } = attributes; const overlayColorClass = getColorClassName( 'background-color', overlayColor ); + const gradientClass = __experimentalGetGradientClass( gradient ); + const style = backgroundType === IMAGE_BACKGROUND_TYPE ? backgroundImageStyles( url ) : {}; @@ -42,6 +47,9 @@ export default function save( { attributes } ) { if ( focalPoint && ! hasParallax ) { style.backgroundPosition = `${ focalPoint.x * 100 }% ${ focalPoint.y * 100 }%`; } + if ( customGradient && ! url ) { + style.background = customGradient; + } style.minHeight = minHeight || undefined; const classes = classnames( @@ -50,11 +58,23 @@ export default function save( { attributes } ) { { 'has-background-dim': dimRatio !== 0, 'has-parallax': hasParallax, + 'has-background-gradient': customGradient, + [ gradientClass ]: ! url && gradientClass, }, ); return ( <div className={ classes } style={ style }> + { url && ( gradient || customGradient ) && dimRatio !== 0 && ( + <span + aria-hidden="true" + className={ classnames( + 'wp-block-cover__gradient-background', + gradientClass + ) } + style={ customGradient ? { background: customGradient } : undefined } + /> + ) } { VIDEO_BACKGROUND_TYPE === backgroundType && url && ( <video className="wp-block-cover__video-background" autoPlay diff --git a/packages/block-library/src/cover/shared.js b/packages/block-library/src/cover/shared.js index af13f2b8f9b18c..532504d0aa24b2 100644 --- a/packages/block-library/src/cover/shared.js +++ b/packages/block-library/src/cover/shared.js @@ -8,7 +8,7 @@ export function backgroundImageStyles( url ) { } export function dimRatioToClass( ratio ) { - return ( ratio === 0 || ratio === 50 ) ? + return ( ratio === 0 || ratio === 50 || ! ratio ) ? null : 'has-background-dim-' + ( 10 * Math.round( ratio / 10 ) ); } diff --git a/packages/block-library/src/cover/style.scss b/packages/block-library/src/cover/style.scss index 8ac60265306036..739742d1e62270 100644 --- a/packages/block-library/src/cover/style.scss +++ b/packages/block-library/src/cover/style.scss @@ -12,52 +12,6 @@ align-items: center; overflow: hidden; - &.has-left-content { - justify-content: flex-start; - - h2, - .wp-block-cover-image-text, - .wp-block-cover-text { - margin-left: 0; - text-align: left; - } - } - - &.has-right-content { - justify-content: flex-end; - - h2, - .wp-block-cover-image-text, - .wp-block-cover-text { - margin-right: 0; - text-align: right; - } - } - - h2, - .wp-block-cover-image-text, - .wp-block-cover-text { - font-size: 2em; - line-height: 1.25; - z-index: 1; - margin-bottom: 0; - max-width: $content-width; - padding: $block-padding; - text-align: center; - } - - h2:not(.has-text-color), - .wp-block-cover-image-text, - .wp-block-cover-text { - color: $white; - a, - a:hover, - a:focus, - a:active { - color: $white; - } - } - &.has-parallax { background-attachment: fixed; @@ -76,19 +30,33 @@ &.has-background-dim::before { content: ""; + background-color: inherit; + } + + &.has-background-dim:not(.has-background-gradient)::before, + .wp-block-cover__gradient-background { position: absolute; top: 0; left: 0; bottom: 0; right: 0; - background-color: inherit; - opacity: 0.5; z-index: z-index(".wp-block-cover.has-background-dim::before"); } + &.has-background-dim:not(.has-background-gradient)::before, + & .wp-block-cover__gradient-background { + opacity: 0.5; + } + + @for $i from 1 through 10 { - &.has-background-dim.has-background-dim-#{ $i * 10 }::before { - opacity: $i * 0.1; + &.has-background-dim.has-background-dim-#{ $i * 10 } { + &:not(.has-background-gradient)::before { + opacity: $i * 0.1; + } + .wp-block-cover__gradient-background { + opacity: $i * 0.1; + } } } @@ -150,3 +118,56 @@ z-index: z-index(".wp-block-cover__video-background"); object-fit: cover; } + +// Styles bellow only exist to support older versions of the block. +// Versions that not had inner blocks and used an h2 heading had a section (and not a div) with a class wp-block-cover-image (and not a wp-block-cover). +// We are using the previous referred differences to target old versions. + +section.wp-block-cover-image h2, +.wp-block-cover-image-text, +.wp-block-cover-text { + color: $white; + a, + a:hover, + a:focus, + a:active { + color: $white; + } +} + +.wp-block-cover-image +.wp-block-cover { + &.has-left-content { + justify-content: flex-start; + } + + &.has-right-content { + justify-content: flex-end; + } +} + +section.wp-block-cover-image.has-left-content > h2, +.wp-block-cover-image.has-left-content .wp-block-cover-image-text, +.wp-block-cover.has-left-content .wp-block-cover-text { + margin-left: 0; + text-align: left; +} + +section.wp-block-cover-image.has-right-content > h2, +.wp-block-cover-image.has-right-content .wp-block-cover-image-text, +.wp-block-cover.has-right-content .wp-block-cover-text { + margin-right: 0; + text-align: right; +} + +section.wp-block-cover-image > h2, +.wp-block-cover-image .wp-block-cover-image-text, +.wp-block-cover .wp-block-cover-text { + font-size: 2em; + line-height: 1.25; + z-index: 1; + margin-bottom: 0; + max-width: $content-width; + padding: $block-padding; + text-align: center; +} diff --git a/packages/block-library/src/cover/transforms.js b/packages/block-library/src/cover/transforms.js index 8b775f73d0040c..13d4082a4052d1 100644 --- a/packages/block-library/src/cover/transforms.js +++ b/packages/block-library/src/cover/transforms.js @@ -40,8 +40,13 @@ const transforms = { { type: 'block', blocks: [ 'core/image' ], - isMatch: ( { backgroundType, url } ) => { - return ! url || backgroundType === IMAGE_BACKGROUND_TYPE; + isMatch: ( { backgroundType, url, overlayColor, customOverlayColor, gradient, customGradient } ) => { + if ( url ) { + // If a url exists the transform could happen if that URL represents an image background. + return backgroundType === IMAGE_BACKGROUND_TYPE; + } + // If a url is not set the transform could happen if the cover has no background color or gradient; + return ! overlayColor && ! customOverlayColor && ! gradient && ! customGradient; }, transform: ( { title, url, align, id } ) => ( createBlock( 'core/image', { @@ -55,8 +60,13 @@ const transforms = { { type: 'block', blocks: [ 'core/video' ], - isMatch: ( { backgroundType, url } ) => { - return ! url || backgroundType === VIDEO_BACKGROUND_TYPE; + isMatch: ( { backgroundType, url, overlayColor, customOverlayColor, gradient, customGradient } ) => { + if ( url ) { + // If a url exists the transform could happen if that URL represents a video background. + return backgroundType === VIDEO_BACKGROUND_TYPE; + } + // If a url is not set the transform could happen if the cover has no background color or gradient; + return ! overlayColor && ! customOverlayColor && ! gradient && ! customGradient; }, transform: ( { title, url, align, id } ) => ( createBlock( 'core/video', { diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index 71d6eb65a27f53..296adef75a20e7 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -17,10 +17,9 @@ @import "./latest-posts/editor.scss"; @import "./legacy-widget/editor.scss"; @import "./media-text/editor.scss"; -@import "./list/editor.scss"; @import "./more/editor.scss"; -@import "./navigation-menu/editor.scss"; -@import "./navigation-menu-item/editor.scss"; +@import "./navigation/editor.scss"; +@import "./navigation-link/editor.scss"; @import "./nextpage/editor.scss"; @import "./paragraph/editor.scss"; @import "./pullquote/editor.scss"; @@ -37,12 +36,12 @@ @import "./text-columns/editor.scss"; @import "./verse/editor.scss"; @import "./video/editor.scss"; +@import "./site-title/editor.scss"; /** * Import styles from internal editor components used by the blocks. */ @import "./block/edit-panel/editor.scss"; -@import "./block/indicator/editor.scss"; /** * Editor Normalization Styles diff --git a/packages/block-library/src/embed/core-embeds.js b/packages/block-library/src/embed/core-embeds.js index 83063ff8a1e9a8..5fd67e73c648ab 100644 --- a/packages/block-library/src/embed/core-embeds.js +++ b/packages/block-library/src/embed/core-embeds.js @@ -137,13 +137,17 @@ export const others = [ patterns: [ /^https?:\/\/cloudup\.com\/.+/i ], }, { + // Deprecated since CollegeHumor content is now powered by YouTube name: 'core-embed/collegehumor', settings: { title: 'CollegeHumor', icon: embedVideoIcon, description: __( 'Embed CollegeHumor content.' ), + supports: { + inserter: false, + }, }, - patterns: [ /^https?:\/\/(www\.)?collegehumor\.com\/.+/i ], + patterns: [], }, { name: 'core-embed/crowdsignal', diff --git a/packages/block-library/src/embed/embed-controls.js b/packages/block-library/src/embed/embed-controls.js index fe115b403be224..09044b43b9abdf 100644 --- a/packages/block-library/src/embed/embed-controls.js +++ b/packages/block-library/src/embed/embed-controls.js @@ -2,7 +2,12 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { IconButton, Toolbar, PanelBody, ToggleControl } from '@wordpress/components'; +import { + IconButton, + PanelBody, + ToggleControl, + ToolbarGroup, +} from '@wordpress/components'; import { BlockControls, InspectorControls } from '@wordpress/block-editor'; const EmbedControls = ( props ) => { @@ -18,7 +23,7 @@ const EmbedControls = ( props ) => { return ( <> <BlockControls> - <Toolbar> + <ToolbarGroup> { showEditButton && ( <IconButton className="components-toolbar__control" @@ -27,7 +32,7 @@ const EmbedControls = ( props ) => { onClick={ switchBackToURLInput } /> ) } - </Toolbar> + </ToolbarGroup> </BlockControls> { themeSupportsResponsive && blockSupportsResponsive && ( <InspectorControls> diff --git a/packages/block-library/src/embed/style.scss b/packages/block-library/src/embed/style.scss index 5409085faf0206..a3e9ee13f4d945 100644 --- a/packages/block-library/src/embed/style.scss +++ b/packages/block-library/src/embed/style.scss @@ -9,6 +9,12 @@ } .wp-block-embed { + // Supply caption styles to embeds, even if the theme hasn't opted in. + // Reason being: the new markup, figcaptions, are not likely to be styled in the majority of existing themes, + // so we supply the styles so as to not appear broken or unstyled in those. + figcaption { + @include caption-style(); + } // The embed block is in a `figure` element, and many themes zero this out. // This rule explicitly sets it, to ensure at least some bottom-margin in the flow. margin-bottom: 1em; diff --git a/packages/block-library/src/embed/test/__snapshots__/index.js.snap b/packages/block-library/src/embed/test/__snapshots__/index.js.snap index 78923aa24c5f6b..2023cab5e5b65f 100644 --- a/packages/block-library/src/embed/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/embed/test/__snapshots__/index.js.snap @@ -61,7 +61,7 @@ exports[`core/embed block edit matches snapshot 1`] = ` > Learn more about embeds <span - class="screen-reader-text" + class="components-visually-hidden" > (opens in a new tab) </span> diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js index c6f6382a1336d4..918849fd88a341 100644 --- a/packages/block-library/src/file/edit.js +++ b/packages/block-library/src/file/edit.js @@ -15,7 +15,7 @@ import { Animate, ClipboardButton, IconButton, - Toolbar, + ToolbarGroup, withNotices, } from '@wordpress/components'; import { compose } from '@wordpress/compose'; @@ -190,7 +190,7 @@ class FileEdit extends Component { /> <BlockControls> <MediaUploadCheck> - <Toolbar> + <ToolbarGroup> <MediaUpload onSelect={ this.onSelectFile } value={ id } @@ -203,7 +203,7 @@ class FileEdit extends Component { /> ) } /> - </Toolbar> + </ToolbarGroup> </MediaUploadCheck> </BlockControls> <Animate type={ isBlobURL( href ) ? 'loading' : null }> @@ -257,11 +257,11 @@ export default compose( [ withSelect( ( select, props ) => { const { getMedia } = select( 'core' ); const { getSettings } = select( 'core/block-editor' ); - const { __experimentalMediaUpload } = getSettings(); + const { mediaUpload } = getSettings(); const { id } = props.attributes; return { media: id === undefined ? undefined : getMedia( id ), - mediaUpload: __experimentalMediaUpload, + mediaUpload, }; } ), withNotices, diff --git a/packages/block-library/src/file/index.js b/packages/block-library/src/file/index.js index 86efc4291d56f9..b11b05080b9cf2 100644 --- a/packages/block-library/src/file/index.js +++ b/packages/block-library/src/file/index.js @@ -20,7 +20,7 @@ export const settings = { title: __( 'File' ), description: __( 'Add a link to a downloadable file.' ), icon, - keywords: [ __( 'document' ), __( 'pdf' ) ], + keywords: [ __( 'document' ), __( 'pdf' ), __( 'download' ) ], supports: { align: true, }, diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index 449711a356ea42..bb4cdb7d2576f4 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -1,7 +1,6 @@ /** * External dependencies */ -import classnames from 'classnames'; import { every, filter, @@ -23,22 +22,21 @@ import { withNotices, } from '@wordpress/components'; import { - BlockIcon, MediaPlaceholder, InspectorControls, - RichText, } from '@wordpress/block-editor'; -import { Component } from '@wordpress/element'; -import { __, sprintf } from '@wordpress/i18n'; +import { Component, Platform } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; import { getBlobByURL, isBlobURL, revokeBlobURL } from '@wordpress/blob'; import { withSelect } from '@wordpress/data'; +import { withViewportMatch } from '@wordpress/viewport'; /** * Internal dependencies */ -import GalleryImage from './gallery-image'; -import { icon } from './icons'; +import { sharedIcon } from './shared-icon'; import { defaultColumnsNumber, pickRelevantMediaFiles } from './shared'; +import Gallery from './gallery'; const MAX_COLUMNS = 8; const linkOptions = [ @@ -48,6 +46,18 @@ const linkOptions = [ ]; const ALLOWED_MEDIA_TYPES = [ 'image' ]; +const PLACEHOLDER_TEXT = Platform.select( { + web: __( 'Drag images, upload new ones or select files from your library.' ), + native: __( 'ADD MEDIA' ), +} ); + +// currently this is needed for consistent controls UI on mobile +// this can be removed after control components settle on consistent defaults +const MOBILE_CONTROL_PROPS = Platform.select( { + web: {}, + native: { separatorType: 'fullWidth' }, +} ); + class GalleryEdit extends Component { constructor() { super( ...arguments ); @@ -227,7 +237,11 @@ class GalleryEdit extends Component { componentDidMount() { const { attributes, mediaUpload } = this.props; const { images } = attributes; - if ( every( images, ( { url } ) => isBlobURL( url ) ) ) { + if ( + Platform.OS === 'web' && + images && images.length > 0 && + every( images, ( { url } ) => isBlobURL( url ) ) + ) { const filesList = map( images, ( { url } ) => getBlobByURL( url ) ); forEach( images, ( { url } ) => revokeBlobURL( url ) ); mediaUpload( { @@ -254,12 +268,9 @@ class GalleryEdit extends Component { className, isSelected, noticeUI, - setAttributes, } = this.props; const { - align, columns = defaultColumnsNumber( attributes ), - caption, imageCrop, images, linkTo, @@ -274,10 +285,10 @@ class GalleryEdit extends Component { isAppender={ hasImages } className={ className } disableMediaButtons={ hasImages && ! isSelected } - icon={ ! hasImages && <BlockIcon icon={ icon } /> } + icon={ ! hasImages && sharedIcon } labels={ { title: ! hasImages && __( 'Gallery' ), - instructions: ! hasImages && __( 'Drag images, upload new ones or select files from your library.' ), + instructions: ! hasImages && PLACEHOLDER_TEXT, } } onSelect={ this.onSelectImages } accept="image/*" @@ -286,25 +297,20 @@ class GalleryEdit extends Component { value={ hasImagesWithId ? images : undefined } onError={ this.onUploadError } notices={ hasImages ? undefined : noticeUI } + onFocus={ this.props.onFocus } /> ); if ( ! hasImages ) { return mediaPlaceholder; } - - const captionClassNames = classnames( - 'blocks-gallery-caption', - { - 'screen-reader-text': ! isSelected && RichText.isEmpty( caption ), - } - ); return ( <> <InspectorControls> <PanelBody title={ __( 'Gallery Settings' ) }> { images.length > 1 && <RangeControl label={ __( 'Columns' ) } + { ...MOBILE_CONTROL_PROPS } value={ columns } onChange={ this.setColumnsNumber } min={ 1 } @@ -313,12 +319,14 @@ class GalleryEdit extends Component { /> } <ToggleControl label={ __( 'Crop Images' ) } + { ...MOBILE_CONTROL_PROPS } checked={ !! imageCrop } onChange={ this.toggleImageCrop } help={ this.getImageCropHelp } /> <SelectControl label={ __( 'Link To' ) } + { ...MOBILE_CONTROL_PROPS } value={ linkTo } onChange={ this.setLinkTo } options={ linkOptions } @@ -326,52 +334,17 @@ class GalleryEdit extends Component { </PanelBody> </InspectorControls> { noticeUI } - <figure className={ classnames( - className, - { - [ `align${ align }` ]: align, - [ `columns-${ columns }` ]: columns, - 'is-cropped': imageCrop, - } - ) } - > - <ul className="blocks-gallery-grid"> - { images.map( ( img, index ) => { - /* translators: %1$d is the order number of the image, %2$d is the total number of images. */ - const ariaLabel = sprintf( __( 'image %1$d of %2$d in gallery' ), ( index + 1 ), images.length ); - - return ( - <li className="blocks-gallery-item" key={ img.id || img.url }> - <GalleryImage - url={ img.url } - alt={ img.alt } - id={ img.id } - isFirstItem={ index === 0 } - isLastItem={ ( index + 1 ) === images.length } - isSelected={ isSelected && this.state.selectedImage === index } - onMoveBackward={ this.onMoveBackward( index ) } - onMoveForward={ this.onMoveForward( index ) } - onRemove={ this.onRemoveImage( index ) } - onSelect={ this.onSelectImage( index ) } - setAttributes={ ( attrs ) => this.setImageAttributes( index, attrs ) } - caption={ img.caption } - aria-label={ ariaLabel } - /> - </li> - ); - } ) } - </ul> - { mediaPlaceholder } - <RichText - tagName="figcaption" - className={ captionClassNames } - placeholder={ __( 'Write gallery caption…' ) } - value={ caption } - unstableOnFocus={ this.onFocusGalleryCaption } - onChange={ ( value ) => setAttributes( { caption: value } ) } - inlineToolbar - /> - </figure> + <Gallery + { ...this.props } + selectedImage={ this.state.selectedImage } + mediaPlaceholder={ mediaPlaceholder } + onMoveBackward={ this.onMoveBackward } + onMoveForward={ this.onMoveForward } + onRemoveImage={ this.onRemoveImage } + onSelectImage={ this.onSelectImage } + onSetImageAttributes={ this.setImageAttributes } + onFocusGalleryCaption={ this.onFocusGalleryCaption } + /> </> ); } @@ -379,13 +352,9 @@ class GalleryEdit extends Component { export default compose( [ withSelect( ( select ) => { const { getSettings } = select( 'core/block-editor' ); - const { - __experimentalMediaUpload, - } = getSettings(); - - return { - mediaUpload: __experimentalMediaUpload, - }; + const { mediaUpload } = getSettings(); + return { mediaUpload }; } ), withNotices, + withViewportMatch( { isNarrow: '< small' } ), ] )( GalleryEdit ); diff --git a/packages/block-library/src/gallery/editor.scss b/packages/block-library/src/gallery/editor.scss index ae36e0aa74ac33..5931d95b5300ff 100644 --- a/packages/block-library/src/gallery/editor.scss +++ b/packages/block-library/src/gallery/editor.scss @@ -17,8 +17,10 @@ figure.wp-block-gallery { margin: 0; } -// need to override default editor ul styles +// Necessary to to override default editor ul styles. .blocks-gallery-grid.blocks-gallery-grid { + padding-left: 0; + margin-left: 0; margin-bottom: 0; } diff --git a/packages/block-library/src/gallery/gallery-button.native.js b/packages/block-library/src/gallery/gallery-button.native.js new file mode 100644 index 00000000000000..8804e99cf2e7e6 --- /dev/null +++ b/packages/block-library/src/gallery/gallery-button.native.js @@ -0,0 +1,47 @@ +/** + * External dependencies + */ +import { StyleSheet, TouchableOpacity } from 'react-native'; + +/** + * WordPress dependencies + */ +import { Icon } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import style from './gallery-image-style.scss'; + +export function Button( props ) { + const { + icon, + iconSize = 24, + onClick, + disabled, + 'aria-disabled': ariaDisabled, + accessibilityLabel = 'button', + style: customStyle, + } = props; + + const buttonStyle = StyleSheet.compose( style.buttonActive, customStyle ); + + const isDisabled = disabled || ariaDisabled; + + const { fill } = isDisabled ? style.buttonDisabled : style.button; + + return ( + <TouchableOpacity + style={ buttonStyle } + activeOpacity={ 0.7 } + accessibilityLabel={ accessibilityLabel } + accessibilityRole={ 'button' } + onPress={ onClick } + disabled={ isDisabled } + > + <Icon icon={ icon } fill={ fill } size={ iconSize } /> + </TouchableOpacity> + ); +} + +export default Button; diff --git a/packages/block-library/src/gallery/gallery-image-style.native.scss b/packages/block-library/src/gallery/gallery-image-style.native.scss new file mode 100644 index 00000000000000..07ab8cf39acc2d --- /dev/null +++ b/packages/block-library/src/gallery/gallery-image-style.native.scss @@ -0,0 +1,161 @@ +$get-gallery-image-container-height: 150px; +$get-overlay-border-width: 3px; +$get-caption-background-color: rgba(0, 0, 0, 0.4); + +.galleryImageContainer { + flex: 1; + height: $get-gallery-image-container-height; + background-color: $gray-lighten-30; +} + +.galleryImageContainerDark { + background-color: $gray-90; +} + +.image { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + opacity: 1; +} + +.imageUploading { + opacity: 0.3; +} + +.overlay { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + border-width: $get-overlay-border-width; + border-color: #0000; +} + +.overlaySelected { + border-color: $blue-wordpress; +} + +.button { + fill: $gray-0; + width: 30px; +} + +.buttonDisabled { + fill: $gray-30; +} + +.buttonActive { + flex-direction: row; + justify-content: center; + align-items: center; + border-radius: 6px; + border-color: $gray-70; + background-color: $gray-70; +} + +.moverButtonContainer { + flex-direction: row; + align-items: center; + border-radius: 3px; + background-color: $gray-70; +} + +.separator { + border-right-color: $gray-30; + // border-right-width: StyleSheet.hairlineWidth; + border-right-width: 1px; + height: 20px; +} + +.removeButton { + width: 30px; + border-radius: 30px; +} + +.toolbar { + padding: 5px; + flex-direction: row; + justify-content: space-between; +} + +.uploadFailedContainer { + position: absolute; + background-color: rgba(0, 0, 0, 0.5); + width: 100%; + height: 100%; + justify-content: center; + align-items: center; +} + +.uploadFailed { + width: 24px; + height: 24px; + justify-content: center; + align-items: center; +} + +.uploadFailedIcon { + fill: #fff; + width: 100%; + height: 100%; +} + +.uploadFailedText { + color: #fff; + font-size: 14px; + margin-top: 5px; +} + +.captionContainer { + flex: 1; + flex-direction: row; + align-items: flex-end; +} + +@mixin caption-shared { + font-size: 12px; + background-color: #0000; + color: #fff; + font-family: $default-regular-font; + min-height: $min-height-paragraph; + text-align: center; +} + +.caption { + @include caption-shared; + background-color: $get-caption-background-color; +} + +.captionPlaceholder { + color: #ccc; +} + +/* +.captionDark { + background-color: #0007; +} +*/ + +// expand caption container to compensate for overlay border +.captionExpandedContainer { + $width: $get-overlay-border-width; + // constrain height to gallery image height for caption scroll + max-height: $get-gallery-image-container-height; + position: absolute; + bottom: - $width; + left: - $width; + right: - $width; + padding-bottom: $width; + padding-left: $width; + padding-right: $width; + // use caption background color on container when expanded + background-color: $get-caption-background-color; +} + +.captionExpanded { + @include caption-shared; +} diff --git a/packages/block-library/src/gallery/gallery-image.native.js b/packages/block-library/src/gallery/gallery-image.native.js new file mode 100644 index 00000000000000..60d6138521c5fd --- /dev/null +++ b/packages/block-library/src/gallery/gallery-image.native.js @@ -0,0 +1,298 @@ +/** + * External dependencies + */ +import { Image, StyleSheet, View, ScrollView, Text, TouchableWithoutFeedback, Platform } from 'react-native'; +import { + requestImageFailedRetryDialog, + requestImageUploadCancelDialog, +} from 'react-native-gutenberg-bridge'; +import { isEmpty } from 'lodash'; + +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { Icon } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { RichText, MediaUploadProgress } from '@wordpress/block-editor'; +import { isURL } from '@wordpress/url'; +import { withPreferredColorScheme } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import Button from './gallery-button'; +import style from './gallery-image-style.scss'; + +const { compose } = StyleSheet; + +const separatorStyle = compose( style.separator, { borderRightWidth: StyleSheet.hairlineWidth } ); +const buttonStyle = compose( style.button, { aspectRatio: 1 } ); +const removeButtonStyle = compose( style.removeButton, { aspectRatio: 1 } ); +const ICON_SIZE_ARROW = 15; +const ICON_SIZE_REMOVE = 20; + +// this platform difference is needed to avoid a regression described here: +// https://github.com/WordPress/gutenberg/pull/18818#issuecomment-559818548 +const CAPTION_TAG_NAME = Platform.select( { + ios: 'p', + android: '', +} ); + +class GalleryImage extends Component { + constructor() { + super( ...arguments ); + + this.onSelectImage = this.onSelectImage.bind( this ); + this.onSelectCaption = this.onSelectCaption.bind( this ); + this.onMediaPressed = this.onMediaPressed.bind( this ); + this.bindContainer = this.bindContainer.bind( this ); + + this.updateMediaProgress = this.updateMediaProgress.bind( this ); + this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this ); + this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( this ); + this.renderContent = this.renderContent.bind( this ); + + this.state = { + captionSelected: false, + isUploadInProgress: false, + didUploadFail: false, + }; + } + + bindContainer( ref ) { + this.container = ref; + } + + onSelectCaption() { + if ( ! this.state.captionSelected ) { + this.setState( { + captionSelected: true, + } ); + } + + if ( ! this.props.isSelected ) { + this.props.onSelect(); + } + } + + onMediaPressed() { + const { id, url } = this.props; + + this.onSelectImage(); + + if ( this.state.isUploadInProgress ) { + requestImageUploadCancelDialog( id ); + } else if ( ( this.state.didUploadFail ) || ( id && ! isURL( url ) ) ) { + requestImageFailedRetryDialog( id ); + } + } + + onSelectImage() { + if ( ! this.props.isBlockSelected ) { + this.props.onSelectBlock(); + } + + if ( ! this.props.isSelected ) { + this.props.onSelect(); + } + + if ( this.state.captionSelected ) { + this.setState( { + captionSelected: false, + } ); + } + } + + componentDidUpdate( prevProps ) { + const { isSelected, image, url } = this.props; + if ( image && ! url ) { + this.props.setAttributes( { + url: image.source_url, + alt: image.alt_text, + } ); + } + + // unselect the caption so when the user selects other image and comeback + // the caption is not immediately selected + if ( this.state.captionSelected && ! isSelected && prevProps.isSelected ) { + this.setState( { + captionSelected: false, + } ); + } + } + + updateMediaProgress() { + if ( ! this.state.isUploadInProgress ) { + this.setState( { isUploadInProgress: true } ); + } + } + + finishMediaUploadWithSuccess( payload ) { + this.props.setAttributes( { + id: payload.mediaServerId, + url: payload.mediaUrl, + } ); + + this.setState( { + isUploadInProgress: false, + didUploadFail: false, + } ); + } + + finishMediaUploadWithFailure() { + this.setState( { + isUploadInProgress: false, + didUploadFail: true, + } ); + } + + renderContent( params ) { + const { + url, isFirstItem, isLastItem, isSelected, caption, onRemove, + onMoveForward, onMoveBackward, setAttributes, 'aria-label': ariaLabel, + isCropped, getStylesFromColorScheme } = this.props; + + const { isUploadInProgress } = this.state; + const { isUploadFailed, retryMessage } = params; + const resizeMode = isCropped ? 'cover' : 'contain'; + + const imageStyle = [ style.image, { resizeMode }, + isUploadInProgress ? style.imageUploading : undefined, + ]; + + const overlayStyle = compose( style.overlay, + isSelected ? style.overlaySelected : undefined, + ); + + const captionPlaceholderStyle = getStylesFromColorScheme( style.captionPlaceholder, style.captionPlaceholderDark ); + + const shouldShowCaptionEditable = ! isUploadFailed && isSelected; + const shouldShowCaptionExpanded = ! isUploadFailed && ( ! isSelected && !! caption ); + + const captionContainerStyle = shouldShowCaptionExpanded ? + style.captionExpandedContainer : + style.captionContainer; + + const captionStyle = shouldShowCaptionExpanded ? + style.captionExpanded : + getStylesFromColorScheme( style.caption, style.captionDark ); + + return ( + <> + <Image + style={ imageStyle } + source={ { uri: url } } + // alt={ alt } + accessibilityLabel={ ariaLabel } + ref={ this.bindContainer } + /> + { isUploadFailed && ( + <View style={ style.uploadFailedContainer }> + <View style={ style.uploadFailed }> + <Icon icon={ 'warning' } { ...style.uploadFailedIcon } /> + </View> + <Text style={ style.uploadFailedText }>{ retryMessage }</Text> + </View> + ) } + <View style={ overlayStyle }> + { ! isUploadInProgress && ( + <> + { isSelected && ( + <View style={ style.toolbar }> + <View style={ style.moverButtonContainer } > + <Button + style={ buttonStyle } + icon="arrow-left-alt" + iconSize={ ICON_SIZE_ARROW } + onClick={ isFirstItem ? undefined : onMoveBackward } + accessibilityLabel={ __( 'Move Image Backward' ) } + aria-disabled={ isFirstItem } + disabled={ ! isSelected } + /> + <View style={ separatorStyle }></View> + <Button + style={ buttonStyle } + icon="arrow-right-alt" + iconSize={ ICON_SIZE_ARROW } + onClick={ isLastItem ? undefined : onMoveForward } + accessibilityLabel={ __( 'Move Image Forward' ) } + aria-disabled={ isLastItem } + disabled={ ! isSelected } + /> + </View> + <Button + style={ removeButtonStyle } + icon="no-alt" + iconSize={ ICON_SIZE_REMOVE } + onClick={ onRemove } + accessibilityLabel={ __( 'Remove Image' ) } + disabled={ ! isSelected } + /> + </View> + ) } + { ( shouldShowCaptionEditable || shouldShowCaptionExpanded ) && ( + <View style={ captionContainerStyle }> + <ScrollView nestedScrollEnabled keyboardShouldPersistTaps="handled"> + <RichText + tagName={ CAPTION_TAG_NAME } + rootTagsToEliminate={ [ 'p' ] } + placeholder={ isSelected ? __( 'Write caption…' ) : null } + value={ caption } + isSelected={ this.state.captionSelected } + onChange={ ( newCaption ) => setAttributes( { caption: newCaption } ) } + unstableOnFocus={ this.onSelectCaption } + fontSize={ captionStyle.fontSize } + style={ captionStyle } + textAlign={ captionStyle.textAlign } + placeholderTextColor={ captionPlaceholderStyle.color } + inlineToolbar + /> + </ScrollView> + </View> + ) } + </> + ) } + </View> + </> + ); + } + + render() { + const { id, onRemove, getStylesFromColorScheme, isSelected } = this.props; + + const containerStyle = getStylesFromColorScheme( style.galleryImageContainer, + style.galleryImageContainerDark ); + + return ( + <TouchableWithoutFeedback + onPress={ this.onMediaPressed } + accessible={ ! isSelected } // We need only child views to be accessible after the selection + accessibilityLabel={ this.accessibilityLabelImageContainer() } // if we don't set this explicitly it reads system provided accessibilityLabels of all child components and those include pretty technical words which don't make sense + accessibilityRole={ 'imagebutton' } // this makes VoiceOver to read a description of image provided by system on iOS and lets user know this is a button which conveys the message of tappablity + > + <View style={ containerStyle }> + <MediaUploadProgress + mediaId={ id } + onUpdateMediaProgress={ this.updateMediaProgress } + onFinishMediaUploadWithSuccess={ this.finishMediaUploadWithSuccess } + onFinishMediaUploadWithFailure={ this.finishMediaUploadWithFailure } + onMediaUploadStateReset={ onRemove } + renderContent={ this.renderContent } + /> + </View> + </TouchableWithoutFeedback> + ); + } + + accessibilityLabelImageContainer() { + const { caption, 'aria-label': ariaLabel } = this.props; + + return isEmpty( caption ) ? ariaLabel : ( ariaLabel + '. ' + sprintf( + /* translators: accessibility text. %s: image caption. */ + __( 'Image caption. %s' ), caption + ) ); + } +} + +export default withPreferredColorScheme( GalleryImage ); diff --git a/packages/block-library/src/gallery/gallery-styles.native.scss b/packages/block-library/src/gallery/gallery-styles.native.scss new file mode 100644 index 00000000000000..3d50b4f47f3a4e --- /dev/null +++ b/packages/block-library/src/gallery/gallery-styles.native.scss @@ -0,0 +1,3 @@ +.galleryTilesContainerSelected { + margin-bottom: 16px; +} diff --git a/packages/block-library/src/gallery/gallery.js b/packages/block-library/src/gallery/gallery.js new file mode 100644 index 00000000000000..2f1a53156c522f --- /dev/null +++ b/packages/block-library/src/gallery/gallery.js @@ -0,0 +1,101 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { + RichText, +} from '@wordpress/block-editor'; +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import GalleryImage from './gallery-image'; +import { defaultColumnsNumber } from './shared'; + +export const Gallery = ( props ) => { + const { + attributes, + className, + isSelected, + setAttributes, + selectedImage, + mediaPlaceholder, + onMoveBackward, + onMoveForward, + onRemoveImage, + onSelectImage, + onSetImageAttributes, + onFocusGalleryCaption, + } = props; + + const { + align, + columns = defaultColumnsNumber( attributes ), + caption, + imageCrop, + images, + } = attributes; + + const captionClassNames = classnames( + 'blocks-gallery-caption', + { + 'screen-reader-text': ! isSelected && RichText.isEmpty( caption ), + } + ); + + return ( + <figure className={ classnames( + className, + { + [ `align${ align }` ]: align, + [ `columns-${ columns }` ]: columns, + 'is-cropped': imageCrop, + } + ) } + > + <ul className="blocks-gallery-grid"> + { images.map( ( img, index ) => { + /* translators: %1$d is the order number of the image, %2$d is the total number of images. */ + const ariaLabel = sprintf( __( 'image %1$d of %2$d in gallery' ), ( index + 1 ), images.length ); + + return ( + <li className="blocks-gallery-item" key={ img.id || img.url }> + <GalleryImage + url={ img.url } + alt={ img.alt } + id={ img.id } + isFirstItem={ index === 0 } + isLastItem={ ( index + 1 ) === images.length } + isSelected={ isSelected && selectedImage === index } + onMoveBackward={ onMoveBackward( index ) } + onMoveForward={ onMoveForward( index ) } + onRemove={ onRemoveImage( index ) } + onSelect={ onSelectImage( index ) } + setAttributes={ ( attrs ) => onSetImageAttributes( index, attrs ) } + caption={ img.caption } + aria-label={ ariaLabel } + /> + </li> + ); + } ) } + </ul> + { mediaPlaceholder } + <RichText + tagName="figcaption" + className={ captionClassNames } + placeholder={ __( 'Write gallery caption…' ) } + value={ caption } + unstableOnFocus={ onFocusGalleryCaption } + onChange={ ( value ) => setAttributes( { caption: value } ) } + inlineToolbar + /> + </figure> + ); +}; + +export default Gallery; diff --git a/packages/block-library/src/gallery/gallery.native.js b/packages/block-library/src/gallery/gallery.native.js new file mode 100644 index 00000000000000..b4f0cb8ba881a1 --- /dev/null +++ b/packages/block-library/src/gallery/gallery.native.js @@ -0,0 +1,132 @@ +/** + * External dependencies + */ +import { View } from 'react-native'; +import { isEmpty } from 'lodash'; + +/** + * Internal dependencies + */ +import GalleryImage from './gallery-image'; +import { defaultColumnsNumber } from './shared'; +import styles from './gallery-styles.scss'; +import Tiles from './tiles'; + +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; +import { Caption } from '@wordpress/block-editor'; +import { useState } from '@wordpress/element'; + +const TILE_SPACING = 15; + +// we must limit displayed columns since readable content max-width is 580px +const MAX_DISPLAYED_COLUMNS = 4; +const MAX_DISPLAYED_COLUMNS_NARROW = 2; + +export const Gallery = ( props ) => { + const [ isCaptionSelected, setIsCaptionSelected ] = useState( false ); + + const { + clientId, + selectedImage, + mediaPlaceholder, + onBlur, + onMoveBackward, + onMoveForward, + onRemoveImage, + onSelectImage, + onSetImageAttributes, + onFocusGalleryCaption, + attributes, + isSelected, + isNarrow, + onFocus, + } = props; + + const { + columns = defaultColumnsNumber( attributes ), + imageCrop, + images, + } = attributes; + + // limit displayed columns when isNarrow is true (i.e. when viewport width is + // less than "small", where small = 600) + const displayedColumns = isNarrow ? + Math.min( columns, MAX_DISPLAYED_COLUMNS_NARROW ) : + Math.min( columns, MAX_DISPLAYED_COLUMNS ); + + const selectImage = ( index ) => { + return () => { + if ( isCaptionSelected ) { + setIsCaptionSelected( false ); + } + // we need to fully invoke the curried function here + onSelectImage( index )(); + }; + }; + + const focusGalleryCaption = () => { + if ( ! isCaptionSelected ) { + setIsCaptionSelected( true ); + } + onFocusGalleryCaption(); + }; + + return ( + <View> + <Tiles + columns={ displayedColumns } + spacing={ TILE_SPACING } + style={ isSelected ? styles.galleryTilesContainerSelected : undefined } + > + { images.map( ( img, index ) => { + /* translators: %1$d is the order number of the image, %2$d is the total number of images. */ + const ariaLabel = sprintf( __( 'image %1$d of %2$d in gallery' ), ( index + 1 ), images.length ); + + return ( + <GalleryImage + key={ img.id || img.url } + url={ img.url } + alt={ img.alt } + id={ img.id } + isCropped={ imageCrop } + isFirstItem={ index === 0 } + isLastItem={ ( index + 1 ) === images.length } + isSelected={ isSelected && selectedImage === index } + isBlockSelected={ isSelected } + onMoveBackward={ onMoveBackward( index ) } + onMoveForward={ onMoveForward( index ) } + onRemove={ onRemoveImage( index ) } + onSelect={ selectImage( index ) } + onSelectBlock={ onFocus } + setAttributes={ ( attrs ) => onSetImageAttributes( index, attrs ) } + caption={ img.caption } + aria-label={ ariaLabel } + /> + ); + } ) } + </Tiles> + { mediaPlaceholder } + <Caption + clientId={ clientId } + isSelected={ isCaptionSelected } + accessible={ true } + accessibilityLabelCreator={ ( caption ) => + isEmpty( caption ) ? + /* translators: accessibility text. Empty gallery caption. */ + ( 'Gallery caption. Empty' ) : + sprintf( + /* translators: accessibility text. %s: gallery caption. */ + __( 'Gallery caption. %s' ), + caption ) + } + onFocus={ focusGalleryCaption } + onBlur={ onBlur } // always assign onBlur as props + /> + </View> + ); +}; + +export default Gallery; diff --git a/packages/block-library/src/gallery/index.js b/packages/block-library/src/gallery/index.js index 0cc414f81100a5..bfeed76dc1a474 100644 --- a/packages/block-library/src/gallery/index.js +++ b/packages/block-library/src/gallery/index.js @@ -26,8 +26,8 @@ export const settings = { attributes: { columns: 2, images: [ - { url: 'https://upload.wikimedia.org/wikipedia/commons/c/c3/Glacial_lakes%2C_Bhutan.jpg' }, - { url: 'https://upload.wikimedia.org/wikipedia/commons/0/01/Sediment_off_the_Yucatan_Peninsula.jpg' }, + { url: 'https://s.w.org/images/core/5.3/Glacial_lakes%2C_Bhutan.jpg' }, + { url: 'https://s.w.org/images/core/5.3/Sediment_off_the_Yucatan_Peninsula.jpg' }, ], }, }, diff --git a/packages/block-library/src/gallery/shared-icon.js b/packages/block-library/src/gallery/shared-icon.js new file mode 100644 index 00000000000000..68ec4aa0f75dda --- /dev/null +++ b/packages/block-library/src/gallery/shared-icon.js @@ -0,0 +1,11 @@ +/** + * WordPress dependencies + */ +import { BlockIcon } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import { icon } from './icons.js'; + +export const sharedIcon = <BlockIcon icon={ icon } />; diff --git a/packages/block-library/src/gallery/shared-icon.native.js b/packages/block-library/src/gallery/shared-icon.native.js new file mode 100644 index 00000000000000..30ccbf2929dc5e --- /dev/null +++ b/packages/block-library/src/gallery/shared-icon.native.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { Icon } from '@wordpress/components'; +import { withPreferredColorScheme } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import { icon } from './icons.js'; +import styles from './styles.scss'; + +const IconWithColorScheme = withPreferredColorScheme( ( { getStylesFromColorScheme } ) => { + const colorSchemeStyles = getStylesFromColorScheme( styles.icon, styles.iconDark ); + return <Icon icon={ icon } { ...colorSchemeStyles } />; +} ); + +export const sharedIcon = <IconWithColorScheme />; diff --git a/packages/block-library/src/gallery/styles.native.scss b/packages/block-library/src/gallery/styles.native.scss new file mode 100644 index 00000000000000..f5c00393677262 --- /dev/null +++ b/packages/block-library/src/gallery/styles.native.scss @@ -0,0 +1,7 @@ +.icon { + fill: $gray-dark; +} + +.iconDark { + fill: $white; +} diff --git a/packages/block-library/src/gallery/tiles-styles.native.scss b/packages/block-library/src/gallery/tiles-styles.native.scss new file mode 100644 index 00000000000000..c2e3d4bad79790 --- /dev/null +++ b/packages/block-library/src/gallery/tiles-styles.native.scss @@ -0,0 +1,11 @@ +.containerStyle { + flex-direction: row; + flex-wrap: wrap; +} + +.tileStyle { + overflow: hidden; + flex-direction: row; + align-items: center; + border-color: transparent; +} diff --git a/packages/block-library/src/gallery/tiles.native.js b/packages/block-library/src/gallery/tiles.native.js new file mode 100644 index 00000000000000..432f4bd69a308a --- /dev/null +++ b/packages/block-library/src/gallery/tiles.native.js @@ -0,0 +1,80 @@ +/** + * External dependencies + */ +import { View, StyleSheet } from 'react-native'; + +/** + * WordPress dependencies + */ +import { Children } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import styles from './tiles-styles.scss'; + +function Tiles( props ) { + const { + columns, + children, + spacing = 10, + style, + } = props; + + const { compose } = StyleSheet; + + const tileCount = Children.count( children ); + const lastTile = tileCount - 1; + const lastRow = Math.floor( lastTile / columns ); + + const wrappedChildren = Children.map( children, ( child, index ) => { + /** Since we don't have `calc()`, we must calculate our spacings here in + * order to preserve even spacing between tiles and equal width for tiles + * in a given row. + * + * In order to ensure equal sizing of tile contents, we distribute the + * spacing such that each tile has an equal "share" of the fixed spacing. To + * keep the tiles properly aligned within their rows, we calculate the left + * and right paddings based on the tile's relative position within the row. + * + * Note: we use padding instead of margins so that the fixed spacing is + * included within the relative spacing (i.e. width percentage), and + * wrapping behavior is preserved. + * + * - The left most tile in a row must have left padding of zero. + * - The right most tile in a row must have a right padding of zero. + * + * The values of these left and right paddings are interpolated for tiles in + * between. The right padding is complementary with the left padding of the + * next tile (i.e. the right padding of [tile n] + the left padding of + * [tile n + 1] will be equal for all tiles except the last one in a given + * row). + */ + + const row = Math.floor( index / columns ); + const rowLength = row === lastRow ? ( lastTile % columns ) + 1 : columns; + const indexInRow = index % columns; + + return ( + <View style={ [ styles.tileStyle, { + width: `${ 100 / rowLength }%`, + paddingLeft: spacing * ( indexInRow / rowLength ), + paddingRight: spacing * ( 1 - ( ( indexInRow + 1 ) / rowLength ) ), + paddingTop: row === 0 ? 0 : spacing / 2, + paddingBottom: row === lastRow ? 0 : spacing / 2, + } ] }> + { child } + </View> + ); + } ); + + const containerStyle = compose( styles.containerStyle, style ); + + return ( + <View style={ containerStyle }> + { wrappedChildren } + </View> + ); +} + +export default Tiles; diff --git a/packages/block-library/src/group/edit.native.js b/packages/block-library/src/group/edit.native.js index 32c87e3863c881..3cfdd34700b81f 100644 --- a/packages/block-library/src/group/edit.native.js +++ b/packages/block-library/src/group/edit.native.js @@ -8,7 +8,7 @@ import { View } from 'react-native'; * WordPress dependencies */ import { withSelect } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; +import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { InnerBlocks, withColors, @@ -21,10 +21,14 @@ import styles from './editor.scss'; function GroupEdit( { hasInnerBlocks, isSelected, + getStylesFromColorScheme, } ) { if ( ! isSelected && ! hasInnerBlocks ) { return ( - <View style={ styles.groupPlaceholder } /> + <View style={ [ + getStylesFromColorScheme( styles.groupPlaceholder, styles.groupPlaceholderDark ), + ! hasInnerBlocks && { ...styles.marginVerticalDense, ...styles.marginHorizontalNone }, + ] } /> ); } @@ -48,4 +52,5 @@ export default compose( [ hasInnerBlocks: !! ( block && block.innerBlocks.length ), }; } ), + withPreferredColorScheme, ] )( GroupEdit ); diff --git a/packages/block-library/src/group/editor.native.scss b/packages/block-library/src/group/editor.native.scss index 5edfa582287ef5..2f426176fe22a2 100644 --- a/packages/block-library/src/group/editor.native.scss +++ b/packages/block-library/src/group/editor.native.scss @@ -1,6 +1,24 @@ .groupPlaceholder { - padding: 12px; + padding: $block-selected-to-content; + margin: $block-edge-to-content; + margin-left: $block-selected-child-margin; + margin-right: $block-selected-child-margin; background-color: $white; - border: $border-width dashed $light-gray-500; + border: $border-width dashed $gray; border-radius: 4px; } + +.groupPlaceholderDark { + background-color: $black; + border: $border-width dashed $gray-70; +} + +.marginVerticalDense { + margin-top: $block-selected-child-margin; + margin-bottom: $block-selected-child-margin; +} + +.marginHorizontalNone { + margin-left: 0; + margin-right: 0; +} diff --git a/packages/block-library/src/group/editor.scss b/packages/block-library/src/group/editor.scss index 166d3992c6daf9..9ef7f28eae6b4d 100644 --- a/packages/block-library/src/group/editor.scss +++ b/packages/block-library/src/group/editor.scss @@ -86,20 +86,6 @@ } } -// Add padding when the block is selected, for easier interaction. -.block-editor-block-list__layout .block-editor-block-list__block[data-type="core/group"].has-child-selected > .block-editor-block-list__block-edit > [data-block] > .wp-block-group, -.block-editor-block-list__layout .block-editor-block-list__block[data-type="core/group"].is-selected > .block-editor-block-list__block-edit > [data-block] > .wp-block-group { - - > .wp-block-group__inner-container > .block-editor-inner-blocks { - padding: $block-padding; - } - - &:not(.has-background) > .wp-block-group__inner-container > .block-editor-inner-blocks > .block-editor-block-list__layout { - margin-top: -$block-padding * 2; - margin-bottom: -$block-padding * 2; - } -} - // Place block list appender in the same place content will appear. [data-type="core/group"].is-selected { diff --git a/packages/block-library/src/group/index.js b/packages/block-library/src/group/index.js index d3bcfb86dca119..956ded85aa22a1 100644 --- a/packages/block-library/src/group/index.js +++ b/packages/block-library/src/group/index.js @@ -97,9 +97,9 @@ export const settings = { const alignments = [ 'wide', 'full' ]; // Determine the widest setting of all the blocks to be grouped - const widestAlignment = blocks.reduce( ( result, block ) => { + const widestAlignment = blocks.reduce( ( accumulator, block ) => { const { align } = block.attributes; - return alignments.indexOf( align ) > alignments.indexOf( result ) ? align : result; + return alignments.indexOf( align ) > alignments.indexOf( accumulator ) ? align : accumulator; }, undefined ); // Clone the Blocks to be Grouped diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index d500309a0c9aad..fd7b336ba1afd0 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -13,38 +13,14 @@ import HeadingToolbar from './heading-toolbar'; */ import { __ } from '@wordpress/i18n'; import { PanelBody } from '@wordpress/components'; -import { compose } from '@wordpress/compose'; import { createBlock } from '@wordpress/blocks'; import { AlignmentToolbar, BlockControls, InspectorControls, RichText, - withColors, - PanelColorSettings, + __experimentalUseColors, } from '@wordpress/block-editor'; -import { memo } from '@wordpress/element'; - -const HeadingColorUI = memo( - function( { - textColorValue, - setTextColor, - } ) { - return ( - <PanelColorSettings - title={ __( 'Color Settings' ) } - initialOpen={ false } - colorSettings={ [ - { - value: textColorValue, - onChange: setTextColor, - label: __( 'Text Color' ), - }, - ] } - /> - ); - } -); function HeadingEdit( { attributes, @@ -52,9 +28,15 @@ function HeadingEdit( { mergeBlocks, onReplace, className, - textColor, - setTextColor, } ) { + const { TextColor, InspectorControlsColorPanel, ColorDetector } = __experimentalUseColors( + [ { name: 'textColor', property: 'color' } ], + { + contrastCheckers: { backgroundColor: true, textColor: true }, + }, + [] + ); + const { align, content, level, placeholder } = attributes; const tagName = 'h' + level; @@ -71,43 +53,36 @@ function HeadingEdit( { <p>{ __( 'Level' ) }</p> <HeadingToolbar isCollapsed={ false } minLevel={ 1 } maxLevel={ 7 } selectedLevel={ level } onChange={ ( newLevel ) => setAttributes( { level: newLevel } ) } /> </PanelBody> - <HeadingColorUI - setTextColor={ setTextColor } - textColorValue={ textColor.color } - /> </InspectorControls> - <RichText - identifier="content" - tagName={ tagName } - value={ content } - onChange={ ( value ) => setAttributes( { content: value } ) } - onMerge={ mergeBlocks } - onSplit={ ( value ) => { - if ( ! value ) { - return createBlock( 'core/paragraph' ); - } + { InspectorControlsColorPanel } + <TextColor> + <ColorDetector querySelector='[contenteditable="true"]' /> + <RichText + identifier="content" + tagName={ tagName } + value={ content } + onChange={ ( value ) => setAttributes( { content: value } ) } + onMerge={ mergeBlocks } + onSplit={ ( value ) => { + if ( ! value ) { + return createBlock( 'core/paragraph' ); + } - return createBlock( 'core/heading', { - ...attributes, - content: value, - } ); - } } - onReplace={ onReplace } - onRemove={ () => onReplace( [] ) } - className={ classnames( className, { - [ `has-text-align-${ align }` ]: align, - 'has-text-color': textColor.color, - [ textColor.class ]: textColor.class, - } ) } - placeholder={ placeholder || __( 'Write heading…' ) } - style={ { - color: textColor.color, - } } - /> + return createBlock( 'core/heading', { + ...attributes, + content: value, + } ); + } } + onReplace={ onReplace } + onRemove={ () => onReplace( [] ) } + className={ classnames( className, { + [ `has-text-align-${ align }` ]: align, + } ) } + placeholder={ placeholder || __( 'Write heading…' ) } + /> + </TextColor> </> ); } -export default compose( [ - withColors( 'backgroundColor', { textColor: 'color' } ), -] )( HeadingEdit ); +export default HeadingEdit; diff --git a/packages/block-library/src/heading/heading-level-icon.js b/packages/block-library/src/heading/heading-level-icon.js index a65d9f4a0ecb64..4780430c3ffed1 100644 --- a/packages/block-library/src/heading/heading-level-icon.js +++ b/packages/block-library/src/heading/heading-level-icon.js @@ -3,7 +3,7 @@ */ import { Path, SVG } from '@wordpress/components'; -export default function HeadingLevelIcon( { level } ) { +export default function HeadingLevelIcon( { level, isPressed = false } ) { const levelToPath = { 1: 'M9 5h2v10H9v-4H5v4H3V5h2v4h4V5zm6.6 0c-.6.9-1.5 1.7-2.6 2v1h2v7h2V5h-1.4z', 2: 'M7 5h2v10H7v-4H3v4H1V5h2v4h4V5zm8 8c.5-.4.6-.6 1.1-1.1.4-.4.8-.8 1.2-1.3.3-.4.6-.8.9-1.3.2-.4.3-.8.3-1.3 0-.4-.1-.9-.3-1.3-.2-.4-.4-.7-.8-1-.3-.3-.7-.5-1.2-.6-.5-.2-1-.2-1.5-.2-.4 0-.7 0-1.1.1-.3.1-.7.2-1 .3-.3.1-.6.3-.9.5-.3.2-.6.4-.8.7l1.2 1.2c.3-.3.6-.5 1-.7.4-.2.7-.3 1.2-.3s.9.1 1.3.4c.3.3.5.7.5 1.1 0 .4-.1.8-.4 1.1-.3.5-.6.9-1 1.2-.4.4-1 .9-1.6 1.4-.6.5-1.4 1.1-2.2 1.6V15h8v-2H15z', @@ -17,7 +17,7 @@ export default function HeadingLevelIcon( { level } ) { } return ( - <SVG width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> + <SVG width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" isPressed={ isPressed } > <Path d={ levelToPath[ level ] } /> </SVG> ); diff --git a/packages/block-library/src/heading/heading-toolbar.js b/packages/block-library/src/heading/heading-toolbar.js index f3a089a8fafefe..0d836193a33b9f 100644 --- a/packages/block-library/src/heading/heading-toolbar.js +++ b/packages/block-library/src/heading/heading-toolbar.js @@ -8,7 +8,7 @@ import { range } from 'lodash'; */ import { __, sprintf } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; -import { Toolbar } from '@wordpress/components'; +import { ToolbarGroup } from '@wordpress/components'; /** * Internal dependencies @@ -17,11 +17,12 @@ import HeadingLevelIcon from './heading-level-icon'; class HeadingToolbar extends Component { createLevelControl( targetLevel, selectedLevel, onChange ) { + const isActive = targetLevel === selectedLevel; return { - icon: <HeadingLevelIcon level={ targetLevel } />, + icon: <HeadingLevelIcon level={ targetLevel } isPressed={ isActive } />, // translators: %s: heading level e.g: "1", "2", "3" title: sprintf( __( 'Heading %d' ), targetLevel ), - isActive: targetLevel === selectedLevel, + isActive, onClick: () => onChange( targetLevel ), }; } @@ -30,7 +31,7 @@ class HeadingToolbar extends Component { const { isCollapsed = true, minLevel, maxLevel, selectedLevel, onChange } = this.props; return ( - <Toolbar + <ToolbarGroup isCollapsed={ isCollapsed } icon={ <HeadingLevelIcon level={ selectedLevel } /> } controls={ range( minLevel, maxLevel ).map( diff --git a/packages/block-library/src/heading/transforms.js b/packages/block-library/src/heading/transforms.js index b043018a400ca1..70904e4b68e4ba 100644 --- a/packages/block-library/src/heading/transforms.js +++ b/packages/block-library/src/heading/transforms.js @@ -3,7 +3,6 @@ */ import { createBlock, - getPhrasingContentSchema, getBlockAttributes, } from '@wordpress/blocks'; @@ -26,14 +25,14 @@ const transforms = { { type: 'raw', selector: 'h1,h2,h3,h4,h5,h6', - schema: { - h1: { children: getPhrasingContentSchema() }, - h2: { children: getPhrasingContentSchema() }, - h3: { children: getPhrasingContentSchema() }, - h4: { children: getPhrasingContentSchema() }, - h5: { children: getPhrasingContentSchema() }, - h6: { children: getPhrasingContentSchema() }, - }, + schema: ( { phrasingContentSchema } ) => ( { + h1: { children: phrasingContentSchema }, + h2: { children: phrasingContentSchema }, + h3: { children: phrasingContentSchema }, + h4: { children: phrasingContentSchema }, + h5: { children: phrasingContentSchema }, + h6: { children: phrasingContentSchema }, + } ), transform( node ) { return createBlock( 'core/heading', { ...getBlockAttributes( diff --git a/packages/block-library/src/html/edit.js b/packages/block-library/src/html/edit.js index 4f1ab45a073dfe..5f9d42c61baefa 100644 --- a/packages/block-library/src/html/edit.js +++ b/packages/block-library/src/html/edit.js @@ -8,7 +8,12 @@ import { PlainText, transformStyles, } from '@wordpress/block-editor'; -import { Disabled, SandBox } from '@wordpress/components'; +import { + Button, + Disabled, + SandBox, + ToolbarGroup, +} from '@wordpress/components'; import { withSelect } from '@wordpress/data'; class HTMLEdit extends Component { @@ -57,20 +62,22 @@ class HTMLEdit extends Component { return ( <div className="wp-block-html"> <BlockControls> - <div className="components-toolbar"> - <button - className={ `components-tab-button ${ ! isPreview ? 'is-active' : '' }` } + <ToolbarGroup> + <Button + className="components-tab-button" + isPressed={ ! isPreview } onClick={ this.switchToHTML } > <span>HTML</span> - </button> - <button - className={ `components-tab-button ${ isPreview ? 'is-active' : '' }` } + </Button> + <Button + className="components-tab-button" + isPressed={ isPreview } onClick={ this.switchToPreview } > <span>{ __( 'Preview' ) }</span> - </button> - </div> + </Button> + </ToolbarGroup> </BlockControls> <Disabled.Consumer> { ( isDisabled ) => ( diff --git a/packages/block-library/src/html/transforms.js b/packages/block-library/src/html/transforms.js index a54c17ef8f6de6..9ab4169ad1dd1a 100644 --- a/packages/block-library/src/html/transforms.js +++ b/packages/block-library/src/html/transforms.js @@ -1,14 +1,9 @@ -/** - * WordPress dependencies - */ -import { getPhrasingContentSchema } from '@wordpress/blocks'; - const transforms = { from: [ { type: 'raw', isMatch: ( node ) => node.nodeName === 'FIGURE' && !! node.querySelector( 'iframe' ), - schema: { + schema: ( { phrasingContentSchema } ) => ( { figure: { require: [ 'iframe' ], children: { @@ -16,11 +11,11 @@ const transforms = { attributes: [ 'src', 'allowfullscreen', 'height', 'width' ], }, figcaption: { - children: getPhrasingContentSchema(), + children: phrasingContentSchema, }, }, }, - }, + } ), }, ], }; diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json index 68d2559ae4f480..23b9ae0239b58c 100644 --- a/packages/block-library/src/image/block.json +++ b/packages/block-library/src/image/block.json @@ -23,6 +23,12 @@ "source": "html", "selector": "figcaption" }, + "title": { + "type": "string", + "source": "attribute", + "selector": "img", + "attribute": "title" + }, "href": { "type": "string", "source": "attribute", diff --git a/packages/block-library/src/image/constants.js b/packages/block-library/src/image/constants.js index cd5e82b323bed5..ec8c41b8b09c31 100644 --- a/packages/block-library/src/image/constants.js +++ b/packages/block-library/src/image/constants.js @@ -3,6 +3,6 @@ export const LINK_DESTINATION_NONE = 'none'; export const LINK_DESTINATION_MEDIA = 'media'; export const LINK_DESTINATION_ATTACHMENT = 'attachment'; export const LINK_DESTINATION_CUSTOM = 'custom'; -export const NEW_TAB_REL = 'noreferrer noopener'; +export const NEW_TAB_REL = [ 'noreferrer', 'noopener' ]; export const ALLOWED_MEDIA_TYPES = [ 'image' ]; export const DEFAULT_SIZE_SLUG = 'large'; diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index b5779e71180e7d..651c348134af59 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -25,7 +25,6 @@ import { NavigableMenu, PanelBody, Path, - Rect, ResizableBox, SelectControl, Spinner, @@ -33,7 +32,7 @@ import { TextareaControl, TextControl, ToggleControl, - Toolbar, + ToolbarGroup, withNotices, } from '@wordpress/components'; import { compose } from '@wordpress/compose'; @@ -51,7 +50,9 @@ import { BlockControls, BlockIcon, InspectorControls, + InspectorAdvancedControls, MediaPlaceholder, + MediaReplaceFlow, URLPopover, RichText, } from '@wordpress/block-editor'; @@ -64,7 +65,6 @@ import { import { __, sprintf } from '@wordpress/i18n'; import { getPath } from '@wordpress/url'; import { withViewportMatch } from '@wordpress/viewport'; -import { speak } from '@wordpress/a11y'; /** * Internal dependencies @@ -72,7 +72,7 @@ import { speak } from '@wordpress/a11y'; import { createUpgradedEmbedBlock } from '../embed/util'; import icon from './icon'; import ImageSize from './image-size'; -import { getUpdatedLinkTargetSettings } from './utils'; +import { getUpdatedLinkTargetSettings, removeNewTabRel } from './utils'; /** * Module constants @@ -265,7 +265,7 @@ const ImageURLInputUI = ( { }; export class ImageEdit extends Component { - constructor( { attributes } ) { + constructor() { super( ...arguments ); this.updateAlt = this.updateAlt.bind( this ); this.updateAlignment = this.updateAlignment.bind( this ); @@ -281,15 +281,14 @@ export class ImageEdit extends Component { this.onSetLinkClass = this.onSetLinkClass.bind( this ); this.onSetLinkRel = this.onSetLinkRel.bind( this ); this.onSetNewTab = this.onSetNewTab.bind( this ); + this.onSetTitle = this.onSetTitle.bind( this ); this.getFilename = this.getFilename.bind( this ); - this.toggleIsEditing = this.toggleIsEditing.bind( this ); this.onUploadError = this.onUploadError.bind( this ); this.onImageError = this.onImageError.bind( this ); this.getLinkDestinations = this.getLinkDestinations.bind( this ); this.state = { captionFocused: false, - isEditing: ! attributes.url, }; } @@ -313,7 +312,6 @@ export class ImageEdit extends Component { allowedTypes: ALLOWED_MEDIA_TYPES, onError: ( message ) => { noticeOperations.createErrorNotice( message ); - this.setState( { isEditing: true } ); }, } ); } @@ -339,9 +337,6 @@ export class ImageEdit extends Component { const { noticeOperations } = this.props; noticeOperations.removeAllNotices(); noticeOperations.createErrorNotice( message ); - this.setState( { - isEditing: true, - } ); } onSelectImage( media ) { @@ -350,16 +345,13 @@ export class ImageEdit extends Component { url: undefined, alt: undefined, id: undefined, + title: undefined, caption: undefined, } ); return; } - this.setState( { - isEditing: false, - } ); - - const { id, url, alt, caption } = this.props.attributes; + const { id, url, alt, caption, linkDestination } = this.props.attributes; let mediaAttributes = pickRelevantMediaFiles( media ); @@ -387,6 +379,18 @@ export class ImageEdit extends Component { additionalAttributes = { url }; } + // Check if the image is linked to it's media. + if ( linkDestination === LINK_DESTINATION_MEDIA ) { + // Update the media link. + mediaAttributes.href = media.url; + } + + // Check if the image is linked to the attachment page. + if ( linkDestination === LINK_DESTINATION_ATTACHMENT ) { + // Update the media link. + mediaAttributes.href = media.link; + } + this.props.setAttributes( { ...mediaAttributes, ...additionalAttributes, @@ -403,10 +407,6 @@ export class ImageEdit extends Component { sizeSlug: DEFAULT_SIZE_SLUG, } ); } - - this.setState( { - isEditing: false, - } ); } onImageError( url ) { @@ -444,6 +444,11 @@ export class ImageEdit extends Component { this.props.setAttributes( { href: value } ); } + onSetTitle( value ) { + // This is the HTML title attribute, separate from the media object title + this.props.setAttributes( { title: value } ); + } + onSetLinkClass( value ) { this.props.setAttributes( { linkClass: value } ); } @@ -539,24 +544,12 @@ export class ImageEdit extends Component { ]; } - toggleIsEditing() { - this.setState( { - isEditing: ! this.state.isEditing, - } ); - if ( this.state.isEditing ) { - speak( __( 'You are now viewing the image in the image block.' ) ); - } else { - speak( __( 'You are now editing the image in the image block.' ) ); - } - } - getImageSizeOptions() { const { imageSizes } = this.props; return map( imageSizes, ( { name, slug } ) => ( { value: slug, label: name } ) ); } render() { - const { isEditing } = this.state; const { attributes, setAttributes, @@ -579,62 +572,60 @@ export class ImageEdit extends Component { rel, linkClass, linkDestination, + title, width, height, linkTarget, sizeSlug, } = attributes; + const cleanRel = removeNewTabRel( rel ); const isExternal = isExternalImage( id, url ); - const editImageIcon = ( <SVG width={ 20 } height={ 20 } viewBox="0 0 20 20"><Rect x={ 11 } y={ 3 } width={ 7 } height={ 5 } rx={ 1 } /><Rect x={ 2 } y={ 12 } width={ 7 } height={ 5 } rx={ 1 } /><Path d="M13,12h1a3,3,0,0,1-3,3v2a5,5,0,0,0,5-5h1L15,9Z" /><Path d="M4,8H3l2,3L7,8H6A3,3,0,0,1,9,5V3A5,5,0,0,0,4,8Z" /></SVG> ); const controls = ( <BlockControls> <BlockAlignmentToolbar value={ align } onChange={ this.updateAlignment } /> + { url && <MediaReplaceFlow + mediaURL={ url } + allowedTypes={ ALLOWED_MEDIA_TYPES } + accept="image/*" + onSelect={ this.onSelectImage } + onSelectURL={ this.onSelectURL } + onError={ this.onUploadError } + /> } { url && ( - <> - <Toolbar> - <IconButton - className={ classnames( 'components-icon-button components-toolbar__control', { 'is-active': this.state.isEditing } ) } - label={ __( 'Edit image' ) } - aria-pressed={ this.state.isEditing } - onClick={ this.toggleIsEditing } - icon={ editImageIcon } - /> - </Toolbar> - <Toolbar> - <ImageURLInputUI - url={ href || '' } - onChangeUrl={ this.onSetHref } - mediaLinks={ this.getLinkDestinations() } - linkDestination={ linkDestination } - advancedOptions={ - <> - <ToggleControl - label={ __( 'Open in New Tab' ) } - onChange={ this.onSetNewTab } - checked={ linkTarget === '_blank' } /> - <TextControl - label={ __( 'Link CSS Class' ) } - value={ linkClass || '' } - onKeyPress={ stopPropagation } - onKeyDown={ stopPropagationRelevantKeys } - onChange={ this.onSetLinkClass } - /> - <TextControl - label={ __( 'Link Rel' ) } - value={ rel || '' } - onChange={ this.onSetLinkRel } - onKeyPress={ stopPropagation } - onKeyDown={ stopPropagationRelevantKeys } - /> - </> - } - /> - </Toolbar> - </> + <ToolbarGroup> + <ImageURLInputUI + url={ href || '' } + onChangeUrl={ this.onSetHref } + mediaLinks={ this.getLinkDestinations() } + linkDestination={ linkDestination } + advancedOptions={ + <> + <ToggleControl + label={ __( 'Open in New Tab' ) } + onChange={ this.onSetNewTab } + checked={ linkTarget === '_blank' } /> + <TextControl + label={ __( 'Link Rel' ) } + value={ cleanRel || '' } + onChange={ this.onSetLinkRel } + onKeyPress={ stopPropagation } + onKeyDown={ stopPropagationRelevantKeys } + /> + <TextControl + label={ __( 'Link CSS Class' ) } + value={ linkClass || '' } + onKeyPress={ stopPropagation } + onKeyDown={ stopPropagationRelevantKeys } + onChange={ this.onSetLinkClass } + /> + </> + } + /> + </ToolbarGroup> ) } </BlockControls> ); @@ -656,18 +647,16 @@ export class ImageEdit extends Component { labels={ labels } onSelect={ this.onSelectImage } onSelectURL={ this.onSelectURL } - onDoubleClick={ this.toggleIsEditing } - onCancel={ !! url && this.toggleIsEditing } notices={ noticeUI } onError={ this.onUploadError } accept="image/*" allowedTypes={ ALLOWED_MEDIA_TYPES } value={ { id, src } } mediaPreview={ mediaPreview } - disableMediaButtons={ ! isEditing && url } + disableMediaButtons={ url } /> ); - if ( isEditing || ! url ) { + if ( ! url ) { return ( <> { controls } @@ -688,84 +677,101 @@ export class ImageEdit extends Component { const imageSizeOptions = this.getImageSizeOptions(); const getInspectorControls = ( imageWidth, imageHeight ) => ( - <InspectorControls> - <PanelBody title={ __( 'Image Settings' ) }> - <TextareaControl - label={ __( 'Alt Text (Alternative Text)' ) } - value={ alt } - onChange={ this.updateAlt } + <> + <InspectorControls> + <PanelBody title={ __( 'Image Settings' ) }> + <TextareaControl + label={ __( 'Alt Text (Alternative Text)' ) } + value={ alt } + onChange={ this.updateAlt } + help={ + <> + <ExternalLink href="https://www.w3.org/WAI/tutorials/images/decision-tree"> + { __( 'Describe the purpose of the image' ) } + </ExternalLink> + { __( 'Leave empty if the image is purely decorative.' ) } + </> + } + /> + { ! isEmpty( imageSizeOptions ) && ( + <SelectControl + label={ __( 'Image Size' ) } + value={ sizeSlug } + options={ imageSizeOptions } + onChange={ this.updateImage } + /> + ) } + { isResizable && ( + <div className="block-library-image__dimensions"> + <p className="block-library-image__dimensions__row"> + { __( 'Image Dimensions' ) } + </p> + <div className="block-library-image__dimensions__row"> + <TextControl + type="number" + className="block-library-image__dimensions__width" + label={ __( 'Width' ) } + value={ width || imageWidth || '' } + min={ 1 } + onChange={ this.updateWidth } + /> + <TextControl + type="number" + className="block-library-image__dimensions__height" + label={ __( 'Height' ) } + value={ height || imageHeight || '' } + min={ 1 } + onChange={ this.updateHeight } + /> + </div> + <div className="block-library-image__dimensions__row"> + <ButtonGroup aria-label={ __( 'Image Size' ) }> + { [ 25, 50, 75, 100 ].map( ( scale ) => { + const scaledWidth = Math.round( imageWidth * ( scale / 100 ) ); + const scaledHeight = Math.round( imageHeight * ( scale / 100 ) ); + + const isCurrent = width === scaledWidth && height === scaledHeight; + + return ( + <Button + key={ scale } + isSmall + isPrimary={ isCurrent } + isPressed={ isCurrent } + onClick={ this.updateDimensions( scaledWidth, scaledHeight ) } + > + { scale }% + </Button> + ); + } ) } + </ButtonGroup> + <Button + isSmall + onClick={ this.updateDimensions() } + > + { __( 'Reset' ) } + </Button> + </div> + </div> + ) } + </PanelBody> + </InspectorControls> + <InspectorAdvancedControls> + <TextControl + label={ __( 'Title Attribute' ) } + value={ title || '' } + onChange={ this.onSetTitle } help={ <> - <ExternalLink href="https://www.w3.org/WAI/tutorials/images/decision-tree"> - { __( 'Describe the purpose of the image' ) } + { __( 'Describe the role of this image on the page.' ) } + <ExternalLink href="https://www.w3.org/TR/html52/dom.html#the-title-attribute"> + { __( '(Note: many devices and browsers do not display this text.)' ) } </ExternalLink> - { __( 'Leave empty if the image is purely decorative.' ) } </> } /> - { ! isEmpty( imageSizeOptions ) && ( - <SelectControl - label={ __( 'Image Size' ) } - value={ sizeSlug } - options={ imageSizeOptions } - onChange={ this.updateImage } - /> - ) } - { isResizable && ( - <div className="block-library-image__dimensions"> - <p className="block-library-image__dimensions__row"> - { __( 'Image Dimensions' ) } - </p> - <div className="block-library-image__dimensions__row"> - <TextControl - type="number" - className="block-library-image__dimensions__width" - label={ __( 'Width' ) } - value={ width || imageWidth || '' } - min={ 1 } - onChange={ this.updateWidth } - /> - <TextControl - type="number" - className="block-library-image__dimensions__height" - label={ __( 'Height' ) } - value={ height || imageHeight || '' } - min={ 1 } - onChange={ this.updateHeight } - /> - </div> - <div className="block-library-image__dimensions__row"> - <ButtonGroup aria-label={ __( 'Image Size' ) }> - { [ 25, 50, 75, 100 ].map( ( scale ) => { - const scaledWidth = Math.round( imageWidth * ( scale / 100 ) ); - const scaledHeight = Math.round( imageHeight * ( scale / 100 ) ); - - const isCurrent = width === scaledWidth && height === scaledHeight; - - return ( - <Button - key={ scale } - isSmall - isPrimary={ isCurrent } - aria-pressed={ isCurrent } - onClick={ this.updateDimensions( scaledWidth, scaledHeight ) } - > - { scale }% - </Button> - ); - } ) } - </ButtonGroup> - <Button - isSmall - onClick={ this.updateDimensions() } - > - { __( 'Reset' ) } - </Button> - </div> - </div> - ) } - </PanelBody> - </InspectorControls> + </InspectorAdvancedControls> + </> ); // Disable reason: Each block can be selected by clicking on it @@ -801,7 +807,6 @@ export class ImageEdit extends Component { <img src={ url } alt={ defaultedAlt } - onDoubleClick={ this.toggleIsEditing } onClick={ this.onImageClick } onError={ () => this.onImageError( url ) } /> @@ -931,7 +936,7 @@ export default compose( [ const { getSettings } = select( 'core/block-editor' ); const { attributes: { id }, isSelected } = props; const { - __experimentalMediaUpload, + mediaUpload, imageSizes, isRTL, maxWidth, @@ -942,7 +947,7 @@ export default compose( [ maxWidth, isRTL, imageSizes, - mediaUpload: __experimentalMediaUpload, + mediaUpload, }; } ), withViewportMatch( { isLargeViewport: 'medium' } ), diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index ce2c1affe275a5..df70ef1a67148e 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -8,19 +8,22 @@ import { mediaUploadSync, requestImageFailedRetryDialog, requestImageUploadCancelDialog, + requestImageFullscreenPreview, } from 'react-native-gutenberg-bridge'; -import { isEmpty } from 'lodash'; +import { isEmpty, map, get } from 'lodash'; /** * WordPress dependencies */ import { + Icon, + PanelBody, + PanelActions, + SelectControl, TextControl, ToggleControl, - Icon, - Toolbar, ToolbarButton, - PanelBody, + ToolbarGroup, } from '@wordpress/components'; import { @@ -31,17 +34,19 @@ import { MEDIA_TYPE_IMAGE, BlockControls, InspectorControls, + BlockAlignmentToolbar, } from '@wordpress/block-editor'; import { __, sprintf } from '@wordpress/i18n'; import { isURL } from '@wordpress/url'; import { doAction, hasAction } from '@wordpress/hooks'; -import { withPreferredColorScheme } from '@wordpress/compose'; +import { compose, withPreferredColorScheme } from '@wordpress/compose'; +import { withSelect } from '@wordpress/data'; /** * Internal dependencies */ import styles from './styles.scss'; -import SvgIcon from './icon'; +import SvgIcon, { editImageIcon } from './icon'; import SvgIconRetry from './icon-retry'; import { getUpdatedLinkTargetSettings } from './utils'; @@ -50,9 +55,26 @@ import { LINK_DESTINATION_NONE, } from './constants'; +const IMAGE_SIZE_THUMBNAIL = 'thumbnail'; +const IMAGE_SIZE_MEDIUM = 'medium'; +const IMAGE_SIZE_LARGE = 'large'; +const IMAGE_SIZE_FULL_SIZE = 'full'; +const DEFAULT_SIZE_SLUG = IMAGE_SIZE_LARGE; +const sizeOptionLabels = { + [ IMAGE_SIZE_THUMBNAIL ]: __( 'Thumbnail' ), + [ IMAGE_SIZE_MEDIUM ]: __( 'Medium' ), + [ IMAGE_SIZE_LARGE ]: __( 'Large' ), + [ IMAGE_SIZE_FULL_SIZE ]: __( 'Full Size' ), +}; +const sizeOptions = map( sizeOptionLabels, ( label, option ) => ( { value: option, label } ) ); + // Default Image ratio 4:3 const IMAGE_ASPECT_RATIO = 4 / 3; +const getUrlForSlug = ( image, { sizeSlug } ) => { + return get( image, [ 'media_details', 'sizes', sizeSlug, 'source_url' ] ); +}; + export class ImageEdit extends React.Component { constructor( props ) { super( props ); @@ -70,9 +92,11 @@ export class ImageEdit extends React.Component { this.updateImageURL = this.updateImageURL.bind( this ); this.onSetLinkDestination = this.onSetLinkDestination.bind( this ); this.onSetNewTab = this.onSetNewTab.bind( this ); + this.onSetSizeSlug = this.onSetSizeSlug.bind( this ); this.onImagePressed = this.onImagePressed.bind( this ); this.onClearSettings = this.onClearSettings.bind( this ); this.onFocusCaption = this.onFocusCaption.bind( this ); + this.updateAlignment = this.updateAlignment.bind( this ); } componentDidMount() { @@ -86,14 +110,18 @@ export class ImageEdit extends React.Component { console.warn( 'Attributes has id with no url.' ); } + // Detect any pasted image and start an upload + if ( ! attributes.id && attributes.url && attributes.url.indexOf( 'file:' ) === 0 ) { + requestMediaImport( attributes.url, ( id, url ) => { + if ( url ) { + setAttributes( { id, url } ); + } + } ); + } + + // Make sure we mark any temporary images as failed if they failed while + // the editor wasn't open if ( attributes.id && attributes.url && ! isURL( attributes.url ) ) { - if ( attributes.url.indexOf( 'file:' ) === 0 ) { - requestMediaImport( attributes.url, ( id, url ) => { - if ( url ) { - setAttributes( { id, url } ); - } - } ); - } mediaUploadSync(); } } @@ -105,6 +133,14 @@ export class ImageEdit extends React.Component { } } + componentDidUpdate( previousProps ) { + if ( ! previousProps.image && this.props.image ) { + const { image, attributes } = this.props; + const url = getUrlForSlug( image, attributes ) || image.source_url; + this.props.setAttributes( { url } ); + } + } + static getDerivedStateFromProps( props, state ) { // Avoid a UI flicker in the toolbar by insuring that isCaptionSelected // is updated immediately any time the isSelected prop becomes false @@ -120,6 +156,8 @@ export class ImageEdit extends React.Component { requestImageUploadCancelDialog( attributes.id ); } else if ( attributes.id && ! isURL( attributes.url ) ) { requestImageFailedRetryDialog( attributes.id ); + } else if ( ! this.state.isCaptionSelected ) { + requestImageFullscreenPreview( attributes.url ); } this.setState( { @@ -167,6 +205,10 @@ export class ImageEdit extends React.Component { this.props.setAttributes( { url, width: undefined, height: undefined } ); } + updateAlignment( nextAlign ) { + this.props.setAttributes( { align: nextAlign } ); + } + onSetLinkDestination( href ) { this.props.setAttributes( { linkDestination: LINK_DESTINATION_CUSTOM, @@ -179,19 +221,58 @@ export class ImageEdit extends React.Component { this.props.setAttributes( updatedLinkTarget ); } + onSetSizeSlug( sizeSlug ) { + const { image } = this.props; + + const url = getUrlForSlug( image, { sizeSlug } ); + if ( ! url ) { + return null; + } + + this.props.setAttributes( { + url, + width: undefined, + height: undefined, + sizeSlug, + } ); + } + onClearSettings() { this.props.setAttributes( { alt: '', linkDestination: LINK_DESTINATION_NONE, href: undefined, linkTarget: undefined, + sizeSlug: DEFAULT_SIZE_SLUG, rel: undefined, } ); } - onSelectMediaUploadOption( { id, url } ) { - const { setAttributes } = this.props; - setAttributes( { id, url } ); + onSelectMediaUploadOption( media ) { + const { id, url } = this.props.attributes; + + const mediaAttributes = { + id: media.id, + url: media.url, + }; + + let additionalAttributes; + // Reset the dimension attributes if changing to a different image. + if ( ! media.id || media.id !== id ) { + additionalAttributes = { + width: undefined, + height: undefined, + sizeSlug: DEFAULT_SIZE_SLUG, + }; + } else { + // Keep the same url when selecting the same file, so "Image Size" option is not changed. + additionalAttributes = { url }; + } + + this.props.setAttributes( { + ...mediaAttributes, + ...additionalAttributes, + } ); } onFocusCaption() { @@ -216,17 +297,24 @@ export class ImageEdit extends React.Component { render() { const { attributes, isSelected } = this.props; - const { url, height, width, alt, href, id, linkTarget } = attributes; + const { align, url, height, width, alt, href, id, linkTarget, sizeSlug } = attributes; + + const actions = [ { label: __( 'Clear All Settings' ), onPress: this.onClearSettings } ]; const getToolbarEditButton = ( open ) => ( <BlockControls> - <Toolbar> + <ToolbarGroup> <ToolbarButton title={ __( 'Edit image' ) } - icon="edit" + icon={ editImageIcon } onClick={ open } /> - </Toolbar> + </ToolbarGroup> + <BlockAlignmentToolbar + value={ align } + onChange={ this.updateAlignment } + isCollapsed={ false } + /> </BlockControls> ); @@ -249,21 +337,26 @@ export class ImageEdit extends React.Component { checked={ linkTarget === '_blank' } onChange={ this.onSetNewTab } /> + { // eslint-disable-next-line no-undef + __DEV__ && + <SelectControl + hideCancelButton + icon={ 'editor-expand' } + label={ __( 'Size' ) } + value={ sizeOptionLabels[ sizeSlug || DEFAULT_SIZE_SLUG ] } + onChangeValue={ ( newValue ) => this.onSetSizeSlug( newValue ) } + options={ sizeOptions } + /> } <TextControl icon={ 'editor-textcolor' } label={ __( 'Alt Text' ) } value={ alt || '' } valuePlaceholder={ __( 'None' ) } - separatorType={ 'fullWidth' } - onChange={ this.updateAlt } - /> - <TextControl - label={ __( 'Clear All Settings' ) } - labelStyle={ styles.clearSettingsButton } separatorType={ 'none' } - onPress={ this.onClearSettings } + onChangeValue={ this.updateAlt } /> </PanelBody> + <PanelActions actions={ actions } /> </InspectorControls> ); @@ -280,7 +373,16 @@ export class ImageEdit extends React.Component { ); } + const alignToFlex = { + left: 'flex-start', + center: 'center', + right: 'flex-end', + full: 'center', + wide: 'center', + }; + const imageContainerHeight = Dimensions.get( 'window' ).width / IMAGE_ASPECT_RATIO; + const getImageComponent = ( openMediaOptions, getMediaOptions ) => ( <TouchableWithoutFeedback accessible={ ! isSelected } @@ -306,6 +408,7 @@ export class ImageEdit extends React.Component { renderContent={ ( { isUploadInProgress, isUploadFailed, finalWidth, finalHeight, imageWidthWithinContainer, retryMessage } ) => { const opacity = isUploadInProgress ? 0.3 : 1; const icon = this.getIcon( isUploadFailed ); + const imageBorderOnSelectedStyle = isSelected && ! ( isUploadInProgress || isUploadFailed || this.state.isCaptionSelected ) ? styles.imageBorder : ''; const iconContainer = ( <View style={ styles.modalIcon }> @@ -314,7 +417,7 @@ export class ImageEdit extends React.Component { ); return ( - <View style={ { flex: 1 } } > + <View style={ { flex: 1, alignSelf: alignToFlex[ align ] } } > { ! imageWidthWithinContainer && <View style={ [ styles.imageContainer, { height: imageContainerHeight } ] } > { this.getIcon( false ) } @@ -325,7 +428,7 @@ export class ImageEdit extends React.Component { accessibilityLabel={ alt } accessibilityHint={ __( 'Double tap and hold to edit' ) } accessibilityRole={ 'imagebutton' } - style={ { width: finalWidth, height: finalHeight, opacity } } + style={ [ imageBorderOnSelectedStyle, { width: finalWidth, height: finalHeight, opacity } ] } resizeMethod="scale" source={ { uri: url } } key={ url } @@ -372,4 +475,14 @@ export class ImageEdit extends React.Component { } } -export default withPreferredColorScheme( ImageEdit ); +export default compose( [ + withSelect( ( select, props ) => { + const { getMedia } = select( 'core' ); + const { attributes: { id }, isSelected } = props; + + return { + image: id && isSelected ? getMedia( id ) : null, + }; + } ), + withPreferredColorScheme, +] )( ImageEdit ); diff --git a/packages/block-library/src/image/editor.scss b/packages/block-library/src/image/editor.scss index 5851c9bf0338a0..1f66521f7e32d3 100644 --- a/packages/block-library/src/image/editor.scss +++ b/packages/block-library/src/image/editor.scss @@ -29,7 +29,8 @@ img { display: block; - width: 100%; + width: inherit; + height: inherit; } } diff --git a/packages/block-library/src/image/icon.js b/packages/block-library/src/image/icon.js index b029bab8fbe98a..44f88783d24cfa 100644 --- a/packages/block-library/src/image/icon.js +++ b/packages/block-library/src/image/icon.js @@ -1,6 +1,8 @@ /** * WordPress dependencies */ -import { Path, SVG } from '@wordpress/components'; +import { Path, Rect, SVG } from '@wordpress/components'; export default <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0,0h24v24H0V0z" fill="none" /><Path d="m19 5v14h-14v-14h14m0-2h-14c-1.1 0-2 0.9-2 2v14c0 1.1 0.9 2 2 2h14c1.1 0 2-0.9 2-2v-14c0-1.1-0.9-2-2-2z" /><Path d="m14.14 11.86l-3 3.87-2.14-2.59-3 3.86h12l-3.86-5.14z" /></SVG>; + +export const editImageIcon = ( <SVG width={ 20 } height={ 20 } viewBox="0 0 20 20"><Rect x={ 11 } y={ 3 } width={ 7 } height={ 5 } rx={ 1 } /><Rect x={ 2 } y={ 12 } width={ 7 } height={ 5 } rx={ 1 } /><Path d="M13,12h1a3,3,0,0,1-3,3v2a5,5,0,0,0,5-5h1L15,9Z" /><Path d="M4,8H3l2,3L7,8H6A3,3,0,0,1,9,5V3A5,5,0,0,0,4,8Z" /></SVG> ); diff --git a/packages/block-library/src/image/index.js b/packages/block-library/src/image/index.js index 2cd1ddaaa3b445..92bd1a57d604c5 100644 --- a/packages/block-library/src/image/index.js +++ b/packages/block-library/src/image/index.js @@ -28,7 +28,8 @@ export const settings = { example: { attributes: { sizeSlug: 'large', - url: 'https://upload.wikimedia.org/wikipedia/commons/1/15/MtBlanc1.JPG', + url: 'https://s.w.org/images/core/5.3/MtBlanc1.jpg', + // translators: Caption accompanying an image of the Mont Blanc, which serves as an example for the Image block. caption: __( 'Mont Blanc appears—still, snowy, and serene.' ), }, }, diff --git a/packages/block-library/src/image/save.js b/packages/block-library/src/image/save.js index 01aff39769152b..6f7ba6fa0f700d 100644 --- a/packages/block-library/src/image/save.js +++ b/packages/block-library/src/image/save.js @@ -2,6 +2,7 @@ * External dependencies */ import classnames from 'classnames'; +import { isEmpty } from 'lodash'; /** * WordPress dependencies @@ -22,8 +23,11 @@ export default function save( { attributes } ) { id, linkTarget, sizeSlug, + title, } = attributes; + const newRel = isEmpty( rel ) ? undefined : rel; + const classes = classnames( { [ `align${ align }` ]: align, [ `size-${ sizeSlug }` ]: sizeSlug, @@ -37,6 +41,7 @@ export default function save( { attributes } ) { className={ id ? `wp-image-${ id }` : null } width={ width } height={ height } + title={ title } /> ); @@ -47,7 +52,7 @@ export default function save( { attributes } ) { className={ linkClass } href={ href } target={ linkTarget } - rel={ rel } + rel={ newRel } > { image } </a> diff --git a/packages/block-library/src/image/style.scss b/packages/block-library/src/image/style.scss index 461b6dc8a5ffd6..26f94d57739c80 100644 --- a/packages/block-library/src/image/style.scss +++ b/packages/block-library/src/image/style.scss @@ -1,6 +1,4 @@ .wp-block-image { - max-width: 100%; - // The image block is in a `figure` element, and many themes zero this out. // This rule explicitly sets it, to ensure at least some bottom-margin in the flow. margin-bottom: 1em; @@ -54,6 +52,13 @@ margin-left: auto; margin-right: auto; } + + // Supply caption styles to images, even if the theme hasn't opted in. + // Reason being: the new markup, <figcaptions>, are not likely to be styled in the majority of existing themes, + // so we supply the styles so as to not appear broken or unstyled in those themes. + figcaption { + @include caption-style(); + } } // Variations diff --git a/packages/block-library/src/image/styles.native.scss b/packages/block-library/src/image/styles.native.scss index 9c5a9cbf5c45e5..e153fae404737b 100644 --- a/packages/block-library/src/image/styles.native.scss +++ b/packages/block-library/src/image/styles.native.scss @@ -6,6 +6,12 @@ background-color: $gray-lighten-30; } +.imageBorder { + border-color: $blue-medium; + border-width: 2px; + border-style: solid; +} + .uploadFailedText { color: #fff; font-size: 14; @@ -18,10 +24,6 @@ align-items: center; } -.clearSettingsButton { - color: $alert-red; -} - .modalIcon { width: 80px; height: 80px; @@ -44,3 +46,13 @@ .iconDark { fill: $white; } + +.content { + flex: 1; +} + +.contentCentered { + flex: 1; + justify-content: center; + align-items: center; +} diff --git a/packages/block-library/src/image/test/edit.native.js b/packages/block-library/src/image/test/edit.native.js index d585f1f1396a9e..9c21c10d90dbaf 100644 --- a/packages/block-library/src/image/test/edit.native.js +++ b/packages/block-library/src/image/test/edit.native.js @@ -40,16 +40,16 @@ describe( 'Image Block', () => { instance.onSetNewTab( true ); - expect( setAttributes ).toBeCalledWith( { linkTarget: '_blank', rel: NEW_TAB_REL } ); + expect( setAttributes ).toHaveBeenCalledWith( { linkTarget: '_blank', rel: undefined } ); } ); it( 'unset link target', () => { - const component = renderer.create( getImageComponent( { linkTarget: '_blank', rel: NEW_TAB_REL } ) ); + const component = renderer.create( getImageComponent( { linkTarget: '_blank', rel: NEW_TAB_REL.join( ' ' ) } ) ); const instance = component.getInstance(); instance.onSetNewTab( false ); - expect( setAttributes ).toBeCalledWith( { linkTarget: undefined, rel: undefined } ); + expect( setAttributes ).toHaveBeenCalledWith( { linkTarget: undefined, rel: undefined } ); } ); } ); diff --git a/packages/block-library/src/image/transforms.js b/packages/block-library/src/image/transforms.js index 2af42cc2244e99..5d06d2e43ca3cc 100644 --- a/packages/block-library/src/image/transforms.js +++ b/packages/block-library/src/image/transforms.js @@ -5,7 +5,6 @@ import { createBlobURL } from '@wordpress/blob'; import { createBlock, getBlockAttributes, - getPhrasingContentSchema, } from '@wordpress/blocks'; export function stripFirstImage( attributes, { shortcode } ) { @@ -44,12 +43,12 @@ function getFirstAnchorAttributeFormHTML( html, attributeName ) { const imageSchema = { img: { - attributes: [ 'src', 'alt' ], + attributes: [ 'src', 'alt', 'title' ], classes: [ 'alignleft', 'aligncenter', 'alignright', 'alignnone', /^wp-image-\d+$/ ], }, }; -const schema = { +const schema = ( { phrasingContentSchema } ) => ( { figure: { require: [ 'img' ], children: { @@ -59,11 +58,11 @@ const schema = { children: imageSchema, }, figcaption: { - children: getPhrasingContentSchema(), + children: phrasingContentSchema, }, }, }, -}; +} ); const transforms = { from: [ diff --git a/packages/block-library/src/image/utils.js b/packages/block-library/src/image/utils.js index 2cb9b8f5ca5c78..1e3e8a07fc3748 100644 --- a/packages/block-library/src/image/utils.js +++ b/packages/block-library/src/image/utils.js @@ -1,3 +1,11 @@ +/** + * External dependencies + */ +import { + isEmpty, + each, +} from 'lodash'; + /** * Internal dependencies */ @@ -12,6 +20,30 @@ export function calculatePreferedImageSize( image, container ) { return { width, height }; } +export function removeNewTabRel( currentRel ) { + let newRel = currentRel; + + if ( currentRel !== undefined && ! isEmpty( newRel ) ) { + if ( ! isEmpty( newRel ) ) { + each( NEW_TAB_REL, function( relVal ) { + const regExp = new RegExp( '\\b' + relVal + '\\b', 'gi' ); + newRel = newRel.replace( regExp, '' ); + } ); + + // Only trim if NEW_TAB_REL values was replaced. + if ( newRel !== currentRel ) { + newRel = newRel.trim(); + } + + if ( isEmpty( newRel ) ) { + newRel = undefined; + } + } + } + + return newRel; +} + /** * Helper to get the link target settings to be stored. * @@ -24,11 +56,11 @@ export function calculatePreferedImageSize( image, container ) { export function getUpdatedLinkTargetSettings( value, { rel } ) { const linkTarget = value ? '_blank' : undefined; - let updatedRel = rel; - if ( linkTarget && ! rel ) { - updatedRel = NEW_TAB_REL; - } else if ( ! linkTarget && rel === NEW_TAB_REL ) { + let updatedRel; + if ( ! linkTarget && ! rel ) { updatedRel = undefined; + } else { + updatedRel = removeNewTabRel( rel ); } return { diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index e49f68553b0a6f..c9cc4378a5e390 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -34,8 +34,8 @@ import * as embed from './embed'; import * as file from './file'; import * as html from './html'; import * as mediaText from './media-text'; -import * as navigationMenu from './navigation-menu'; -import * as navigationMenuItem from './navigation-menu-item'; +import * as navigation from './navigation'; +import * as navigationLink from './navigation-link'; import * as latestComments from './latest-comments'; import * as latestPosts from './latest-posts'; import * as legacyWidget from './legacy-widget'; @@ -62,6 +62,12 @@ import * as classic from './classic'; import * as socialLinks from './social-links'; import * as socialLink from './social-link'; +// Full Site Editing Blocks +import * as siteTitle from './site-title'; +import * as templatePart from './template-part'; +import * as postTitle from './post-title'; +import * as postContent from './post-content'; + /** * Function to register an individual block. * @@ -123,6 +129,8 @@ export const registerCoreBlocks = () => { latestPosts, missing, more, + navigation, + navigationLink, nextpage, preformatted, pullquote, @@ -162,14 +170,23 @@ export const registerCoreBlocks = () => { * __experimentalRegisterExperimentalCoreBlocks( settings ); * ``` */ -export const __experimentalRegisterExperimentalCoreBlocks = process.env.GUTENBERG_PHASE === 2 ? ( settings ) => { - const { __experimentalEnableLegacyWidgetBlock, __experimentalEnableMenuBlock } = settings; +export const __experimentalRegisterExperimentalCoreBlocks = + process.env.GUTENBERG_PHASE === 2 ? + ( settings ) => { + const { + __experimentalEnableLegacyWidgetBlock, + __experimentalEnableFullSiteEditing, + } = settings; - [ - __experimentalEnableLegacyWidgetBlock ? legacyWidget : null, - __experimentalEnableMenuBlock ? navigationMenu : null, - __experimentalEnableMenuBlock ? navigationMenuItem : null, - socialLinks, - ...socialLink.sites, - ].forEach( registerBlock ); -} : undefined; + [ + __experimentalEnableLegacyWidgetBlock ? legacyWidget : null, + socialLinks, + ...socialLink.sites, + + // Register Full Site Editing Blocks. + ...( __experimentalEnableFullSiteEditing ? + [ siteTitle, templatePart, postTitle, postContent ] : + [] ), + ].forEach( registerBlock ); + } : + undefined; diff --git a/packages/block-library/src/index.native.js b/packages/block-library/src/index.native.js index 41806df19dc178..89f650d6ba1e54 100644 --- a/packages/block-library/src/index.native.js +++ b/packages/block-library/src/index.native.js @@ -5,6 +5,7 @@ import { registerBlockType, setDefaultBlockName, setUnregisteredTypeHandlerName, + setGroupingBlockName, } from '@wordpress/blocks'; /** @@ -95,9 +96,9 @@ export const coreBlocks = [ textColumns, verse, video, -].reduce( ( memo, block ) => { - memo[ block.name ] = block; - return memo; +].reduce( ( accumulator, block ) => { + accumulator[ block.name ] = block; + return accumulator; }, {} ); /** @@ -140,12 +141,21 @@ export const registerCoreBlocks = () => { separator, list, quote, + mediaText, + preformatted, + gallery, // eslint-disable-next-line no-undef - !! __DEV__ ? mediaText : null, + !! __DEV__ ? columns : null, + // eslint-disable-next-line no-undef + !! __DEV__ ? column : null, // eslint-disable-next-line no-undef !! __DEV__ ? group : null, + spacer, ].forEach( registerBlock ); setDefaultBlockName( paragraph.name ); setUnregisteredTypeHandlerName( missing.name ); + if ( group ) { + setGroupingBlockName( group.name ); + } }; diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index 6d0e4198e8242e..50e4add6e8fff7 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -12,11 +12,11 @@ import { PanelBody, Placeholder, QueryControls, + RadioControl, RangeControl, Spinner, ToggleControl, - Toolbar, - RadioControl, + ToolbarGroup, } from '@wordpress/components'; import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; @@ -82,11 +82,11 @@ class LatestPostsEdit extends Component { /> { displayPostContent && <RadioControl - label="Show:" + label={ __( 'Show:' ) } selected={ displayPostContentRadio } options={ [ - { label: 'Excerpt', value: 'excerpt' }, - { label: 'Full Post', value: 'full_post' }, + { label: __( 'Excerpt' ), value: 'excerpt' }, + { label: __( 'Full Post' ), value: 'full_post' }, ] } onChange={ ( value ) => setAttributes( { displayPostContentRadio: value } ) } /> @@ -179,7 +179,7 @@ class LatestPostsEdit extends Component { <> { inspectorControls } <BlockControls> - <Toolbar controls={ layoutControls } /> + <ToolbarGroup controls={ layoutControls } /> </BlockControls> <ul className={ classnames( this.props.className, { diff --git a/packages/block-library/src/legacy-widget/edit/dom-manager.js b/packages/block-library/src/legacy-widget/edit/dom-manager.js index d4a0b43829f29c..ccabc4408f0666 100644 --- a/packages/block-library/src/legacy-widget/edit/dom-manager.js +++ b/packages/block-library/src/legacy-widget/edit/dom-manager.js @@ -16,6 +16,8 @@ class LegacyWidgetEditDomManager extends Component { this.containerRef = createRef(); this.formRef = createRef(); this.widgetContentRef = createRef(); + this.idBaseInputRef = createRef(); + this.widgetNumberInputRef = createRef(); this.triggerWidgetEvent = this.triggerWidgetEvent.bind( this ); } @@ -27,11 +29,22 @@ class LegacyWidgetEditDomManager extends Component { } shouldComponentUpdate( nextProps ) { + let shouldTriggerWidgetUpdateEvent = false; // We can not leverage react render otherwise we would destroy dom changes applied by the plugins. // We manually update the required dom node replicating what the widget screen and the customizer do. + if ( nextProps.idBase !== this.props.idBase && this.idBaseInputRef.current ) { + this.idBaseInputRef.current.value = nextProps.idBase; + shouldTriggerWidgetUpdateEvent = true; + } + if ( nextProps.number !== this.props.number && this.widgetNumberInputRef.current ) { + this.widgetNumberInputRef.current.value = nextProps.number; + } if ( nextProps.form !== this.props.form && this.widgetContentRef.current ) { const widgetContent = this.widgetContentRef.current; widgetContent.innerHTML = nextProps.form; + shouldTriggerWidgetUpdateEvent = true; + } + if ( shouldTriggerWidgetUpdateEvent ) { this.triggerWidgetEvent( 'widget-updated' ); this.previousFormData = new window.FormData( this.formRef.current @@ -41,7 +54,7 @@ class LegacyWidgetEditDomManager extends Component { } render() { - const { id, idBase, widgetNumber, form } = this.props; + const { id, idBase, number, form, isReferenceWidget } = this.props; return ( <div className="widget open" ref={ this.containerRef }> <div className="widget-inside"> @@ -50,6 +63,11 @@ class LegacyWidgetEditDomManager extends Component { method="post" onBlur={ () => { if ( this.shouldTriggerInstanceUpdate() ) { + if ( isReferenceWidget ) { + if ( this.containerRef.current ) { + window.wpWidgets.save( window.jQuery( this.containerRef.current ) ); + } + } this.props.onInstanceChange( this.retrieveUpdatedInstance() ); @@ -61,11 +79,15 @@ class LegacyWidgetEditDomManager extends Component { className="widget-content" dangerouslySetInnerHTML={ { __html: form } } /> - <input type="hidden" name="widget-id" className="widget-id" value={ id } /> - <input type="hidden" name="id_base" className="id_base" value={ idBase } /> - <input type="hidden" name="widget_number" className="widget_number" value={ widgetNumber } /> - <input type="hidden" name="multi_number" className="multi_number" value="" /> - <input type="hidden" name="add_new" className="add_new" value="" /> + { isReferenceWidget && ( + <> + <input type="hidden" name="widget-id" className="widget-id" value={ id } /> + <input ref={ this.idBaseInputRef } type="hidden" name="id_base" className="id_base" value={ idBase } /> + <input ref={ this.widgetNumberInputRef } type="hidden" name="widget_number" className="widget_number" value={ number } /> + <input type="hidden" name="multi_number" className="multi_number" value="" /> + <input type="hidden" name="add_new" className="add_new" value="" /> + </> + ) } </form> </div> </div> @@ -102,20 +124,17 @@ class LegacyWidgetEditDomManager extends Component { } triggerWidgetEvent( event ) { - window.$( window.document ).trigger( + window.jQuery( window.document ).trigger( event, - [ window.$( this.containerRef.current ) ] + [ window.jQuery( this.containerRef.current ) ] ); } retrieveUpdatedInstance() { if ( this.formRef.current ) { - const { idBase, widgetNumber } = this.props; const form = this.formRef.current; const formData = new window.FormData( form ); const updatedInstance = {}; - const keyPrefixLength = `widget-${ idBase }[${ widgetNumber }][`.length; - const keySuffixLength = `]`.length; for ( const rawKey of formData.keys() ) { // This fields are added to the form because the widget JavaScript code may use this values. // They are not relevant for the update mechanism. @@ -125,8 +144,8 @@ class LegacyWidgetEditDomManager extends Component { ) ) { continue; } - const keyParsed = rawKey.substring( keyPrefixLength, rawKey.length - keySuffixLength ); - + const matches = rawKey.match( /[^\[]*\[[-\d]*\]\[([^\]]*)\]/ ); + const keyParsed = matches && matches[ 1 ] ? matches[ 1 ] : rawKey; const value = formData.getAll( rawKey ); if ( value.length > 1 ) { updatedInstance[ keyParsed ] = value; diff --git a/packages/block-library/src/legacy-widget/edit/handler.js b/packages/block-library/src/legacy-widget/edit/handler.js index 157ab653fb89f5..96983ffac1bf29 100644 --- a/packages/block-library/src/legacy-widget/edit/handler.js +++ b/packages/block-library/src/legacy-widget/edit/handler.js @@ -1,8 +1,12 @@ +/** + * External dependencies + */ +import { get } from 'lodash'; + /** * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; import apiFetch from '@wordpress/api-fetch'; import { withInstanceId } from '@wordpress/compose'; @@ -11,13 +15,15 @@ import { withInstanceId } from '@wordpress/compose'; */ import LegacyWidgetEditDomManager from './dom-manager'; +const { XMLHttpRequest, FormData } = window; + class LegacyWidgetEditHandler extends Component { constructor() { super( ...arguments ); this.state = { form: null, - idBase: null, }; + this.widgetNonce = null; this.instanceUpdating = null; this.onInstanceChange = this.onInstanceChange.bind( this ); this.requestWidgetUpdater = this.requestWidgetUpdater.bind( this ); @@ -25,15 +31,23 @@ class LegacyWidgetEditHandler extends Component { componentDidMount() { this.isStillMounted = true; - this.requestWidgetUpdater(); + this.trySetNonce(); + this.requestWidgetUpdater( undefined, ( response ) => { + this.props.onInstanceChange( null, !! response.form ); + } ); } componentDidUpdate( prevProps ) { + if ( ! this.widgetNonce ) { + this.trySetNonce(); + } if ( prevProps.instance !== this.props.instance && this.instanceUpdating !== this.props.instance ) { - this.requestWidgetUpdater(); + this.requestWidgetUpdater( undefined, ( response ) => { + this.props.onInstanceChange( null, !! response.form ); + } ); } if ( this.instanceUpdating === this.props.instance ) { this.instanceUpdating = null; @@ -45,78 +59,133 @@ class LegacyWidgetEditHandler extends Component { } render() { - const { instanceId, identifier } = this.props; - const { id, idBase, form } = this.state; - if ( ! identifier ) { - return __( 'Not a valid widget.' ); - } + const { instanceId, id, number, idBase, instance, isSelected, widgetName } = this.props; + const { form } = this.state; + if ( ! form ) { return null; } + + 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; + } + } return ( - <div - className="wp-block-legacy-widget__edit-container" - // Display none is used because when we switch from edit to preview, - // we don't want to unmount the component. - // Otherwise when we went back to edit we wound need to trigger - // all widgets events again and some scripts may not deal well with this. - style={ { - display: this.props.isVisible ? 'block' : 'none', - } } - > - <LegacyWidgetEditDomManager - ref={ ( ref ) => { - this.widgetEditDomManagerRef = ref; + <> + { title && ( + <div className="wp-block-legacy-widget__edit-widget-title"> + { title } + </div> + ) } + <div + className="wp-block-legacy-widget__edit-container" + // Display none is used because when we switch from edit to preview, + // we don't want to unmount the component. + // Otherwise when we went back to edit we wound need to trigger + // all widgets events again and some scripts may not deal well with this. + style={ { + display: this.props.isVisible ? 'block' : 'none', } } - onInstanceChange={ this.onInstanceChange } - widgetNumber={ instanceId * -1 } - id={ id } - idBase={ idBase } - form={ form } - /> - </div> + > + + <LegacyWidgetEditDomManager + isReferenceWidget={ !! id } + ref={ ( ref ) => { + this.widgetEditDomManagerRef = ref; + } } + onInstanceChange={ this.onInstanceChange } + number={ number ? number : instanceId * -1 } + id={ id } + idBase={ idBase } + form={ form } + /> + </div> + </> ); } + trySetNonce() { + const element = document.getElementById( '_wpnonce_widgets' ); + if ( element && element.value ) { + this.widgetNonce = element.value; + } + } + onInstanceChange( instanceChanges ) { + const { id } = this.props; + if ( id ) { + // For reference widgets there is no need to query an endpoint, + // the widget is already saved with ajax. + this.props.onInstanceChange( instanceChanges, true ); + return; + } this.requestWidgetUpdater( instanceChanges, ( response ) => { this.instanceUpdating = response.instance; - this.props.onInstanceChange( response.instance ); + this.props.onInstanceChange( response.instance, !! response.form ); } ); } requestWidgetUpdater( instanceChanges, callback ) { - const { identifier, instanceId, instance } = this.props; - if ( ! identifier ) { + const { id, idBase, instance, widgetClass } = this.props; + const { isStillMounted } = this; + if ( ! id && ! widgetClass ) { return; } - apiFetch( { - path: `/wp/v2/widgets/${ identifier }/`, - data: { - identifier, - instance, - // use negative ids to make sure the id does not exist on the database. - id_to_use: instanceId * -1, - instance_changes: instanceChanges, - }, - method: 'POST', - } ).then( - ( response ) => { - if ( this.isStillMounted ) { - this.setState( { - form: response.form, - idBase: response.id_base, - id: response.id, - } ); + // If we are in the presence of a reference widget, do a save ajax request + // with empty changes so we retrieve the widget edit form. + if ( id ) { + const httpRequest = new XMLHttpRequest(); + const formData = new FormData(); + formData.append( 'action', 'save-widget' ); + formData.append( 'id_base', idBase ); + formData.append( 'widget-id', id ); + formData.append( 'widget-width', '250' ); + formData.append( 'widget-height', '200' ); + formData.append( 'savewidgets', this.widgetNonce ); + httpRequest.open( 'POST', window.ajaxurl ); + httpRequest.addEventListener( 'load', () => { + if ( isStillMounted ) { + const form = httpRequest.responseText; + this.setState( { form } ); if ( callback ) { - callback( response ); + callback( { form } ); } } - } - ); + } ); + httpRequest.send( formData ); + return; + } + + if ( widgetClass ) { + apiFetch( { + path: `/__experimental/widget-forms/${ widgetClass }/`, + data: { + instance, + instance_changes: instanceChanges, + }, + method: 'POST', + } ).then( + ( response ) => { + if ( isStillMounted ) { + this.setState( { + form: response.form, + } ); + if ( callback ) { + callback( response ); + } + } + } + ); + } } } export default withInstanceId( LegacyWidgetEditHandler ); - diff --git a/packages/block-library/src/legacy-widget/edit/index.js b/packages/block-library/src/legacy-widget/edit/index.js index 31f36c2f7fac94..fdcb51e092a993 100644 --- a/packages/block-library/src/legacy-widget/edit/index.js +++ b/packages/block-library/src/legacy-widget/edit/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { get } from 'lodash'; + /** * WordPress dependencies */ @@ -6,7 +11,7 @@ import { Button, IconButton, PanelBody, - Toolbar, + ToolbarGroup, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { withSelect } from '@wordpress/data'; @@ -26,6 +31,7 @@ class LegacyWidgetEdit extends Component { constructor() { super( ...arguments ); this.state = { + hasEditForm: true, isPreview: false, }; this.switchToEdit = this.switchToEdit.bind( this ); @@ -38,33 +44,38 @@ class LegacyWidgetEdit extends Component { attributes, availableLegacyWidgets, hasPermissionsToManageWidgets, + isSelected, setAttributes, } = this.props; - const { isPreview } = this.state; - const { identifier, isCallbackWidget } = attributes; - const widgetObject = identifier && availableLegacyWidgets[ identifier ]; - if ( ! widgetObject ) { + const { isPreview, hasEditForm } = this.state; + const { id, widgetClass } = attributes; + const widgetObject = + ( id && availableLegacyWidgets[ id ] ) || + ( widgetClass && availableLegacyWidgets[ widgetClass ] ); + if ( ! id && ! widgetClass ) { return ( <LegacyWidgetPlaceholder availableLegacyWidgets={ availableLegacyWidgets } - currentWidget={ identifier } hasPermissionsToManageWidgets={ hasPermissionsToManageWidgets } - onChangeWidget={ ( newWidget ) => setAttributes( { - instance: {}, - identifier: newWidget, - isCallbackWidget: availableLegacyWidgets[ newWidget ].isCallbackWidget, - } ) } + onChangeWidget={ ( newWidget ) => { + const { isReferenceWidget } = availableLegacyWidgets[ newWidget ]; + setAttributes( { + instance: {}, + id: isReferenceWidget ? newWidget : undefined, + widgetClass: isReferenceWidget ? undefined : newWidget, + } ); + } } /> ); } - const inspectorControls = ( + const inspectorControls = widgetObject ? ( <InspectorControls> <PanelBody title={ widgetObject.name }> { widgetObject.description } </PanelBody> </InspectorControls> - ); + ) : null; if ( ! hasPermissionsToManageWidgets ) { return ( <> @@ -77,48 +88,62 @@ class LegacyWidgetEdit extends Component { return ( <> <BlockControls> - <Toolbar> - { ! widgetObject.isHidden && ( + <ToolbarGroup> + { ( widgetObject && ! widgetObject.isHidden ) && ( <IconButton onClick={ this.changeWidget } label={ __( 'Change widget' ) } icon="update" /> ) } - { ! isCallbackWidget && ( + { hasEditForm && ( <> <Button - className={ `components-tab-button ${ ! isPreview ? 'is-active' : '' }` } + className="components-tab-button" + isPressed={ ! isPreview } onClick={ this.switchToEdit } > <span>{ __( 'Edit' ) }</span> </Button> <Button - className={ `components-tab-button ${ isPreview ? 'is-active' : '' }` } + className="components-tab-button" + isPressed={ isPreview } onClick={ this.switchToPreview } > <span>{ __( 'Preview' ) }</span> </Button> </> ) } - </Toolbar> + </ToolbarGroup> </BlockControls> { inspectorControls } - { ! isCallbackWidget && ( + { hasEditForm && ( <LegacyWidgetEditHandler + isSelected={ isSelected } isVisible={ ! isPreview } - identifier={ attributes.identifier } + id={ id } + idBase={ attributes.idBase || attributes.id } + number={ attributes.number } + widgetName={ get( widgetObject, [ 'name' ] ) } + widgetClass={ attributes.widgetClass } instance={ attributes.instance } onInstanceChange={ - ( newInstance ) => { - this.props.setAttributes( { - instance: newInstance, - } ); + ( newInstance, newHasEditForm ) => { + if ( newInstance ) { + this.props.setAttributes( { + instance: newInstance, + } ); + } + if ( newHasEditForm !== this.hasEditForm ) { + this.setState( { + hasEditForm: newHasEditForm, + } ); + } } } /> ) } - { ( isPreview || isCallbackWidget ) && this.renderWidgetPreview() } + { ( isPreview || ! hasEditForm ) && this.renderWidgetPreview() } </> ); } @@ -127,7 +152,11 @@ class LegacyWidgetEdit extends Component { this.switchToEdit(); this.props.setAttributes( { instance: {}, - identifier: undefined, + id: undefined, + widgetClass: undefined, + } ); + this.setState( { + hasEditForm: true, } ); } diff --git a/packages/block-library/src/legacy-widget/edit/placeholder.js b/packages/block-library/src/legacy-widget/edit/placeholder.js index b19f5654ab4c6f..0a5e2ae0b4a39f 100644 --- a/packages/block-library/src/legacy-widget/edit/placeholder.js +++ b/packages/block-library/src/legacy-widget/edit/placeholder.js @@ -27,25 +27,25 @@ export default function LegacyWidgetPlaceholder( { let placeholderContent; if ( ! hasPermissionsToManageWidgets ) { placeholderContent = __( 'You don\'t have permissions to use widgets on this site.' ); - } - if ( isEmpty( visibleLegacyWidgets ) ) { + } else if ( isEmpty( visibleLegacyWidgets ) ) { placeholderContent = __( 'There are no widgets available.' ); + } else { + placeholderContent = ( + <SelectControl + label={ __( 'Select a legacy widget to display:' ) } + value={ currentWidget || 'none' } + onChange={ onChangeWidget } + options={ [ { value: 'none', label: 'Select widget' } ].concat( + map( visibleLegacyWidgets, ( widget, key ) => { + return { + value: key, + label: widget.name, + }; + } ) + ) } + /> + ); } - placeholderContent = ( - <SelectControl - label={ __( 'Select a legacy widget to display:' ) } - value={ currentWidget || 'none' } - onChange={ onChangeWidget } - options={ [ { value: 'none', label: 'Select widget' } ].concat( - map( visibleLegacyWidgets, ( widget, key ) => { - return { - value: key, - label: widget.name, - }; - } ) - ) } - /> - ); return ( <Placeholder icon={ <BlockIcon icon="admin-customizer" /> } diff --git a/packages/block-library/src/legacy-widget/editor.scss b/packages/block-library/src/legacy-widget/editor.scss index 29f9c0b869731e..00d91e5528c9fc 100644 --- a/packages/block-library/src/legacy-widget/editor.scss +++ b/packages/block-library/src/legacy-widget/editor.scss @@ -20,3 +20,15 @@ .wp-block-legacy-widget__preview { overflow: auto; } + +.wp-block-legacy-widget__edit-widget-title { + margin: 0 -$block-padding 0 -$block-padding+1px 0; + background: $light-gray-100; + color: $dark-gray-500; + top: -$block-padding+1px; + position: relative; + font-family: $default-font; + font-size: $default-font-size; + padding: $grid-size $block-padding; + font-weight: 600; +} diff --git a/packages/block-library/src/legacy-widget/index.php b/packages/block-library/src/legacy-widget/index.php index b812f8e3c710ff..d0a91f3f6c619e 100644 --- a/packages/block-library/src/legacy-widget/index.php +++ b/packages/block-library/src/legacy-widget/index.php @@ -5,6 +5,63 @@ * @package WordPress */ +/** + * Returns the result of rendering a widget having its instance id. + * + * @param string $id Widget id. + * + * @return string Returns the rendered widget as a string. + */ +function render_widget_by_id( $id ) { + // Code extracted from src/wp-includes/widgets.php dynamic_sidebar function. + // Todo: When merging to core extract this part of dynamic_sidebar into its own function. + global $wp_registered_widgets; + + if ( ! isset( $wp_registered_widgets[ $id ] ) ) { + return false; + } + $params = array_merge( + array( + array_merge( + array( + 'before_widget' => '<div class="widget %s">', + 'after_widget' => '</div>', + 'before_title' => '<h2 class="widgettitle">', + 'after_title' => '</h2>', + ), + array( + 'widget_id' => $id, + 'widget_name' => $wp_registered_widgets[ $id ]['name'], + ) + ), + ), + (array) $wp_registered_widgets[ $id ]['params'] + ); + + // Substitute HTML id and class attributes into before_widget. + $classname_ = ''; + foreach ( (array) $wp_registered_widgets[ $id ]['classname'] as $cn ) { + if ( is_string( $cn ) ) { + $classname_ .= '_' . $cn; + } elseif ( is_object( $cn ) ) { + $classname_ .= '_' . get_class( $cn ); + } + } + $classname_ = ltrim( $classname_, '_' ); + $params[0]['before_widget'] = sprintf( $params[0]['before_widget'], $id, $classname_ ); + + $params = apply_filters( 'dynamic_sidebar_params', $params ); + $callback = $wp_registered_widgets[ $id ]['callback']; + do_action( 'dynamic_sidebar', $wp_registered_widgets[ $id ] ); + + if ( is_callable( $callback ) ) { + ob_start(); + call_user_func_array( $callback, $params ); + return ob_get_clean(); + } + return false; +} + /** * Renders the `core/legacy-widget` block on server. * @@ -15,41 +72,29 @@ * @return string Returns the post content with the legacy widget added. */ function render_block_legacy_widget( $attributes ) { - if ( ! isset( $attributes['identifier'] ) ) { - return ''; + $id = null; + $widget_class = null; + if ( isset( $attributes['id'] ) ) { + $id = $attributes['id']; + } + if ( isset( $attributes['widgetClass'] ) ) { + $widget_class = $attributes['widgetClass']; } - $identifier = $attributes['identifier']; - if ( - isset( $attributes['isCallbackWidget'] ) && - $attributes['isCallbackWidget'] - ) { - global $wp_registered_widgets; - if ( ! isset( $wp_registered_widgets[ $identifier ] ) ) { - return ''; - } - $widget = $wp_registered_widgets[ $identifier ]; - $params = array_merge( - array( - 'widget_id' => $identifier, - 'widget_name' => $widget['name'], - ), - (array) $wp_registered_widgets[ $identifier ]['params'] - ); - $params = apply_filters( 'dynamic_sidebar_params', $params ); - - $callback = $widget['callback']; - if ( is_callable( $callback ) ) { - ob_start(); - call_user_func_array( $callback, $params ); - return ob_get_clean(); - } + if ( $id ) { + return render_widget_by_id( $id ); + } + if ( ! $widget_class ) { return ''; } + ob_start(); - the_widget( $identifier, $attributes['instance'] ); + $instance = null; + if ( isset( $attributes['instance'] ) ) { + $instance = $attributes['instance']; + } + the_widget( $widget_class, $instance ); return ob_get_clean(); - } /** @@ -60,14 +105,20 @@ function register_block_core_legacy_widget() { 'core/legacy-widget', array( 'attributes' => array( - 'identifier' => array( + 'widgetClass' => array( 'type' => 'string', ), - 'instance' => array( - 'type' => 'object', + 'id' => array( + 'type' => 'string', ), - 'isCallbackWidget' => array( - 'type' => 'boolean', + 'idBase' => array( + 'type' => 'string', + ), + 'number' => array( + 'type' => 'number', + ), + 'instance' => array( + 'type' => 'object', ), ), 'render_callback' => 'render_block_legacy_widget', diff --git a/packages/block-library/src/list/block.json b/packages/block-library/src/list/block.json index 654bd3e281ccf5..71bca30f1a8ac3 100644 --- a/packages/block-library/src/list/block.json +++ b/packages/block-library/src/list/block.json @@ -11,8 +11,12 @@ "source": "html", "selector": "ol,ul", "multiline": "li", + "__unstableMultilineWrapperTags": [ "ol", "ul" ], "default": "" }, + "type": { + "type": "string" + }, "start": { "type": "number" }, diff --git a/packages/block-library/src/list/edit.js b/packages/block-library/src/list/edit.js index e9496cf359d9b2..3ca1bb26ec544d 100644 --- a/packages/block-library/src/list/edit.js +++ b/packages/block-library/src/list/edit.js @@ -9,9 +9,11 @@ import { RichTextShortcut, } from '@wordpress/block-editor'; import { - Toolbar, + ToolbarGroup, } from '@wordpress/components'; import { + __unstableCanIndentListItems as canIndentListItems, + __unstableCanOutdentListItems as canOutdentListItems, __unstableIndentListItems as indentListItems, __unstableOutdentListItems as outdentListItems, __unstableChangeListType as changeListType, @@ -32,15 +34,11 @@ export default function ListEdit( { onReplace, className, } ) { - const { ordered, values, reversed, start } = attributes; + const { ordered, values, type, reversed, start } = attributes; const tagName = ordered ? 'ol' : 'ul'; - const controls = ( { value, onChange } ) => { - if ( value.start === undefined ) { - return; - } - - return <> + const controls = ( { value, onChange } ) => ( + <> <RichTextShortcut type="primary" character="[" @@ -70,7 +68,7 @@ export default function ListEdit( { } } /> <BlockControls> - <Toolbar + <ToolbarGroup controls={ [ { icon: 'editor-ul', @@ -100,6 +98,7 @@ export default function ListEdit( { icon: 'editor-outdent', title: __( 'Outdent list item' ), shortcut: _x( 'Backspace', 'keyboard key' ), + isDisabled: ! canOutdentListItems( value ), onClick() { onChange( outdentListItems( value ) ); }, @@ -108,6 +107,7 @@ export default function ListEdit( { icon: 'editor-indent', title: __( 'Indent list item' ), shortcut: _x( 'Space', 'keyboard key' ), + isDisabled: ! canIndentListItems( value ), onClick() { onChange( indentListItems( value, { type: tagName } ) ); }, @@ -115,8 +115,8 @@ export default function ListEdit( { ] } /> </BlockControls> - </>; - }; + </> + ); return <> <RichText @@ -128,12 +128,13 @@ export default function ListEdit( { className={ className } placeholder={ __( 'Write list…' ) } onMerge={ mergeBlocks } - onSplit={ ( value ) => createBlock( name, { ordered, values: value } ) } + onSplit={ ( value ) => createBlock( name, { ...attributes, values: value } ) } __unstableOnSplitMiddle={ () => createBlock( 'core/paragraph' ) } onReplace={ onReplace } onRemove={ () => onReplace( [] ) } start={ start } reversed={ reversed } + type={ type } > { controls } </RichText> diff --git a/packages/block-library/src/list/editor.scss b/packages/block-library/src/list/editor.scss deleted file mode 100644 index fffff9ebaf6ee1..00000000000000 --- a/packages/block-library/src/list/editor.scss +++ /dev/null @@ -1,5 +0,0 @@ -.editor-styles-wrapper div[data-type="core/list"] ul, -.editor-styles-wrapper div[data-type="core/list"] ol { - padding-left: 1.3em; - margin-left: 1.3em; -} diff --git a/packages/block-library/src/list/ordered-list-settings.native.js b/packages/block-library/src/list/ordered-list-settings.native.js deleted file mode 100644 index 43bab31a4330eb..00000000000000 --- a/packages/block-library/src/list/ordered-list-settings.native.js +++ /dev/null @@ -1,3 +0,0 @@ -// Mobile has no additional list settings at this time, so render nothing -const AdditionalSettings = () => null; -export default AdditionalSettings; diff --git a/packages/block-library/src/list/save.js b/packages/block-library/src/list/save.js index 18458c2c1ce5a5..8cd1d3870148ba 100644 --- a/packages/block-library/src/list/save.js +++ b/packages/block-library/src/list/save.js @@ -4,13 +4,14 @@ import { RichText } from '@wordpress/block-editor'; export default function save( { attributes } ) { - const { ordered, values, reversed, start } = attributes; + const { ordered, values, type, reversed, start } = attributes; const tagName = ordered ? 'ol' : 'ul'; return ( <RichText.Content tagName={ tagName } value={ values } + type={ type } reversed={ reversed } start={ start } multiline="li" diff --git a/packages/block-library/src/list/transforms.js b/packages/block-library/src/list/transforms.js index baf09b1b945c6d..edfed2b3ed9c8a 100644 --- a/packages/block-library/src/list/transforms.js +++ b/packages/block-library/src/list/transforms.js @@ -4,7 +4,6 @@ import { createBlock, getBlockAttributes, - getPhrasingContentSchema, } from '@wordpress/blocks'; import { __UNSTABLE_LINE_SEPARATOR, @@ -15,22 +14,26 @@ import { toHTMLString, } from '@wordpress/rich-text'; -const listContentSchema = { - ...getPhrasingContentSchema(), - ul: {}, - ol: { attributes: [ 'type' ] }, -}; - -// Recursion is needed. -// Possible: ul > li > ul. -// Impossible: ul > ul. -[ 'ul', 'ol' ].forEach( ( tag ) => { - listContentSchema[ tag ].children = { - li: { - children: listContentSchema, - }, +function getListContentSchema( { phrasingContentSchema } ) { + const listContentSchema = { + ...phrasingContentSchema, + ul: {}, + ol: { attributes: [ 'type', 'start', 'reversed' ] }, }; -} ); + + // Recursion is needed. + // Possible: ul > li > ul. + // Impossible: ul > ul. + [ 'ul', 'ol' ].forEach( ( tag ) => { + listContentSchema[ tag ].children = { + li: { + children: listContentSchema, + }, + }; + } ); + + return listContentSchema; +} const transforms = { from: [ @@ -72,17 +75,40 @@ const transforms = { { type: 'raw', selector: 'ol,ul', - schema: { - ol: listContentSchema.ol, - ul: listContentSchema.ul, - }, + schema: ( args ) => ( { + ol: getListContentSchema( args ).ol, + ul: getListContentSchema( args ).ul, + } ), transform( node ) { - return createBlock( 'core/list', { - ...getBlockAttributes( - 'core/list', - node.outerHTML - ), + const attributes = { ordered: node.nodeName === 'OL', + }; + + if ( attributes.ordered ) { + const type = node.getAttribute( 'type' ); + + if ( type ) { + attributes.type = type; + } + + if ( node.getAttribute( 'reversed' ) !== null ) { + attributes.reversed = true; + } + + const start = parseInt( node.getAttribute( 'start' ), 10 ); + + if ( + ! isNaN( start ) && + // start=1 only makes sense if the list is reversed. + ( start !== 1 || attributes.reversed ) + ) { + attributes.start = start; + } + } + + return createBlock( 'core/list', { + ...getBlockAttributes( 'core/list', node.outerHTML ), + ...attributes, } ); }, }, diff --git a/packages/block-library/src/media-text/edit.js b/packages/block-library/src/media-text/edit.js index da93226253817a..44f15a8ce00ee8 100644 --- a/packages/block-library/src/media-text/edit.js +++ b/packages/block-library/src/media-text/edit.js @@ -18,12 +18,12 @@ import { } from '@wordpress/block-editor'; import { Component } from '@wordpress/element'; import { + ExternalLink, + FocalPointPicker, PanelBody, TextareaControl, ToggleControl, - Toolbar, - ExternalLink, - FocalPointPicker, + ToolbarGroup, } from '@wordpress/components'; /** * Internal dependencies @@ -80,7 +80,6 @@ class MediaTextEdit extends Component { mediaId: media.id, mediaType, mediaUrl: src || media.url, - imageFill: undefined, focalPoint: undefined, } ); } @@ -148,8 +147,10 @@ class MediaTextEdit extends Component { 'is-image-fill': imageFill, } ); const widthString = `${ temporaryMediaWidth || mediaWidth }%`; + const gridTemplateColumns = 'right' === mediaPosition ? `1fr ${ widthString }` : `${ widthString } 1fr`; const style = { - gridTemplateColumns: 'right' === mediaPosition ? `auto ${ widthString }` : `${ widthString } auto`, + gridTemplateColumns, + msGridColumns: gridTemplateColumns, backgroundColor: backgroundColor.color, }; const colorSettings = [ { @@ -222,7 +223,7 @@ class MediaTextEdit extends Component { /> </InspectorControls> <BlockControls> - <Toolbar + <ToolbarGroup controls={ toolbarControls } /> <BlockVerticalAlignmentToolbar diff --git a/packages/block-library/src/media-text/edit.native.js b/packages/block-library/src/media-text/edit.native.js index 3cfb91b48ce949..5645e40bed1548 100644 --- a/packages/block-library/src/media-text/edit.native.js +++ b/packages/block-library/src/media-text/edit.native.js @@ -7,7 +7,7 @@ import { View } from 'react-native'; /** * WordPress dependencies */ -import { __, _x } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { BlockControls, BlockVerticalAlignmentToolbar, @@ -16,8 +16,11 @@ import { } from '@wordpress/block-editor'; import { Component } from '@wordpress/element'; import { - Toolbar, + ToolbarGroup, } from '@wordpress/components'; +import { withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; +import { withViewportMatch } from '@wordpress/viewport'; /** * Internal dependencies @@ -30,7 +33,7 @@ import styles from './style.scss'; */ const ALLOWED_BLOCKS = [ 'core/button', 'core/paragraph', 'core/heading', 'core/list' ]; const TEMPLATE = [ - [ 'core/paragraph', { fontSize: 'large', placeholder: _x( 'Content…', 'content placeholder' ) } ], + [ 'core/paragraph' ], ]; // this limits the resize to a safe zone to avoid making broken layouts const WIDTH_CONSTRAINT_PERCENTAGE = 15; @@ -41,6 +44,7 @@ class MediaTextEdit extends Component { super( ...arguments ); this.onSelectMedia = this.onSelectMedia.bind( this ); + this.onMediaUpdate = this.onMediaUpdate.bind( this ); this.onWidthChange = this.onWidthChange.bind( this ); this.commitWidthChange = this.commitWidthChange.bind( this ); this.state = { @@ -66,7 +70,7 @@ class MediaTextEdit extends Component { mediaType = media.type; } - if ( mediaType === 'image' ) { + if ( mediaType === 'image' && media.sizes ) { // Try the "large" size URL, falling back to the "full" size URL below. src = get( media, [ 'sizes', 'large', 'url' ] ) || get( media, [ 'media_details', 'sizes', 'large', 'source_url' ] ); } @@ -81,6 +85,15 @@ class MediaTextEdit extends Component { } ); } + onMediaUpdate( media ) { + const { setAttributes } = this.props; + + setAttributes( { + mediaId: media.id, + mediaUrl: media.url, + } ); + } + onWidthChange( width ) { this.setState( { mediaWidth: applyWidthConstraints( width ), @@ -99,16 +112,17 @@ class MediaTextEdit extends Component { } renderMediaArea() { - const { attributes } = this.props; + const { attributes, isSelected } = this.props; const { mediaAlt, mediaId, mediaPosition, mediaType, mediaUrl, mediaWidth, imageFill, focalPoint } = attributes; return ( <MediaContainer onSelectMedia={ this.onSelectMedia } + onMediaUpdate={ this.onMediaUpdate } onWidthChange={ this.onWidthChange } commitWidthChange={ this.commitWidthChange } onFocus={ this.props.onFocus } - { ...{ mediaAlt, mediaId, mediaType, mediaUrl, mediaPosition, mediaWidth, imageFill, focalPoint } } + { ...{ mediaAlt, mediaId, mediaType, mediaUrl, mediaPosition, mediaWidth, imageFill, focalPoint, isSelected } } /> ); } @@ -118,6 +132,10 @@ class MediaTextEdit extends Component { attributes, backgroundColor, setAttributes, + isMobile, + isSelected, + isParentSelected, + isAncestorSelected, } = this.props; const { isStackedOnMobile, @@ -125,18 +143,29 @@ class MediaTextEdit extends Component { mediaWidth, verticalAlignment, } = attributes; - const temporaryMediaWidth = this.state.mediaWidth || mediaWidth; + const shouldStack = isStackedOnMobile && isMobile; + const temporaryMediaWidth = shouldStack ? 100 : ( this.state.mediaWidth || mediaWidth ); const widthString = `${ temporaryMediaWidth }%`; + + const innerBlockContainerStyle = ! shouldStack && { + ...styles.paddingHorizontalNone, + ...( mediaPosition === 'right' && styles.innerPaddingMediaOnRight ), + ...( mediaPosition === 'left' && styles.innerPaddingMediaOnLeft ), + }; const containerStyles = { ...styles[ 'wp-block-media-text' ], - ...styles[ `is-vertically-aligned-${ verticalAlignment }` ], + ...styles[ `is-vertically-aligned-${ verticalAlignment || 'center' }` ], ...( mediaPosition === 'right' ? styles[ 'has-media-on-the-right' ] : {} ), - ...( isStackedOnMobile ? styles[ 'is-stacked-on-mobile' ] : {} ), - ...( isStackedOnMobile && mediaPosition === 'right' ? styles[ 'is-stacked-on-mobile.has-media-on-the-right' ] : {} ), + ...( shouldStack ? styles[ 'is-stacked-on-mobile' ] : {} ), + ...( shouldStack && mediaPosition === 'right' ? styles[ 'is-stacked-on-mobile.has-media-on-the-right' ] : {} ), backgroundColor: backgroundColor.color, }; - const innerBlockWidth = 100 - temporaryMediaWidth; + const innerBlockWidth = shouldStack ? 100 : ( 100 - temporaryMediaWidth ); const innerBlockWidthString = `${ innerBlockWidth }%`; + const mediaContainerStyle = { + ...( isParentSelected || isAncestorSelected ? styles.denseMediaPadding : styles.regularMediaPadding ), + ...( isSelected && styles.innerPadding ), + }; const toolbarControls = [ { icon: 'align-pull-left', @@ -157,7 +186,7 @@ class MediaTextEdit extends Component { return ( <> <BlockControls> - <Toolbar + <ToolbarGroup controls={ toolbarControls } /> <BlockVerticalAlignmentToolbar @@ -167,10 +196,10 @@ class MediaTextEdit extends Component { /> </BlockControls> <View style={ containerStyles }> - <View style={ { width: widthString } }> + <View style={ { width: widthString, ...mediaContainerStyle } } > { this.renderMediaArea() } </View> - <View style={ { width: innerBlockWidthString } }> + <View style={ { width: innerBlockWidthString, ...innerBlockContainerStyle } }> <InnerBlocks allowedBlocks={ ALLOWED_BLOCKS } template={ TEMPLATE } @@ -183,4 +212,26 @@ class MediaTextEdit extends Component { } } -export default withColors( 'backgroundColor' )( MediaTextEdit ); +export default compose( + withColors( 'backgroundColor' ), + withViewportMatch( { isMobile: '< small' } ), + withSelect( ( select, { clientId } ) => { + const { + getSelectedBlockClientId, + getBlockRootClientId, + getBlockParents, + } = select( 'core/block-editor' ); + + const parents = getBlockParents( clientId, true ); + + const selectedBlockClientId = getSelectedBlockClientId(); + const isParentSelected = selectedBlockClientId && selectedBlockClientId === getBlockRootClientId( clientId ); + const isAncestorSelected = selectedBlockClientId && parents.includes( selectedBlockClientId ); + + return { + isSelected: selectedBlockClientId === clientId, + isParentSelected, + isAncestorSelected, + }; + } ), +)( MediaTextEdit ); diff --git a/packages/block-library/src/media-text/editor.scss b/packages/block-library/src/media-text/editor.scss index 3d0ad0361bd947..5f6cabb151f90b 100644 --- a/packages/block-library/src/media-text/editor.scss +++ b/packages/block-library/src/media-text/editor.scss @@ -1,22 +1,32 @@ -.wp-block-media-text { - grid-template-areas: - "media-text-media media-text-content" - "resizer resizer"; - align-items: center; +.wp-block-media-text .__resizable_base__ { + grid-column: 1 / span 2; + grid-row: 2; } -.wp-block-media-text.has-media-on-the-right { - grid-template-areas: - "media-text-content media-text-media" - "resizer resizer"; +.wp-block-media-text.is-vertically-aligned-top { + .block-editor-inner-blocks, + .editor-media-container__resizer { + align-self: start; + } +} +.wp-block-media-text, +.wp-block-media-text.is-vertically-aligned-center { + .block-editor-inner-blocks, + .editor-media-container__resizer { + align-self: center; + } } -.wp-block-media-text .__resizable_base__ { - grid-area: resizer; +.wp-block-media-text.is-vertically-aligned-bottom { + .block-editor-inner-blocks, + .editor-media-container__resizer { + align-self: end; + } } .wp-block-media-text .editor-media-container__resizer { - grid-area: media-text-media; + grid-column: 1; + grid-row: 1; // The resizer sets a inline width but as we are using a grid layout, // we set the width on container. width: 100% !important; @@ -27,13 +37,24 @@ height: 100% !important; } -.wp-block-media-text .block-editor-inner-blocks { +.wp-block-media-text.has-media-on-the-right .editor-media-container__resizer { + grid-column: 2; + grid-row: 1; +} + +.wp-block-media-text .editor-inner-blocks { word-break: break-word; - grid-area: media-text-content; + grid-column: 2; + grid-row: 1; text-align: initial; padding: 0 8% 0 8%; } +.wp-block-media-text.has-media-on-the-right .block-editor-inner-blocks { + grid-column: 1; + grid-row: 1; +} + .wp-block-media-text > .block-editor-inner-blocks > .block-editor-block-list__layout > .block-editor-block-list__block { max-width: unset; } @@ -67,3 +88,27 @@ figure.block-library-media-text__media-container { } } } + +@media (max-width: #{ ($break-small) }) { + .wp-block-media-text.is-stacked-on-mobile { + .block-editor-inner-blocks { + grid-column: 1; + grid-row: 2; + } + .editor-media-container__resizer { + grid-column: 1; + grid-row: 1; + } + } + .wp-block-media-text.is-stacked-on-mobile.has-media-on-the-right { + .block-editor-inner-blocks { + grid-column: 1; + grid-row: 1; + } + .editor-media-container__resizer { + grid-column: 1; + grid-row: 2; + } + } +} + diff --git a/packages/block-library/src/media-text/icon-retry.native.js b/packages/block-library/src/media-text/icon-retry.native.js new file mode 100644 index 00000000000000..bdd40f69efd64a --- /dev/null +++ b/packages/block-library/src/media-text/icon-retry.native.js @@ -0,0 +1,6 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; + +export default <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" /><Path d="M0 0h24v24H0z" fill={ 'none' } /></SVG>; diff --git a/packages/block-library/src/media-text/index.js b/packages/block-library/src/media-text/index.js index 242bcddf2fbcbd..73f1ee2080a1b4 100644 --- a/packages/block-library/src/media-text/index.js +++ b/packages/block-library/src/media-text/index.js @@ -29,7 +29,7 @@ export const settings = { example: { attributes: { mediaType: 'image', - mediaUrl: 'https://upload.wikimedia.org/wikipedia/commons/d/d4/Biologia_Centrali-Americana_-_Cantorchilus_semibadius_1902.jpg', + mediaUrl: 'https://s.w.org/images/core/5.3/Biologia_Centrali-Americana_-_Cantorchilus_semibadius_1902.jpg', }, innerBlocks: [ { diff --git a/packages/block-library/src/media-text/media-container.js b/packages/block-library/src/media-text/media-container.js index fce35487d6a03b..2d132121d4a2c6 100644 --- a/packages/block-library/src/media-text/media-container.js +++ b/packages/block-library/src/media-text/media-container.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { IconButton, ResizableBox, Toolbar, withNotices } from '@wordpress/components'; +import { IconButton, ResizableBox, ToolbarGroup, withNotices } from '@wordpress/components'; import { BlockControls, BlockIcon, @@ -48,7 +48,7 @@ class MediaContainer extends Component { const { mediaId, onSelectMedia } = this.props; return ( <BlockControls> - <Toolbar> + <ToolbarGroup> <MediaUpload onSelect={ onSelectMedia } allowedTypes={ ALLOWED_MEDIA_TYPES } @@ -62,7 +62,7 @@ class MediaContainer extends Component { /> ) } /> - </Toolbar> + </ToolbarGroup> </BlockControls> ); } diff --git a/packages/block-library/src/media-text/media-container.native.js b/packages/block-library/src/media-text/media-container.native.js index 0b47812e5f5ac0..718e035469d0ed 100644 --- a/packages/block-library/src/media-text/media-container.native.js +++ b/packages/block-library/src/media-text/media-container.native.js @@ -1,51 +1,74 @@ /** * External dependencies */ -import { View, Image, ImageBackground } from 'react-native'; +import { View, ImageBackground, Text, TouchableWithoutFeedback } from 'react-native'; +import { + mediaUploadSync, + requestImageFailedRetryDialog, + requestImageUploadCancelDialog, +} from 'react-native-gutenberg-bridge'; /** * WordPress dependencies */ -import { IconButton, Toolbar, withNotices } from '@wordpress/components'; +import { + Icon, + IconButton, + ToolbarGroup, + withNotices, +} from '@wordpress/components'; import { BlockControls, - BlockIcon, - MediaPlaceholder, MEDIA_TYPE_IMAGE, + MEDIA_TYPE_VIDEO, + MediaPlaceholder, MediaUpload, + MediaUploadProgress, + VIDEO_ASPECT_RATIO, + VideoPlayer, } from '@wordpress/block-editor'; import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +import { isURL } from '@wordpress/url'; +import { compose, withPreferredColorScheme } from '@wordpress/compose'; /** * Internal dependencies */ +import styles from './style.scss'; import icon from './media-container-icon'; +import SvgIconRetry from './icon-retry'; -export function calculatePreferedImageSize( image, container ) { - const maxWidth = container.clientWidth; - const exceedMaxWidth = image.width > maxWidth; - const ratio = image.height / image.width; - const width = exceedMaxWidth ? maxWidth : image.width; - const height = exceedMaxWidth ? maxWidth * ratio : image.height; - return { width, height }; -} +/** + * Constants + */ +const ALLOWED_MEDIA_TYPES = [ MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO ]; + +export { imageFillStyles } from './media-container.js'; class MediaContainer extends Component { constructor() { super( ...arguments ); this.onUploadError = this.onUploadError.bind( this ); - this.calculateSize = this.calculateSize.bind( this ); - this.onLayout = this.onLayout.bind( this ); + this.updateMediaProgress = this.updateMediaProgress.bind( this ); + this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this ); + this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( this ); + this.mediaUploadStateReset = this.mediaUploadStateReset.bind( this ); this.onSelectMediaUploadOption = this.onSelectMediaUploadOption.bind( this ); + this.onMediaPressed = this.onMediaPressed.bind( this ); this.state = { - width: 0, - height: 0, + isUploadInProgress: false, }; + } - if ( this.props.mediaUrl ) { - this.onMediaChange(); + componentDidMount() { + const { mediaId, mediaUrl } = this.props; + + // Make sure we mark any temporary images as failed if they failed while + // the editor wasn't open + if ( mediaId && mediaUrl && mediaUrl.indexOf( 'file:' ) === 0 ) { + mediaUploadSync(); } } @@ -55,137 +78,233 @@ class MediaContainer extends Component { noticeOperations.createErrorNotice( message ); } - onSelectMediaUploadOption( { id, url } ) { + onSelectMediaUploadOption( params ) { + const { id, url, type } = params; const { onSelectMedia } = this.props; onSelectMedia( { - media_type: 'image', + media_type: type, id, url, } ); } - renderToolbarEditButton() { - const { mediaId } = this.props; + onMediaPressed() { + const { mediaId, mediaUrl } = this.props; + + if ( this.state.isUploadInProgress ) { + requestImageUploadCancelDialog( mediaId ); + } else if ( mediaId && ! isURL( mediaUrl ) ) { + requestImageFailedRetryDialog( mediaId ); + } + } + + getIcon( isRetryIcon, isVideo ) { + if ( isRetryIcon ) { + return <Icon icon={ SvgIconRetry } { ...( styles.iconRetry, isVideo ? styles.iconRetryVideo : {} ) } />; + } + + const iconStyle = this.props.getStylesFromColorScheme( styles.icon, styles.iconDark ); + return <Icon icon={ icon } { ...iconStyle } />; + } + + renderToolbarEditButton( open ) { return ( <BlockControls> - <Toolbar> - <MediaUpload - onSelect={ this.onSelectMediaUploadOption } - allowedTypes={ [ MEDIA_TYPE_IMAGE ] } - value={ mediaId } - render={ ( { open } ) => ( - <IconButton - className="components-toolbar__control" - label={ __( 'Edit media' ) } - icon="edit" - onClick={ open } - /> - ) } + <ToolbarGroup> + <IconButton + className="components-toolbar__control" + label={ __( 'Edit media' ) } + icon="edit" + onClick={ open } /> - </Toolbar> + </ToolbarGroup> </BlockControls> ); } - componentDidUpdate( prevProps ) { - if ( prevProps.mediaUrl !== this.props.mediaUrl ) { - this.onMediaChange(); + updateMediaProgress() { + if ( ! this.state.isUploadInProgress ) { + this.setState( { isUploadInProgress: true } ); } } - onMediaChange() { - const mediaType = this.props.mediaType; - if ( mediaType === 'video' ) { + finishMediaUploadWithSuccess( payload ) { + const { onMediaUpdate } = this.props; - } else if ( mediaType === 'image' ) { - Image.getSize( this.props.mediaUrl, ( width, height ) => { - this.media = { width, height }; - this.calculateSize(); - } ); - } + onMediaUpdate( { + id: payload.mediaServerId, + url: payload.mediaUrl, + } ); + this.setState( { isUploadInProgress: false } ); } - calculateSize() { - if ( this.media === undefined || this.container === undefined ) { - return; - } - - const { width, height } = calculatePreferedImageSize( this.media, this.container ); - this.setState( { width, height } ); + finishMediaUploadWithFailure() { + this.setState( { isUploadInProgress: false } ); } - onLayout( event ) { - const { width, height } = event.nativeEvent.layout; - this.container = { - clientWidth: width, - clientHeight: height, - }; - this.calculateSize(); + mediaUploadStateReset() { + const { onMediaUpdate } = this.props; + + onMediaUpdate( { id: null, url: null } ); + this.setState( { isUploadInProgress: false } ); } - renderImage() { - const { mediaAlt, mediaUrl } = this.props; + renderImage( params, openMediaOptions ) { + const { isUploadInProgress } = this.state; + const { mediaAlt, mediaUrl, isSelected } = this.props; + const { finalWidth, finalHeight, imageWidthWithinContainer, isUploadFailed, retryMessage } = params; + const opacity = isUploadInProgress ? 0.3 : 1; + + const contentStyle = ! imageWidthWithinContainer ? styles.content : styles.contentCentered; return ( - <View onLayout={ this.onLayout }> - <ImageBackground - accessible={ true } - //disabled={ ! isSelected } - accessibilityLabel={ mediaAlt } - accessibilityHint={ __( 'Double tap and hold to edit' ) } - accessibilityRole={ 'imagebutton' } - style={ { width: this.state.width, height: this.state.height } } - resizeMethod="scale" - source={ { uri: mediaUrl } } - key={ mediaUrl } - > - </ImageBackground> - </View> + <TouchableWithoutFeedback + accessible={ ! isSelected } + onPress={ this.onMediaPressed } + onLongPress={ openMediaOptions } + disabled={ ! isSelected } + > + <View style={ contentStyle }> + { ! imageWidthWithinContainer && + <View style={ styles.imageContainer }> + { this.getIcon( false ) } + </View> } + <ImageBackground + accessible={ true } + accessibilityLabel={ mediaAlt } + accessibilityHint={ __( 'Double tap and hold to edit' ) } + accessibilityRole={ 'imagebutton' } + style={ { width: finalWidth, height: finalHeight, opacity } } + resizeMethod="scale" + source={ { uri: mediaUrl } } + key={ mediaUrl } + > + { isUploadFailed && + <View style={ [ styles.imageContainer, styles.uploadFailed ] }> + <View style={ styles.modalIcon }> + { this.getIcon( isUploadFailed ) } + </View> + <Text style={ styles.uploadFailedText }>{ retryMessage }</Text> + </View> + } + </ImageBackground> + </View> + </TouchableWithoutFeedback> ); } - renderVideo() { - const style = { videoContainer: {} }; + renderVideo( params, openMediaOptions ) { + const { mediaUrl, isSelected } = this.props; + const { isUploadInProgress } = this.state; + const { isUploadFailed, retryMessage } = params; + const showVideo = isURL( mediaUrl ) && ! isUploadInProgress && ! isUploadFailed; + return ( - <View onLayout={ this.onLayout }> - <View style={ style.videoContainer }> - { /* TODO: show video preview */ } + <TouchableWithoutFeedback + accessible={ ! isSelected } + onPress={ this.onMediaPressed } + onLongPress={ openMediaOptions } + disabled={ ! isSelected } + > + <View aspectRatio={ VIDEO_ASPECT_RATIO }> + { showVideo && + <View style={ styles.videoContainer }> + <VideoPlayer + isSelected={ isSelected } + style={ styles.video } + source={ { uri: mediaUrl } } + paused={ true } + /> + </View> + } + { ! showVideo && + <View style={ styles.videoPlaceholder }> + <View style={ styles.modalIcon } > + { isUploadFailed ? this.getIcon( isUploadFailed ) : this.getIcon( false ) } + </View> + { isUploadFailed && <Text style={ [ styles.uploadFailedText, styles.uploadFailedTextVideo ] }>{ retryMessage }</Text> } + </View> + } </View> - </View> + </TouchableWithoutFeedback> ); } + renderContent( params, openMediaOptions ) { + const { mediaType } = this.props; + let mediaElement = null; + + switch ( mediaType ) { + case MEDIA_TYPE_IMAGE: + mediaElement = this.renderImage( params, openMediaOptions ); + break; + case MEDIA_TYPE_VIDEO: + mediaElement = this.renderVideo( params, openMediaOptions ); + break; + } + return mediaElement; + } + renderPlaceholder() { return ( <MediaPlaceholder - icon={ <BlockIcon icon={ icon } /> } + icon={ this.getIcon( false ) } labels={ { title: __( 'Media area' ), } } onSelect={ this.onSelectMediaUploadOption } - allowedTypes={ [ MEDIA_TYPE_IMAGE ] } + allowedTypes={ ALLOWED_MEDIA_TYPES } onFocus={ this.props.onFocus } + onError={ this.onUploadError } /> ); } render() { - const { mediaUrl, mediaType } = this.props; - if ( mediaType && mediaUrl ) { - let mediaElement = null; - switch ( mediaType ) { - case 'image': - mediaElement = this.renderImage(); - break; - case 'video': - mediaElement = this.renderVideo(); - break; - } - return mediaElement; + const { mediaUrl, mediaId, mediaType } = this.props; + const coverUrl = mediaType === MEDIA_TYPE_IMAGE ? mediaUrl : null; + + if ( mediaUrl ) { + return ( + <View> + <MediaUpload + onSelect={ this.onSelectMediaUploadOption } + allowedTypes={ ALLOWED_MEDIA_TYPES } + value={ mediaId } + render={ ( { open, getMediaOptions } ) => { + return ( + <View style={ { flex: 1 } }> + { getMediaOptions() } + { this.renderToolbarEditButton( open ) } + + <MediaUploadProgress + coverUrl={ coverUrl } + mediaId={ mediaId } + onUpdateMediaProgress={ this.updateMediaProgress } + onFinishMediaUploadWithSuccess={ this.finishMediaUploadWithSuccess } + onFinishMediaUploadWithFailure={ this.finishMediaUploadWithFailure } + onMediaUploadStateReset={ this.mediaUploadStateReset } + renderContent={ ( params ) => { + return ( + <View style={ styles.content }> + { this.renderContent( params, open ) } + </View> + ); + } } + /> + </View> + ); + } } + /> + </View> + ); } return this.renderPlaceholder(); } } -export default withNotices( MediaContainer ); +export default compose( + withNotices, + withPreferredColorScheme, +)( MediaContainer ); diff --git a/packages/block-library/src/media-text/style.native.scss b/packages/block-library/src/media-text/style.native.scss index f1c3550f29c1e9..24c06303501330 100644 --- a/packages/block-library/src/media-text/style.native.scss +++ b/packages/block-library/src/media-text/style.native.scss @@ -27,3 +27,105 @@ .is-vertically-aligned-bottom { align-items: flex-end; } + +.content { + flex: 1; +} + +.contentCentered { + flex: 1; + justify-content: center; + align-items: center; +} + +.imageContainer { + align-items: center; + background-color: $gray-lighten-30; + height: 144; + justify-content: center; +} + +.modalIcon { + width: 24px; + height: 24px; + justify-content: center; + align-items: center; +} + +.icon { + fill: $gray-dark; + width: 24px; + height: 24px; +} + +.iconDark { + fill: $white; +} + +.iconRetry { + fill: #fff; + width: 100%; + height: 100%; +} + +.iconRetryVideo { + fill: $gray-dark; +} + +.video { + width: 100%; + height: 100%; +} + +.videoContainer { + flex: 1; + background-color: #000; +} + +.videoPlaceholder { + width: 100%; + height: 100%; + align-items: center; + background-color: $gray-lighten-30; + justify-content: center; +} + +.uploadFailed { + background-color: rgba(0, 0, 0, 0.5); + flex: 1; +} + +.uploadFailedText { + color: #fff; + font-size: 14; + margin-top: 5; +} + +.uploadFailedTextVideo { + color: $gray-dark; +} + +.innerPadding { + padding: $block-selected-to-content; +} + +.innerPaddingMediaOnLeft { + padding-right: $block-selected-to-content; +} + +.innerPaddingMediaOnRight { + padding-left: $block-selected-to-content; +} + +.paddingHorizontalNone { + padding-left: 0; + padding-right: 0; +} + +.regularMediaPadding { + padding: $block-edge-to-content; +} + +.denseMediaPadding { + padding: $block-media-container-to-content; +} diff --git a/packages/block-library/src/media-text/style.scss b/packages/block-library/src/media-text/style.scss index c8dc4f8ac2b73e..59364bbdcee9d0 100644 --- a/packages/block-library/src/media-text/style.scss +++ b/packages/block-library/src/media-text/style.scss @@ -1,39 +1,56 @@ .wp-block-media-text { display: grid; grid-template-rows: auto; - align-items: center; - grid-template-areas: "media-text-media media-text-content"; - grid-template-columns: 50% auto; - - &.has-media-on-the-right { - grid-template-areas: "media-text-content media-text-media"; - grid-template-columns: auto 50%; + grid-template-columns: 50% 1fr; + .has-media-on-the-right { + grid-template-columns: 1fr 50%; } } .wp-block-media-text.is-vertically-aligned-top { - align-items: start; + .wp-block-media-text__content, + .wp-block-media-text__media { + align-self: start; + } } - +.wp-block-media-text, .wp-block-media-text.is-vertically-aligned-center { - align-items: center; + .wp-block-media-text__content, + .wp-block-media-text__media { + align-self: center; + } } .wp-block-media-text.is-vertically-aligned-bottom { - align-items: end; + .wp-block-media-text__content, + .wp-block-media-text__media { + align-self: end; + } } .wp-block-media-text .wp-block-media-text__media { - grid-area: media-text-media; + grid-column: 1; + grid-row: 1; margin: 0; } .wp-block-media-text .wp-block-media-text__content { + grid-column: 2; + grid-row: 1; word-break: break-word; - grid-area: media-text-content; padding: 0 8% 0 8%; } +.wp-block-media-text.has-media-on-the-right .wp-block-media-text__media { + grid-column: 2; + grid-row: 1; +} + +.wp-block-media-text.has-media-on-the-right .wp-block-media-text__content { + grid-column: 1; + grid-row: 1; +} + .wp-block-media-text > figure > img, .wp-block-media-text > figure > video { max-width: unset; @@ -41,13 +58,13 @@ vertical-align: middle; } -.wp-block-media-text.is-image-fill figure { +.wp-block-media-text.is-image-fill figure.wp-block-media-text__media { height: 100%; min-height: 250px; background-size: cover; } -.wp-block-media-text.is-image-fill figure > img { +.wp-block-media-text.is-image-fill figure.wp-block-media-text__media > img { // The image is visually hidden but accessible to assistive technologies. position: absolute; width: 1px; @@ -58,7 +75,6 @@ clip: rect(0, 0, 0, 0); border: 0; } - /* * Here we here not able to use a mobile first CSS approach. * Custom widths are set using inline styles, and on mobile, @@ -69,14 +85,24 @@ @media (max-width: #{ ($break-small) }) { .wp-block-media-text.is-stacked-on-mobile { grid-template-columns: 100% !important; - grid-template-areas: - "media-text-media" - "media-text-content"; + .wp-block-media-text__media { + grid-column: 1; + grid-row: 1; + } + .wp-block-media-text__content { + grid-column: 1; + grid-row: 2; + } } .wp-block-media-text.is-stacked-on-mobile.has-media-on-the-right { - grid-template-areas: - "media-text-content" - "media-text-media"; + .wp-block-media-text__media { + grid-column: 1; + grid-row: 2; + } + .wp-block-media-text__content { + grid-column: 1; + grid-row: 1; + } } } diff --git a/packages/block-library/src/missing/edit.native.js b/packages/block-library/src/missing/edit.native.js index cd4898a4e8c692..92013b3032df0d 100644 --- a/packages/block-library/src/missing/edit.native.js +++ b/packages/block-library/src/missing/edit.native.js @@ -1,17 +1,17 @@ /** * External dependencies */ -import { View, Text } from 'react-native'; +import { Platform, View, Text, TouchableWithoutFeedback } from 'react-native'; /** * WordPress dependencies */ -import { Icon } from '@wordpress/components'; +import { BottomSheet, Icon } from '@wordpress/components'; import { withPreferredColorScheme } from '@wordpress/compose'; import { coreBlocks } from '@wordpress/block-library'; import { normalizeIconObject } from '@wordpress/blocks'; import { Component } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies @@ -19,21 +19,94 @@ import { __ } from '@wordpress/i18n'; import styles from './style.scss'; export class UnsupportedBlockEdit extends Component { + constructor( props ) { + super( props ); + this.state = { showHelp: false }; + } + + toggleSheet() { + this.setState( { + showHelp: ! this.state.showHelp, + } ); + } + + renderHelpIcon() { + const infoIconStyle = this.props.getStylesFromColorScheme( styles.infoIcon, styles.infoIconDark ); + + return ( + <TouchableWithoutFeedback + accessibilityLabel={ __( 'Help icon' ) } + accessibilityRole={ 'button' } + accessibilityHint={ __( 'Tap here to show help' ) } + onPress={ this.toggleSheet.bind( this ) } + > + <View style={ styles.helpIconContainer } > + <Icon + className="unsupported-icon-help" + label={ __( 'Help icon' ) } + icon="editor-help" + color={ infoIconStyle.color } + /> + </View> + </TouchableWithoutFeedback> + ); + } + + renderSheet( title ) { + const { getStylesFromColorScheme } = this.props; + const infoTextStyle = getStylesFromColorScheme( styles.infoText, styles.infoTextDark ); + const infoTitleStyle = getStylesFromColorScheme( styles.infoTitle, styles.infoTitleDark ); + const infoDescriptionStyle = getStylesFromColorScheme( styles.infoDescription, styles.infoDescriptionDark ); + const infoSheetIconStyle = getStylesFromColorScheme( styles.infoSheetIcon, styles.infoSheetIconDark ); + + // translators: %s: Name of the block + const titleFormat = Platform.OS === 'android' ? __( '\'%s\' isn\'t yet supported on WordPress for Android' ) : + __( '\'%s\' isn\'t yet supported on WordPress for iOS' ); + const infoTitle = sprintf( + titleFormat, + title, + ); + + return ( + <BottomSheet + isVisible={ this.state.showHelp } + hideHeader + onClose={ this.toggleSheet.bind( this ) } + > + <View style={ styles.infoContainer } > + <Icon icon="editor-help" color={ infoSheetIconStyle.color } size={ styles.infoSheetIcon.size } /> + <Text style={ [ infoTextStyle, infoTitleStyle ] }> + { infoTitle } + </Text> + <Text style={ [ infoTextStyle, infoDescriptionStyle ] }> + { __( 'We are working hard to add more blocks with each release. In the meantime, you can also edit this post on the web.' ) } + </Text> + </View> + </BottomSheet> + ); + } + render() { const { originalName } = this.props.attributes; const { getStylesFromColorScheme, preferredColorScheme } = this.props; const blockType = coreBlocks[ originalName ]; - const title = blockType ? blockType.settings.title : __( 'Unsupported' ); + const title = blockType ? blockType.settings.title : originalName; const titleStyle = getStylesFromColorScheme( styles.unsupportedBlockMessage, styles.unsupportedBlockMessageDark ); + const subTitleStyle = getStylesFromColorScheme( styles.unsupportedBlockSubtitle, styles.unsupportedBlockSubtitleDark ); + const subtitle = <Text style={ subTitleStyle }>{ __( 'Unsupported' ) }</Text>; + const icon = blockType ? normalizeIconObject( blockType.settings.icon ) : 'admin-plugins'; const iconStyle = getStylesFromColorScheme( styles.unsupportedBlockIcon, styles.unsupportedBlockIconDark ); const iconClassName = 'unsupported-icon' + '-' + preferredColorScheme; return ( <View style={ getStylesFromColorScheme( styles.unsupportedBlock, styles.unsupportedBlockDark ) }> + { this.renderHelpIcon() } <Icon className={ iconClassName } icon={ icon && icon.src ? icon.src : icon } color={ iconStyle.color } /> <Text style={ titleStyle }>{ title }</Text> + { subtitle } + { this.renderSheet( title ) } </View> ); } diff --git a/packages/block-library/src/missing/style.native.scss b/packages/block-library/src/missing/style.native.scss index 63cd4258cd23b0..bb1b3c72260fc3 100644 --- a/packages/block-library/src/missing/style.native.scss +++ b/packages/block-library/src/missing/style.native.scss @@ -1,7 +1,88 @@ /** @format */ +.content { + padding-top: 8; + padding-bottom: 0; + padding-left: 24; + padding-right: 24; + align-items: center; + justify-content: space-evenly; +} + +.helpIconContainer { + position: absolute; + top: 0; + right: 0; + height: 48; + width: 48; + padding-top: 12; + padding-right: 12; + justify-content: flex-start; + align-items: flex-end; +} + +.infoContainer { + flex-direction: column; + align-items: center; + justify-content: flex-end; +} + +.infoIcon { + size: 36; + height: 36; + padding-top: 8; + padding-bottom: 8; + color: $gray-darken-20; +} + +.infoIconDark { + color: $gray-20; +} + +.infoSheetIcon { + size: 36; + height: 36; + padding-top: 8; + padding-bottom: 8; + color: $gray; +} + +.infoSheetIconDark { + color: $gray-20; +} + +.infoText { + text-align: center; + color: $gray-dark; +} + +.infoTextDark { + color: $white; +} + +.infoTitle { + padding-top: 8; + padding-bottom: 12; + font-size: 20; + font-weight: bold; + color: $gray-dark; +} + +.infoTitleDark { + color: $white; +} + +.infoDescription { + padding-bottom: 24; + font-size: 16; + color: $gray-darken-20; +} + +.infoDescriptionDark { + color: $gray-20; +} .unsupportedBlock { - background-color: #e9eff3; // grey lighten 30 + background-color: $gray-lighten-30; padding-top: 24; padding-bottom: 24; padding-left: 8; @@ -27,12 +108,24 @@ } .unsupportedBlockMessage { - margin-top: 2; + margin-top: 4; text-align: center; color: $gray-dark; font-size: 14; + font-weight: 600; } .unsupportedBlockMessageDark { color: $white; } + +.unsupportedBlockSubtitle { + margin-top: 2; + text-align: center; + color: $gray-darken-20; + font-size: 12; +} + +.unsupportedBlockSubtitleDark { + color: $gray-20; +} diff --git a/packages/block-library/src/missing/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/missing/test/__snapshots__/edit.native.js.snap new file mode 100644 index 00000000000000..eadf3ee1a935da --- /dev/null +++ b/packages/block-library/src/missing/test/__snapshots__/edit.native.js.snap @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Missing block renders without crashing 1`] = ` +<View> + <View + accessibilityHint="Tap here to show help" + accessibilityLabel="Help icon" + accessibilityRole="button" + accessible={true} + clickable={true} + onClick={[Function]} + onResponderGrant={[Function]} + onResponderMove={[Function]} + onResponderRelease={[Function]} + onResponderTerminate={[Function]} + onResponderTerminationRequest={[Function]} + onStartShouldSetResponder={[Function]} + > + Svg + </View> + Svg + <Text> + missing/block/title + </Text> + <Text> + Unsupported + </Text> + Modal +</View> +`; diff --git a/packages/block-library/src/missing/test/edit.native.js b/packages/block-library/src/missing/test/edit.native.js new file mode 100644 index 00000000000000..1bbfc0f342151d --- /dev/null +++ b/packages/block-library/src/missing/test/edit.native.js @@ -0,0 +1,77 @@ +/** + * External dependencies + */ +import renderer from 'react-test-renderer'; +import { Text, Platform } from 'react-native'; + +/** + * WordPress dependencies + */ +import { BottomSheet, Icon } from '@wordpress/components'; +jest.mock( '@wordpress/blocks' ); + +/** + * Internal dependencies + */ +import UnsupportedBlockEdit from '../edit.native.js'; + +const defaultAttributes = { + originalName: 'missing/block/title', +}; + +const getTestComponentWithContent = ( attributes = defaultAttributes ) => { + return renderer.create( <UnsupportedBlockEdit attributes={ attributes } /> ); +}; + +describe( 'Missing block', () => { + it( 'renders without crashing', () => { + const component = getTestComponentWithContent(); + const rendered = component.toJSON(); + expect( rendered ).toMatchSnapshot(); + } ); + + describe( 'help modal', () => { + it( 'renders help icon', () => { + const component = getTestComponentWithContent(); + const testInstance = component.root; + const icons = testInstance.findAllByType( Icon ); + expect( icons.length ).toBe( 2 ); + expect( icons[ 0 ].props.icon ).toBe( 'editor-help' ); + } ); + + it( 'renders info icon on modal', () => { + const component = getTestComponentWithContent(); + const testInstance = component.root; + const bottomSheet = testInstance.findByType( BottomSheet ); + const children = bottomSheet.props.children.props.children; + expect( children.length ).toBe( 3 ); // 4 children in the bottom sheet: the icon, the "isn't yet supported" title and the "We are working hard..." message + expect( children[ 0 ].props.icon ).toBe( 'editor-help' ); + } ); + + it( 'renders unsupported text on modal', () => { + const component = getTestComponentWithContent(); + const testInstance = component.root; + const bottomSheet = testInstance.findByType( BottomSheet ); + const children = bottomSheet.props.children.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 ); + } ); + } ); + + it( 'renders admin plugins icon', () => { + const component = getTestComponentWithContent(); + const testInstance = component.root; + const icons = testInstance.findAllByType( Icon ); + expect( icons.length ).toBe( 2 ); + expect( icons[ 1 ].props.icon ).toBe( 'admin-plugins' ); + } ); + + it( 'renders title text without crashing', () => { + const component = getTestComponentWithContent(); + const testInstance = component.root; + const texts = testInstance.findAllByType( Text ); + expect( texts.length ).toBe( 2 ); + expect( texts[ 0 ].props.children ).toBe( 'missing/block/title' ); + expect( texts[ 1 ].props.children ).toBe( 'Unsupported' ); + } ); +} ); diff --git a/packages/block-library/src/navigation-menu-item/block.json b/packages/block-library/src/navigation-link/block.json similarity index 73% rename from packages/block-library/src/navigation-menu-item/block.json rename to packages/block-library/src/navigation-link/block.json index 915c63931cb2be..3d6d79f73bde2c 100644 --- a/packages/block-library/src/navigation-menu-item/block.json +++ b/packages/block-library/src/navigation-link/block.json @@ -1,13 +1,10 @@ { - "name": "core/navigation-menu-item", + "name": "core/navigation-link", "category": "layout", "attributes": { "label": { "type": "string" }, - "destination": { - "type": "string" - }, "nofollow": { "type": "boolean", "default": false @@ -15,12 +12,21 @@ "title": { "type": "string" }, + "type": { + "type": "string" + }, "description": { "type": "string" }, + "id": { + "type": "number" + }, "opensInNewTab": { "type": "boolean", "default": false + }, + "url": { + "type": "string" } } } diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js new file mode 100644 index 00000000000000..48e792ca981f67 --- /dev/null +++ b/packages/block-library/src/navigation-link/edit.js @@ -0,0 +1,282 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { escape, unescape } from 'lodash'; + +/** + * WordPress dependencies + */ +import { compose } from '@wordpress/compose'; +import { createBlock } from '@wordpress/blocks'; +import { withDispatch, withSelect } from '@wordpress/data'; +import { + ExternalLink, + KeyboardShortcuts, + PanelBody, + Path, + SVG, + TextareaControl, + TextControl, + ToggleControl, + ToolbarButton, + ToolbarGroup, +} from '@wordpress/components'; +import { + LEFT, + RIGHT, + UP, + DOWN, + BACKSPACE, + ENTER, + rawShortcut, + displayShortcut, +} from '@wordpress/keycodes'; +import { __ } from '@wordpress/i18n'; +import { + BlockControls, + InnerBlocks, + InspectorControls, + RichText, + __experimentalLinkControl as LinkControl, +} from '@wordpress/block-editor'; +import { Fragment, useState, useEffect } from '@wordpress/element'; + +/** + * It updates the link attribute when the + * link settings changes. + * + * @param {Function} setter Setter attribute function. + */ +const updateLinkSetting = ( setter ) => ( setting, value ) => { + setter( { [ setting ]: value } ); +}; + +/** + * Updates the link attribute when it changes + * through of the `onLinkChange` LinkControl callback. + * + * @param {Function} setter Setter attribute function. + * @param {string} label Link label. + */ +const updateLink = ( setter, label ) => ( { title: newTitle = '', url: newURL = '' } = {} ) => { + setter( { + title: escape( newTitle ), + url: newURL, + } ); + + // Set the item label as well if it isn't already defined. + if ( ! label ) { + setter( { label: escape( newTitle ) } ); + } +}; + +function NavigationLinkEdit( { + attributes, + hasDescendants, + isSelected, + isParentOfSelectedBlock, + setAttributes, + insertLinkBlock, +} ) { + const { label, opensInNewTab, title, url, nofollow, description } = attributes; + const link = title ? { title: unescape( title ), url } : null; + const [ isLinkOpen, setIsLinkOpen ] = useState( ! label && isSelected ); + + let onCloseTimerId = null; + + /** + * It's a kind of hack to handle closing the LinkControl popover + * clicking on the ToolbarButton link. + */ + useEffect( () => { + if ( ! isSelected ) { + setIsLinkOpen( false ); + } + + return () => { + // Clear LinkControl.OnClose timeout. + if ( onCloseTimerId ) { + clearTimeout( onCloseTimerId ); + } + }; + }, [ isSelected ] ); + + /** + * Opens the LinkControl popup + */ + const openLinkControl = () => { + if ( isLinkOpen ) { + return; + } + + setIsLinkOpen( ! isLinkOpen ); + }; + + /** + * `onKeyDown` LinkControl handler. + * It takes over to stop the event propagation to make the + * navigation work, avoiding undesired behaviors. + * For instance, it will block to move between link blocks + * when the LinkControl is focused. + * + * @param {Event} event + */ + const handleLinkControlOnKeyDown = ( event ) => { + const { keyCode } = event; + + if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( keyCode ) > -1 ) { + // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. + event.stopPropagation(); + } + }; + + const itemLabelPlaceholder = __( 'Add link…' ); + + return ( + <Fragment> + <BlockControls> + <ToolbarGroup> + <KeyboardShortcuts + bindGlobal + shortcuts={ { + [ rawShortcut.primary( 'k' ) ]: openLinkControl, + } } + /> + <ToolbarButton + name="link" + icon="admin-links" + title={ __( 'Link' ) } + shortcut={ displayShortcut.primary( 'k' ) } + onClick={ openLinkControl } + /> + <ToolbarButton + name="submenu" + icon={ <SVG xmlns="http://www.w3.org/2000/svg" width="24" height="24"><Path d="M14 5h8v2h-8zm0 5.5h8v2h-8zm0 5.5h8v2h-8zM2 11.5C2 15.08 4.92 18 8.5 18H9v2l3-3-3-3v2h-.5C6.02 16 4 13.98 4 11.5S6.02 7 8.5 7H12V5H8.5C4.92 5 2 7.92 2 11.5z" /><Path fill="none" d="M0 0h24v24H0z" /></SVG> } + title={ __( 'Add Submenu' ) } + onClick={ insertLinkBlock } + /> + </ToolbarGroup> + </BlockControls> + <InspectorControls> + <PanelBody + title={ __( 'Link Settings' ) } + > + <TextareaControl + value={ description || '' } + onChange={ ( descriptionValue ) => { + setAttributes( { description: descriptionValue } ); + } } + label={ __( 'Description' ) } + /> + </PanelBody> + <PanelBody + title={ __( 'SEO Settings' ) } + > + <TextControl + value={ title || '' } + onChange={ ( titleValue ) => { + setAttributes( { title: titleValue } ); + } } + label={ __( 'Title Attribute' ) } + help={ __( 'Provide more context about where the link goes.' ) } + /> + <ToggleControl + checked={ nofollow } + onChange={ ( nofollowValue ) => { + setAttributes( { nofollow: nofollowValue } ); + } } + label={ __( 'Add nofollow to link' ) } + help={ ( + <Fragment> + { __( 'Don\'t let search engines follow this link.' ) } + <ExternalLink + className="wp-block-navigation-link__nofollow-external-link" + href={ __( 'https://codex.wordpress.org/Nofollow' ) } + > + { __( 'What\'s this?' ) } + </ExternalLink> + </Fragment> + ) } + /> + </PanelBody> + </InspectorControls> + <div className={ classnames( + 'wp-block-navigation-link', { + 'is-editing': isSelected || isParentOfSelectedBlock, + 'is-selected': isSelected, + 'has-link': !! url, + } ) } + > + <div> + <RichText + className="wp-block-navigation-link__content" + value={ label } + onChange={ ( labelValue ) => setAttributes( { label: labelValue } ) } + placeholder={ itemLabelPlaceholder } + withoutInteractiveFormatting + /> + { isLinkOpen && ( + <LinkControl + className="wp-block-navigation-link__inline-link-input" + onKeyDown={ handleLinkControlOnKeyDown } + onKeyPress={ ( event ) => event.stopPropagation() } + currentLink={ link } + onLinkChange={ updateLink( setAttributes, label ) } + onClose={ () => { + onCloseTimerId = setTimeout( () => setIsLinkOpen( false ), 100 ); + } } + currentSettings={ [ + { + id: 'opensInNewTab', + title: __( 'Open in new tab' ), + checked: opensInNewTab, + }, + ] } + onSettingsChange={ updateLinkSetting( setAttributes ) } + /> + ) } + </div> + <InnerBlocks + allowedBlocks={ [ 'core/navigation-link' ] } + renderAppender={ hasDescendants ? InnerBlocks.ButtonBlockAppender : false } + /> + </div> + </Fragment> + ); +} + +export default compose( [ + withSelect( ( select, ownProps ) => { + const { getClientIdsOfDescendants, hasSelectedInnerBlock } = select( 'core/block-editor' ); + const { clientId } = ownProps; + + return { + isParentOfSelectedBlock: hasSelectedInnerBlock( clientId, true ), + hasDescendants: !! getClientIdsOfDescendants( [ clientId ] ).length, + }; + } ), + withDispatch( ( dispatch, ownProps, registry ) => { + return { + insertLinkBlock() { + const { clientId } = ownProps; + + const { + insertBlock, + } = dispatch( 'core/block-editor' ); + + const { getClientIdsOfDescendants } = registry.select( 'core/block-editor' ); + const navItems = getClientIdsOfDescendants( [ clientId ] ); + const insertionPoint = navItems.length ? navItems.length : 0; + + const blockToInsert = createBlock( 'core/navigation-link' ); + + insertBlock( + blockToInsert, + insertionPoint, + clientId, + ); + }, + }; + } ), +] )( NavigationLinkEdit ); diff --git a/packages/block-library/src/navigation-link/editor.scss b/packages/block-library/src/navigation-link/editor.scss new file mode 100644 index 00000000000000..d1ed945a92ff5c --- /dev/null +++ b/packages/block-library/src/navigation-link/editor.scss @@ -0,0 +1,110 @@ + +// Normalize navigation link and edit containers, to look mostly the same. +.wp-block-navigation-link__field .components-text-control__input.components-text-control__input, +.wp-block-navigation-link__container { + border-radius: 0; + // Make it the same height as the appender to prevent a jump. Maybe revisit this. + line-height: $icon-button-size; + min-height: $icon-button-size; +} + +.wp-block-navigation-link__edit-container { + display: flex; + white-space: nowrap; + + // Compensate for navigation link base padding. + margin-left: -$grid-size; + + .wp-block-navigation-link__content { + margin-right: $grid-size; + + // This should match the padding of the navigation link. + padding: 0 $grid-size; + + // This make it look like an input field. + // We may want to not style this at all, but let's try this. + // We don't use the mixins because they increase the size of the input, which doesn't work with PlainText. + box-shadow: inset 0 0 0 1px $dark-gray-200; + transition: box-shadow 0.1s linear; + border-radius: $radius-round-rectangle; + @include reduce-motion("transition"); + + &:focus { + color: $dark-gray-900; + box-shadow: inset 0 0 0 2px $blue-medium-focus; + + // Windows High Contrast mode will show this outline, but not the box-shadow. + outline: 2px solid transparent; + } + } +} + +.wp-block-navigation-link { + margin-right: $grid-size; + // Provide a base menu item margin. + // This should be the same inside the field, + // and the edit container should compensate. + // This is to make sure the edit and view are the same. + padding: 0 $grid-size; + padding-right: 55px; // allow space for Block "mover" + + .block-editor-block-list__layout { + display: block; + margin: $grid-size; + } + + // Only display inner blocks when the block is being edited. + .block-editor-inner-blocks { + display: none; + } + + &.is-editing, + &.is-selected { + min-width: 20px; + } + + &.is-editing .block-editor-inner-blocks { + display: block; + } + + &.has-link .wp-block-navigation-link__content { + border-bottom-style: solid; + border-bottom-width: 1px; + } + + .block-editor-rich-text__editable.is-selected:not(.keep-placeholder-on-focus):not(:focus) [data-rich-text-placeholder]::after { + display: inline-block; + } +} + +[data-type="core/navigation-link"] { + .block-editor-block-toolbar { + left: 15px; + } +} + +.wp-block-navigation-link__nofollow-external-link { + display: block; +} + +// Separator +.wp-block-navigation-link__separator { + margin: $grid-size 0 $grid-size; + border-top: $border-width solid $light-gray-500; +} + +// Popover styles +.components-popover.wp-block-navigation-link__dropdown-content { + margin-top: -1px; + margin-left: -4px; +} + +.wp-block-navigation-link__dropdown-content .components-popover__content { + padding: $grid-size 0; +} + +.wp-block-navigation .block-editor-block-list__block[data-type="core/navigation-link"] { + & > .block-editor-block-list__insertion-point { + display: none; + } +} diff --git a/packages/block-library/src/navigation-link/index.js b/packages/block-library/src/navigation-link/index.js new file mode 100644 index 00000000000000..83171711977826 --- /dev/null +++ b/packages/block-library/src/navigation-link/index.js @@ -0,0 +1,30 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Path, SVG } from '@wordpress/components'; +/** + * Internal dependencies + */ +import metadata from './block.json'; +import edit from './edit'; +import save from './save'; + +const { name } = metadata; +export { metadata, name }; + +export const settings = { + title: __( 'Navigation Link' ), + + parent: [ 'core/navigation' ], + + icon: <SVG xmlns="http://www.w3.org/2000/svg" width="24" height="24"><Path d="M12 7.27l4.28 10.43-3.47-1.53-.81-.36-.81.36-3.47 1.53L12 7.27M12 2L4.5 20.29l.71.71L12 18l6.79 3 .71-.71L12 2z" /></SVG>, + + description: __( 'Add a page, link, or other item to your navigation.' ), + + __experimentalDisplayName: 'label', + + edit, + save, +}; + diff --git a/packages/block-library/src/navigation-menu-item/save.js b/packages/block-library/src/navigation-link/save.js similarity index 100% rename from packages/block-library/src/navigation-menu-item/save.js rename to packages/block-library/src/navigation-link/save.js diff --git a/packages/block-library/src/navigation-menu-item/edit.js b/packages/block-library/src/navigation-menu-item/edit.js deleted file mode 100644 index f4e456321347c6..00000000000000 --- a/packages/block-library/src/navigation-menu-item/edit.js +++ /dev/null @@ -1,160 +0,0 @@ -/** - * External dependencies - */ -import { invoke } from 'lodash'; - -/** - * WordPress dependencies - */ -import { withSelect } from '@wordpress/data'; -import { - Dropdown, - ExternalLink, - IconButton, - PanelBody, - TextareaControl, - TextControl, - ToggleControl, -} from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { - InnerBlocks, - InspectorControls, - PlainText, -} from '@wordpress/block-editor'; -import { - Fragment, - useCallback, - useRef, -} from '@wordpress/element'; - -/** - * Internal dependencies - */ -import MenuItemActions from './menu-item-actions'; -const POPOVER_PROPS = { noArrow: true }; - -function NavigationMenuItemEdit( { - attributes, - clientId, - isSelected, - isParentOfSelectedBlock, - setAttributes, -} ) { - const plainTextRef = useRef( null ); - const onEditLableClicked = useCallback( - ( onClose ) => () => { - onClose(); - invoke( plainTextRef, [ 'current', 'textarea', 'focus' ] ); - }, - [ plainTextRef ] - ); - let content; - if ( isSelected ) { - content = ( - <div className="wp-block-navigation-menu-item__edit-container"> - <PlainText - ref={ plainTextRef } - className="wp-block-navigation-menu-item__field" - value={ attributes.label } - onChange={ ( label ) => setAttributes( { label } ) } - aria-label={ __( 'Navigation Label' ) } - maxRows={ 1 } - /> - <Dropdown - contentClassName="wp-block-navigation-menu-item__dropdown-content" - position="bottom left" - popoverProps={ POPOVER_PROPS } - renderToggle={ ( { isOpen, onToggle } ) => ( - <IconButton - icon={ isOpen ? 'arrow-up-alt2' : 'arrow-down-alt2' } - label={ __( 'More options' ) } - onClick={ onToggle } - aria-expanded={ isOpen } - /> - ) } - renderContent={ ( { onClose } ) => ( - <MenuItemActions - clientId={ clientId } - destination={ attributes.destination } - onEditLableClicked={ onEditLableClicked( onClose ) } - /> - ) } - /> - </div> - ); - } else { - content = attributes.label; - } - return ( - <Fragment> - <InspectorControls> - <PanelBody - title={ __( 'Menu Settings' ) } - > - <ToggleControl - checked={ attributes.opensInNewTab } - onChange={ ( opensInNewTab ) => { - setAttributes( { opensInNewTab } ); - } } - label={ __( 'Open in new tab' ) } - /> - <TextareaControl - value={ attributes.description || '' } - onChange={ ( description ) => { - setAttributes( { description } ); - } } - label={ __( 'Description' ) } - /> - </PanelBody> - <PanelBody - title={ __( 'SEO Settings' ) } - > - <TextControl - value={ attributes.title || '' } - onChange={ ( title ) => { - setAttributes( { title } ); - } } - label={ __( 'Title Attribute' ) } - help={ __( 'Provide more context about where the link goes.' ) } - /> - <ToggleControl - checked={ attributes.nofollow } - onChange={ ( nofollow ) => { - setAttributes( { nofollow } ); - } } - label={ __( 'Add nofollow to menu item' ) } - help={ ( - <Fragment> - { __( 'Don\'t let search engines follow this link.' ) } - <ExternalLink - className="wp-block-navigation-menu-item__nofollow-external-link" - href={ __( 'https://codex.wordpress.org/Nofollow' ) } - > - { __( 'What\'s this?' ) } - </ExternalLink> - </Fragment> - ) } - /> - </PanelBody> - </InspectorControls> - <div className="wp-block-navigation-menu-item"> - { content } - { ( isSelected || isParentOfSelectedBlock ) && - <InnerBlocks - allowedBlocks={ [ 'core/navigation-menu-item' ] } - /> - } - </div> - </Fragment> - ); -} - -export default withSelect( ( select, ownProps ) => { - const { hasSelectedInnerBlock } = select( 'core/block-editor' ); - const { clientId } = ownProps; - - return { - isParentOfSelectedBlock: hasSelectedInnerBlock( clientId, true ), - }; -} )( NavigationMenuItemEdit ); diff --git a/packages/block-library/src/navigation-menu-item/editor.scss b/packages/block-library/src/navigation-menu-item/editor.scss deleted file mode 100644 index 2d801052ba4107..00000000000000 --- a/packages/block-library/src/navigation-menu-item/editor.scss +++ /dev/null @@ -1,70 +0,0 @@ -$menu-label-field-width: 140px; - -.wp-block-navigation-menu-item__edit-container { - display: grid; - grid-auto-columns: min-content; - grid-auto-flow: column; - align-items: center; - white-space: nowrap; - border: 1px solid $light-gray-500; - // two pixes comes from two times one pixel border - width: $menu-label-field-width + $icon-button-size + 2px; - padding-left: 1px; -} - -.wp-block-navigation-menu-item__edit-container .wp-block-navigation-menu-item__field { - border-right: 1px solid $light-gray-500 !important; - width: $menu-label-field-width; - border: none; - border-radius: 0; - padding-left: $grid-size-large; - - min-height: $icon-button-size - 1px; - line-height: $icon-button-size - 1px; - - &, - &:focus { - color: $dark-gray-500; - } -} - - -.wp-block-navigation-menu-item { - font-family: $editor-font; - color: #0073af; - font-weight: bold; - font-size: $text-editor-font-size; -} - -.wp-block-navigation-menu-item__nofollow-external-link { - display: block; -} - -// Separator -.wp-block-navigation-menu-item__separator { - margin-top: $grid-size; - margin-bottom: $grid-size; - margin-left: 0; - margin-right: 0; - border-top: $border-width solid $light-gray-500; -} - -// Popover styles -.components-popover:not(.is-mobile).wp-block-navigation-menu-item__dropdown-content { - margin-top: -1px; - margin-left: -4px; -} - -.wp-block-navigation-menu-item__dropdown-content .components-popover__content { - padding: $grid-size 0; -} - -.wp-block-navigation-menu .block-editor-block-list__block[data-type="core/navigation-menu-item"] { - & > .block-editor-block-list__block-edit > div[role="toolbar"] { - display: none; - } - - & > .block-editor-block-list__insertion-point { - display: none; - } -} diff --git a/packages/block-library/src/navigation-menu-item/menu-item-actions.js b/packages/block-library/src/navigation-menu-item/menu-item-actions.js deleted file mode 100644 index 6e39036bab408a..00000000000000 --- a/packages/block-library/src/navigation-menu-item/menu-item-actions.js +++ /dev/null @@ -1,111 +0,0 @@ -/** - * WordPress dependencies - */ -import { - MenuItem, - NavigableMenu, -} from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; -import { withDispatch } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; - -function MenuItemActions( { - destination, - moveLeft, - moveRight, - moveToEnd, - moveToStart, - onEditLableClicked, - remove, -} ) { - return ( - <NavigableMenu> - <MenuItem - icon="admin-links" - > - { destination } - </MenuItem> - <MenuItem - onClick={ onEditLableClicked } - icon="edit" - > - { __( 'Edit label text' ) } - </MenuItem> - <div className="wp-block-navigation-menu-item__separator" /> - <MenuItem - onClick={ moveToStart } - icon="arrow-up-alt2" - > - { __( 'Move to start' ) } - </MenuItem> - <MenuItem - onClick={ moveLeft } - icon="arrow-left-alt2" - > - { __( 'Move left' ) } - </MenuItem> - <MenuItem - onClick={ moveRight } - icon="arrow-right-alt2" - > - { __( 'Move right' ) } - </MenuItem> - <MenuItem - onClick={ moveToEnd } - icon="arrow-down-alt2" - > - { __( 'Move to end' ) } - </MenuItem> - <MenuItem - icon="arrow-left-alt2" - > - { __( 'Nest underneath…' ) } - </MenuItem> - <div className="navigation-menu-item__separator" /> - <MenuItem - onClick={ remove } - icon="trash" - > - { __( 'Remove from menu' ) } - </MenuItem> - </NavigableMenu> - ); -} - -export default compose( [ - withDispatch( ( dispatch, { clientId }, { select } ) => { - const { - getBlockOrder, - getBlockRootClientId, - } = select( 'core/block-editor' ); - const parentID = getBlockRootClientId( clientId ); - const { - moveBlocksDown, - moveBlocksUp, - moveBlockToPosition, - removeBlocks, - } = dispatch( 'core/block-editor' ); - return { - moveToStart() { - moveBlockToPosition( clientId, parentID, parentID, 0 ); - }, - moveRight() { - moveBlocksDown( clientId, parentID ); - }, - moveLeft() { - moveBlocksUp( clientId, parentID ); - }, - moveToEnd() { - moveBlockToPosition( - clientId, - parentID, - parentID, - getBlockOrder( parentID ).length - 1 - ); - }, - remove() { - removeBlocks( clientId ); - }, - }; - } ), -] )( MenuItemActions ); diff --git a/packages/block-library/src/navigation-menu/edit.js b/packages/block-library/src/navigation-menu/edit.js deleted file mode 100644 index bc7053d58b1eef..00000000000000 --- a/packages/block-library/src/navigation-menu/edit.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - * WordPress dependencies - */ -import { - Fragment, - useMemo, -} from '@wordpress/element'; -import { - InnerBlocks, - InspectorControls, - BlockControls, -} from '@wordpress/block-editor'; -import { withSelect } from '@wordpress/data'; -import { - CheckboxControl, - PanelBody, - Spinner, - Toolbar, -} from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import useBlockNavigator from './use-block-navigator'; - -function NavigationMenu( { - attributes, - setAttributes, - clientId, - pages, - isRequesting, -} ) { - const { navigatorToolbarButton, navigatorModal } = useBlockNavigator( clientId ); - const defaultMenuItems = useMemo( - () => { - if ( ! pages ) { - return null; - } - return pages.map( ( page ) => { - return [ 'core/navigation-menu-item', - { label: page.title.rendered, destination: page.permalink_template }, - ]; - } ); - }, - [ pages ] - ); - - return ( - <Fragment> - <BlockControls> - <Toolbar> - { navigatorToolbarButton } - </Toolbar> - </BlockControls> - { navigatorModal } - <InspectorControls> - <PanelBody - title={ __( 'Menu Settings' ) } - > - <CheckboxControl - value={ attributes.automaticallyAdd } - onChange={ ( automaticallyAdd ) => { - setAttributes( { automaticallyAdd } ); - } } - label={ __( 'Automatically add new pages' ) } - help={ __( 'Automatically add new top level pages to this menu.' ) } - /> - </PanelBody> - </InspectorControls> - <div className="wp-block-navigation-menu"> - { isRequesting && - <Spinner /> - } - { pages && - <InnerBlocks - template={ defaultMenuItems ? defaultMenuItems : null } - allowedBlocks={ [ 'core/navigation-menu-item' ] } - /> - } - </div> - </Fragment> - ); -} - -export default withSelect( ( select ) => { - const { getEntityRecords } = select( 'core' ); - const { isResolving } = select( 'core/data' ); - const filterDefaultPages = { - parent: 0, - order: 'asc', - orderby: 'id', - }; - return { - pages: getEntityRecords( 'postType', 'page', filterDefaultPages ), - isRequesting: isResolving( 'core', 'getEntityRecords', [ 'postType', 'page', filterDefaultPages ] ), - }; -} )( NavigationMenu ); - diff --git a/packages/block-library/src/navigation-menu/editor.scss b/packages/block-library/src/navigation-menu/editor.scss deleted file mode 100644 index 2238b2fae8dd7c..00000000000000 --- a/packages/block-library/src/navigation-menu/editor.scss +++ /dev/null @@ -1,18 +0,0 @@ -.wp-block-navigation-menu .block-editor-block-list__layout, -.wp-block-navigation-menu { - display: flex; - grid-auto-columns: min-content; - grid-auto-flow: column; - align-items: top; - white-space: nowrap; -} - -.wp-block-navigation-menu__inserter-content { - padding: $grid-size-large; -} - -.wp-block-navigation-menu-item { - .wp-block-navigation-menu-item { - margin-left: $grid-size-large; - } -} diff --git a/packages/block-library/src/navigation-menu/index.php b/packages/block-library/src/navigation-menu/index.php deleted file mode 100644 index 73858feea053be..00000000000000 --- a/packages/block-library/src/navigation-menu/index.php +++ /dev/null @@ -1,72 +0,0 @@ -<?php -/** - * Server-side rendering of the `core/navigation-menu` block. - * - * @package gutenberg - */ - -/** - * Renders the `core/navigation-menu` block on server. - * - * @param array $attributes The block attributes. - * @param array $content The saved content. - * @param array $block The parsed block. - * - * @return string Returns the post content with the legacy widget added. - */ -function render_block_navigation_menu( $attributes, $content, $block ) { - return '<nav className="wp-block-navigation-menu">' . build_navigation_menu_html( $block ) . '</nav>'; -} - -/** - * Walks the inner block structure and returns an HTML list for it. - * - * @param array $block The block. - * - * @return string Returns an HTML list from innerBlocks. - */ -function build_navigation_menu_html( $block ) { - $html = ''; - foreach ( (array) $block['innerBlocks'] as $key => $menu_item ) { - $html .= '<li class="wp-block-navigation-menu-item"><a class="wp-block-navigation-menu-item"'; - if ( isset( $menu_item['attrs']['destination'] ) ) { - $html .= ' href="' . $menu_item['attrs']['destination'] . '"'; - } - if ( isset( $menu_item['attrs']['title'] ) ) { - $html .= ' title="' . $menu_item['attrs']['title'] . '"'; - } - $html .= '>'; - if ( isset( $menu_item['attrs']['label'] ) ) { - $html .= $menu_item['attrs']['label']; - } - $html .= '</a>'; - - if ( count( (array) $menu_item['innerBlocks'] ) > 0 ) { - $html .= build_navigation_menu_html( $menu_item ); - } - - $html .= '</li>'; - } - return '<ul>' . $html . '</ul>'; -} - -/** - * Register the navigation menu block. - */ -function register_block_core_navigation_menu() { - register_block_type( - 'core/navigation-menu', - array( - 'category' => 'layout', - 'attributes' => array( - 'automaticallyAdd' => array( - 'type' => 'boolean', - 'default' => false, - ), - ), - 'render_callback' => 'render_block_navigation_menu', - ) - ); -} - -add_action( 'init', 'register_block_core_navigation_menu' ); diff --git a/packages/block-library/src/navigation/block-colors-selector.js b/packages/block-library/src/navigation/block-colors-selector.js new file mode 100644 index 00000000000000..52605ff0512cfa --- /dev/null +++ b/packages/block-library/src/navigation/block-colors-selector.js @@ -0,0 +1,89 @@ +/** + * External dependencies + */ +import { noop } from 'lodash'; + +/** + * WordPress dependencies + */ +import { IconButton, Dropdown, ToolbarGroup, SVG, Path } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { DOWN } from '@wordpress/keycodes'; +import { ColorPaletteControl } from '@wordpress/block-editor'; + +const ColorSelectorSVGIcon = () => ( + <SVG xmlns="https://www.w3.org/2000/svg" viewBox="0 0 20 20"> + <Path d="M7.434 5l3.18 9.16H8.538l-.692-2.184H4.628l-.705 2.184H2L5.18 5h2.254zm-1.13 1.904h-.115l-1.148 3.593H7.44L6.304 6.904zM14.348 7.006c1.853 0 2.9.876 2.9 2.374v4.78h-1.79v-.914h-.114c-.362.64-1.123 1.022-2.031 1.022-1.346 0-2.292-.826-2.292-2.108 0-1.27.972-2.006 2.71-2.107l1.696-.102V9.38c0-.584-.42-.914-1.18-.914-.667 0-1.112.228-1.264.647h-1.701c.12-1.295 1.307-2.107 3.066-2.107zm1.079 4.1l-1.416.09c-.793.056-1.18.342-1.18.844 0 .52.45.837 1.091.837.857 0 1.505-.545 1.505-1.256v-.515z" /> + </SVG> +); + +/** + * Color Selector Icon component. + * + * @param {Object} colorControlProps colorControl properties. + * @return {*} React Icon component. + */ +const ColorSelectorIcon = ( { color } ) => { + return ( + <div className="block-library-colors-selector__icon-container"> + <div + className="block-library-colors-selector__state-selection" + style={ { ...( color && { color } ) } } + > + <ColorSelectorSVGIcon /> + </div> + </div> + ); +}; + +/** + * Renders the Colors Selector Toolbar with the icon button. + * + * @param {Object} colorControlProps colorControl properties. + * @return {*} React toggle button component. + */ +const renderToggleComponent = ( { value } ) => ( { onToggle, isOpen } ) => { + const openOnArrowDown = ( event ) => { + if ( ! isOpen && event.keyCode === DOWN ) { + event.preventDefault(); + event.stopPropagation(); + onToggle(); + } + }; + + return ( + <ToolbarGroup> + <IconButton + className="components-icon-button components-toolbar__control block-library-colors-selector__toggle" + label={ __( 'Open Colors Selector' ) } + onClick={ onToggle } + onKeyDown={ openOnArrowDown } + icon={ <ColorSelectorIcon color={ value } /> } + /> + </ToolbarGroup> + ); +}; + +const renderContent = ( { value, onChange = noop } ) => ( () => { + return ( + <> + <div className="color-palette-controller-container"> + <ColorPaletteControl + value={ value } + onChange={ onChange } + label={ __( 'Text Color' ) } + /> + </div> + </> + ); +} ); + +export default ( colorControlProps ) => ( + <Dropdown + position="bottom right" + className="block-library-colors-selector" + contentClassName="block-library-colors-selector__popover" + renderToggle={ renderToggleComponent( colorControlProps ) } + renderContent={ renderContent( colorControlProps ) } + /> +); diff --git a/packages/block-library/src/navigation/block-navigation-list.js b/packages/block-library/src/navigation/block-navigation-list.js new file mode 100644 index 00000000000000..77adcd01cae366 --- /dev/null +++ b/packages/block-library/src/navigation/block-navigation-list.js @@ -0,0 +1,41 @@ +/** + * WordPress dependencies + */ +import { + __experimentalBlockNavigationList, +} from '@wordpress/block-editor'; +import { + useSelect, + useDispatch, +} from '@wordpress/data'; + +export default function BlockNavigationList( { clientId } ) { + const { + block, + selectedBlockClientId, + } = useSelect( ( select ) => { + const { + getSelectedBlockClientId, + getBlock, + } = select( 'core/block-editor' ); + + return { + block: getBlock( clientId ), + selectedBlockClientId: getSelectedBlockClientId(), + }; + }, [ clientId ] ); + + const { + selectBlock, + } = useDispatch( 'core/block-editor' ); + + return ( + <__experimentalBlockNavigationList + blocks={ [ block ] } + selectedBlockClientId={ selectedBlockClientId } + selectBlock={ selectBlock } + showNestedBlocks + showAppender + /> + ); +} diff --git a/packages/block-library/src/navigation/edit.js b/packages/block-library/src/navigation/edit.js new file mode 100644 index 00000000000000..eb927c11e78d5e --- /dev/null +++ b/packages/block-library/src/navigation/edit.js @@ -0,0 +1,221 @@ +/** + * External dependencies + */ +import { escape, upperFirst } from 'lodash'; +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { + useMemo, + Fragment, +} from '@wordpress/element'; +import { + InnerBlocks, + InspectorControls, + BlockControls, + __experimentalUseColors, +} from '@wordpress/block-editor'; + +import { createBlock } from '@wordpress/blocks'; +import { withSelect, withDispatch } from '@wordpress/data'; +import { + Button, + PanelBody, + Placeholder, + Spinner, + Toolbar, + ToolbarGroup, +} from '@wordpress/components'; +import { compose } from '@wordpress/compose'; + +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import useBlockNavigator from './use-block-navigator'; +import BlockNavigationList from './block-navigation-list'; +import BlockColorsStyleSelector from './block-colors-selector'; +import * as navIcons from './icons'; + +function Navigation( { + attributes, + clientId, + pages, + isRequestingPages, + hasResolvedPages, + hasExistingNavItems, + updateNavItemBlocks, + setAttributes, +} ) { + // + // HOOKS + // + /* eslint-disable @wordpress/no-unused-vars-before-return */ + const { TextColor } = __experimentalUseColors( + [ { name: 'textColor', property: 'color' } ], + ); + /* eslint-enable @wordpress/no-unused-vars-before-return */ + const { navigatorToolbarButton, navigatorModal } = useBlockNavigator( clientId ); + + // Builds navigation links from default Pages. + const defaultPagesNavigationItems = useMemo( + () => { + if ( ! pages ) { + return null; + } + + return pages.map( ( { title, type, link: url, id } ) => ( + createBlock( 'core/navigation-link', + { + type, + id, + url, + label: escape( title.rendered ), + title: escape( title.raw ), + opensInNewTab: false, + } + ) + ) ); + }, + [ pages ] + ); + + // + // HANDLERS + // + function handleItemsAlignment( align ) { + return () => { + const itemsJustification = attributes.itemsJustification === align ? undefined : align; + setAttributes( { + itemsJustification, + } ); + }; + } + + function handleCreateEmpty() { + const emptyNavLinkBlock = createBlock( 'core/navigation-link' ); + updateNavItemBlocks( [ emptyNavLinkBlock ] ); + } + + function handleCreateFromExistingPages() { + updateNavItemBlocks( defaultPagesNavigationItems ); + } + + const hasPages = hasResolvedPages && pages && pages.length; + + const blockClassNames = classnames( 'wp-block-navigation', { + [ `items-justification-${ attributes.itemsJustification }` ]: attributes.itemsJustification, + } ); + + // If we don't have existing items or the User hasn't + // indicated they want to automatically add top level Pages + // then show the Placeholder + if ( ! hasExistingNavItems ) { + return ( + <Fragment> + <Placeholder + className="wp-block-navigation-placeholder" + icon="menu" + label={ __( 'Navigation' ) } + instructions={ __( 'Create a Navigation from all existing pages, or create an empty one.' ) } + > + <div className="wp-block-navigation-placeholder__buttons"> + <Button + isDefault + className="wp-block-navigation-placeholder__button" + onClick={ handleCreateFromExistingPages } + disabled={ ! hasPages } + > + { __( 'Create from all top pages' ) } + </Button> + + <Button + isLink + className="wp-block-navigation-placeholder__button" + onClick={ handleCreateEmpty } + > + { __( 'Create empty' ) } + </Button> + </div> + </Placeholder> + </Fragment> + ); + } + + // UI State: rendered Block UI + return ( + <Fragment> + <BlockControls> + <Toolbar + icon={ attributes.itemsJustification ? navIcons[ `justify${ upperFirst( attributes.itemsJustification ) }Icon` ] : navIcons.justifyLeftIcon } + label={ __( 'Change items justification' ) } + isCollapsed + controls={ [ + { icon: navIcons.justifyLeftIcon, title: __( 'Justify items left' ), isActive: 'left' === attributes.itemsJustification, onClick: handleItemsAlignment( 'left' ) }, + { icon: navIcons.justifyCenterIcon, title: __( 'Justify items center' ), isActive: 'center' === attributes.itemsJustification, onClick: handleItemsAlignment( 'center' ) }, + { icon: navIcons.justifyRightIcon, title: __( 'Justify items right' ), isActive: 'right' === attributes.itemsJustification, onClick: handleItemsAlignment( 'right' ) }, + ] } + /> + <ToolbarGroup> + { navigatorToolbarButton } + </ToolbarGroup> + <BlockColorsStyleSelector + value={ TextColor.color } + onChange={ TextColor.setColor } + /> + + </BlockControls> + { navigatorModal } + <InspectorControls> + <PanelBody + title={ __( 'Navigation Structure' ) } + > + <BlockNavigationList clientId={ clientId } /> + </PanelBody> + </InspectorControls> + <TextColor> + <div className={ blockClassNames }> + { ! hasExistingNavItems && isRequestingPages && <><Spinner /> { __( 'Loading Navigation…' ) } </> } + + <InnerBlocks + allowedBlocks={ [ 'core/navigation-link' ] } + templateInsertUpdatesSelection={ false } + __experimentalMoverDirection={ 'horizontal' } + /> + + </div> + </TextColor> + </Fragment> + ); +} + +export default compose( [ + withSelect( ( select, { clientId } ) => { + const innerBlocks = select( 'core/block-editor' ).getBlocks( clientId ); + + const filterDefaultPages = { + parent: 0, + order: 'asc', + orderby: 'id', + }; + + const pagesSelect = [ 'core', 'getEntityRecords', [ 'postType', 'page', filterDefaultPages ] ]; + + return { + hasExistingNavItems: !! innerBlocks.length, + pages: select( 'core' ).getEntityRecords( 'postType', 'page', filterDefaultPages ), + isRequestingPages: select( 'core/data' ).isResolving( ...pagesSelect ), + hasResolvedPages: select( 'core/data' ).hasFinishedResolution( ...pagesSelect ), + }; + } ), + withDispatch( ( dispatch, { clientId } ) => { + return { + updateNavItemBlocks( blocks ) { + dispatch( 'core/block-editor' ).replaceInnerBlocks( clientId, blocks ); + }, + }; + } ), +] )( Navigation ); diff --git a/packages/block-library/src/navigation/editor.scss b/packages/block-library/src/navigation/editor.scss new file mode 100644 index 00000000000000..95f5a5f845a97b --- /dev/null +++ b/packages/block-library/src/navigation/editor.scss @@ -0,0 +1,202 @@ +// Reduce the paddings, margins, and UI of inner-blocks. +// @todo: eventually we may add a feature that lets a parent container absorb the block UI of a child block. +// When that happens, leverage that instead of the following overrides. +[data-type="core/navigation"] { + + .wp-block-navigation .block-editor-inner-blocks { + flex: 1; // expand to fill available space required for justification + } + + // 1. Reset margins on immediate innerblocks container. + .wp-block-navigation .block-editor-inner-blocks > .block-editor-block-list__layout { + margin-left: 0; + margin-right: 0; + } + + .wp-block-navigation.items-justification-left .block-editor-inner-blocks > .block-editor-block-list__layout { + justify-content: flex-start; + } + + .wp-block-navigation.items-justification-center .block-editor-inner-blocks > .block-editor-block-list__layout { + justify-content: center; + } + + .wp-block-navigation.items-justification-right .block-editor-inner-blocks > .block-editor-block-list__layout { + justify-content: flex-end; + } + + // 2. Remove paddings on subsequent immediate children. + .wp-block-navigation .block-editor-inner-blocks > .block-editor-block-list__layout > .wp-block { + width: auto; + padding-left: 0; + padding-right: 0; + margin-left: 0; + margin-right: 0; + margin-bottom: 1em; // Useful for when items wrap. + } + + // 3. Remove margins on subsequent Edit container. + .wp-block-navigation .block-editor-inner-blocks > .block-editor-block-list__layout > .wp-block > .block-editor-block-list__block-edit { + margin-left: 0; + margin-right: 0; + } + + // 4. Remove vertical margins on subsequent block container. + .wp-block-navigation .block-editor-inner-blocks > .block-editor-block-list__layout .wp-block > .block-editor-block-list__block-edit > [data-block] { + margin-top: 0; + margin-bottom: 0; + } + + .wp-block-navigation .block-editor-block-list__block-edit::before { + left: 0; + right: 0; + } + + // Remove the dashed outlines for child blocks. + &.is-hovered .wp-block-navigation .block-editor-block-list__block-edit::before, + &.is-selected .wp-block-navigation .block-editor-block-list__block-edit::before, + &.has-child-selected .wp-block-navigation .block-editor-block-list__block-edit::before { + border-color: transparent !important; // !important used to keep the selector from growing any more complex. + } + + // Hide the sibling inserter. + .wp-block-navigation .block-editor-block-list__insertion-point { + display: none; + } + + // Polish the Appender. + .wp-block-navigation .block-list-appender { + margin: 0; + + .block-editor-button-block-appender { + padding: $grid-size; + outline: none; + background: none; + } + } +} + +.wp-block-navigation .block-editor-block-list__layout, +.wp-block-navigation { + display: flex; + flex-wrap: wrap; +} + +.wp-block-navigation__inserter-content { + padding: $grid-size-large; +} + +/* + * Adjust Navigation Item. + */ +.wp-block-navigation .wp-block-navigation-link { + margin-right: $grid-size; + margin-left: $grid-size; + + .block-editor-block-list__layout { + display: block; + margin: $grid-size; + } + + // Provide a base navigation item margin. + // This should be the same inside the field, + // and the edit container should compensate. + // This is to make sure the edit and view are the same. + padding: 0 $grid-size; + + // Only display inner blocks when the block is being edited. + .block-editor-inner-blocks { + display: none; + } + + &.is-editing .block-editor-inner-blocks { + display: block; + } +} + +/** + * Colors Selector component + */ +$colors-selector-size: 22px; +.block-library-colors-selector { + width: auto; + + // Toolbar colors-selector button. + .block-library-colors-selector__toggle { + display: block; + margin: 0 auto; + padding: 3px; + width: auto; + } + + // Button container. + .block-library-colors-selector__icon-container { + width: 42px; + height: 30px; + position: relative; + margin: 0 auto; + padding: 3px; + display: flex; + align-items: center; + border-radius: 4px; + + // Add the button arrow. + &::after { + @include dropdown-arrow(); + } + + // Styling button states. + &:focus, + &:hover { + color: $dark-gray-500; + box-shadow: inset 0 0 0 1px $dark-gray-500, inset 0 0 0 2px #fff; + } + } + + // colors-selector - selection status. + .block-library-colors-selector__state-selection { + border-radius: $colors-selector-size / 2; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2); + + width: $colors-selector-size; + min-width: $colors-selector-size; + height: $colors-selector-size; + min-height: $colors-selector-size; + line-height: ($colors-selector-size - 2); + padding: 2px; + + // Styling icon color. + &.has-text-color { + > svg, + > svg path { + color: inherit; + } + } + } +} + +// Colors Selector Popover. +$color-control-label-height: 20px; +.block-library-colors-selector__popover { + .color-palette-controller-container { + padding: 16px; + } + + .components-base-control__label { + height: $color-control-label-height; + line-height: $color-control-label-height; + } + + .component-color-indicator { + float: right; + margin-top: 2px; + } +} + +.block-editor-block-mover { + &.is-horizontal { + .block-editor-block-mover__control-drag-handle { + display: none; + } + } +} diff --git a/packages/block-library/src/navigation/icons.js b/packages/block-library/src/navigation/icons.js new file mode 100644 index 00000000000000..fbfd742bd712f2 --- /dev/null +++ b/packages/block-library/src/navigation/icons.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; + +export const justifyLeftIcon = ( + <SVG width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M11 16v-3h10v-2H11V8l-4 4 4 4zM5 4H3v16h2V4z" /> + </SVG> +); + +export const justifyCenterIcon = ( + <SVG width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M5 8v3H1v2h4v3l4-4-4-4zm14 8v-3h4v-2h-4V8l-4 4 4 4zM13 4h-2v16h2V4z" /> + </SVG> +); + +export const justifyRightIcon = ( + <SVG width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M13 8v3H3v2h10v3l4-4-4-4zm8-4h-2v16h2V4z" /></SVG> +); diff --git a/packages/block-library/src/navigation-menu/index.js b/packages/block-library/src/navigation/index.js similarity index 59% rename from packages/block-library/src/navigation-menu/index.js rename to packages/block-library/src/navigation/index.js index 00ae521ecba522..a898420ad6b750 100644 --- a/packages/block-library/src/navigation-menu/index.js +++ b/packages/block-library/src/navigation/index.js @@ -9,17 +9,19 @@ import { __ } from '@wordpress/i18n'; import edit from './edit'; import save from './save'; -export const name = 'core/navigation-menu'; +export const name = 'core/navigation'; export const settings = { - title: __( 'Navigation Menu (Experimental)' ), + title: __( 'Navigation' ), icon: 'menu', - description: __( 'Add a navigation menu to your site.' ), + description: __( 'Add a navigation block to your site.' ), keywords: [ __( 'menu' ), __( 'navigation' ), __( 'links' ) ], + category: 'layout', + supports: { align: [ 'wide', 'full' ], anchor: true, @@ -27,6 +29,11 @@ export const settings = { inserter: true, }, + styles: [ + { name: 'light', label: __( 'Light' ), isDefault: true }, + { name: 'dark', label: __( 'Dark' ) }, + ], + edit, save, diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php new file mode 100644 index 00000000000000..fd6537e028ef2b --- /dev/null +++ b/packages/block-library/src/navigation/index.php @@ -0,0 +1,150 @@ +<?php +/** + * Server-side rendering of the `core/navigation` block. + * + * @package gutenberg + */ + +/** + * Build an array with CSS classes and inline styles defining the colors + * which will be applied to the navigation markup in the front-end. + * + * @param array $attributes Navigation block attributes. + * @return array Colors CSS classes and inline styles. + */ +function build_css_colors( $attributes ) { + // CSS classes. + $colors = array( + 'css_classes' => array(), + 'inline_styles' => '', + ); + + $has_named_text_color = array_key_exists( 'textColor', $attributes ); + $has_custom_text_color = array_key_exists( 'customTextColor', $attributes ); + + // If has text color. + if ( $has_custom_text_color || $has_named_text_color ) { + // Add has-text-color class. + $colors['css_classes'][] = 'has-text-color'; + } + + if ( $has_named_text_color ) { + // Add the color class. + $colors['css_classes'][] = sprintf( 'has-%s-color', $attributes['textColor'] ); + } elseif ( $has_custom_text_color ) { + // Add the custom color inline style. + $colors['inline_styles'] = sprintf( 'color: %s;', $attributes['customTextColor'] ); + } + + return $colors; +} + +/** + * Renders the `core/navigation` block on server. + * + * @param array $attributes The block attributes. + * @param array $content The saved content. + * @param array $block The parsed block. + * + * @return string Returns the post content with the legacy widget added. + */ +function render_block_navigation( $attributes, $content, $block ) { + $colors = build_css_colors( $attributes ); + $classes = array_merge( + $colors['css_classes'], + array( 'wp-block-navigation' ), + isset( $attributes['className'] ) ? array( $attributes['className'] ) : array(), + isset( $attributes['itemsJustification'] ) ? array( 'items-justified-' . $attributes['itemsJustification'] ) : array(), + isset( $attributes['align'] ) ? array( 'align' . $attributes['align'] ) : array() + ); + $class_attribute = sprintf( ' class="%s"', esc_attr( implode( ' ', $classes ) ) ); + $style_attribute = $colors['inline_styles'] ? sprintf( ' style="%s"', esc_attr( $colors['inline_styles'] ) ) : ''; + + return sprintf( + '<nav %1$s %2$s>%3$s</nav>', + $class_attribute, + $style_attribute, + build_navigation_html( $block, $colors ) + ); +} + +/** + * Walks the inner block structure and returns an HTML list for it. + * + * @param array $block The block. + * @param array $colors Contains inline styles and CSS classes to apply to navigation item. + * + * @return string Returns an HTML list from innerBlocks. + */ +function build_navigation_html( $block, $colors ) { + $html = ''; + $css_classes = implode( ' ', $colors['css_classes'] ); + $class_attribute = sprintf( ' class="wp-block-navigation-link__content %s"', esc_attr( trim( $css_classes ) ) ); + $style_attribute = $colors['inline_styles'] ? sprintf( ' style="%s"', esc_attr( $colors['inline_styles'] ) ) : ''; + + foreach ( (array) $block['innerBlocks'] as $key => $block ) { + + $html .= '<li class="wp-block-navigation-link">' . + '<a' . $class_attribute . $style_attribute; + + // Start appending HTML attributes to anchor tag. + if ( isset( $block['attrs']['url'] ) ) { + $html .= ' href="' . esc_url( $block['attrs']['url'] ) . '"'; + } + if ( isset( $block['attrs']['title'] ) ) { + $html .= ' title="' . esc_attr( $block['attrs']['title'] ) . '"'; + } + + if ( isset( $block['attrs']['opensInNewTab'] ) && true === $block['attrs']['opensInNewTab'] ) { + $html .= ' target="_blank" '; + } + // End appending HTML attributes to anchor tag. + + // Start anchor tag content. + $html .= '>'; + if ( isset( $block['attrs']['label'] ) ) { + $html .= esc_html( $block['attrs']['label'] ); + } + $html .= '</a>'; + // End anchor tag content. + + if ( count( (array) $block['innerBlocks'] ) > 0 ) { + $html .= build_navigation_html( $block, $colors ); + } + + $html .= '</li>'; + } + return '<ul>' . $html . '</ul>'; +} + +/** + * Register the navigation block. + * + * @uses render_block_navigation() + * @throws WP_Error An WP_Error exception parsing the block definition. + */ +function register_block_core_navigation() { + + register_block_type( + 'core/navigation', + array( + 'attributes' => array( + 'className' => array( + 'type' => 'string', + ), + 'textColor' => array( + 'type' => 'string', + ), + 'customTextColor' => array( + 'type' => 'string', + ), + 'itemsJustification' => array( + 'type' => 'string', + ), + ), + + 'render_callback' => 'render_block_navigation', + ) + ); +} +add_action( 'init', 'register_block_core_navigation' ); diff --git a/packages/block-library/src/navigation-menu/save.js b/packages/block-library/src/navigation/save.js similarity index 100% rename from packages/block-library/src/navigation-menu/save.js rename to packages/block-library/src/navigation/save.js diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss new file mode 100644 index 00000000000000..b309aa2c165990 --- /dev/null +++ b/packages/block-library/src/navigation/style.scss @@ -0,0 +1,176 @@ +.wp-block-navigation { + + & > ul { + display: block; + list-style: none; + margin: 0; + max-width: none; + padding-left: 0; + position: relative; + + @include break-small { + display: flex; + flex-wrap: wrap; + } + + ul { + padding-left: 0; + } + + li { + position: relative; + z-index: 1; + + &:hover, + &:focus-within { + cursor: pointer; + z-index: 99999; + } + + // Submenu Display + &:hover > ul, + &:focus-within > ul, + & ul:hover, + & ul:focus { + visibility: visible; + opacity: 1; + display: block; + } + } + + & > li { + + & > a { + padding-left: 0; + + @include break-small { + padding-left: 16px; + } + } + + &:first-of-type > a { + padding-left: 0; + } + + &:last-of-type > a { + padding-right: 0; + } + } + + // Sub-menus Flyout + & > li > ul { + background: #fff; + -webkit-box-shadow: -2px 4px 4px -1px rgba(97, 97, 97, 0.153); + -moz-box-shadow: -2px 4px 4px -1px rgba(97, 97, 97, 0.153); + border-radius: 4px; + box-shadow: -2px 4px 4px -1px rgba(97, 97, 97, 0.153); + margin: 0; + position: absolute; + left: 0; + top: 80%; + min-width: max-content; + opacity: 0; + transition: opacity 0.15s linear, transform 0.15s linear, right 0s 0.15s; + transform: translateY(0.6rem); + visibility: hidden; + padding: 5px 0; + + + li { + margin: 2px 8px; + } + + a { + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + + &::after { + bottom: 100%; + left: 50%; + border: solid transparent; + border-color: rgba(255, 255, 255, 0); + border-bottom-color: #fff; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + border-width: 10px; + margin-left: -10px; + } + + ul { + width: 100%; + } + } + } + + .has-background-color + ul { + background: inherit; + } + + // Navigation Link + a { + display: block; + padding: 16px; + } + + // Sub-menu depth indicators + ul ul { + list-style: none; + margin-left: 0; + + li a { + padding-top: 8px; + padding-bottom: 8px; + } + } + + // Top-level sub-menu indicators + & .has-sub-menu > a { + + &::after { + content: "\00a0\25BC"; + display: inline-block; + font-size: 0.6rem; + height: inherit; + width: inherit; + } + } + + &.items-justified-left > ul { + justify-content: flex-start; + } + + &.items-justified-center > ul { + justify-content: center; + } + + &.items-justified-right > ul { + justify-content: flex-end; + } +} + +.is-style-dark > ul > li > ul { + background: #333; + border-radius: 4px; + + a { + text-decoration: none; + color: #fff; + + &:hover { + text-decoration: underline; + color: #eee; + } + } + + &::after { + border-color: rgba(24, 24, 24, 0); + border-bottom-color: #333; + } +} diff --git a/packages/block-library/src/navigation/theme.scss b/packages/block-library/src/navigation/theme.scss new file mode 100644 index 00000000000000..0b80d85004697a --- /dev/null +++ b/packages/block-library/src/navigation/theme.scss @@ -0,0 +1,6 @@ +.wp-block-navigation { + ul, + ul li { + list-style: none; + } +} diff --git a/packages/block-library/src/navigation-menu/use-block-navigator.js b/packages/block-library/src/navigation/use-block-navigator.js similarity index 61% rename from packages/block-library/src/navigation-menu/use-block-navigator.js rename to packages/block-library/src/navigation/use-block-navigator.js index dd924d3eb5f8b3..02c20e54b5308e 100644 --- a/packages/block-library/src/navigation-menu/use-block-navigator.js +++ b/packages/block-library/src/navigation/use-block-navigator.js @@ -4,13 +4,6 @@ import { useState, } from '@wordpress/element'; -import { - useSelect, - useDispatch, -} from '@wordpress/data'; -import { - __experimentalBlockNavigationList, -} from '@wordpress/block-editor'; import { IconButton, SVG, @@ -19,6 +12,11 @@ import { } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import BlockNavigationList from './block-navigation-list'; + const NavigatorIcon = ( <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20"> <Path d="M5 5H3v2h2V5zm3 8h11v-2H8v2zm9-8H6v2h11V5zM7 11H5v2h2v-2zm0 8h2v-2H7v2zm3-2v2h11v-2H10z" /> @@ -28,25 +26,6 @@ const NavigatorIcon = ( export default function useBlockNavigator( clientId ) { const [ isNavigationListOpen, setIsNavigationListOpen ] = useState( false ); - const { - block, - selectedBlockClientId, - } = useSelect( ( select ) => { - const { - getSelectedBlockClientId, - getBlock, - } = select( 'core/block-editor' ); - - return { - block: getBlock( clientId ), - selectedBlockClientId: getSelectedBlockClientId(), - }; - }, [ clientId ] ); - - const { - selectBlock, - } = useDispatch( 'core/block-editor' ); - const navigatorToolbarButton = ( <IconButton className="components-toolbar__control" @@ -64,12 +43,7 @@ export default function useBlockNavigator( clientId ) { setIsNavigationListOpen( false ); } } > - <__experimentalBlockNavigationList - blocks={ [ block ] } - selectedBlockClientId={ selectedBlockClientId } - selectBlock={ selectBlock } - showNestedBlocks - /> + <BlockNavigationList clientId={ clientId } /> </Modal> ); diff --git a/packages/block-library/src/nextpage/index.js b/packages/block-library/src/nextpage/index.js index 9f39f881d83def..00dedee31d7ff6 100644 --- a/packages/block-library/src/nextpage/index.js +++ b/packages/block-library/src/nextpage/index.js @@ -18,6 +18,7 @@ export { metadata, name }; export const settings = { title: __( 'Page Break' ), + parent: [ 'core/post-content' ], description: __( 'Separate your content into a multi-page experience.' ), icon, keywords: [ __( 'next page' ), __( 'pagination' ) ], diff --git a/packages/block-library/src/paragraph/edit.js b/packages/block-library/src/paragraph/edit.js index 0d41efd5c26836..36578af996189e 100644 --- a/packages/block-library/src/paragraph/edit.js +++ b/packages/block-library/src/paragraph/edit.js @@ -7,212 +7,183 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __, _x } from '@wordpress/i18n'; -import { Component } from '@wordpress/element'; import { PanelBody, ToggleControl, - Toolbar, - withFallbackStyles, + ToolbarGroup, } from '@wordpress/components'; import { - withColors, AlignmentToolbar, BlockControls, - ContrastChecker, FontSizePicker, InspectorControls, - PanelColorSettings, RichText, withFontSizes, + __experimentalUseColors, } from '@wordpress/block-editor'; import { createBlock } from '@wordpress/blocks'; import { compose } from '@wordpress/compose'; -import { withSelect } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; +import { useEffect, useState } from '@wordpress/element'; +/** + * Browser dependencies + */ const { getComputedStyle } = window; +const querySelector = window.document.querySelector.bind( document ); const name = 'core/paragraph'; +const PARAGRAPH_DROP_CAP_SELECTOR = 'p.has-drop-cap'; -const applyFallbackStyles = withFallbackStyles( ( node, ownProps ) => { - const { textColor, backgroundColor, fontSize, customFontSize } = ownProps.attributes; - const editableNode = node.querySelector( '[contenteditable="true"]' ); - //verify if editableNode is available, before using getComputedStyle. - const computedStyles = editableNode ? getComputedStyle( editableNode ) : null; - return { - fallbackBackgroundColor: backgroundColor || ! computedStyles ? undefined : computedStyles.backgroundColor, - fallbackTextColor: textColor || ! computedStyles ? undefined : computedStyles.color, - fallbackFontSize: fontSize || customFontSize || ! computedStyles ? undefined : parseInt( computedStyles.fontSize ) || undefined, - }; -} ); - -class ParagraphBlock extends Component { - constructor() { - super( ...arguments ); - - this.toggleDropCap = this.toggleDropCap.bind( this ); - } +function ParagraphRTLToolbar( { direction, setDirection } ) { + const isRTL = useSelect( ( select ) => { + return !! select( 'core/block-editor' ).getSettings().isRTL; + }, [] ); - toggleDropCap() { - const { attributes, setAttributes } = this.props; - setAttributes( { dropCap: ! attributes.dropCap } ); - } + return ( isRTL && ( + <ToolbarGroup + controls={ [ + { + icon: 'editor-ltr', + title: _x( 'Left to right', 'editor button' ), + isActive: direction === 'ltr', + onClick() { + setDirection( direction === 'ltr' ? undefined : 'ltr' ); + }, + }, + ] } + /> + ) ); +} - getDropCapHelp( checked ) { - return checked ? __( 'Showing large initial letter.' ) : __( 'Toggle to show a large initial letter.' ); - } +function useDropCapMinimumHeight( isDropCap, deps ) { + const [ minimumHeight, setMinimumHeight ] = useState(); + useEffect( + () => { + const element = querySelector( PARAGRAPH_DROP_CAP_SELECTOR ); + if ( isDropCap && element ) { + setMinimumHeight( + getComputedStyle( + element, + 'first-letter' + ).height + ); + } else if ( minimumHeight ) { + setMinimumHeight( undefined ); + } + }, + [ isDropCap, minimumHeight, setMinimumHeight, ...deps ] + ); + return minimumHeight; +} - render() { - const { - attributes, - setAttributes, - mergeBlocks, - onReplace, - className, - backgroundColor, - textColor, - setBackgroundColor, - setTextColor, - fallbackBackgroundColor, - fallbackTextColor, - fallbackFontSize, - fontSize, - setFontSize, - isRTL, - } = this.props; +function ParagraphBlock( { + attributes, + className, + fontSize, + mergeBlocks, + onReplace, + setAttributes, + setFontSize, +} ) { + const { + align, + content, + dropCap, + placeholder, + direction, + } = attributes; - const { - align, - content, - dropCap, - placeholder, - direction, - } = attributes; + const dropCapMinimumHeight = useDropCapMinimumHeight( dropCap, [ fontSize.size ] ); + const { + TextColor, + BackgroundColor, + InspectorControlsColorPanel, + ColorDetector, + } = __experimentalUseColors( + [ + { name: 'textColor', property: 'color' }, + { name: 'backgroundColor', className: 'has-background' }, + ], + { + contrastCheckers: [ { backgroundColor: true, textColor: true, fontSize: fontSize.size } ], + }, + [ fontSize.size ] + ); - return ( - <> - <BlockControls> - <AlignmentToolbar - value={ align } - onChange={ ( nextAlign ) => { - setAttributes( { align: nextAlign } ); - } } + return ( + <> + <BlockControls> + <AlignmentToolbar + value={ align } + onChange={ ( newAlign ) => setAttributes( { align: newAlign } ) } + /> + <ParagraphRTLToolbar + direction={ direction } + setDirection={ ( newDirection ) => setAttributes( { direction: newDirection } ) } + /> + </BlockControls> + <InspectorControls> + <PanelBody title={ __( 'Text Settings' ) } className="blocks-font-size"> + <FontSizePicker + value={ fontSize.size } + onChange={ setFontSize } /> - { isRTL && ( - <Toolbar - controls={ [ - { - icon: 'editor-ltr', - title: _x( 'Left to right', 'editor button' ), - isActive: direction === 'ltr', - onClick() { - const nextDirection = direction === 'ltr' ? undefined : 'ltr'; - setAttributes( { - direction: nextDirection, - } ); - }, - }, - ] } - /> - ) } - </BlockControls> - <InspectorControls> - <PanelBody title={ __( 'Text Settings' ) } className="blocks-font-size"> - <FontSizePicker - fallbackFontSize={ fallbackFontSize } - value={ fontSize.size } - onChange={ setFontSize } - /> - <ToggleControl - label={ __( 'Drop Cap' ) } - checked={ !! dropCap } - onChange={ this.toggleDropCap } - help={ this.getDropCapHelp } - /> - </PanelBody> - <PanelColorSettings - title={ __( 'Color Settings' ) } - initialOpen={ false } - colorSettings={ [ - { - value: backgroundColor.color, - onChange: setBackgroundColor, - label: __( 'Background Color' ), - }, - { - value: textColor.color, - onChange: setTextColor, - label: __( 'Text Color' ), - }, - ] } - > - <ContrastChecker - { ...{ - textColor: textColor.color, - backgroundColor: backgroundColor.color, - fallbackTextColor, - fallbackBackgroundColor, - } } - fontSize={ fontSize.size } - /> - </PanelColorSettings> - </InspectorControls> - <RichText - identifier="content" - tagName="p" - className={ classnames( 'wp-block-paragraph', className, { - 'has-text-color': textColor.color, - 'has-background': backgroundColor.color, - 'has-drop-cap': dropCap, - [ `has-text-align-${ align }` ]: align, - [ backgroundColor.class ]: backgroundColor.class, - [ textColor.class ]: textColor.class, - [ fontSize.class ]: fontSize.class, - } ) } - style={ { - backgroundColor: backgroundColor.color, - color: textColor.color, - fontSize: fontSize.size ? fontSize.size + 'px' : undefined, - direction, - } } - value={ content } - onChange={ ( nextContent ) => { - setAttributes( { - content: nextContent, - } ); - } } - onSplit={ ( value ) => { - if ( ! value ) { - return createBlock( name ); + <ToggleControl + label={ __( 'Drop Cap' ) } + checked={ !! dropCap } + onChange={ () => setAttributes( { dropCap: ! dropCap } ) } + help={ dropCap ? + __( 'Showing large initial letter.' ) : + __( 'Toggle to show a large initial letter.' ) } + /> + </PanelBody> + </InspectorControls> + { InspectorControlsColorPanel } + <BackgroundColor> + <TextColor> + <ColorDetector querySelector='[contenteditable="true"]' /> + <RichText + identifier="content" + tagName="p" + className={ classnames( 'wp-block-paragraph', className, { + 'has-drop-cap': dropCap, + [ `has-text-align-${ align }` ]: align, + [ fontSize.class ]: fontSize.class, + } ) } + style={ { + fontSize: fontSize.size ? fontSize.size + 'px' : undefined, + direction, + minHeight: dropCapMinimumHeight, + } } + value={ content } + onChange={ ( newContent ) => setAttributes( { content: newContent } ) } + onSplit={ ( value ) => { + if ( ! value ) { + return createBlock( name ); + } - return createBlock( name, { - ...attributes, - content: value, - } ); - } } - onMerge={ mergeBlocks } - onReplace={ onReplace } - onRemove={ onReplace ? () => onReplace( [] ) : undefined } - aria-label={ content ? __( 'Paragraph block' ) : __( 'Empty block; start writing or type forward slash to choose a block' ) } - placeholder={ placeholder || __( 'Start writing or type / to choose a block' ) } - __unstableEmbedURLOnPaste - /> - </> - ); - } + return createBlock( name, { + ...attributes, + content: value, + } ); + } } + onMerge={ mergeBlocks } + onReplace={ onReplace } + onRemove={ onReplace ? () => onReplace( [] ) : undefined } + aria-label={ content ? __( 'Paragraph block' ) : __( 'Empty block; start writing or type forward slash to choose a block' ) } + placeholder={ placeholder || __( 'Start writing or type / to choose a block' ) } + __unstableEmbedURLOnPaste + /> + </TextColor> + </BackgroundColor> + </> + ); } const ParagraphEdit = compose( [ - withColors( 'backgroundColor', { textColor: 'color' } ), withFontSizes( 'fontSize' ), - applyFallbackStyles, - withSelect( ( select ) => { - const { getSettings } = select( 'core/block-editor' ); - - return { - isRTL: getSettings().isRTL, - }; - } ), ] )( ParagraphBlock ); export default ParagraphEdit; diff --git a/packages/block-library/src/paragraph/edit.native.js b/packages/block-library/src/paragraph/edit.native.js index 6dbcaf8a1459dd..9d018a428040e8 100644 --- a/packages/block-library/src/paragraph/edit.native.js +++ b/packages/block-library/src/paragraph/edit.native.js @@ -9,7 +9,7 @@ import { View } from 'react-native'; import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { createBlock } from '@wordpress/blocks'; -import { RichText } from '@wordpress/block-editor'; +import { AlignmentToolbar, BlockControls, RichText } from '@wordpress/block-editor'; /** * Internal dependencies @@ -47,12 +47,22 @@ class ParagraphEdit extends Component { } = this.props; const { - placeholder, + align, content, + placeholder, } = attributes; return ( <View> + <BlockControls> + <AlignmentToolbar + isCollapsed={ false } + value={ align } + onChange={ ( nextAlign ) => { + setAttributes( { align: nextAlign } ); + } } + /> + </BlockControls> <RichText identifier="content" tagName="p" @@ -78,6 +88,7 @@ class ParagraphEdit extends Component { onReplace={ onReplace } onRemove={ onReplace ? () => onReplace( [] ) : undefined } placeholder={ placeholder || __( 'Start writing…' ) } + textAlign={ align } /> </View> ); diff --git a/packages/block-library/src/paragraph/editor.scss b/packages/block-library/src/paragraph/editor.scss index c5435f7c9afaf1..224c5b5a2941d5 100644 --- a/packages/block-library/src/paragraph/editor.scss +++ b/packages/block-library/src/paragraph/editor.scss @@ -15,3 +15,8 @@ min-height: $empty-paragraph-height / 2; line-height: $editor-line-height; } + +// Overwrite the inline style to make the height collapse when the paragraph editable gets focus. +.block-editor-block-list__block[data-type="core/paragraph"] .has-drop-cap:focus { + min-height: $empty-paragraph-height / 2 !important; +} diff --git a/packages/block-library/src/paragraph/style.scss b/packages/block-library/src/paragraph/style.scss index 62f47a757a7bc3..ddea388de29ba5 100644 --- a/packages/block-library/src/paragraph/style.scss +++ b/packages/block-library/src/paragraph/style.scss @@ -28,13 +28,6 @@ font-style: normal; } -.has-drop-cap:not(:focus)::after { - content: ""; - display: table; - clear: both; - padding-top: $block-padding; -} - p.has-background { padding: $block-bg-padding--v $block-bg-padding--h; } diff --git a/packages/block-library/src/paragraph/transforms.js b/packages/block-library/src/paragraph/transforms.js index 51aa06292ef861..98ef95b68cfefe 100644 --- a/packages/block-library/src/paragraph/transforms.js +++ b/packages/block-library/src/paragraph/transforms.js @@ -1,8 +1,3 @@ -/** - * WordPress dependencies - */ -import { getPhrasingContentSchema } from '@wordpress/blocks'; - const transforms = { from: [ { @@ -10,11 +5,11 @@ const transforms = { // Paragraph is a fallback and should be matched last. priority: 20, selector: 'p', - schema: { + schema: ( { phrasingContentSchema } ) => ( { p: { - children: getPhrasingContentSchema(), + children: phrasingContentSchema, }, - }, + } ), }, ], }; diff --git a/packages/block-library/src/post-content/block.json b/packages/block-library/src/post-content/block.json new file mode 100644 index 00000000000000..5c598d0970f490 --- /dev/null +++ b/packages/block-library/src/post-content/block.json @@ -0,0 +1,4 @@ +{ + "name": "core/post-content", + "category": "layout" +} diff --git a/packages/block-library/src/post-content/edit.js b/packages/block-library/src/post-content/edit.js new file mode 100644 index 00000000000000..d80b308b384b16 --- /dev/null +++ b/packages/block-library/src/post-content/edit.js @@ -0,0 +1,3 @@ +export default function PostContentEdit() { + return 'Post Content Placeholder'; +} diff --git a/packages/block-library/src/post-content/icon.js b/packages/block-library/src/post-content/icon.js new file mode 100644 index 00000000000000..2cd26e9d5f5a31 --- /dev/null +++ b/packages/block-library/src/post-content/icon.js @@ -0,0 +1,11 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/components'; + +export default ( + <SVG xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path fill="none" d="M0 0h24v24H0V0z" /> + <Path d="M3 15h18v-2H3v2zm0 4h18v-2H3v2zm0-8h18V9H3v2zm0-6v2h18V5H3z" /> + </SVG> +); diff --git a/packages/block-library/src/navigation-menu-item/index.js b/packages/block-library/src/post-content/index.js similarity index 54% rename from packages/block-library/src/navigation-menu-item/index.js rename to packages/block-library/src/post-content/index.js index 496ef92070428e..76a1a07148094e 100644 --- a/packages/block-library/src/navigation-menu-item/index.js +++ b/packages/block-library/src/post-content/index.js @@ -7,22 +7,14 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import metadata from './block.json'; +import icon from './icon'; import edit from './edit'; -import save from './save'; const { name } = metadata; export { metadata, name }; export const settings = { - title: __( 'Menu Item (Experimental)' ), - - parent: [ 'core/navigation-menu' ], - - icon: 'admin-links', - - description: __( 'Add a page, link, or other item to your Navigation Menu.' ), - + title: __( 'Post Content' ), + icon, edit, - save, }; - diff --git a/packages/block-library/src/post-content/index.php b/packages/block-library/src/post-content/index.php new file mode 100644 index 00000000000000..5765d383f76061 --- /dev/null +++ b/packages/block-library/src/post-content/index.php @@ -0,0 +1,36 @@ +<?php +/** + * Server-side rendering of the `core/post-content` block. + * + * @package WordPress + */ + +/** + * Renders the `core/post-content` block on the server. + * + * @return string Returns the filtered post content of the current post. + */ +function render_block_core_post_content() { + $post = gutenberg_get_post_from_context(); + if ( ! $post ) { + return ''; + } + return ( + '<div class="entry-content">' . + apply_filters( 'the_content', str_replace( ']]>', ']]&gt;', get_the_content( $post ) ) ) . + '</div>' + ); +} + +/** + * Registers the `core/post-content` block on the server. + */ +function register_block_core_post_content() { + register_block_type( + 'core/post-content', + array( + 'render_callback' => 'render_block_core_post_content', + ) + ); +} +add_action( 'init', 'register_block_core_post_content' ); diff --git a/packages/block-library/src/post-title/block.json b/packages/block-library/src/post-title/block.json new file mode 100644 index 00000000000000..b64dbc3740ab49 --- /dev/null +++ b/packages/block-library/src/post-title/block.json @@ -0,0 +1,4 @@ +{ + "name": "core/post-title", + "category": "layout" +} diff --git a/packages/block-library/src/post-title/edit.js b/packages/block-library/src/post-title/edit.js new file mode 100644 index 00000000000000..ed9782fa4b5892 --- /dev/null +++ b/packages/block-library/src/post-title/edit.js @@ -0,0 +1,3 @@ +export default function PostTitleEdit() { + return 'Post Title Placeholder'; +} diff --git a/packages/block-library/src/post-title/icon.js b/packages/block-library/src/post-title/icon.js new file mode 100644 index 00000000000000..6dc60909619f82 --- /dev/null +++ b/packages/block-library/src/post-title/icon.js @@ -0,0 +1,11 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/components'; + +export default ( + <SVG xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path fill="none" d="M0 0h24v24H0V0z" /> + <Path d="M5 4v3h5.5v12h3V7H19V4H5z" /> + </SVG> +); diff --git a/packages/block-library/src/post-title/index.js b/packages/block-library/src/post-title/index.js new file mode 100644 index 00000000000000..ba834fe69b0384 --- /dev/null +++ b/packages/block-library/src/post-title/index.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import icon from './icon'; +import edit from './edit'; + +const { name } = metadata; +export { metadata, name }; + +export const settings = { + title: __( 'Post Title' ), + icon, + edit, +}; diff --git a/packages/block-library/src/post-title/index.php b/packages/block-library/src/post-title/index.php new file mode 100644 index 00000000000000..8b6b372e8faa72 --- /dev/null +++ b/packages/block-library/src/post-title/index.php @@ -0,0 +1,32 @@ +<?php +/** + * Server-side rendering of the `core/post-title` block. + * + * @package WordPress + */ + +/** + * Renders the `core/post-title` block on the server. + * + * @return string Returns the filtered post title for the current post wrapped inside "h1" tags. + */ +function render_block_core_post_title() { + $post = gutenberg_get_post_from_context(); + if ( ! $post ) { + return ''; + } + return '<h1>' . get_the_title( $post ) . '</h1>'; +} + +/** + * Registers the `core/post-title` block on the server. + */ +function register_block_core_post_title() { + register_block_type( + 'core/post-title', + array( + 'render_callback' => 'render_block_core_post_title', + ) + ); +} +add_action( 'init', 'register_block_core_post_title' ); diff --git a/packages/block-library/src/preformatted/block.json b/packages/block-library/src/preformatted/block.json index fadcbd4a860edf..d582451f5eb670 100644 --- a/packages/block-library/src/preformatted/block.json +++ b/packages/block-library/src/preformatted/block.json @@ -6,7 +6,8 @@ "type": "string", "source": "html", "selector": "pre", - "default": "" + "default": "", + "__unstablePreserveWhiteSpace": true } } } diff --git a/packages/block-library/src/preformatted/edit.js b/packages/block-library/src/preformatted/edit.js index 620543d090004d..d7ce79438080ea 100644 --- a/packages/block-library/src/preformatted/edit.js +++ b/packages/block-library/src/preformatted/edit.js @@ -4,24 +4,23 @@ import { __ } from '@wordpress/i18n'; import { RichText } from '@wordpress/block-editor'; -export default function PreformattedEdit( { attributes, mergeBlocks, setAttributes, className } ) { +export default function PreformattedEdit( { attributes, mergeBlocks, setAttributes, className, style } ) { const { content } = attributes; return ( <RichText tagName="pre" - // Ensure line breaks are normalised to HTML. - value={ content.replace( /\n/g, '<br>' ) } + identifier="content" + preserveWhiteSpace + value={ content } onChange={ ( nextContent ) => { setAttributes( { - // Ensure line breaks are normalised to characters. This - // saves space, is easier to read, and ensures display - // filters work correctly. - content: nextContent.replace( /<br ?\/?>/g, '\n' ), + content: nextContent, } ); } } placeholder={ __( 'Write preformatted text…' ) } className={ className } + style={ style } onMerge={ mergeBlocks } /> ); diff --git a/packages/block-library/src/preformatted/edit.native.js b/packages/block-library/src/preformatted/edit.native.js new file mode 100644 index 00000000000000..43d5200dba8754 --- /dev/null +++ b/packages/block-library/src/preformatted/edit.native.js @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import { View } from 'react-native'; +/** + * WordPress dependencies + */ +import { withPreferredColorScheme } from '@wordpress/compose'; +/** + * Internal dependencies + */ +import WebPreformattedEdit from './edit.js'; +import styles from './styles.scss'; + +function PreformattedEdit( props ) { + const { getStylesFromColorScheme } = props; + const richTextStyle = getStylesFromColorScheme( styles.wpRichTextLight, styles.wpRichTextDark ); + const wpBlockPreformatted = getStylesFromColorScheme( styles.wpBlockPreformattedLight, styles.wpBlockPreformattedDark ); + const propsWithStyle = { + ...props, + style: richTextStyle, + }; + return ( + <View style={ wpBlockPreformatted } > + <WebPreformattedEdit + { ...propsWithStyle } + /> + </View> + ); +} + +export default withPreferredColorScheme( PreformattedEdit ); diff --git a/packages/block-library/src/preformatted/index.js b/packages/block-library/src/preformatted/index.js index 8a0bed67727347..1b897ce2c87791 100644 --- a/packages/block-library/src/preformatted/index.js +++ b/packages/block-library/src/preformatted/index.js @@ -22,9 +22,8 @@ export const settings = { icon, example: { attributes: { - content: __( 'EXT. XANADU - FAINT DAWN - 1940 (MINIATURE)' ) + '\n' + - __( 'Window, very small in the distance, illuminated.' ) + '\n' + - __( 'All around this is an almost totally black screen. Now, as the camera moves slowly towards the window which is almost a postage stamp in the frame, other forms appear;' ), + // translators: Sample content for the Preformatted block. Can be replaced with a more locale-adequate work. + content: __( 'EXT. XANADU - FAINT DAWN - 1940 (MINIATURE)\nWindow, very small in the distance, illuminated.\nAll around this is an almost totally black screen. Now, as the camera moves slowly towards the window which is almost a postage stamp in the frame, other forms appear;' ), }, }, transforms, diff --git a/packages/block-library/src/preformatted/styles.native.scss b/packages/block-library/src/preformatted/styles.native.scss new file mode 100644 index 00000000000000..c3d4cf129f2ba6 --- /dev/null +++ b/packages/block-library/src/preformatted/styles.native.scss @@ -0,0 +1,25 @@ +%wpBlockPreformattedLightColor { + background-color: $gray-light; +} + +%wpBlockPreformattedDarkColor { + background-color: $gray-100; +} + +.wpBlockPreformattedLight { + @extend %wpBlockPreformattedLightColor; + padding: 12px 16px; + border-radius: 4px; +} + +.wpRichTextLight { + @extend %wpBlockPreformattedLightColor; +} + +.wpBlockPreformattedDark { + @extend %wpBlockPreformattedDarkColor; +} + +.wpRichTextDark { + @extend %wpBlockPreformattedDarkColor; +} diff --git a/packages/block-library/src/preformatted/transforms.js b/packages/block-library/src/preformatted/transforms.js index f7d736ef5770a7..b759773c0d3cbb 100644 --- a/packages/block-library/src/preformatted/transforms.js +++ b/packages/block-library/src/preformatted/transforms.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { createBlock, getPhrasingContentSchema } from '@wordpress/blocks'; +import { createBlock } from '@wordpress/blocks'; const transforms = { from: [ @@ -22,11 +22,11 @@ const transforms = { node.firstChild.nodeName === 'CODE' ) ), - schema: { + schema: ( { phrasingContentSchema } ) => ( { pre: { - children: getPhrasingContentSchema(), + children: phrasingContentSchema, }, - }, + } ), }, ], to: [ diff --git a/packages/block-library/src/pullquote/deprecated.js b/packages/block-library/src/pullquote/deprecated.js index dc323edf32ca26..150bd1eb615c9c 100644 --- a/packages/block-library/src/pullquote/deprecated.js +++ b/packages/block-library/src/pullquote/deprecated.js @@ -48,7 +48,113 @@ const blockAttributes = { }, }; +function parseBorderColor( styleString ) { + if ( ! styleString ) { + return; + } + const matches = styleString.match( /border-color:([^;]+)[;]?/ ); + if ( matches && matches[ 1 ] ) { + return matches[ 1 ]; + } +} + const deprecated = [ + { + attributes: { + ...blockAttributes, + // figureStyle is an attribute that never existed. + // We are using it as a way to access the styles previously applied to the figure. + figureStyle: { + source: 'attribute', + selector: 'figure', + attribute: 'style', + }, + }, + save( { attributes } ) { + const { + mainColor, + customMainColor, + textColor, + customTextColor, + value, + citation, + className, + figureStyle, + } = attributes; + + const isSolidColorStyle = includes( className, SOLID_COLOR_CLASS ); + + let figureClasses, figureStyles; + + // Is solid color style + if ( isSolidColorStyle ) { + const backgroundClass = getColorClassName( 'background-color', mainColor ); + + figureClasses = classnames( { + 'has-background': ( backgroundClass || customMainColor ), + [ backgroundClass ]: backgroundClass, + } ); + + figureStyles = { + backgroundColor: backgroundClass ? undefined : customMainColor, + }; + // Is normal style and a custom color is being used ( we can set a style directly with its value) + } else if ( customMainColor ) { + figureStyles = { + borderColor: customMainColor, + }; + // If normal style and a named color are being used, we need to retrieve the color value to set the style, + // as there is no expectation that themes create classes that set border colors. + } else if ( mainColor ) { + // Previously here we queried the color settings to know the color value + // of a named color. This made the save function impure and the block was refactored, + // because meanwhile a change in the editor made it impossible to query color settings in the save function. + // Here instead of querying the color settings to know the color value, we retrieve the value + // directly from the style previously serialized. + const borderColor = parseBorderColor( figureStyle ); + figureStyles = { + borderColor, + }; + } + + const blockquoteTextColorClass = getColorClassName( 'color', textColor ); + const blockquoteClasses = ( textColor || customTextColor ) && classnames( 'has-text-color', { + [ blockquoteTextColorClass ]: blockquoteTextColorClass, + } ); + + const blockquoteStyles = blockquoteTextColorClass ? undefined : { color: customTextColor }; + + return ( + <figure className={ figureClasses } style={ figureStyles }> + <blockquote className={ blockquoteClasses } style={ blockquoteStyles } > + <RichText.Content value={ value } multiline /> + { ! RichText.isEmpty( citation ) && <RichText.Content tagName="cite" value={ citation } /> } + </blockquote> + </figure> + ); + }, + migrate( { className, figureStyle, mainColor, ...attributes } ) { + const isSolidColorStyle = includes( className, SOLID_COLOR_CLASS ); + // If is the default style, and a main color is set, + // migrate the main color value into a custom color. + // The custom color value is retrived by parsing the figure styles. + if ( ! isSolidColorStyle && mainColor && figureStyle ) { + const borderColor = parseBorderColor( figureStyle ); + if ( borderColor ) { + return { + ...attributes, + className, + customMainColor: borderColor, + }; + } + } + return { + className, + mainColor, + ...attributes, + }; + }, + }, { attributes: blockAttributes, save( { attributes } ) { diff --git a/packages/block-library/src/pullquote/edit.js b/packages/block-library/src/pullquote/edit.js index 010ac8f5078dbd..5b37164535a281 100644 --- a/packages/block-library/src/pullquote/edit.js +++ b/packages/block-library/src/pullquote/edit.js @@ -32,12 +32,20 @@ class PullQuoteEdit extends Component { } pullQuoteMainColorSetter( colorValue ) { - const { colorUtils, textColor, setTextColor, setMainColor, className } = this.props; + const { colorUtils, textColor, setAttributes, setTextColor, setMainColor, className } = this.props; const isSolidColorStyle = includes( className, SOLID_COLOR_CLASS ); const needTextColor = ! textColor.color || this.wasTextColorAutomaticallyComputed; const shouldSetTextColor = isSolidColorStyle && needTextColor && colorValue; - setMainColor( colorValue ); + if ( isSolidColorStyle ) { + // If we use the solid color style, set the color using the normal mechanism. + setMainColor( colorValue ); + } else { + // If we use the default style, set the color as a custom color to force the usage of an inline style. + // Default style uses a border color for which classes are not available. + setAttributes( { customMainColor: colorValue } ); + } + if ( shouldSetTextColor ) { this.wasTextColorAutomaticallyComputed = true; setTextColor( colorUtils.getMostReadableColor( colorValue ) ); @@ -50,6 +58,23 @@ class PullQuoteEdit extends Component { this.wasTextColorAutomaticallyComputed = false; } + componentDidUpdate( prevProps ) { + const { + attributes, + className, + mainColor, + setAttributes, + } = this.props; + // If the block includes a named color and we switched from the + // solid color style to the default style. + if ( attributes.mainColor && ! includes( className, SOLID_COLOR_CLASS ) && includes( prevProps.className, SOLID_COLOR_CLASS ) ) { + // Remove the named color, and set the color as a custom color. + // This is done because named colors use classes, in the default style we use a border color, + // and themes don't set classes for border colors. + setAttributes( { mainColor: undefined, customMainColor: mainColor.color } ); + } + } + render() { const { attributes, diff --git a/packages/block-library/src/pullquote/index.js b/packages/block-library/src/pullquote/index.js index c3dfcdeed9b950..220b7bc2d0cc84 100644 --- a/packages/block-library/src/pullquote/index.js +++ b/packages/block-library/src/pullquote/index.js @@ -23,8 +23,11 @@ export const settings = { icon, example: { attributes: { - value: '<p>' + __( 'One of the hardest things to do in technology is disrupt yourself.' ) + '</p>', - citation: 'Matt Mullenweg', + value: '<p>' + + // translators: Quote serving as example for the Pullquote block. Attributed to Matt Mullenweg. + __( 'One of the hardest things to do in technology is disrupt yourself.' ) + + '</p>', + citation: __( 'Matt Mullenweg' ), }, }, styles: [ diff --git a/packages/block-library/src/pullquote/save.js b/packages/block-library/src/pullquote/save.js index bb07a42d681466..c2f9833046fa00 100644 --- a/packages/block-library/src/pullquote/save.js +++ b/packages/block-library/src/pullquote/save.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { get, includes } from 'lodash'; +import { includes } from 'lodash'; /** * WordPress dependencies @@ -10,11 +10,7 @@ import { get, includes } from 'lodash'; import { getColorClassName, RichText, - getColorObjectByAttributeValues, } from '@wordpress/block-editor'; -import { - select, -} from '@wordpress/data'; /** * Internal dependencies @@ -53,14 +49,6 @@ export default function save( { attributes } ) { figureStyles = { borderColor: customMainColor, }; - // If normal style and a named color are being used, we need to retrieve the color value to set the style, - // as there is no expectation that themes create classes that set border colors. - } else if ( mainColor ) { - const colors = get( select( 'core/block-editor' ).getSettings(), [ 'colors' ], [] ); - const colorObject = getColorObjectByAttributeValues( colors, mainColor ); - figureStyles = { - borderColor: colorObject.color, - }; } const blockquoteTextColorClass = getColorClassName( 'color', textColor ); diff --git a/packages/block-library/src/quote/transforms.js b/packages/block-library/src/quote/transforms.js index c05c8b4b50a799..d7deb376ae2da9 100644 --- a/packages/block-library/src/quote/transforms.js +++ b/packages/block-library/src/quote/transforms.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { createBlock, getPhrasingContentSchema } from '@wordpress/blocks'; +import { createBlock } from '@wordpress/blocks'; import { create, join, split, toHTMLString } from '@wordpress/rich-text'; const transforms = { @@ -74,18 +74,18 @@ const transforms = { isParagraphOrSingleCite ); }, - schema: { + schema: ( { phrasingContentSchema } ) => ( { blockquote: { children: { p: { - children: getPhrasingContentSchema(), + children: phrasingContentSchema, }, cite: { - children: getPhrasingContentSchema(), + children: phrasingContentSchema, }, }, }, - }, + } ), }, ], to: [ diff --git a/packages/block-library/src/rss/edit.js b/packages/block-library/src/rss/edit.js index 81c6497f352522..d6e7b3299476d8 100644 --- a/packages/block-library/src/rss/edit.js +++ b/packages/block-library/src/rss/edit.js @@ -10,7 +10,7 @@ import { RangeControl, TextControl, ToggleControl, - Toolbar, + ToolbarGroup, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { @@ -109,7 +109,7 @@ class RSSEdit extends Component { return ( <> <BlockControls> - <Toolbar controls={ toolbarControls } /> + <ToolbarGroup controls={ toolbarControls } /> </BlockControls> <InspectorControls> <PanelBody title={ __( 'RSS Settings' ) }> diff --git a/packages/block-library/src/rss/index.php b/packages/block-library/src/rss/index.php index 0300585de24f02..0315cfc01325ff 100644 --- a/packages/block-library/src/rss/index.php +++ b/packages/block-library/src/rss/index.php @@ -20,10 +20,6 @@ function render_block_core_rss( $attributes ) { } if ( ! $rss->get_item_quantity() ) { - // PHP 5.2 compatibility. See: http://simplepie.org/wiki/faq/i_m_getting_memory_leaks. - $rss->__destruct(); - unset( $rss ); - return '<div class="components-placeholder"><div class="notice notice-error">' . __( 'An error has occurred, which probably means the feed is down. Try again later.' ) . '</div></div>'; } @@ -96,13 +92,7 @@ function render_block_core_rss( $attributes ) { $class .= ' ' . $attributes['className']; } - $list_items_markup = "<ul class='{$class}'>{$list_items}</ul>"; - - // PHP 5.2 compatibility. See: http://simplepie.org/wiki/faq/i_m_getting_memory_leaks. - $rss->__destruct(); - unset( $rss ); - - return $list_items_markup; + return "<ul class='{$class}'>{$list_items}</ul>"; } /** diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php index 6cd909fc6fa3ae..67bc90ff359232 100644 --- a/packages/block-library/src/search/index.php +++ b/packages/block-library/src/search/index.php @@ -25,6 +25,12 @@ function render_block_core_search( $attributes ) { $input_id, $attributes['label'] ); + } else { + $label_markup = sprintf( + '<label for="%s" class="wp-block-search__label screen-reader-text">%s</label>', + $input_id, + __( 'Search' ) + ); } $input_markup = sprintf( @@ -45,7 +51,6 @@ function render_block_core_search( $attributes ) { if ( isset( $attributes['className'] ) ) { $class .= ' ' . $attributes['className']; } - if ( isset( $attributes['align'] ) ) { $class .= ' align' . $attributes['align']; } diff --git a/packages/block-library/src/search/style.scss b/packages/block-library/src/search/style.scss index 80a8a20b5fd37a..0a0a512c570baa 100644 --- a/packages/block-library/src/search/style.scss +++ b/packages/block-library/src/search/style.scss @@ -8,6 +8,7 @@ .wp-block-search__input { flex-grow: 1; + max-width: 360px; } .wp-block-search__button { diff --git a/packages/block-library/src/site-title/block.json b/packages/block-library/src/site-title/block.json new file mode 100644 index 00000000000000..a853cde2517b51 --- /dev/null +++ b/packages/block-library/src/site-title/block.json @@ -0,0 +1,4 @@ +{ + "name": "core/site-title", + "category": "layout" +} diff --git a/packages/block-library/src/site-title/edit.js b/packages/block-library/src/site-title/edit.js new file mode 100644 index 00000000000000..0294fb82d17a18 --- /dev/null +++ b/packages/block-library/src/site-title/edit.js @@ -0,0 +1,39 @@ +/** + * WordPress dependencies + */ +import { + useEntityProp, + __experimentalUseEntitySaving, +} from '@wordpress/core-data'; +import { Button } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { RichText } from '@wordpress/block-editor'; + +export default function SiteTitleEdit() { + const [ title, setTitle ] = useEntityProp( 'root', 'site', 'title' ); + const [ isDirty, isSaving, save ] = __experimentalUseEntitySaving( + 'root', + 'site', + 'title' + ); + return ( + <> + <Button + isPrimary + className="wp-block-site-title__save-button" + disabled={ ! isDirty || ! title } + isBusy={ isSaving } + onClick={ save } + > + { __( 'Update' ) } + </Button> + <RichText + tagName="h1" + placeholder={ __( 'Site Title' ) } + value={ title } + onChange={ setTitle } + allowedFormats={ [] } + /> + </> + ); +} diff --git a/packages/block-library/src/site-title/editor.scss b/packages/block-library/src/site-title/editor.scss new file mode 100644 index 00000000000000..f2b8359cf3790b --- /dev/null +++ b/packages/block-library/src/site-title/editor.scss @@ -0,0 +1,6 @@ +.wp-block-site-title__save-button { + position: absolute; + right: 0; + top: 0; + z-index: z-index(".wp-block-site-title__save-button"); +} diff --git a/packages/block-library/src/site-title/icon.js b/packages/block-library/src/site-title/icon.js new file mode 100644 index 00000000000000..1ab74dec2c32d3 --- /dev/null +++ b/packages/block-library/src/site-title/icon.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +import { SVG, Path, Circle } from '@wordpress/components'; + +export default ( + <SVG xmlns="https://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path fill="none" d="M0 0h24v24H0V0z" /> + <Path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zM7 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 2.88-2.88 7.19-5 9.88C9.92 16.21 7 11.85 7 9z" /> + <Circle cx="12" cy="9" r="2.5" /> + </SVG> +); diff --git a/packages/block-library/src/site-title/index.js b/packages/block-library/src/site-title/index.js new file mode 100644 index 00000000000000..4b8fc50b86b34f --- /dev/null +++ b/packages/block-library/src/site-title/index.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import icon from './icon'; +import edit from './edit'; + +const { name } = metadata; +export { metadata, name }; + +export const settings = { + title: __( 'Site Title' ), + icon, + edit, +}; diff --git a/packages/block-library/src/site-title/index.php b/packages/block-library/src/site-title/index.php new file mode 100644 index 00000000000000..6e5b5786436f09 --- /dev/null +++ b/packages/block-library/src/site-title/index.php @@ -0,0 +1,28 @@ +<?php +/** + * Server-side rendering of the `core/site-title` block. + * + * @package WordPress + */ + +/** + * Renders the `core/site-title` block on the server. + * + * @return string The render. + */ +function render_block_core_site_title() { + return sprintf( '<h1>%s</h1>', get_bloginfo( 'name' ) ); +} + +/** + * Registers the `core/site-title` block on the server. + */ +function register_block_core_site_title() { + register_block_type( + 'core/site-title', + array( + 'render_callback' => 'render_block_core_site_title', + ) + ); +} +add_action( 'init', 'register_block_core_site_title' ); diff --git a/packages/block-library/src/social-link/edit.js b/packages/block-library/src/social-link/edit.js index 986de58e60b920..8a7739e59330c7 100644 --- a/packages/block-library/src/social-link/edit.js +++ b/packages/block-library/src/social-link/edit.js @@ -6,7 +6,7 @@ import classNames from 'classnames'; /** * WordPress dependencies */ -import { URLPopover } from '@wordpress/block-editor'; +import { URLPopover, URLInput } from '@wordpress/block-editor'; import { useState } from '@wordpress/element'; import { Button, @@ -47,13 +47,12 @@ const SocialLinkEdit = ( { attributes, setAttributes, isSelected } ) => { event.preventDefault(); setPopover( false ); } } > - <div className="editor-url-input block-editor-url-input"> - <input type="text" - value={ url } - onChange={ ( event ) => setAttributes( { url: event.target.value } ) } - placeholder={ __( 'Enter Address' ) } - /> - </div> + <URLInput + value={ url } + onChange={ ( nextURL ) => setAttributes( { url: nextURL } ) } + placeholder={ __( 'Enter Address' ) } + disableSuggestions={ true } + /> <IconButton icon="editor-break" label={ __( 'Apply' ) } type="submit" /> </form> </URLPopover> diff --git a/packages/block-library/src/social-link/index.php b/packages/block-library/src/social-link/index.php index 4d04c9cd1c12c4..86d83b685e21e8 100644 --- a/packages/block-library/src/social-link/index.php +++ b/packages/block-library/src/social-link/index.php @@ -22,7 +22,7 @@ function render_core_social_link( $attributes ) { } $icon = core_social_link_get_icon( $site ); - return '<li class="wp-social-link wp-social-link-' . $site . '"><a href="' . esc_attr( $url ) . '"> ' . $icon . '</a></li>'; + return '<li class="wp-social-link wp-social-link-' . $site . '"><a href="' . esc_url( $url ) . '"> ' . $icon . '</a></li>'; } /** diff --git a/packages/block-library/src/social-link/social-list.js b/packages/block-library/src/social-link/social-list.js index 98c8120449c5ae..9cb8df0ef70ff3 100644 --- a/packages/block-library/src/social-link/social-list.js +++ b/packages/block-library/src/social-link/social-list.js @@ -109,7 +109,7 @@ const socialList = { icon: GoogleIcon, }, github: { - name: 'Github', + name: 'GitHub', icon: GithubIcon, }, instagram: { @@ -121,7 +121,7 @@ const socialList = { icon: LastfmIcon, }, linkedin: { - name: 'Linkedin', + name: 'LinkedIn', icon: LinkedinIcon, }, mail: { diff --git a/packages/block-library/src/social-links/edit.js b/packages/block-library/src/social-links/edit.js index 5b063ef476ec4b..56c25ca57727dd 100644 --- a/packages/block-library/src/social-links/edit.js +++ b/packages/block-library/src/social-links/edit.js @@ -32,6 +32,7 @@ export const SocialLinksEdit = function( { className } ) { allowedBlocks={ ALLOWED_BLOCKS } templateLock={ false } template={ TEMPLATE } + __experimentalMoverDirection={ 'horizontal' } /> </div> ); diff --git a/packages/block-library/src/social-links/editor.scss b/packages/block-library/src/social-links/editor.scss index 9996e30b4e12a8..bcd2b4edf99d9a 100644 --- a/packages/block-library/src/social-links/editor.scss +++ b/packages/block-library/src/social-links/editor.scss @@ -58,12 +58,9 @@ margin-bottom: 0; } - // Hide the breadcrumb. // Hide the mover. // Hide the sibling inserter. - .wp-block-social-links .block-editor-block-list__insertion-point, - .wp-block-social-links .block-editor-block-list__breadcrumb, - .wp-block-social-links .block-editor-block-mover.block-editor-block-mover { // Needs specificity. + .wp-block-social-links .block-editor-block-list__insertion-point { // Needs specificity. display: none; } } @@ -125,6 +122,7 @@ // Unconfigured placeholder links are semitransparent. .wp-social-link.wp-social-link__is-incomplete { opacity: 0.5; + @include reduce-motion("transition"); } .wp-block-social-links .is-selected .wp-social-link__is-incomplete, diff --git a/packages/block-library/src/social-links/style.scss b/packages/block-library/src/social-links/style.scss index 7ef01118875939..3bfa4992e224a8 100644 --- a/packages/block-library/src/social-links/style.scss +++ b/packages/block-library/src/social-links/style.scss @@ -5,6 +5,14 @@ padding-right: 0; // Some themes give all <ul> default margin instead of padding. margin-left: 0; + + // Some themes add underlines, false underlines (via shadows), and borders to <a>. + .wp-social-link a, + .wp-social-link a:hover { + text-decoration: none; + border-bottom: 0; + box-shadow: none; + } } .wp-social-link { @@ -14,6 +22,7 @@ border-radius: 36px; // This makes it pill-shaped instead of oval, in cases where the image fed is not perfectly sized. margin-right: 8px; transition: transform 0.1s ease; + @include reduce-motion("transition"); a { padding: 6px; diff --git a/packages/block-library/src/spacer/edit.native.js b/packages/block-library/src/spacer/edit.native.js new file mode 100644 index 00000000000000..e0d0d3245cdd20 --- /dev/null +++ b/packages/block-library/src/spacer/edit.native.js @@ -0,0 +1,66 @@ + +/** + * External dependencies + */ +import { View } from 'react-native'; +/** + * WordPress dependencies + */ +import { + RangeControl, + PanelBody, +} from '@wordpress/components'; +import { withPreferredColorScheme } from '@wordpress/compose'; +import { useState, useEffect } from '@wordpress/element'; +import { + InspectorControls, +} from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import styles from './editor.scss'; + +const minSpacerHeight = 20; +const maxSpacerHeight = 500; + +const SpacerEdit = ( { isSelected, attributes, setAttributes, getStylesFromColorScheme } ) => { + const { height } = attributes; + const [ sliderSpacerMaxHeight, setSpacerMaxHeight ] = useState( height ); + + // Height defined on the web can be higher than + // `maxSpacerHeight`, so there is a need to `setSpacerMaxHeight` + // after the initial render. + useEffect( () => { + setSpacerMaxHeight( height > maxSpacerHeight ? height * 2 : maxSpacerHeight ); + }, [] ); + + const changeAttribute = ( value ) => { + setAttributes( { + height: value, + } ); + }; + + const defaultStyle = getStylesFromColorScheme( styles.staticSpacer, styles.staticDarkSpacer ); + + return ( + <View style={ [ defaultStyle, isSelected && styles.selectedSpacer, { height } ] }> + <InspectorControls> + <PanelBody title={ __( 'Spacer Settings' ) } > + <RangeControl + label={ __( 'Height in pixels' ) } + minimumValue={ minSpacerHeight } + maximumValue={ sliderSpacerMaxHeight } + separatorType={ 'none' } + value={ height } + onChange={ changeAttribute } + style={ styles.rangeCellContainer } + /> + </PanelBody> + </InspectorControls> + </View> + ); +}; + +export default withPreferredColorScheme( SpacerEdit ); diff --git a/packages/block-library/src/spacer/editor.native.scss b/packages/block-library/src/spacer/editor.native.scss new file mode 100644 index 00000000000000..cc8f941a968b18 --- /dev/null +++ b/packages/block-library/src/spacer/editor.native.scss @@ -0,0 +1,18 @@ +.staticSpacer { + height: 20px; + background-color: transparent; + border: $border-width dashed $light-gray-500; + border-radius: 1px; +} + +.staticDarkSpacer { + border: $border-width dashed rgba($color: $light-gray-500, $alpha: 0.3); +} + +.selectedSpacer { + border: $border-width * 2 solid $blue-30; +} + +.rangeCellContainer { + padding-bottom: 16px; +} diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss index 433ef8cb3aa2f3..a08151f857554d 100644 --- a/packages/block-library/src/style.scss +++ b/packages/block-library/src/style.scss @@ -11,6 +11,7 @@ @import "./latest-comments/style.scss"; @import "./latest-posts/style.scss"; @import "./media-text/style.scss"; +@import "./navigation/style.scss"; @import "./paragraph/style.scss"; @import "./pullquote/style.scss"; @import "./quote/style.scss"; @@ -129,6 +130,82 @@ .has-very-dark-gray-color { color: #313131; } + + // Gradients + // Our classes uses the same values we set for gradient value attributes, and we can not use spacing because of WP multi site kses rule. + /* stylelint-disable function-comma-space-after */ + .has-vivid-cyan-blue-to-vivid-purple-gradient-background { + background: linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%); + } + + .has-vivid-green-cyan-to-vivid-cyan-blue-gradient-background { + background: linear-gradient(135deg,rgba(0,208,132,1) 0%,rgba(6,147,227,1) 100%); + } + + .has-light-green-cyan-to-vivid-green-cyan-gradient-background { + background: linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%); + } + + .has-luminous-vivid-amber-to-luminous-vivid-orange-gradient-background { + background: linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%); + } + + .has-luminous-vivid-orange-to-vivid-red-gradient-background { + background: linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%); + } + + .has-very-light-gray-to-cyan-bluish-gray-gradient-background { + background: linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%); + } + + .has-cool-to-warm-spectrum-gradient-background { + background: linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%); + } + + .has-blush-light-purple-gradient-background { + background: linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%); + } + + .has-blush-bordeaux-gradient-background { + background: linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%); + } + + .has-purple-crush-gradient-background { + background: linear-gradient(135deg,rgb(52,226,228) 0%,rgb(71,33,251) 50%,rgb(171,29,254) 100%); + } + + .has-luminous-dusk-gradient-background { + background: linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%); + } + + .has-hazy-dawn-gradient-background { + background: linear-gradient(135deg,rgb(250,172,168) 0%,rgb(218,208,236) 100%); + } + + .has-pale-ocean-gradient-background { + background: linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%); + } + + .has-electric-grass-gradient-background { + background: linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%); + } + + .has-subdued-olive-gradient-background { + background: linear-gradient(135deg,rgb(250,250,225) 0%,rgb(103,166,113) 100%); + } + + .has-atomic-cream-gradient-background { + background: linear-gradient(135deg,rgb(253,215,154) 0%,rgb(0,74,89) 100%); + } + + .has-nightshade-gradient-background { + background: linear-gradient(135deg,rgb(51,9,104) 0%,rgb(49,205,207) 100%); + } + + .has-midnight-gradient-background { + background: linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%); + } + /* stylelint-enable function-comma-space-after */ } @@ -170,27 +247,3 @@ /*rtl:ignore*/ text-align: right; } - -/** - * Vanilla Block Styles - * These are base styles that apply across blocks, meant to provide a baseline. - * They are applied both to the editor and the theme, so we should have as few of these as possible. - * Please note that some styles are stored in packages/editor/src/editor-styles.scss, as they pertain to CSS bleed for the editor only. - */ - -// Caption styles. -// Supply these even if the theme hasn't opted in, because the figcaption element is not likely to be styled in the majority of existing themes. -// By providing a minimum of margin styles, we ensure it doesn't look broken or unstyled in those themes. -figcaption { - margin-top: 0.5em; -} - -// Apply some base styles to blocks that need them. -img { - max-width: 100%; - height: auto; -} - -iframe { - width: 100%; -} diff --git a/packages/block-library/src/table/block.json b/packages/block-library/src/table/block.json index 539c4df88ab7c7..d5e52c7de813d3 100644 --- a/packages/block-library/src/table/block.json +++ b/packages/block-library/src/table/block.json @@ -9,6 +9,12 @@ "backgroundColor": { "type": "string" }, + "caption": { + "type": "string", + "source": "html", + "selector": "figcaption", + "default": "" + }, "head": { "type": "array", "default": [], diff --git a/packages/block-library/src/table/deprecated.js b/packages/block-library/src/table/deprecated.js index 79fc5726ea23fb..1dd641db9bc07e 100644 --- a/packages/block-library/src/table/deprecated.js +++ b/packages/block-library/src/table/deprecated.js @@ -8,18 +8,111 @@ import classnames from 'classnames'; */ import { RichText, getColorClassName } from '@wordpress/block-editor'; -/** - * Internal dependencies - */ -import metadata from './block.json'; - const supports = { align: true, }; const deprecated = [ { - attributes: metadata.attributes, + attributes: { + hasFixedLayout: { + type: 'boolean', + default: false, + }, + backgroundColor: { + type: 'string', + }, + head: { + type: 'array', + default: [], + source: 'query', + selector: 'thead tr', + query: { + cells: { + type: 'array', + default: [], + source: 'query', + selector: 'td,th', + query: { + content: { + type: 'string', + source: 'html', + }, + tag: { + type: 'string', + default: 'td', + source: 'tag', + }, + scope: { + type: 'string', + source: 'attribute', + attribute: 'scope', + }, + }, + }, + }, + }, + body: { + type: 'array', + default: [], + source: 'query', + selector: 'tbody tr', + query: { + cells: { + type: 'array', + default: [], + source: 'query', + selector: 'td,th', + query: { + content: { + type: 'string', + source: 'html', + }, + tag: { + type: 'string', + default: 'td', + source: 'tag', + }, + scope: { + type: 'string', + source: 'attribute', + attribute: 'scope', + }, + }, + }, + }, + }, + foot: { + type: 'array', + default: [], + source: 'query', + selector: 'tfoot tr', + query: { + cells: { + type: 'array', + default: [], + source: 'query', + selector: 'td,th', + query: { + content: { + type: 'string', + source: 'html', + }, + tag: { + type: 'string', + default: 'td', + source: 'tag', + }, + scope: { + type: 'string', + source: 'attribute', + attribute: 'scope', + }, + }, + }, + }, + }, + }, supports, save( { attributes } ) { const { diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index b86c0e282d7f9b..663f97eb3c1c16 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -18,13 +18,13 @@ import { } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { - PanelBody, - ToggleControl, - TextControl, Button, - Toolbar, DropdownMenu, + PanelBody, Placeholder, + TextControl, + ToggleControl, + ToolbarGroup, } from '@wordpress/components'; /** @@ -40,7 +40,6 @@ import { deleteColumn, toggleSection, isEmptyTableSection, - isCellSelected, } from './state'; import icon from './icon'; @@ -435,7 +434,6 @@ export class TableEdit extends Component { } const Tag = `t${ name }`; - const { selectedCell } = this.state; return ( <Tag> @@ -447,35 +445,21 @@ export class TableEdit extends Component { rowIndex, columnIndex, }; - const isSelected = isCellSelected( cellLocation, selectedCell ); const cellClasses = classnames( { - 'is-selected': isSelected, [ `has-text-align-${ align }` ]: align, - } ); - const richTextClassName = 'wp-block-table__cell-content'; + }, 'wp-block-table__cell-content' ); return ( - <CellTag + <RichText + tagName={ CellTag } key={ columnIndex } className={ cellClasses } scope={ CellTag === 'th' ? scope : undefined } - onClick={ ( event ) => { - // When a cell is selected, forward focus to the child RichText. This solves an issue where the - // user may click inside a cell, but outside of the RichText, resulting in nothing happening. - const richTextElement = event && event.target && event.target.querySelector( `.${ richTextClassName }` ); - if ( richTextElement ) { - richTextElement.focus(); - } - } } - > - <RichText - className={ richTextClassName } - value={ content } - onChange={ this.onChange } - unstableOnFocus={ this.createOnFocus( cellLocation ) } - /> - </CellTag> + value={ content } + onChange={ this.onChange } + unstableOnFocus={ this.createOnFocus( cellLocation ) } + /> ); } ) } </tr> @@ -499,9 +483,16 @@ export class TableEdit extends Component { className, backgroundColor, setBackgroundColor, + setAttributes, } = this.props; const { initialRowCount, initialColumnCount } = this.state; - const { hasFixedLayout, head, body, foot } = attributes; + const { + hasFixedLayout, + caption, + head, + body, + foot, + } = attributes; const isEmpty = isEmptyTableSection( head ) && isEmptyTableSection( body ) && isEmptyTableSection( foot ); const Section = this.renderSection; @@ -544,14 +535,14 @@ export class TableEdit extends Component { return ( <> <BlockControls> - <Toolbar> + <ToolbarGroup> <DropdownMenu hasArrowIndicator icon="editor-table" label={ __( 'Edit table' ) } controls={ this.getTableControls() } /> - </Toolbar> + </ToolbarGroup> <AlignmentToolbar label={ __( 'Change column alignment' ) } alignmentControls={ ALIGNMENT_CONTROLS } @@ -598,6 +589,14 @@ export class TableEdit extends Component { <Section name="body" rows={ body } /> <Section name="foot" rows={ foot } /> </table> + <RichText + tagName="figcaption" + placeholder={ __( 'Write caption…' ) } + value={ caption } + onChange={ ( value ) => setAttributes( { caption: value } ) } + // Deselect the selected table cell when the caption is focused. + unstableOnFocus={ () => this.setState( { selectedCell: null } ) } + /> </figure> </> ); diff --git a/packages/block-library/src/table/editor.scss b/packages/block-library/src/table/editor.scss index f5dbb5f2643c04..195144afe1be76 100644 --- a/packages/block-library/src/table/editor.scss +++ b/packages/block-library/src/table/editor.scss @@ -36,7 +36,6 @@ td, th { - padding: 0; border: $border-width solid; } @@ -47,19 +46,22 @@ border-style: double; } - &__cell-content { - padding: 0.5em; - } // Extra specificity to override default Placeholder styles. &__placeholder-form.wp-block-table__placeholder-form { text-align: left; align-items: center; } + &__placeholder-input { width: 100px; } + &__placeholder-button { min-width: 100px; justify-content: center; } + + figcaption { + @include caption-style-theme(); + } } diff --git a/packages/block-library/src/table/save.js b/packages/block-library/src/table/save.js index 0911f20fe85bfd..dbceac8aaab15e 100644 --- a/packages/block-library/src/table/save.js +++ b/packages/block-library/src/table/save.js @@ -15,6 +15,7 @@ export default function save( { attributes } ) { body, foot, backgroundColor, + caption, } = attributes; const isEmpty = ! head.length && ! body.length && ! foot.length; @@ -29,6 +30,8 @@ export default function save( { attributes } ) { 'has-background': !! backgroundClass, } ); + const hasCaption = ! RichText.isEmpty( caption ); + const Section = ( { type, rows } ) => { if ( ! rows.length ) { return null; @@ -64,11 +67,17 @@ export default function save( { attributes } ) { return ( <figure> - <table className={ classes }> + <table className={ classes === '' ? undefined : classes }> <Section type="head" rows={ head } /> <Section type="body" rows={ body } /> <Section type="foot" rows={ foot } /> </table> + { hasCaption && ( + <RichText.Content + tagName="figcaption" + value={ caption } + /> + ) } </figure> ); } diff --git a/packages/block-library/src/table/theme.scss b/packages/block-library/src/table/theme.scss index 664a4f57cf53dd..9c0158c6b5c693 100644 --- a/packages/block-library/src/table/theme.scss +++ b/packages/block-library/src/table/theme.scss @@ -7,4 +7,8 @@ border: 1px solid; word-break: normal; } + + figcaption { + @include caption-style-theme(); + } } diff --git a/packages/block-library/src/table/transforms.js b/packages/block-library/src/table/transforms.js index e002dec2219150..15ee7dff1178f4 100644 --- a/packages/block-library/src/table/transforms.js +++ b/packages/block-library/src/table/transforms.js @@ -1,43 +1,38 @@ -/** - * WordPress dependencies - */ -import { getPhrasingContentSchema } from '@wordpress/blocks'; - -const tableContentPasteSchema = { +const tableContentPasteSchema = ( { phrasingContentSchema } ) => ( { tr: { allowEmpty: true, children: { th: { allowEmpty: true, - children: getPhrasingContentSchema(), + children: phrasingContentSchema, attributes: [ 'scope' ], }, td: { allowEmpty: true, - children: getPhrasingContentSchema(), + children: phrasingContentSchema, }, }, }, -}; +} ); -const tablePasteSchema = { +const tablePasteSchema = ( args ) => ( { table: { children: { thead: { allowEmpty: true, - children: tableContentPasteSchema, + children: tableContentPasteSchema( args ), }, tfoot: { allowEmpty: true, - children: tableContentPasteSchema, + children: tableContentPasteSchema( args ), }, tbody: { allowEmpty: true, - children: tableContentPasteSchema, + children: tableContentPasteSchema( args ), }, }, }, -}; +} ); const transforms = { from: [ diff --git a/packages/block-library/src/template-part/block.json b/packages/block-library/src/template-part/block.json new file mode 100644 index 00000000000000..7d3870aff2d108 --- /dev/null +++ b/packages/block-library/src/template-part/block.json @@ -0,0 +1,15 @@ +{ + "name": "core/template-part", + "category": "layout", + "attributes": { + "postId": { + "type": "number" + }, + "slug": { + "type": "string" + }, + "theme": { + "type": "string" + } + } +} diff --git a/packages/block-library/src/template-part/edit.js b/packages/block-library/src/template-part/edit.js new file mode 100644 index 00000000000000..66566932ed3905 --- /dev/null +++ b/packages/block-library/src/template-part/edit.js @@ -0,0 +1,3 @@ +export default function TemplatePartEdit() { + return 'Template Part Placeholder'; +} diff --git a/packages/block-library/src/template-part/index.js b/packages/block-library/src/template-part/index.js new file mode 100644 index 00000000000000..e93f69fb6ed42e --- /dev/null +++ b/packages/block-library/src/template-part/index.js @@ -0,0 +1,18 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import edit from './edit'; + +const { name } = metadata; +export { metadata, name }; + +export const settings = { + title: __( 'Template Part' ), + edit, +}; diff --git a/packages/block-library/src/template-part/index.php b/packages/block-library/src/template-part/index.php new file mode 100644 index 00000000000000..0d561ad8ee005c --- /dev/null +++ b/packages/block-library/src/template-part/index.php @@ -0,0 +1,60 @@ +<?php +/** + * Server-side rendering of the `core/template-part` block. + * + * @package WordPress + */ + +/** + * Renders the `core/template-part` block on the server. + * + * @param array $attributes The block attributes. + * + * @return string The render. + */ +function render_block_core_template_part( $attributes ) { + $content = null; + + if ( ! empty( $attributes['postId'] ) ) { + // If we have a post ID, which means this template part + // is user-customized, render the corresponding post content. + $content = get_post( $attributes['postId'] )->post_content; + } elseif ( wp_get_theme()->get( 'TextDomain' ) === $attributes['theme'] ) { + // Else, if the template part was provided by the active theme, + // render the corresponding file content. + $template_part_file_path = + get_stylesheet_directory() . '/block-template-parts/' . $attributes['slug'] . '.html'; + if ( file_exists( $template_part_file_path ) ) { + $content = file_get_contents( $template_part_file_path ); + } + } + + if ( is_null( $content ) ) { + return 'Template Part Not Found'; + } + return apply_filters( 'the_content', str_replace( ']]>', ']]&gt;', $content ) ); +} + +/** + * Registers the `core/template-part` block on the server. + */ +function register_block_core_template_part() { + register_block_type( + 'core/template-part', + array( + 'attributes' => array( + 'postId' => array( + 'type' => 'number', + ), + 'slug' => array( + 'type' => 'string', + ), + 'theme' => array( + 'type' => 'string', + ), + ), + 'render_callback' => 'render_block_core_template_part', + ) + ); +} +add_action( 'init', 'register_block_core_template_part' ); diff --git a/packages/block-library/src/theme.scss b/packages/block-library/src/theme.scss index d595550aa07bdb..9aedbc019a62ba 100644 --- a/packages/block-library/src/theme.scss +++ b/packages/block-library/src/theme.scss @@ -4,6 +4,7 @@ @import "./gallery/theme.scss"; @import "./image/theme.scss"; @import "./pullquote/theme.scss"; +@import "./navigation/theme.scss"; @import "./quote/theme.scss"; @import "./search/theme.scss"; @import "./group/theme.scss"; diff --git a/packages/block-library/src/verse/block.json b/packages/block-library/src/verse/block.json index d0def4bb7b74f9..6342361685d340 100644 --- a/packages/block-library/src/verse/block.json +++ b/packages/block-library/src/verse/block.json @@ -6,7 +6,8 @@ "type": "string", "source": "html", "selector": "pre", - "default": "" + "default": "", + "__unstablePreserveWhiteSpace": true }, "textAlign": { "type": "string" diff --git a/packages/block-library/src/verse/edit.js b/packages/block-library/src/verse/edit.js index 2af248ab83f726..5d971cd834fbf2 100644 --- a/packages/block-library/src/verse/edit.js +++ b/packages/block-library/src/verse/edit.js @@ -28,6 +28,7 @@ export default function VerseEdit( { attributes, setAttributes, className, merge </BlockControls> <RichText tagName="pre" + preserveWhiteSpace value={ content } onChange={ ( nextContent ) => { setAttributes( { diff --git a/packages/block-library/src/verse/index.js b/packages/block-library/src/verse/index.js index b17515b262af1c..0bded562eab3d2 100644 --- a/packages/block-library/src/verse/index.js +++ b/packages/block-library/src/verse/index.js @@ -23,12 +23,8 @@ export const settings = { icon, example: { attributes: { - content: __( 'WHAT was he doing, the great god Pan,' ) + '<br>' + - __( ' Down in the reeds by the river?' ) + '<br>' + - __( 'Spreading ruin and scattering ban,' ) + '<br>' + - __( 'Splashing and paddling with hoofs of a goat,' ) + '<br>' + - __( 'And breaking the golden lilies afloat' ) + '<br>' + - __( ' With the dragon-fly on the river.' ), + // translators: Sample content for the Verse block. Can be replaced with a more locale-adequate work. + content: __( 'WHAT was he doing, the great god Pan,\n Down in the reeds by the river?\nSpreading ruin and scattering ban,\nSplashing and paddling with hoofs of a goat,\nAnd breaking the golden lilies afloat\n With the dragon-fly on the river.' ), }, }, keywords: [ __( 'poetry' ) ], diff --git a/packages/block-library/src/video/edit-common-settings.js b/packages/block-library/src/video/edit-common-settings.js new file mode 100644 index 00000000000000..0d46bce952feb0 --- /dev/null +++ b/packages/block-library/src/video/edit-common-settings.js @@ -0,0 +1,72 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + ToggleControl, + SelectControl, +} from '@wordpress/components'; + +const VideoSettings = ( { setAttributes, attributes } ) => { + const { + autoplay, + controls, + loop, + muted, + playsInline, + preload, + } = attributes; + + const getAutoplayHelp = ( checked ) => { + return checked ? __( 'Note: Autoplaying videos may cause usability issues for some visitors.' ) : null; + }; + + const toggleAttribute = ( attribute ) => { + return ( newValue ) => { + setAttributes( { [ attribute ]: newValue } ); + }; + }; + + return ( + <> + <ToggleControl + label={ __( 'Autoplay' ) } + onChange={ toggleAttribute( 'autoplay' ) } + checked={ autoplay } + help={ getAutoplayHelp } + /> + <ToggleControl + label={ __( 'Loop' ) } + onChange={ toggleAttribute( 'loop' ) } + checked={ loop } + /> + <ToggleControl + label={ __( 'Muted' ) } + onChange={ toggleAttribute( 'muted' ) } + checked={ muted } + /> + <ToggleControl + label={ __( 'Playback Controls' ) } + onChange={ toggleAttribute( 'controls' ) } + checked={ controls } + /> + <ToggleControl + label={ __( 'Play inline' ) } + onChange={ toggleAttribute( 'playsInline' ) } + checked={ playsInline } + /> + <SelectControl + label={ __( 'Preload' ) } + value={ preload } + onChange={ ( value ) => setAttributes( { preload: value } ) } + options={ [ + { value: 'auto', label: __( 'Auto' ) }, + { value: 'metadata', label: __( 'Metadata' ) }, + { value: 'none', label: __( 'None' ) }, + ] } + /> + </> + ); +}; + +export default VideoSettings; diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js index 6236e51a95c935..84492ec552c154 100644 --- a/packages/block-library/src/video/edit.js +++ b/packages/block-library/src/video/edit.js @@ -8,9 +8,7 @@ import { Disabled, IconButton, PanelBody, - SelectControl, - ToggleControl, - Toolbar, + ToolbarGroup, withNotices, } from '@wordpress/components'; import { @@ -40,6 +38,7 @@ import { */ import { createUpgradedEmbedBlock } from '../embed/util'; import icon from './icon'; +import VideoCommonSettings from './edit-common-settings'; const ALLOWED_MEDIA_TYPES = [ 'video' ]; const VIDEO_POSTER_ALLOWED_MEDIA_TYPES = [ 'image' ]; @@ -55,7 +54,6 @@ class VideoEdit extends Component { this.videoPlayer = createRef(); this.posterImageButton = createRef(); - this.toggleAttribute = this.toggleAttribute.bind( this ); this.onSelectURL = this.onSelectURL.bind( this ); this.onSelectPoster = this.onSelectPoster.bind( this ); this.onRemovePoster = this.onRemovePoster.bind( this ); @@ -94,12 +92,6 @@ class VideoEdit extends Component { } } - toggleAttribute( attribute ) { - return ( newValue ) => { - this.props.setAttributes( { [ attribute ]: newValue } ); - }; - } - onSelectURL( newSrc ) { const { attributes, setAttributes } = this.props; const { src } = attributes; @@ -140,20 +132,11 @@ class VideoEdit extends Component { noticeOperations.createErrorNotice( message ); } - getAutoplayHelp( checked ) { - return checked ? __( 'Note: Autoplaying videos may cause usability issues for some visitors.' ) : null; - } - render() { const { - autoplay, caption, controls, - loop, - muted, - playsInline, poster, - preload, src, } = this.props.attributes; const { @@ -161,6 +144,7 @@ class VideoEdit extends Component { instanceId, isSelected, noticeUI, + attributes, setAttributes, } = this.props; const { editing } = this.state; @@ -201,52 +185,20 @@ class VideoEdit extends Component { return ( <> <BlockControls> - <Toolbar> + <ToolbarGroup> <IconButton className="components-icon-button components-toolbar__control" label={ __( 'Edit video' ) } onClick={ switchToEditing } icon="edit" /> - </Toolbar> + </ToolbarGroup> </BlockControls> <InspectorControls> <PanelBody title={ __( 'Video Settings' ) }> - <ToggleControl - label={ __( 'Autoplay' ) } - onChange={ this.toggleAttribute( 'autoplay' ) } - checked={ autoplay } - help={ this.getAutoplayHelp } - /> - <ToggleControl - label={ __( 'Loop' ) } - onChange={ this.toggleAttribute( 'loop' ) } - checked={ loop } - /> - <ToggleControl - label={ __( 'Muted' ) } - onChange={ this.toggleAttribute( 'muted' ) } - checked={ muted } - /> - <ToggleControl - label={ __( 'Playback Controls' ) } - onChange={ this.toggleAttribute( 'controls' ) } - checked={ controls } - /> - <ToggleControl - label={ __( 'Play inline' ) } - onChange={ this.toggleAttribute( 'playsInline' ) } - checked={ playsInline } - /> - <SelectControl - label={ __( 'Preload' ) } - value={ preload } - onChange={ ( value ) => setAttributes( { preload: value } ) } - options={ [ - { value: 'auto', label: __( 'Auto' ) }, - { value: 'metadata', label: __( 'Metadata' ) }, - { value: 'none', label: __( 'None' ) }, - ] } + <VideoCommonSettings + setAttributes={ setAttributes } + attributes={ attributes } /> <MediaUploadCheck> <BaseControl @@ -319,10 +271,8 @@ class VideoEdit extends Component { export default compose( [ withSelect( ( select ) => { const { getSettings } = select( 'core/block-editor' ); - const { __experimentalMediaUpload } = getSettings(); - return { - mediaUpload: __experimentalMediaUpload, - }; + const { mediaUpload } = getSettings(); + return { mediaUpload }; } ), withNotices, withInstanceId, diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index 1a193a521e7032..de12486e9075d8 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -6,7 +6,6 @@ import { View, TouchableWithoutFeedback, Text } from 'react-native'; /** * Internal dependencies */ -import Video from './video-player'; import { mediaUploadSync, requestImageFailedRetryDialog, @@ -18,8 +17,9 @@ import { */ import { Icon, - Toolbar, ToolbarButton, + ToolbarGroup, + PanelBody, } from '@wordpress/components'; import { withPreferredColorScheme } from '@wordpress/compose'; import { @@ -29,6 +29,9 @@ import { MediaUploadProgress, MEDIA_TYPE_VIDEO, BlockControls, + VIDEO_ASPECT_RATIO, + VideoPlayer, + InspectorControls, } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import { isURL } from '@wordpress/url'; @@ -40,8 +43,7 @@ import { doAction, hasAction } from '@wordpress/hooks'; import style from './style.scss'; import SvgIcon from './icon'; import SvgIconRetry from './icon-retry'; - -const VIDEO_ASPECT_RATIO = 1.7; +import VideoCommonSettings from './edit-common-settings'; class VideoEdit extends React.Component { constructor( props ) { @@ -155,8 +157,11 @@ class VideoEdit extends React.Component { } render() { - const { attributes, isSelected } = this.props; - const { id, src } = attributes; + const { setAttributes, attributes, isSelected } = this.props; + const { + id, + src, + } = attributes; const { videoContainerHeight } = this.state; const toolbarEditButton = ( @@ -164,14 +169,14 @@ class VideoEdit extends React.Component { onSelect={ this.onSelectMediaUploadOption } render={ ( { open, getMediaOptions } ) => { return ( - <Toolbar> + <ToolbarGroup> { getMediaOptions() } <ToolbarButton label={ __( 'Edit video' ) } icon="edit" onClick={ open } /> - </Toolbar> + </ToolbarGroup> ); } } > </MediaUpload> @@ -197,6 +202,14 @@ class VideoEdit extends React.Component { <BlockControls> { toolbarEditButton } </BlockControls> } + <InspectorControls> + <PanelBody title={ __( 'Video Settings' ) }> + <VideoCommonSettings + setAttributes={ setAttributes } + attributes={ attributes } + /> + </PanelBody> + </InspectorControls> <MediaUploadProgress mediaId={ id } onFinishMediaUploadWithSuccess={ this.finishMediaUploadWithSuccess } @@ -225,7 +238,7 @@ class VideoEdit extends React.Component { <View onLayout={ this.onVideoContanerLayout } style={ containerStyle }> { showVideo && <View style={ style.videoContainer }> - <Video + <VideoPlayer isSelected={ isSelected && ! this.state.isCaptionSelected } style={ videoStyle } source={ { uri: src } } diff --git a/packages/block-library/src/video/style.scss b/packages/block-library/src/video/style.scss index e426eaa6e2f70e..de4e9499dd67c5 100644 --- a/packages/block-library/src/video/style.scss +++ b/packages/block-library/src/video/style.scss @@ -16,4 +16,11 @@ &.aligncenter { text-align: center; } + + // Supply caption styles to videos, even if the theme hasn't opted in. + // Reason being: the new markup, <figcaptions>, are not likely to be styled in the majority of existing themes, + // so we supply the styles so as to not appear broken or unstyled in those themes. + figcaption { + @include caption-style(); + } } diff --git a/packages/block-serialization-default-parser/package.json b/packages/block-serialization-default-parser/package.json index 4936e8bc2568b1..8f2695921aa110 100644 --- a/packages/block-serialization-default-parser/package.json +++ b/packages/block-serialization-default-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-default-parser", - "version": "3.4.0", + "version": "3.4.1", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -21,6 +21,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "sideEffects": false, "dependencies": { "@babel/runtime": "^7.4.4" }, diff --git a/packages/block-serialization-spec-parser/package.json b/packages/block-serialization-spec-parser/package.json index d7e7fad2551e75..289e8b8e86afd4 100644 --- a/packages/block-serialization-spec-parser/package.json +++ b/packages/block-serialization-spec-parser/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/block-serialization-spec-parser", - "version": "3.3.0", + "version": "3.3.1", "description": "Block serialization specification parser for WordPress posts.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -20,6 +20,7 @@ "url": "https://github.com/WordPress/gutenberg/issues" }, "main": "parser.js", + "sideEffects": false, "dependencies": { "pegjs": "^0.10.0", "phpegjs": "^1.0.0-beta7" diff --git a/packages/blocks/README.md b/packages/blocks/README.md index 1af3f49adef88a..50aa27e1b3f7a6 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -401,6 +401,10 @@ _Related_ - <https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Phrasing_content> +_Parameters_ + +- _context_ `string`: Set to "paste" to exclude invisible elements and sensitive data. + _Returns_ - `Object`: Schema. @@ -768,7 +772,7 @@ wrapped component. _Returns_ -- `Component`: Enhanced component with injected BlockContent as prop. +- `WPComponent`: Enhanced component with injected BlockContent as prop. <!-- END TOKEN(Autogenerated API docs) --> diff --git a/packages/blocks/package.json b/packages/blocks/package.json index 3456e2386251e0..3007ee2d1fb4d3 100644 --- a/packages/blocks/package.json +++ b/packages/blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/blocks", - "version": "6.7.0", + "version": "6.8.0", "description": "Block API for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/blocks/src/api/factory.js b/packages/blocks/src/api/factory.js index fae2e0e4da9c06..e793800ecb1362 100644 --- a/packages/blocks/src/api/factory.js +++ b/packages/blocks/src/api/factory.js @@ -44,26 +44,26 @@ export function createBlock( name, attributes = {}, innerBlocks = [] ) { // Ensure attributes contains only values defined by block type, and merge // default values for missing attributes. - const sanitizedAttributes = reduce( blockType.attributes, ( result, schema, key ) => { + const sanitizedAttributes = reduce( blockType.attributes, ( accumulator, schema, key ) => { const value = attributes[ key ]; if ( undefined !== value ) { - result[ key ] = value; + accumulator[ key ] = value; } else if ( schema.hasOwnProperty( 'default' ) ) { - result[ key ] = schema.default; + accumulator[ key ] = schema.default; } if ( [ 'node', 'children' ].indexOf( schema.source ) !== -1 ) { // Ensure value passed is always an array, which we're expecting in // the RichText component to handle the deprecated value. - if ( typeof result[ key ] === 'string' ) { - result[ key ] = [ result[ key ] ]; - } else if ( ! Array.isArray( result[ key ] ) ) { - result[ key ] = []; + if ( typeof accumulator[ key ] === 'string' ) { + accumulator[ key ] = [ accumulator[ key ] ]; + } else if ( ! Array.isArray( accumulator[ key ] ) ) { + accumulator[ key ] = []; } } - return result; + return accumulator; }, {} ); const clientId = uuid(); diff --git a/packages/blocks/src/api/index.js b/packages/blocks/src/api/index.js index 62074213559e6d..3a007954c66421 100644 --- a/packages/blocks/src/api/index.js +++ b/packages/blocks/src/api/index.js @@ -49,6 +49,8 @@ export { unstable__bootstrapServerSideBlockDefinitions, // eslint-disable-line camelcase registerBlockStyle, unregisterBlockStyle, + __experimentalRegisterBlockPattern, + __experimentalUnregisterBlockPattern, } from './registration'; export { isUnmodifiedDefaultBlock, diff --git a/packages/blocks/src/api/index.native.js b/packages/blocks/src/api/index.native.js deleted file mode 100644 index f8d4c03298aebe..00000000000000 --- a/packages/blocks/src/api/index.native.js +++ /dev/null @@ -1,43 +0,0 @@ -export { - cloneBlock, - createBlock, - switchToBlockType, -} from './factory'; -export { - default as parse, - getBlockAttributes, - parseWithAttributeSchema, -} from './parser'; -export { - default as serialize, - getBlockContent, - getBlockDefaultClassName, - getSaveContent, -} from './serializer'; -export { - registerBlockType, - unregisterBlockType, - getFreeformContentHandlerName, - setUnregisteredTypeHandlerName, - getUnregisteredTypeHandlerName, - getBlockType, - getBlockTypes, - getBlockSupport, - hasBlockSupport, - isReusableBlock, - getChildBlockNames, - hasChildBlocks, - hasChildBlocksWithInserterSupport, - setDefaultBlockName, - getDefaultBlockName, -} from './registration'; -export { - isUnmodifiedDefaultBlock, - normalizeIconObject, -} from './utils'; -export { - doBlocksMatchTemplate, - synchronizeBlocksWithTemplate, -} from './templates'; -export { pasteHandler, getPhrasingContentSchema } from './raw-handling'; -export { default as children } from './children'; diff --git a/packages/blocks/src/api/parser.js b/packages/blocks/src/api/parser.js index 89b4f5ee5cc9f9..6300eac329f153 100644 --- a/packages/blocks/src/api/parser.js +++ b/packages/blocks/src/api/parser.js @@ -565,12 +565,12 @@ export function serializeBlockNode( blockNode, options = {} ) { * @return {Function} An implementation which parses the post content. */ const createParse = ( parseImplementation ) => - ( content ) => parseImplementation( content ).reduce( ( memo, blockNode ) => { + ( content ) => parseImplementation( content ).reduce( ( accumulator, blockNode ) => { const block = createBlockWithFallback( blockNode ); if ( block ) { - memo.push( block ); + accumulator.push( block ); } - return memo; + return accumulator; }, [] ); /** diff --git a/packages/blocks/src/api/raw-handling/br-remover.js b/packages/blocks/src/api/raw-handling/br-remover.js new file mode 100644 index 00000000000000..ade92182b541fc --- /dev/null +++ b/packages/blocks/src/api/raw-handling/br-remover.js @@ -0,0 +1,21 @@ +/** + * Internal dependencies + */ +import { getSibling } from './utils'; + +/** + * Removes trailing br elements from text-level content. + * + * @param {Element} node Node to check. + */ +export default function( node ) { + if ( node.nodeName !== 'BR' ) { + return; + } + + if ( getSibling( node, 'next' ) ) { + return; + } + + node.parentNode.removeChild( node ); +} diff --git a/packages/blocks/src/api/raw-handling/empty-paragraph-remover.js b/packages/blocks/src/api/raw-handling/empty-paragraph-remover.js new file mode 100644 index 00000000000000..5bd6f263692a54 --- /dev/null +++ b/packages/blocks/src/api/raw-handling/empty-paragraph-remover.js @@ -0,0 +1,16 @@ +/** + * Removes empty paragraph elements. + * + * @param {Element} node Node to check. + */ +export default function( node ) { + if ( node.nodeName !== 'P' ) { + return; + } + + if ( node.hasChildNodes() ) { + return; + } + + node.parentNode.removeChild( node ); +} diff --git a/packages/blocks/src/api/raw-handling/html-formatting-remover.js b/packages/blocks/src/api/raw-handling/html-formatting-remover.js new file mode 100644 index 00000000000000..f0b80c4c22c317 --- /dev/null +++ b/packages/blocks/src/api/raw-handling/html-formatting-remover.js @@ -0,0 +1,76 @@ +/** + * Internal dependencies + */ +import { getSibling } from './utils'; + +function isFormattingSpace( character ) { + return ( + character === ' ' || + character === '\r' || + character === '\n' || + character === '\t' + ); +} + +/** + * Removes spacing that formats HTML. + * + * @see https://www.w3.org/TR/css-text-3/#white-space-processing + * + * @param {Node} node The node to be processed. + * @return {void} + */ +export default function( node ) { + if ( node.nodeType !== node.TEXT_NODE ) { + return; + } + + // Ignore pre content. + if ( node.parentElement.closest( 'pre' ) ) { + return; + } + + // First, replace any sequence of HTML formatting space with a single space. + let newData = node.data.replace( /[ \r\n\t]+/g, ' ' ); + + // Remove the leading space if the text element is at the start of a block, + // is preceded by a line break element, or has a space in the previous + // node. + if ( newData[ 0 ] === ' ' ) { + const previousSibling = getSibling( node, 'previous' ); + + if ( + ! previousSibling || + previousSibling.nodeName === 'BR' || + previousSibling.textContent.slice( -1 ) === ' ' + ) { + newData = newData.slice( 1 ); + } + } + + // Remove the trailing space if the text element is at the end of a block, + // is succeded by a line break element, or has a space in the next text + // node. + if ( newData[ newData.length - 1 ] === ' ' ) { + const nextSibling = getSibling( node, 'next' ); + + if ( + ! nextSibling || + nextSibling.nodeName === 'BR' || + ( + nextSibling.nodeType === nextSibling.TEXT_NODE && + isFormattingSpace( nextSibling.textContent[ 0 ] ) + ) + ) { + newData = newData.slice( 0, -1 ); + } + } + + // If there's no data left, remove the node, so `previousSibling` stays + // accurate. Otherwise, update the node data. + if ( ! newData ) { + node.parentNode.removeChild( node ); + } else { + node.data = newData; + } +} diff --git a/packages/blocks/src/api/raw-handling/index.js b/packages/blocks/src/api/raw-handling/index.js index 37908f96d7d76a..d69ade4dac94a3 100644 --- a/packages/blocks/src/api/raw-handling/index.js +++ b/packages/blocks/src/api/raw-handling/index.js @@ -19,8 +19,10 @@ import { getBlockContentSchema, } from './utils'; -export { getPhrasingContentSchema } from './phrasing-content'; +import { getPhrasingContentSchema } from './phrasing-content'; + export { pasteHandler } from './paste-handler'; +export { getPhrasingContentSchema }; function getRawTransformations() { return filter( getBlockTransforms( 'from' ), { type: 'raw' } ) @@ -96,7 +98,8 @@ export function rawHandler( { HTML = '' } ) { // shortcodes. const pieces = shortcodeConverter( HTML ); const rawTransforms = getRawTransformations(); - const blockContentSchema = getBlockContentSchema( rawTransforms ); + const phrasingContentSchema = getPhrasingContentSchema(); + const blockContentSchema = getBlockContentSchema( rawTransforms, phrasingContentSchema ); return compact( flatMap( pieces, ( piece ) => { // Already a block from shortcode. diff --git a/packages/blocks/src/api/raw-handling/paste-handler.js b/packages/blocks/src/api/raw-handling/paste-handler.js index fdea17bd0eba2f..f126e050c15c08 100644 --- a/packages/blocks/src/api/raw-handling/paste-handler.js +++ b/packages/blocks/src/api/raw-handling/paste-handler.js @@ -24,6 +24,8 @@ import shortcodeConverter from './shortcode-converter'; import markdownConverter from './markdown-converter'; import iframeRemover from './iframe-remover'; import googleDocsUIDRemover from './google-docs-uid-remover'; +import htmlFormattingRemover from './html-formatting-remover'; +import brRemover from './br-remover'; import { getPhrasingContentSchema } from './phrasing-content'; import { deepFilterHTML, @@ -31,6 +33,7 @@ import { removeInvalidHTML, getBlockContentSchema, } from './utils'; +import emptyParagraphRemover from './empty-paragraph-remover'; /** * Browser dependencies @@ -46,7 +49,8 @@ const { console } = window; */ function filterInlineHTML( HTML ) { HTML = deepFilterHTML( HTML, [ googleDocsUIDRemover, phrasingContentReducer, commentRemover ] ); - HTML = removeInvalidHTML( HTML, getPhrasingContentSchema(), { inline: true } ); + HTML = removeInvalidHTML( HTML, getPhrasingContentSchema( 'paste' ), { inline: true } ); + HTML = deepFilterHTML( HTML, [ htmlFormattingRemover, brRemover ] ); // Allows us to ask for this information when we get a report. console.log( 'Processed inline HTML:\n\n', HTML ); @@ -127,7 +131,10 @@ function htmlToBlocks( { html, rawTransforms } ) { */ export function pasteHandler( { HTML = '', plainText = '', mode = 'AUTO', tagName, canUserUseUnfilteredHTML = false } ) { // First of all, strip any meta tags. - HTML = HTML.replace( /<meta[^>]+>/, '' ); + HTML = HTML.replace( /<meta[^>]+>/g, '' ); + // Strip Windows markers. + HTML = HTML.replace( /^\s*<html[^>]*>\s*<body[^>]*>(?:\s*<!--\s*StartFragment\s*-->)?/i, '' ); + HTML = HTML.replace( /(?:<!--\s*EndFragment\s*-->\s*)?<\/body>\s*<\/html>\s*$/i, '' ); // If we detect block delimiters in HTML, parse entirely as blocks. if ( mode !== 'INLINE' ) { @@ -189,8 +196,8 @@ export function pasteHandler( { HTML = '', plainText = '', mode = 'AUTO', tagNam } const rawTransforms = getRawTransformations(); - const phrasingContentSchema = getPhrasingContentSchema(); - const blockContentSchema = getBlockContentSchema( rawTransforms ); + const phrasingContentSchema = getPhrasingContentSchema( 'paste' ); + const blockContentSchema = getBlockContentSchema( rawTransforms, phrasingContentSchema ); const blocks = compact( flatMap( pieces, ( piece ) => { // Already a block from shortcode. @@ -225,6 +232,11 @@ export function pasteHandler( { HTML = '', plainText = '', mode = 'AUTO', tagNam piece = deepFilterHTML( piece, filters, blockContentSchema ); piece = removeInvalidHTML( piece, schema ); piece = normaliseBlocks( piece ); + piece = deepFilterHTML( piece, [ + htmlFormattingRemover, + brRemover, + emptyParagraphRemover, + ], blockContentSchema ); // Allows us to ask for this information when we get a report. console.log( 'Processed HTML piece:\n\n', piece ); diff --git a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js index 5c5ef223298fe6..4f3c3a2a810a99 100644 --- a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js +++ b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { includes } from 'lodash'; + /** * WordPress dependencies */ @@ -11,6 +16,7 @@ export default function( node, doc ) { fontWeight, fontStyle, textDecorationLine, + textDecoration, verticalAlign, } = node.style; @@ -22,7 +28,10 @@ export default function( node, doc ) { wrap( doc.createElement( 'em' ), node ); } - if ( textDecorationLine === 'line-through' ) { + // Some DOM implementations (Safari, JSDom) don't support + // style.textDecorationLine, so we check style.textDecoration as a + // fallback. + if ( textDecorationLine === 'line-through' || includes( textDecoration, 'line-through' ) ) { wrap( doc.createElement( 's' ), node ); } diff --git a/packages/blocks/src/api/raw-handling/phrasing-content.js b/packages/blocks/src/api/raw-handling/phrasing-content.js index 028c61e9b89581..6ad617ce3fb6f8 100644 --- a/packages/blocks/src/api/raw-handling/phrasing-content.js +++ b/packages/blocks/src/api/raw-handling/phrasing-content.js @@ -1,8 +1,13 @@ /** * External dependencies */ -import { omit } from 'lodash'; +import { omit, without } from 'lodash'; +/** + * All text-level semantic elements. + * + * @see https://html.spec.whatwg.org/multipage/text-level-semantics.html + */ const phrasingContentSchema = { strong: {}, em: {}, @@ -15,13 +20,33 @@ const phrasingContentSchema = { sub: {}, sup: {}, br: {}, + small: {}, + // To do: fix blockquote. + // cite: {}, + q: { attributes: [ 'cite' ] }, + dfn: { attributes: [ 'title' ] }, + data: { attributes: [ 'value' ] }, + time: { attributes: [ 'datetime' ] }, + var: {}, + samp: {}, + kbd: {}, + i: {}, + b: {}, + u: {}, + mark: {}, + ruby: {}, + rt: {}, + rp: {}, + bdi: { attributes: [ 'dir' ] }, + bdo: { attributes: [ 'dir' ] }, + wbr: {}, '#text': {}, }; // Recursion is needed. // Possible: strong > em > strong. // Impossible: strong > strong. -[ 'strong', 'em', 's', 'del', 'ins', 'a', 'code', 'abbr', 'sub', 'sup' ].forEach( ( tag ) => { +without( Object.keys( phrasingContentSchema ), '#text', 'br' ).forEach( ( tag ) => { phrasingContentSchema[ tag ].children = omit( phrasingContentSchema, tag ); } ); @@ -30,10 +55,31 @@ const phrasingContentSchema = { * * @see https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Phrasing_content * + * @param {string} context Set to "paste" to exclude invisible elements and + * sensitive data. + * * @return {Object} Schema. */ -export function getPhrasingContentSchema() { - return phrasingContentSchema; +export function getPhrasingContentSchema( context ) { + if ( context !== 'paste' ) { + return phrasingContentSchema; + } + + return omit( { + ...phrasingContentSchema, + // We shouldn't paste potentially sensitive information which is not + // visible to the user when pasted, so strip the attributes. + ins: { children: phrasingContentSchema.ins.children }, + del: { children: phrasingContentSchema.del.children }, + }, [ + 'u', // Used to mark misspelling. Shouldn't be pasted. + 'abbr', // Invisible. + 'data', // Invisible. + 'time', // Invisible. + 'wbr', // Invisible. + 'bdi', // Invisible. + 'bdo', // Invisible. + ] ); } /** diff --git a/packages/blocks/src/api/raw-handling/shortcode-converter.js b/packages/blocks/src/api/raw-handling/shortcode-converter.js index 5385f0aed41502..807b90e279f79f 100644 --- a/packages/blocks/src/api/raw-handling/shortcode-converter.js +++ b/packages/blocks/src/api/raw-handling/shortcode-converter.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { some, castArray, first, mapValues, pickBy, includes } from 'lodash'; +import { some, castArray, find, mapValues, pickBy, includes } from 'lodash'; /** * WordPress dependencies @@ -15,11 +15,12 @@ import { createBlock, getBlockTransforms, findTransform } from '../factory'; import { getBlockType } from '../registration'; import { getBlockAttributes } from '../parser'; -function segmentHTMLToShortcodeBlock( HTML, lastIndex = 0 ) { +function segmentHTMLToShortcodeBlock( HTML, lastIndex = 0, excludedBlockNames = [] ) { // Get all matches. const transformsFrom = getBlockTransforms( 'from' ); const transformation = findTransform( transformsFrom, ( transform ) => ( + excludedBlockNames.indexOf( transform.blockName ) === -1 && transform.type === 'shortcode' && some( castArray( transform.tag ), ( tag ) => regexp( tag ).test( HTML ) ) ) ); @@ -29,9 +30,10 @@ function segmentHTMLToShortcodeBlock( HTML, lastIndex = 0 ) { } const transformTags = castArray( transformation.tag ); - const transformTag = first( transformTags ); + const transformTag = find( transformTags, ( tag ) => regexp( tag ).test( HTML ) ); let match; + const previousIndex = lastIndex; if ( ( match = next( transformTag, HTML, lastIndex ) ) ) { const beforeHTML = HTML.substr( 0, match.index ); @@ -49,6 +51,22 @@ function segmentHTMLToShortcodeBlock( HTML, lastIndex = 0 ) { return segmentHTMLToShortcodeBlock( HTML, lastIndex ); } + // If a transformation's `isMatch` predicate fails for the inbound + // shortcode, try again by excluding the current block type. + // + // This is the only call to `segmentHTMLToShortcodeBlock` that should + // ever carry over `excludedBlockNames`. Other calls in the module + // should skip that argument as a way to reset the exclusion state, so + // that one `isMatch` fail in an HTML fragment doesn't prevent any + // valid matches in subsequent fragments. + if ( transformation.isMatch && ! transformation.isMatch( match.shortcode.attrs ) ) { + return segmentHTMLToShortcodeBlock( + HTML, + previousIndex, + [ ...excludedBlockNames, transformation.blockName ], + ); + } + const attributes = mapValues( pickBy( transformation.attributes, ( schema ) => schema.shortcode ), // Passing all of `match` as second argument is intentionally broad diff --git a/packages/blocks/src/api/raw-handling/test/html-formatting-remover.js b/packages/blocks/src/api/raw-handling/test/html-formatting-remover.js new file mode 100644 index 00000000000000..d5efd50f4db857 --- /dev/null +++ b/packages/blocks/src/api/raw-handling/test/html-formatting-remover.js @@ -0,0 +1,110 @@ +/** + * Internal dependencies + */ +import filter from '../html-formatting-remover'; +import { deepFilterHTML } from '../utils'; + +describe( 'HTMLFormattingRemover', () => { + it( 'should trim text node without parent', () => { + const input = 'a'; + expect( deepFilterHTML( input, [ filter ] ) ).toEqual( input ); + } ); + + it( 'should remove formatting space', () => { + const input = ` + <div> + a + b + </div> + `; + const output = '<div>a b</div>'; + expect( deepFilterHTML( input, [ filter ] ) ).toEqual( output ); + } ); + + it( 'should remove nested formatting space', () => { + const input = ` + <div> + <strong> + a + b + </strong> + </div> + `; + const output = '<div><strong>a b</strong></div>'; + expect( deepFilterHTML( input, [ filter ] ) ).toEqual( output ); + } ); + + it( 'should not remove leading or trailing space if previous or next element has no space', () => { + const input = ` + <div> + a + <strong>b</strong> + c + </div> + `; + const output = '<div>a <strong>b</strong> c</div>'; + expect( deepFilterHTML( input, [ filter ] ) ).toEqual( output ); + } ); + + it( 'should remove formatting space (empty)', () => { + const input = ` + <div> + </div> + `; + const output = '<div></div>'; + expect( deepFilterHTML( input, [ filter ] ) ).toEqual( output ); + } ); + + it( 'should remove block level formatting space', () => { + const input = ` + <div> + <div> + a + </div> + <div> + b + </div> + </div> + `; + const output = '<div><div>a</div><div>b</div></div>'; + expect( deepFilterHTML( input, [ filter ] ) ).toEqual( output ); + } ); + + it( 'should remove formatting space around br', () => { + const input = ` + <div> + a + <br> + b + </div> + `; + const output = '<div>a<br>b</div>'; + expect( deepFilterHTML( input, [ filter ] ) ).toEqual( output ); + } ); + + it( 'should remove formatting space around phasing content elements', () => { + const input = ` + <div> + <strong> + a + </strong> + <strong> + b + </strong> + </div> + `; + const output = '<div><strong>a</strong> <strong>b</strong></div>'; + expect( deepFilterHTML( input, [ filter ] ) ).toEqual( output ); + } ); + + it( 'should ignore pre', () => { + const input = `<pre> a\n b\n</pre>`; + expect( deepFilterHTML( input, [ filter ] ) ).toEqual( input ); + } ); + + it( 'should not remove white space if next elemnt has none', () => { + const input = `<div><strong>a </strong>b</div>`; + const output = '<div><strong>a </strong>b</div>'; + expect( deepFilterHTML( input, [ filter ] ) ).toEqual( output ); + } ); +} ); diff --git a/packages/blocks/src/api/raw-handling/utils.js b/packages/blocks/src/api/raw-handling/utils.js index 896bb6b8d4a5e0..20ed5a1e9dec31 100644 --- a/packages/blocks/src/api/raw-handling/utils.js +++ b/packages/blocks/src/api/raw-handling/utils.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { mapValues, mergeWith, includes, noop } from 'lodash'; +import { mapValues, mergeWith, includes, noop, isFunction } from 'lodash'; /** * WordPress dependencies @@ -22,13 +22,17 @@ const { ELEMENT_NODE, TEXT_NODE } = window.Node; /** * Given raw transforms from blocks, merges all schemas into one. * - * @param {Array} transforms Block transforms, of the `raw` type. + * @param {Array} transforms Block transforms, of the `raw` type. + * @param {Object} phrasingContentSchema The phrasing content schema. * * @return {Object} A complete block content schema. */ -export function getBlockContentSchema( transforms ) { +export function getBlockContentSchema( transforms, phrasingContentSchema ) { const schemas = transforms.map( ( { isMatch, blockName, schema } ) => { const hasAnchorSupport = hasBlockSupport( blockName, 'anchor' ); + + schema = isFunction( schema ) ? schema( { phrasingContentSchema } ) : schema; + // If the block does not has anchor support and the transform does not // provides an isMatch we can return the schema right away. if ( ! hasAnchorSupport && ! isMatch ) { @@ -303,3 +307,25 @@ export function removeInvalidHTML( HTML, schema, inline ) { return doc.body.innerHTML; } + +/** + * Gets a sibling within text-level context. + * + * @param {Element} node The subject node. + * @param {string} which "next" or "previous". + */ +export function getSibling( node, which ) { + const sibling = node[ `${ which }Sibling` ]; + + if ( sibling && isPhrasingContent( sibling ) ) { + return sibling; + } + + const { parentNode } = node; + + if ( ! parentNode || ! isPhrasingContent( parentNode ) ) { + return; + } + + return getSibling( parentNode, which ); +} diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 024f9e9d55a3e3..92e4472eb9578b 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -25,14 +25,21 @@ import { isValidIcon, normalizeIconObject } from './utils'; import { DEPRECATED_ENTRY_KEYS } from './constants'; /** - * Render behavior of a block type icon; one of a Dashicon slug, an element, + * An icon type definition. One of a Dashicon slug, an element, * or a component. * - * @typedef {(string|WPElement|WPComponent)} WPBlockTypeIconRender + * @typedef {(string|WPElement|WPComponent)} WPIcon * * @see https://developer.wordpress.org/resource/dashicons/ */ +/** + * Render behavior of a block type icon; one of a Dashicon slug, an element, + * or a component. + * + * @typedef {WPIcon} WPBlockTypeIconRender + */ + /** * An object describing a normalized block type icon. * @@ -58,25 +65,40 @@ import { DEPRECATED_ENTRY_KEYS } from './constants'; */ /** - * Defined behavior of a block type. + * An object describing a pattern defined for the block type. * - * @typedef {Object} WPBlockType + * @typedef {Object} WPBlockPattern * - * @property {string} name Block type's namespaced name. - * @property {string} title Human-readable block type label. - * @property {string} category Block type category classification, - * used in search interfaces to arrange - * block types by category. - * @property {WPBlockTypeIcon} [icon] Block type icon. - * @property {string[]} [keywords] Additional keywords to produce block - * type as result in search interfaces. - * @property {Object} [attributes] Block type attributes. - * @property {WPComponent} [save] Optional component describing - * serialized markup structure of a - * block type. - * @property {WPComponent} edit Component rendering an element to - * manipulate the attributes of a block - * in the context of an editor. + * @property {string} name The unique and machine-readable name. + * @property {string} label A human-readable label. + * @property {WPIcon} [icon] An icon helping to visualize the pattern. + * @property {boolean} [isDefault] Indicates whether the current pattern is the default one. + * Defaults to `false`. + * @property {Object} [attributes] Values which override block attributes. + * @property {Array[]} [innerBlocks] Initial configuration of nested blocks. + */ + +/** + * Defined behavior of a block type. + * + * @typedef {Object} WPBlock + * + * @property {string} name Block type's namespaced name. + * @property {string} title Human-readable block type label. + * @property {string} category Block type category classification, + * used in search interfaces to arrange + * block types by category. + * @property {WPBlockTypeIcon} [icon] Block type icon. + * @property {string[]} [keywords] Additional keywords to produce block + * type as result in search interfaces. + * @property {Object} [attributes] Block type attributes. + * @property {WPComponent} [save] Optional component describing + * serialized markup structure of a + * block type. + * @property {WPComponent} edit Component rendering an element to + * manipulate the attributes of a block + * in the context of an editor. + * @property {WPBlockPattern[]} [patterns] The list of block patterns. */ /** @@ -114,7 +136,7 @@ export function unstable__bootstrapServerSideBlockDefinitions( definitions ) { / * @param {Object} settings Block settings. * * @return {?WPBlock} The block, if it has been successfully registered; - * otherwise `undefined`. + * otherwise `undefined`. */ export function registerBlockType( name, settings ) { settings = { @@ -234,7 +256,7 @@ export function registerBlockType( name, settings ) { * @param {string} name Block name. * * @return {?WPBlock} The previous block value, if it has been successfully - * unregistered; otherwise `undefined`. + * unregistered; otherwise `undefined`. */ export function unregisterBlockType( name ) { const oldBlock = select( 'core/blocks' ).getBlockType( name ); @@ -436,3 +458,23 @@ export const registerBlockStyle = ( blockName, styleVariation ) => { export const unregisterBlockStyle = ( blockName, styleVariationName ) => { dispatch( 'core/blocks' ).removeBlockStyles( blockName, styleVariationName ); }; + +/** + * Registers a new block pattern for the given block. + * + * @param {string} blockName Name of the block (example: “core/columns”). + * @param {WPBlockPattern} pattern Object describing a block pattern. + */ +export const __experimentalRegisterBlockPattern = ( blockName, pattern ) => { + dispatch( 'core/blocks' ).__experimentalAddBlockPatterns( blockName, pattern ); +}; + +/** + * Unregisters a block pattern defined for the given block. + * + * @param {string} blockName Name of the block (example: “core/columns”). + * @param {string} patternName Name of the pattern defined for the block. + */ +export const __experimentalUnregisterBlockPattern = ( blockName, patternName ) => { + dispatch( 'core/blocks' ).__experimentalRemoveBlockPatterns( blockName, patternName ); +}; diff --git a/packages/blocks/src/api/serializer.js b/packages/blocks/src/api/serializer.js index 1776a32e362999..d2dfbcab2d672b 100644 --- a/packages/blocks/src/api/serializer.js +++ b/packages/blocks/src/api/serializer.js @@ -85,9 +85,9 @@ export function getSaveElement( blockTypeOrName, attributes, innerBlocks = [] ) /** * Filters the props applied to the block save result element. * - * @param {Object} props Props applied to save element. - * @param {WPBlockType} blockType Block type definition. - * @param {Object} attributes Block attributes. + * @param {Object} props Props applied to save element. + * @param {WPBlock} blockType Block type definition. + * @param {Object} attributes Block attributes. */ const props = applyFilters( 'blocks.getSaveContent.extraProps', @@ -104,9 +104,9 @@ export function getSaveElement( blockTypeOrName, attributes, innerBlocks = [] ) /** * Filters the save result of a block during serialization. * - * @param {WPElement} element Block save result. - * @param {WPBlockType} blockType Block type definition. - * @param {Object} attributes Block attributes. + * @param {WPElement} element Block save result. + * @param {WPBlock} blockType Block type definition. + * @param {Object} attributes Block attributes. */ element = applyFilters( 'blocks.getSaveElement', element, blockType, attributes ); @@ -150,28 +150,27 @@ export function getSaveContent( blockTypeOrName, attributes, innerBlocks ) { * @return {Object<string,*>} Subset of attributes for comment serialization. */ export function getCommentAttributes( blockType, attributes ) { - return reduce( blockType.attributes, ( result, attributeSchema, key ) => { + return reduce( blockType.attributes, ( accumulator, attributeSchema, key ) => { const value = attributes[ key ]; - // Ignore undefined values. if ( undefined === value ) { - return result; + return accumulator; } // Ignore all attributes but the ones with an "undefined" source // "undefined" source refers to attributes saved in the block comment. if ( attributeSchema.source !== undefined ) { - return result; + return accumulator; } // Ignore default value. if ( 'default' in attributeSchema && attributeSchema.default === value ) { - return result; + return accumulator; } // Otherwise, include in comment set. - result[ key ] = value; - return result; + accumulator[ key ] = value; + return accumulator; }, {} ); } diff --git a/packages/blocks/src/block-content-provider/index.js b/packages/blocks/src/block-content-provider/index.js index 352550a87be80f..96b9a4d31bcd1b 100644 --- a/packages/blocks/src/block-content-provider/index.js +++ b/packages/blocks/src/block-content-provider/index.js @@ -26,7 +26,7 @@ const { Consumer, Provider } = createContext( () => {} ); * </BlockContentProvider> * ``` * - * @return {WPElement} Element with BlockContent injected via context. + * @return {WPComponent} Element with BlockContent injected via context. */ const BlockContentProvider = ( { children, innerBlocks } ) => { const BlockContent = () => { @@ -48,7 +48,7 @@ const BlockContentProvider = ( { children, innerBlocks } ) => { * A Higher Order Component used to inject BlockContent using context to the * wrapped component. * - * @return {Component} Enhanced component with injected BlockContent as prop. + * @return {WPComponent} Enhanced component with injected BlockContent as prop. */ export const withBlockContentContext = createHigherOrderComponent( ( OriginalComponent ) => { return ( props ) => ( diff --git a/packages/blocks/src/store/actions.js b/packages/blocks/src/store/actions.js index 220ae9be0ea0d3..c7b6ae42cb81b9 100644 --- a/packages/blocks/src/store/actions.js +++ b/packages/blocks/src/store/actions.js @@ -63,6 +63,38 @@ export function removeBlockStyles( blockName, styleNames ) { }; } +/** + * Returns an action object used in signalling that new block patterns have been added. + * + * @param {string} blockName Block name. + * @param {WPBlockPattern|WPBlockPattern[]} patterns Block patterns. + * + * @return {Object} Action object. + */ +export function __experimentalAddBlockPatterns( blockName, patterns ) { + return { + type: 'ADD_BLOCK_PATTERNS', + patterns: castArray( patterns ), + blockName, + }; +} + +/** + * Returns an action object used in signalling that block patterns have been removed. + * + * @param {string} blockName Block name. + * @param {string|string[]} patternNames Block pattern names. + * + * @return {Object} Action object. + */ +export function __experimentalRemoveBlockPatterns( blockName, patternNames ) { + return { + type: 'REMOVE_BLOCK_PATTERNS', + patternNames: castArray( patternNames ), + blockName, + }; +} + /** * Returns an action object used to set the default block name. * diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index 405e60109adda3..3b8534a99fdf3f 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -97,6 +97,47 @@ export function blockStyles( state = {}, action ) { return state; } +/** + * Reducer managing the block patterns. + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +export function blockPatterns( state = {}, action ) { + switch ( action.type ) { + case 'ADD_BLOCK_TYPES': + return { + ...state, + ...mapValues( keyBy( action.blockTypes, 'name' ), ( blockType ) => { + return uniqBy( [ + ...get( blockType, [ 'patterns' ], [] ), + ...get( state, [ blockType.name ], [] ), + ], ( pattern ) => pattern.name ); + } ), + }; + case 'ADD_BLOCK_PATTERNS': + return { + ...state, + [ action.blockName ]: uniqBy( [ + ...get( state, [ action.blockName ], [] ), + ...( action.patterns ), + ], ( pattern ) => pattern.name ), + }; + case 'REMOVE_BLOCK_PATTERNS': + return { + ...state, + [ action.blockName ]: filter( + get( state, [ action.blockName ], [] ), + ( pattern ) => action.patternNames.indexOf( pattern.name ) === -1, + ), + }; + } + + return state; +} + /** * Higher-order Reducer creating a reducer keeping track of given block name. * @@ -162,6 +203,7 @@ export function categories( state = DEFAULT_CATEGORIES, action ) { export default combineReducers( { blockTypes, blockStyles, + blockPatterns, defaultBlockName, freeformFallbackBlockName, unregisteredFallbackBlockName, diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index cb9333e3f6ff12..8e07052ad55844 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -2,7 +2,17 @@ * External dependencies */ import createSelector from 'rememo'; -import { filter, get, includes, map, some, flow, deburr } from 'lodash'; +import { + deburr, + filter, + findLast, + first, + flow, + get, + includes, + map, + some, +} from 'lodash'; /** * Given a block name or block type object, returns the corresponding @@ -57,6 +67,35 @@ export function getBlockStyles( state, name ) { return state.blockStyles[ name ]; } +/** + * Returns block patterns by block name. + * + * @param {Object} state Data state. + * @param {string} blockName Block type name. + * + * @return {(WPBlockPattern[]|void)} Block patterns. + */ +export function __experimentalGetBlockPatterns( state, blockName ) { + return state.blockPatterns[ blockName ]; +} + +/** + * Returns the default block pattern for the given block type. + * When there are multiple patterns annotated as the default one, + * the last added item is picked. This simplifies registering overrides. + * When there is no default pattern set, it returns the first item. + * + * @param {Object} state Data state. + * @param {string} blockName Block type name. + * + * @return {?WPBlockPattern} The default block pattern. + */ +export function __experimentalGetDefaultBlockPattern( state, blockName ) { + const patterns = __experimentalGetBlockPatterns( state, blockName ); + + return findLast( patterns, 'isDefault' ) || first( patterns ); +} + /** * Returns all the available categories. * diff --git a/packages/blocks/src/store/test/actions.js b/packages/blocks/src/store/test/actions.js new file mode 100644 index 00000000000000..9e537432f406ab --- /dev/null +++ b/packages/blocks/src/store/test/actions.js @@ -0,0 +1,43 @@ +/** + * Internal dependencies + */ +import { + __experimentalAddBlockPatterns, + __experimentalRemoveBlockPatterns, +} from '../actions'; + +describe( 'actions', () => { + describe( 'addBlockPatterns', () => { + const blockName = 'block/name'; + const patternName = 'my-pattern'; + + it( 'should return the ADD_BLOCK_PATTERNS action', () => { + const pattern = { + name: patternName, + label: 'My Pattern', + attributes: { + example: 'foo', + }, + }; + const result = __experimentalAddBlockPatterns( blockName, pattern ); + expect( result ).toEqual( { + type: 'ADD_BLOCK_PATTERNS', + patterns: [ + pattern, + ], + blockName, + } ); + } ); + + it( 'should return the REMOVE_BLOCK_PATTERNS action', () => { + const result = __experimentalRemoveBlockPatterns( blockName, patternName ); + expect( result ).toEqual( { + type: 'REMOVE_BLOCK_PATTERNS', + patternNames: [ + patternName, + ], + blockName, + } ); + } ); + } ); +} ); diff --git a/packages/blocks/src/store/test/reducer.js b/packages/blocks/src/store/test/reducer.js index 346bb2e9aab1bf..2567903962d14f 100644 --- a/packages/blocks/src/store/test/reducer.js +++ b/packages/blocks/src/store/test/reducer.js @@ -7,6 +7,12 @@ import deepFreeze from 'deep-freeze'; * Internal dependencies */ import { + __experimentalAddBlockPatterns, + addBlockTypes, + __experimentalRemoveBlockPatterns, +} from '../actions'; +import { + blockPatterns, blockStyles, blockTypes, categories, @@ -136,6 +142,108 @@ describe( 'blockStyles', () => { } ); } ); +describe( 'blockPatterns', () => { + const blockName = 'block/name'; + + const blockPatternName = 'pattern-name'; + const blockPattern = { + name: blockPatternName, + label: 'My pattern', + }; + + const secondBlockPatternName = 'second-pattern-name'; + const secondBlockPattern = { + name: secondBlockPatternName, + label: 'My Second Pattern', + }; + + it( 'should return an empty object as default state', () => { + const state = blockPatterns( undefined, {} ); + + expect( state ).toEqual( {} ); + } ); + + it( 'should add a new block pattern when no pattern register', () => { + const initialState = deepFreeze( {} ); + + const state = blockPatterns( + initialState, + __experimentalAddBlockPatterns( blockName, blockPattern ) + ); + + expect( state ).toEqual( { + [ blockName ]: [ + blockPattern, + ], + } ); + } ); + + it( 'should add another pattern when a block pattern already present for the block', () => { + const initialState = deepFreeze( { + [ blockName ]: [ + blockPattern, + ], + } ); + + const state = blockPatterns( + initialState, + __experimentalAddBlockPatterns( blockName, secondBlockPattern ), + ); + + expect( state ).toEqual( { + [ blockName ]: [ + blockPattern, + secondBlockPattern, + ], + } ); + } ); + + it( 'should prepend block patterns added when adding a block', () => { + const initialState = deepFreeze( { + [ blockName ]: [ + secondBlockPattern, + ], + } ); + + const state = blockPatterns( + initialState, + addBlockTypes( { + name: blockName, + patterns: [ + blockPattern, + ], + } ) + ); + + expect( state ).toEqual( { + [ blockName ]: [ + blockPattern, + secondBlockPattern, + ], + } ); + } ); + + it( 'should remove a block pattern', () => { + const initialState = deepFreeze( { + [ blockName ]: [ + blockPattern, + secondBlockPattern, + ], + } ); + + const state = blockPatterns( + initialState, + __experimentalRemoveBlockPatterns( blockName, blockPatternName ) + ); + + expect( state ).toEqual( { + [ blockName ]: [ + secondBlockPattern, + ], + } ); + } ); +} ); + describe( 'defaultBlockName', () => { it( 'should return null as default state', () => { expect( defaultBlockName( undefined, {} ) ).toBeNull(); diff --git a/packages/blocks/src/store/test/selectors.js b/packages/blocks/src/store/test/selectors.js index 05fc5a23040a85..a7833648bda515 100644 --- a/packages/blocks/src/store/test/selectors.js +++ b/packages/blocks/src/store/test/selectors.js @@ -1,10 +1,16 @@ +/** + * External dependencies + */ +import deepFreeze from 'deep-freeze'; + /** * Internal dependencies */ import { getChildBlockNames, - isMatchingSearchTerm, + __experimentalGetDefaultBlockPattern, getGroupingBlockName, + isMatchingSearchTerm, } from '../selectors'; describe( 'selectors', () => { @@ -139,6 +145,76 @@ describe( 'selectors', () => { } ); } ); + describe( '__experimentalGetDefaultBlockPattern', () => { + const blockName = 'block/name'; + const createBlockPatternsState = ( patterns ) => { + return deepFreeze( { + blockPatterns: { + [ blockName ]: patterns, + }, + } ); + }; + const firstBlockPattern = { + name: 'first-block-pattern', + }; + const secondBlockPattern = { + name: 'second-block-pattern', + }; + const thirdBlockPattern = { + name: 'third-block-pattern', + }; + + it( 'should return the default pattern when set', () => { + const defaultBlockPattern = { + ...secondBlockPattern, + isDefault: true, + }; + const state = createBlockPatternsState( [ + firstBlockPattern, + defaultBlockPattern, + thirdBlockPattern, + ] ); + + const result = __experimentalGetDefaultBlockPattern( state, blockName ); + + expect( result ).toEqual( defaultBlockPattern ); + } ); + + it( 'should return the last pattern when multiple default patterns added', () => { + const defaultBlockPattern = { + ...thirdBlockPattern, + isDefault: true, + }; + const state = createBlockPatternsState( [ + { + ...firstBlockPattern, + isDefault: true, + }, + { + ...secondBlockPattern, + isDefault: true, + }, + defaultBlockPattern, + ] ); + + const result = __experimentalGetDefaultBlockPattern( state, blockName ); + + expect( result ).toEqual( defaultBlockPattern ); + } ); + + it( 'should return the first pattern when no default pattern set', () => { + const state = createBlockPatternsState( [ + firstBlockPattern, + secondBlockPattern, + thirdBlockPattern, + ] ); + + const result = __experimentalGetDefaultBlockPattern( state, blockName ); + + expect( result ).toEqual( firstBlockPattern ); + } ); + } ); + describe( 'isMatchingSearchTerm', () => { const name = 'core/paragraph'; const blockType = { diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 00cd959951484e..28dd4971244937 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -1,3 +1,9 @@ +# 8.3.0 (Unreleased) + +### New Features + +- Added a new `Guide` component which allows developers to easily present a user guide. + ## 8.2.0 (2019-08-29) ### New Features diff --git a/packages/components/package.json b/packages/components/package.json index d19667eca46cf5..ce48be95d2aae5 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/components", - "version": "8.3.0", + "version": "8.4.0", "description": "UI components for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -20,8 +20,11 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "sideEffects": false, "dependencies": { "@babel/runtime": "^7.4.4", + "@emotion/core": "10.0.22", + "@emotion/styled": "10.0.23", "@wordpress/a11y": "file:../a11y", "@wordpress/compose": "file:../compose", "@wordpress/deprecated": "file:../deprecated", @@ -35,6 +38,8 @@ "classnames": "^2.2.5", "clipboard": "^2.0.1", "dom-scroll-into-view": "^1.2.1", + "downshift": "^3.3.4", + "gradient-parser": "^0.1.5", "lodash": "^4.17.15", "memize": "^1.0.5", "moment": "^2.22.1", @@ -42,6 +47,7 @@ "re-resizable": "^6.0.0", "react-dates": "^17.1.1", "react-spring": "^8.0.20", + "reakit": "^1.0.0-beta.12", "rememo": "^3.0.0", "tinycolor2": "^1.4.1", "uuid": "^3.3.2" diff --git a/packages/components/src/animate/stories/index.js b/packages/components/src/animate/stories/index.js new file mode 100644 index 00000000000000..bc83faee7753d4 --- /dev/null +++ b/packages/components/src/animate/stories/index.js @@ -0,0 +1,56 @@ +/** + * Internal dependencies + */ +import Animate from '../'; +import Notice from '../../notice'; + +export default { title: 'Components|Animate', component: Animate }; + +export const _default = () => ( + <Animate> + { ( { className } ) => ( + <Notice className={ className } status="success"> + <p>{ `No default animation. Use one of type = "appear", "slide-in", or "loading".` }</p> + </Notice> + ) } + </Animate> +); + +// Unexported helper for different origins. +const Appear = ( { origin } ) => ( + <Animate + type="appear" + options={ { origin } } + > + { ( { className } ) => ( + <Notice className={ className } status="success"> + <p>Appear animation. Origin: { origin }.</p> + </Notice> + ) } + </Animate> +); + +export const appearTopLeft = () => <Appear origin="top left" />; +export const appearTopRight = () => <Appear origin="top right" />; +export const appearBottomLeft = () => <Appear origin="bottom left" />; +export const appearBottomRight = () => <Appear origin="bottom right" />; + +export const loading = () => ( + <Animate type="loading"> + { ( { className } ) => ( + <Notice className={ className } status="success"> + <p>Loading animation.</p> + </Notice> + ) } + </Animate> +); + +export const slideIn = () => ( + <Animate type="slide-in"> + { ( { className } ) => ( + <Notice className={ className } status="success"> + <p>Slide-in animation.</p> + </Notice> + ) } + </Animate> +); diff --git a/packages/components/src/autocomplete/index.js b/packages/components/src/autocomplete/index.js index 283ccd92633b04..b95bdf641252c3 100644 --- a/packages/components/src/autocomplete/index.js +++ b/packages/components/src/autocomplete/index.js @@ -18,7 +18,6 @@ import { isCollapsed, getTextContent, } from '@wordpress/rich-text'; -import { getRectangleFromRange } from '@wordpress/dom'; /** * Internal dependencies @@ -91,7 +90,7 @@ import withSpokenMessages from '../higher-order/with-spoken-messages'; */ /** - * @typedef {Object} Completer + * @typedef {Object} WPCompleter * @property {string} name a way to identify a completer, useful for selective overriding. * @property {?string} className A class to apply to the popup menu. * @property {string} triggerPrefix the prefix that will display the menu. @@ -130,13 +129,9 @@ function filterOptions( search, options = [], maxResults = 10 ) { return filtered; } -function getCaretRect() { +function getRange() { const selection = window.getSelection(); - const range = selection.rangeCount ? selection.getRangeAt( 0 ) : null; - - if ( range ) { - return getRectangleFromRange( range ); - } + return selection.rangeCount ? selection.getRangeAt( 0 ) : null; } export class Autocomplete extends Component { @@ -232,8 +227,8 @@ export class Autocomplete extends Component { /** * Load options for an autocompleter. * - * @param {Completer} completer The autocompleter. - * @param {string} query The query, if any. + * @param {WPCompleter} completer The autocompleter. + * @param {string} query The query, if any. */ loadOptions( completer, query ) { const { options } = completer; @@ -426,7 +421,7 @@ export class Autocomplete extends Component { onClose={ this.reset } position="top right" className="components-autocomplete__popover" - getAnchorRect={ getCaretRect } + anchorRef={ getRange() } > <div id={ listBoxId } diff --git a/packages/components/src/base-control/index.js b/packages/components/src/base-control/index.js index 3abe25ee062823..689cc98f424205 100644 --- a/packages/components/src/base-control/index.js +++ b/packages/components/src/base-control/index.js @@ -3,15 +3,28 @@ */ import classnames from 'classnames'; +/** + * Internal dependencies + */ +import VisuallyHidden from '../visually-hidden'; + function BaseControl( { id, label, hideLabelFromVision, help, className, children } ) { return ( <div className={ classnames( 'components-base-control', className ) }> <div className="components-base-control__field"> - { label && id && <label - className={ classnames( 'components-base-control__label', { 'screen-reader-text': hideLabelFromVision } ) } - htmlFor={ id }>{ label } - </label> } - { label && ! id && <BaseControl.VisualLabel>{ label }</BaseControl.VisualLabel> } + { label && id && ( hideLabelFromVision ? + <VisuallyHidden + as="label" + htmlFor={ id }>{ label }</VisuallyHidden> : + <label + className="components-base-control__label" + htmlFor={ id }>{ label }</label> + ) } + { label && ! id && ( hideLabelFromVision ? + <VisuallyHidden + as="label">{ label }</VisuallyHidden> : + <BaseControl.VisualLabel>{ label }</BaseControl.VisualLabel> + ) } { children } </div> { !! help && <p id={ id + '__help' } className="components-base-control__help">{ help }</p> } diff --git a/packages/components/src/base-control/stories/index.js b/packages/components/src/base-control/stories/index.js new file mode 100644 index 00000000000000..88f229f14c9216 --- /dev/null +++ b/packages/components/src/base-control/stories/index.js @@ -0,0 +1,33 @@ +/** + * External dependencies + */ +import { boolean, text } from '@storybook/addon-knobs'; + +/** + * Internal dependencies + */ +import BaseControl from '../'; + +export default { title: 'Components|BaseControl', component: BaseControl }; + +export const _default = () => { + const id = text( 'Id', 'textarea-1' ); + const label = text( 'Label', 'Label text' ); + const hideLabelFromVision = boolean( 'Hide label from vision', false ); + const help = text( 'Help', 'Help text' ); + const className = text( 'ClassName', '' ); + + return ( + <BaseControl + id={ id } + label={ label } + help={ help } + hideLabelFromVision={ hideLabelFromVision } + className={ className } + > + <textarea + id={ id } + /> + </BaseControl> + ); +}; diff --git a/packages/components/src/button-group/stories/index.js b/packages/components/src/button-group/stories/index.js new file mode 100644 index 00000000000000..018e7b1782f5cf --- /dev/null +++ b/packages/components/src/button-group/stories/index.js @@ -0,0 +1,17 @@ +/** + * Internal dependencies + */ +import Button from '../../button'; +import ButtonGroup from '../'; + +export default { title: 'Components|ButtonGroup', component: ButtonGroup }; + +export const _default = () => { + const style = { margin: '0 4px' }; + return ( + <ButtonGroup> + <Button isPrimary style={ style }>Button 1</Button> + <Button isPrimary style={ style }>Button 2</Button> + </ButtonGroup> + ); +}; diff --git a/packages/components/src/button/README.md b/packages/components/src/button/README.md index 722740d443af54..284f1fba6b0bd2 100644 --- a/packages/components/src/button/README.md +++ b/packages/components/src/button/README.md @@ -134,7 +134,7 @@ Name | Type | Default | Description `isDestructive` | `bool` | `false` | Renders a red text-based button style to indicate destructive behavior. `isLarge` | `bool` | `false` | Increases the size of the button. `isSmall` | `bool` | `false` | Decreases the size of the button. -`isToggled` | `bool` | `false` | Renders a toggled button style. +`isPressed` | `bool` | `false` | Renders a pressed button style. `isBusy` | `bool` | `false` | Indicates activity while a action is being performed. `isLink` | `bool` | `false` | Renders a button with an anchor style. `focus` | `bool` | `false` | Whether the button is focused. diff --git a/packages/components/src/button/index.js b/packages/components/src/button/index.js index da3441e7f2982a..57de08de8bf79e 100644 --- a/packages/components/src/button/index.js +++ b/packages/components/src/button/index.js @@ -16,7 +16,7 @@ export function Button( props, ref ) { isLarge, isSmall, isTertiary, - isToggled, + isPressed, isBusy, isDefault, isLink, @@ -33,14 +33,16 @@ export function Button( props, ref ) { 'is-large': isLarge, 'is-small': isSmall, 'is-tertiary': isTertiary, - 'is-toggled': isToggled, + 'is-pressed': isPressed, 'is-busy': isBusy, 'is-link': isLink, 'is-destructive': isDestructive, } ); const tag = href !== undefined && ! disabled ? 'a' : 'button'; - const tagProps = tag === 'a' ? { href, target } : { type: 'button', disabled }; + const tagProps = tag === 'a' ? + { href, target } : + { type: 'button', disabled, 'aria-pressed': isPressed }; return createElement( tag, { ...tagProps, diff --git a/packages/components/src/button/index.native.js b/packages/components/src/button/index.native.js index c4a48c84eb4ff3..8d4ac26995c797 100644 --- a/packages/components/src/button/index.native.js +++ b/packages/components/src/button/index.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { StyleSheet, TouchableOpacity, Text, View, Platform } from 'react-native'; -import { compact } from 'lodash'; /** * WordPress dependencies @@ -68,9 +67,9 @@ export function Button( props ) { hint, fixedRatio = true, getStylesFromColorScheme, + isPressed, 'aria-disabled': ariaDisabled, 'aria-label': ariaLabel, - 'aria-pressed': ariaPressed, 'data-subscript': subscript, } = props; @@ -79,11 +78,11 @@ export function Button( props ) { const buttonViewStyle = { opacity: isDisabled ? 0.3 : 1, ...( fixedRatio && styles.fixedRatio ), - ...( ariaPressed ? styles.buttonActive : styles.buttonInactive ), + ...( isPressed ? styles.buttonActive : styles.buttonInactive ), }; const states = []; - if ( ariaPressed ) { + if ( isPressed ) { states.push( 'selected' ); } @@ -93,8 +92,8 @@ export function Button( props ) { const subscriptInactive = getStylesFromColorScheme( styles.subscriptInactive, styles.subscriptInactiveDark ); - const newChildren = Children.map( compact( children ), ( child ) => { - return cloneElement( child, { colorScheme: props.preferredColorScheme, active: ariaPressed } ); + const newChildren = Children.map( children, ( child ) => { + return child ? cloneElement( child, { colorScheme: props.preferredColorScheme, isPressed } ) : child; } ); return ( @@ -112,7 +111,7 @@ export function Button( props ) { <View style={ buttonViewStyle }> <View style={ { flexDirection: 'row' } }> { newChildren } - { subscript && ( <Text style={ ariaPressed ? styles.subscriptActive : subscriptInactive }>{ subscript }</Text> ) } + { subscript && ( <Text style={ isPressed ? styles.subscriptActive : subscriptInactive }>{ subscript }</Text> ) } </View> </View> </TouchableOpacity> diff --git a/packages/components/src/button/stories/index.js b/packages/components/src/button/stories/index.js index 27ac846c4c572e..5d60381ed1768f 100644 --- a/packages/components/src/button/stories/index.js +++ b/packages/components/src/button/stories/index.js @@ -1,41 +1,87 @@ /** * External dependencies */ -import React from 'react'; +import { text } from '@storybook/addon-knobs'; /** * Internal dependencies */ import Button from '../'; -export default { title: 'Button', component: Button }; +export default { title: 'Components|Button', component: Button }; -export const _default = () => <Button>Hello Button</Button>; +export const _default = () => { + const label = text( 'Label', 'Default Button' ); -export const primary = () => <Button isPrimary>Hello Button</Button>; + return ( + <Button>{ label }</Button> + ); +}; -export const large = () => <Button isLarge>Hello Button</Button>; +export const primary = () => { + const label = text( 'Label', 'Primary Button' ); -export const largePrimary = () => ( - <Button isPrimary isLarge> - Hello Button - </Button> -); + return ( + <Button isPrimary>{ label }</Button> + ); +}; -export const small = () => <Button isSmall>Hello Button</Button>; +export const large = () => { + const label = text( 'Label', 'Large Button' ); -export const toggled = () => <Button isToggled>Hello Button</Button>; + return ( + <Button isLarge>{ label }</Button> + ); +}; -export const disabled = () => <Button disabled>Hello Button</Button>; +export const largePrimary = () => { + const label = text( 'Label', 'Large Primary Button' ); -export const link = () => ( - <Button href="https://wordpress.org/" target="_blank"> - Hello Button - </Button> -); + return ( + <Button isPrimary isLarge>{ label }</Button> + ); +}; -export const disabledLink = () => ( - <Button href="https://wordpress.org/" target="_blank" disabled> - Hello Button - </Button> -); +export const small = () => { + const label = text( 'Label', 'Small Button' ); + + return ( + <Button isSmall>{ label }</Button> + ); +}; + +export const pressed = () => { + const label = text( 'Label', 'Pressed Button' ); + + return ( + <Button isPressed>{ label }</Button> + ); +}; + +export const disabled = () => { + const label = text( 'Label', 'Disabled Button' ); + + return ( + <Button disabled>{ label }</Button> + ); +}; + +export const link = () => { + const label = text( 'Label', 'Link Button' ); + + return ( + <Button href="https://wordpress.org/" target="_blank"> + { label } + </Button> + ); +}; + +export const disabledLink = () => { + const label = text( 'Label', 'Disabled Link Button' ); + + return ( + <Button href="https://wordpress.org/" target="_blank" disabled> + { label } + </Button> + ); +}; diff --git a/packages/components/src/button/style.scss b/packages/components/src/button/style.scss index 6d3f94a18a1c24..2f62f199794eda 100644 --- a/packages/components/src/button/style.scss +++ b/packages/components/src/button/style.scss @@ -37,7 +37,7 @@ background: #f3f5f6; color: color(theme(button) shade(25%)); border-color: color(theme(button) shade(5%)); - box-shadow: 0 0 0 1px color(theme(button) shade(5%)); + box-shadow: 0 0 0 $border-width color(theme(button) shade(5%)); text-decoration: none; } @@ -54,7 +54,7 @@ color: #a0a5aa; border-color: #ddd; background: #f7f7f7; - text-shadow: 0 1px 0 #fff; + text-shadow: 0 $border-width 0 #fff; transform: none; opacity: 1; } @@ -76,7 +76,7 @@ &:focus:enabled { box-shadow: - 0 0 0 1px $white, + 0 0 0 $border-width $white, 0 0 0 3px color(theme(button)); } @@ -107,7 +107,7 @@ &:focus:enabled { box-shadow: - 0 0 0 1px $white, + 0 0 0 $border-width $white, 0 0 0 3px color(theme(button)); } } @@ -157,8 +157,8 @@ &:focus { color: #124964; box-shadow: - 0 0 0 1px #5b9dd9, - 0 0 2px 1px rgba(30, 140, 190, 0.8); + 0 0 0 $border-width #5b9dd9, + 0 0 2px $border-width rgba(30, 140, 190, 0.8); } } diff --git a/packages/components/src/button/test/index.js b/packages/components/src/button/test/index.js index 05e3580bfacbe6..27c5a24f490e87 100644 --- a/packages/components/src/button/test/index.js +++ b/packages/components/src/button/test/index.js @@ -21,7 +21,7 @@ describe( 'Button', () => { expect( button.hasClass( 'components-button' ) ).toBe( true ); expect( button.hasClass( 'is-large' ) ).toBe( false ); expect( button.hasClass( 'is-primary' ) ).toBe( false ); - expect( button.hasClass( 'is-toggled' ) ).toBe( false ); + expect( button.hasClass( 'is-pressed' ) ).toBe( false ); expect( button.prop( 'disabled' ) ).toBeUndefined(); expect( button.prop( 'type' ) ).toBe( 'button' ); expect( button.type() ).toBe( 'button' ); @@ -56,10 +56,10 @@ describe( 'Button', () => { expect( button.hasClass( 'is-primary' ) ).toBe( false ); } ); - it( 'should render a button element with is-toggled without button class', () => { - const button = shallow( <Button isToggled /> ); + it( 'should render a button element with is-pressed without button class', () => { + const button = shallow( <Button isPressed /> ); expect( button.hasClass( 'is-button' ) ).toBe( false ); - expect( button.hasClass( 'is-toggled' ) ).toBe( true ); + expect( button.hasClass( 'is-pressed' ) ).toBe( true ); } ); it( 'should add a disabled prop to the button', () => { diff --git a/packages/components/src/card/README.md b/packages/components/src/card/README.md new file mode 100644 index 00000000000000..4a401b021a842d --- /dev/null +++ b/packages/components/src/card/README.md @@ -0,0 +1,97 @@ +# Card + +Card provides a flexible and extensible content container. + +## Usage + +```jsx +import { Card, CardBody } from '@wordpress/components'; + +const Example = () => ( + <Card> + <CardBody>...</CardBody> + </Card> +); +``` + +## Props + +Name | Type | Default | Description +--- | --- | --- | --- +`isBorderless` | `boolean` | `false` | Determines the border style of the card. +`isElevated` | `boolean` | `false` | Determines the elevation style of the card. +`size` | `string` | `medium` | Determines the amount of padding within the card. + +## Sub-Components + +This component provides a collection of sub-component that can be used to compose various interfaces. + +- [`<CardBody />`](./docs/body.md) +- [`<CardDivider />`](./docs/divider.md) +- [`<CardFooter />`](./docs/footer.md) +- [`<CardHeader />`](./docs/header.md) +- [`<CardMedia />`](./docs/media.md) + +### Sub-Components Example + +```jsx +import { + Card, + CardBody, + CardDivider, + CardFooter, + CardHeader, + CardMedia +} from '@wordpress/components'; + +const Example = () => ( + <Card> + <CardHeader> + ... + </CardHeader> + <CardBody> + ... + <CardBody> + <CardDivider /> + <CardBody> + ... + <CardBody> + <CardMedia> + <img src="..." /> + </CardMedia> + <CardHeader> + ... + </CardHeader> + </Card> +); +``` + +### Context + +`<Card />`'s sub-components are connected to `<Card />` using [Context](https://reactjs.org/docs/context.html). Certain props like `size` and `variant` are passed through to the sub-components. + +In the following example, the `<CardBody />` will render with a size of `small`: + +```jsx +import { Card, CardBody } from '@wordpress/components'; + +const Example = () => ( + <Card size="small"> + <CardBody>...</CardBody> + </Card> +); +``` + +These sub-components are designed to be flexible. The Context props can be overridden by the sub-component(s) as required. In the following example, the last `<CardBody />` will render it's specified size: + +```jsx +import { Card, CardBody } from '@wordpress/components'; + +const Example = () => ( + <Card size="small"> + <CardBody>...</CardBody> + <CardBody>...</CardBody> + <CardBody size="large">...</CardBody> + </Card> +); +``` diff --git a/packages/components/src/card/body.js b/packages/components/src/card/body.js new file mode 100644 index 00000000000000..9194d601169ea9 --- /dev/null +++ b/packages/components/src/card/body.js @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import { BodyUI } from './styles/card-styles'; +import { useCardContext } from './context'; + +export const defaultProps = { + isShady: false, + size: 'medium', +}; + +export function CardBody( props ) { + const { className, isShady, ...additionalProps } = props; + const mergedProps = { ...defaultProps, ...useCardContext(), ...props }; + const { size } = mergedProps; + + const classes = classnames( + 'components-card__body', + isShady && 'is-shady', + size && `is-size-${ size }`, + className + ); + + return <BodyUI { ...additionalProps } className={ classes } />; +} + +export default CardBody; diff --git a/packages/components/src/card/context.js b/packages/components/src/card/context.js new file mode 100644 index 00000000000000..2bcc31eaf65d4e --- /dev/null +++ b/packages/components/src/card/context.js @@ -0,0 +1,7 @@ +/** + * WordPress dependencies + */ +import { createContext, useContext } from '@wordpress/element'; + +export const CardContext = createContext( {} ); +export const useCardContext = () => useContext( CardContext ); diff --git a/packages/components/src/card/divider.js b/packages/components/src/card/divider.js new file mode 100644 index 00000000000000..09a4a59c726643 --- /dev/null +++ b/packages/components/src/card/divider.js @@ -0,0 +1,26 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import { DividerUI } from './styles/card-styles'; + +export function CardDivider( props ) { + const { className, ...additionalProps } = props; + + const classes = classnames( 'components-card__divider', className ); + + return ( + <DividerUI + { ...additionalProps } + children={ null } + className={ classes } + role="separator" + /> + ); +} + +export default CardDivider; diff --git a/packages/components/src/card/docs/body.md b/packages/components/src/card/docs/body.md new file mode 100644 index 00000000000000..6693bf644b27c4 --- /dev/null +++ b/packages/components/src/card/docs/body.md @@ -0,0 +1,24 @@ +# CardBody + +CardBody renders an optional content area for a [`<Card />`](../). + +## Usage + +```jsx +import { Card, CardBody } from '@wordpress/components'; + +const Example = () => ( + <Card> + <CardBody>...</CardBody> + </Card> +); +``` + +## Props + +Name | Type | Default | Description +--- | --- | --- | --- +`isShady` | `boolean` | `false` | Renders with a light gray background color. +`size` | `string` | `medium` | Determines the amount of padding within the component. + +Note: This component is connected to [`<Card />`'s Context](../README.md#context). Passing props directly to this component will override the props derived from context. diff --git a/packages/components/src/card/docs/divider.md b/packages/components/src/card/docs/divider.md new file mode 100644 index 00000000000000..feac94af529568 --- /dev/null +++ b/packages/components/src/card/docs/divider.md @@ -0,0 +1,17 @@ +# CardDivider + +CardDivider renders an optional divider within a [`<Card />`](../). + +## Usage + +```jsx +import { Card, CardBody, CardDivider } from '@wordpress/components'; + +const Example = () => ( + <Card> + <CardBody>...</CardBody> + <CardDivider /> + <CardBody>...</CardBody> + </Card> +); +``` diff --git a/packages/components/src/card/docs/footer.md b/packages/components/src/card/docs/footer.md new file mode 100644 index 00000000000000..f109db255672af --- /dev/null +++ b/packages/components/src/card/docs/footer.md @@ -0,0 +1,26 @@ +# CardFooter + +CardFooter renders an optional footer within a [`<Card />`](../). + +## Usage + +```jsx +import { Card, CardFooter } from '@wordpress/components'; + +const Example = () => ( + <Card> + <CardFooter>...</CardFooter> + </Card> +); +``` + +## Props + +Name | Type | Default | Description +--- | --- | --- | --- +`isBorderless` | `boolean` | `false` | Determines the border style of the card. +`isElevated` | `boolean` | `false` | Determines the elevation style of the card. +`isShady` | `boolean` | `false` | Renders with a light gray background color. +`size` | `string` | `medium` | Determines the amount of padding within the component. + +Note: This component is connected to [`<Card />`'s Context](../README.md#context). Passing props directly to this component will override the props derived from context. diff --git a/packages/components/src/card/docs/header.md b/packages/components/src/card/docs/header.md new file mode 100644 index 00000000000000..b3901f15625cca --- /dev/null +++ b/packages/components/src/card/docs/header.md @@ -0,0 +1,26 @@ +# CardHeader + +CardHeader renders an optional header within a [`<Card />`](../). + +## Usage + +```jsx +import { Card, CardHeader } from '@wordpress/components'; + +const Example = () => ( + <Card> + <CardHeader>...</CardHeader> + </Card> +); +``` + +## Props + +Name | Type | Default | Description +--- | --- | --- | --- +`isBorderless` | `boolean` | `false` | Determines the border style of the card. +`isElevated` | `boolean` | `false` | Determines the elevation style of the card. +`isShady` | `boolean` | `false` | Renders with a light gray background color. +`size` | `string` | `medium` | Determines the amount of padding within the component. + +Note: This component is connected to [`<Card />`'s Context](../README.md#context). Passing props directly to this component will override the props derived from context. diff --git a/packages/components/src/card/docs/media.md b/packages/components/src/card/docs/media.md new file mode 100644 index 00000000000000..980e32127792ec --- /dev/null +++ b/packages/components/src/card/docs/media.md @@ -0,0 +1,84 @@ +# CardMedia + +CardMedia provides a container for media elements, and renders within a [`<Card />`](../). + +## Usage + +```jsx +import { Card, CardBody, CardMedia } from '@wordpress/components'; + +const Example = () => ( + <Card> + <CardMedia> + <img src="..." /> + </CardMedia> + <CardBody>...</CardBody> + </Card> +); +``` + +## Placement + +`<CardMedia />` can be placed in any order as a direct child of a `<Card />`. The styles will automatically round the corners of the inner media element. + +### Top + +```jsx +import { Card, CardBody, CardMedia } from '@wordpress/components'; + +const Example = () => ( + <Card> + <CardMedia> + <img src="..." /> + </CardMedia> + <CardBody>...</CardBody> + </Card> +); +``` + +### Center + +```jsx +import { Card, CardBody, CardMedia } from '@wordpress/components'; + +const Example = () => ( + <Card> + <CardBody>...</CardBody> + <CardMedia> + <img src="..." /> + </CardMedia> + <CardBody>...</CardBody> + </Card> +); +``` + +### Bottom + +```jsx +import { Card, CardBody, CardMedia } from '@wordpress/components'; + +const Example = () => ( + <Card> + <CardBody>...</CardBody> + <CardMedia> + <img src="..." /> + </CardMedia> + </Card> +); +``` + +### Only + +`<CardMedia />` can also exist as the only child component of `<Card />`. + +```jsx +import { Card, CardMedia } from '@wordpress/components'; + +const Example = () => ( + <Card> + <CardMedia> + <img src="..." /> + </CardMedia> + </Card> +); +``` diff --git a/packages/components/src/card/footer.js b/packages/components/src/card/footer.js new file mode 100644 index 00000000000000..e4b66ce4022c78 --- /dev/null +++ b/packages/components/src/card/footer.js @@ -0,0 +1,34 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import { FooterUI } from './styles/card-styles'; +import { useCardContext } from './context'; + +export const defaultProps = { + isBorderless: false, + isShady: false, + size: 'medium', +}; + +export function CardFooter( props ) { + const { className, isShady, ...additionalProps } = props; + const mergedProps = { ...defaultProps, ...useCardContext(), ...props }; + const { isBorderless, size } = mergedProps; + + const classes = classnames( + 'components-card__footer', + isBorderless && 'is-borderless', + isShady && 'is-shady', + size && `is-size-${ size }`, + className + ); + + return <FooterUI { ...additionalProps } className={ classes } />; +} + +export default CardFooter; diff --git a/packages/components/src/card/header.js b/packages/components/src/card/header.js new file mode 100644 index 00000000000000..ece890cddd4604 --- /dev/null +++ b/packages/components/src/card/header.js @@ -0,0 +1,34 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import { HeaderUI } from './styles/card-styles'; +import { useCardContext } from './context'; + +export const defaultProps = { + isBorderless: false, + isShady: false, + size: 'medium', +}; + +export function CardHeader( props ) { + const { className, isShady, ...additionalProps } = props; + const mergedProps = { ...defaultProps, ...useCardContext(), ...props }; + const { isBorderless, size } = mergedProps; + + const classes = classnames( + 'components-card__header', + isBorderless && 'is-borderless', + isShady && 'is-shady', + size && `is-size-${ size }`, + className + ); + + return <HeaderUI { ...additionalProps } className={ classes } />; +} + +export default CardHeader; diff --git a/packages/components/src/card/index.js b/packages/components/src/card/index.js new file mode 100644 index 00000000000000..765b980567abf5 --- /dev/null +++ b/packages/components/src/card/index.js @@ -0,0 +1,51 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import { CardContext } from './context'; +import { CardUI } from './styles/card-styles'; + +export const defaultProps = { + isBorderless: false, + isElevated: false, + size: 'medium', +}; + +export function Card( props ) { + const { + className, + isBorderless, + isElevated, + size, + ...additionalProps + } = props; + const { Provider } = CardContext; + + const contextProps = { + isBorderless, + isElevated, + size, + }; + + const classes = classnames( + 'components-card', + isBorderless && 'is-borderless', + isElevated && 'is-elevated', + size && `is-size-${ size }`, + className + ); + + return ( + <Provider value={ contextProps }> + <CardUI { ...additionalProps } className={ classes } /> + </Provider> + ); +} + +Card.defaultProps = defaultProps; + +export default Card; diff --git a/packages/components/src/card/media.js b/packages/components/src/card/media.js new file mode 100644 index 00000000000000..c660f6705649de --- /dev/null +++ b/packages/components/src/card/media.js @@ -0,0 +1,19 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import { MediaUI } from './styles/card-styles'; + +export function CardMedia( props ) { + const { className, ...additionalProps } = props; + + const classes = classnames( 'components-card__media', className ); + + return <MediaUI { ...additionalProps } className={ classes } />; +} + +export default CardMedia; diff --git a/packages/components/src/card/stories/_index.js b/packages/components/src/card/stories/_index.js new file mode 100644 index 00000000000000..590a988df931d9 --- /dev/null +++ b/packages/components/src/card/stories/_index.js @@ -0,0 +1,43 @@ +/** + * External dependencies + */ + +/* eslint-disable import/no-extraneous-dependencies */ +import { text, boolean } from '@storybook/addon-knobs'; +/* eslint-enable import/no-extraneous-dependencies */ + +/** + * Internal dependencies + */ +import Card from '../index'; +import CardBody from '../body'; +import CardFooter from '../footer'; +import CardHeader from '../header'; +import { getCardStoryProps } from './_utils'; + +export default { title: 'Components|Card', component: Card }; + +export const _default = () => { + const props = getCardStoryProps(); + + const body = text( 'Body: children', 'Code is Poetry' ); + const isBodyShady = boolean( 'Body: isShady', false ); + + const header = text( 'Header: children', 'Header' ); + const isHeaderShady = boolean( 'Header: isShady', false ); + + const footer = text( 'Footer: children', 'Footer' ); + const isFooterShady = boolean( 'Footer: isShady', false ); + + return ( + <Card { ...props }> + { header && ( + <CardHeader isShady={ isHeaderShady }>{ header }</CardHeader> + ) } + { body && <CardBody isShady={ isBodyShady }>{ body }</CardBody> } + { footer && ( + <CardFooter isShady={ isFooterShady }>{ footer }</CardFooter> + ) } + </Card> + ); +}; diff --git a/packages/components/src/card/stories/_utils.js b/packages/components/src/card/stories/_utils.js new file mode 100644 index 00000000000000..8e34f08d16a814 --- /dev/null +++ b/packages/components/src/card/stories/_utils.js @@ -0,0 +1,52 @@ +/** + * External dependencies + */ +/* eslint-disable import/no-extraneous-dependencies */ +import { boolean, select } from '@storybook/addon-knobs'; +/* eslint-enable import/no-extraneous-dependencies */ + +export const getCardProps = ( props = {} ) => { + const { size } = props; + + return { + isBorderless: boolean( 'Card: isBorderless', false ), + isElevated: boolean( 'Card: isElevated', false ), + size: select( + 'Card: size', + { + large: 'large', + medium: 'medium', + small: 'small', + extraSmall: 'extraSmall', + }, + size || 'medium' + ), + }; +}; + +export const getCardStyleProps = ( props = {} ) => { + const width = select( + 'Example: Width', + { + '640px': 640, + '360px': 360, + '240px': 240, + '50%': '50%', + '100%': '100%', + }, + props.width || '360px' + ); + + return { + style: { + width, + }, + }; +}; + +export const getCardStoryProps = () => { + return { + ...getCardStyleProps(), + ...getCardProps(), + }; +}; diff --git a/packages/components/src/card/stories/body.js b/packages/components/src/card/stories/body.js new file mode 100644 index 00000000000000..de226b72e7e89f --- /dev/null +++ b/packages/components/src/card/stories/body.js @@ -0,0 +1,27 @@ +/** + * External dependencies + */ +/* eslint-disable import/no-extraneous-dependencies */ +import { boolean, text } from '@storybook/addon-knobs'; +/* eslint-enable import/no-extraneous-dependencies */ + +/** + * Internal dependencies + */ +import Card from '../index'; +import CardBody from '../body'; +import { getCardStoryProps } from './_utils'; + +export default { title: 'Components|Card/Body', component: CardBody }; + +export const _default = () => { + const props = getCardStoryProps(); + const content = text( 'Body: children', 'Content' ); + const isShady = boolean( 'Body: isShady', false ); + + return ( + <Card { ...props }> + <CardBody isShady={ isShady }>{ content }</CardBody> + </Card> + ); +}; diff --git a/packages/components/src/card/stories/divider.js b/packages/components/src/card/stories/divider.js new file mode 100644 index 00000000000000..c506cef28e7d58 --- /dev/null +++ b/packages/components/src/card/stories/divider.js @@ -0,0 +1,21 @@ +/** + * Internal dependencies + */ +import Card from '../index'; +import CardBody from '../body'; +import CardDivider from '../divider'; +import { getCardStoryProps } from './_utils'; + +export default { title: 'Components|Card/Divider', component: CardDivider }; + +export const _default = () => { + const props = getCardStoryProps(); + + return ( + <Card { ...props }> + <CardBody>...</CardBody> + <CardDivider /> + <CardBody>...</CardBody> + </Card> + ); +}; diff --git a/packages/components/src/card/stories/footer.js b/packages/components/src/card/stories/footer.js new file mode 100644 index 00000000000000..f21e640550c2e3 --- /dev/null +++ b/packages/components/src/card/stories/footer.js @@ -0,0 +1,27 @@ +/** + * External dependencies + */ +/* eslint-disable import/no-extraneous-dependencies */ +import { boolean, text } from '@storybook/addon-knobs'; +/* eslint-enable import/no-extraneous-dependencies */ + +/** + * Internal dependencies + */ +import Card from '../index'; +import CardFooter from '../footer'; +import { getCardStoryProps } from './_utils'; + +export default { title: 'Components|Card/Footer', component: CardFooter }; + +export const _default = () => { + const props = getCardStoryProps(); + const content = text( 'Footer: children', 'Content' ); + const isShady = boolean( 'Footer: isShady', false ); + + return ( + <Card { ...props }> + <CardFooter isShady={ isShady }>{ content }</CardFooter> + </Card> + ); +}; diff --git a/packages/components/src/card/stories/header.js b/packages/components/src/card/stories/header.js new file mode 100644 index 00000000000000..1314b701a93681 --- /dev/null +++ b/packages/components/src/card/stories/header.js @@ -0,0 +1,27 @@ +/** + * External dependencies + */ +/* eslint-disable import/no-extraneous-dependencies */ +import { boolean, text } from '@storybook/addon-knobs'; +/* eslint-enable import/no-extraneous-dependencies */ + +/** + * Internal dependencies + */ +import Card from '../index'; +import CardHeader from '../header'; +import { getCardStoryProps } from './_utils'; + +export default { title: 'Components|Card/Header', component: CardHeader }; + +export const _default = () => { + const props = getCardStoryProps(); + const content = text( 'Footer: children', 'Content' ); + const isShady = boolean( 'Footer: isShady', false ); + + return ( + <Card { ...props }> + <CardHeader isShady={ isShady }>{ content }</CardHeader> + </Card> + ); +}; diff --git a/packages/components/src/card/stories/media.js b/packages/components/src/card/stories/media.js new file mode 100644 index 00000000000000..4ecade543fc720 --- /dev/null +++ b/packages/components/src/card/stories/media.js @@ -0,0 +1,166 @@ +/** + * External dependencies + */ +/* eslint-disable import/no-extraneous-dependencies */ +import { boolean, number, select, text } from '@storybook/addon-knobs'; +/* eslint-enable import/no-extraneous-dependencies */ +import styled from '@emotion/styled'; + +/** + * Internal dependencies + */ +import Card from '../index'; +import CardBody from '../body'; +import CardFooter from '../footer'; +import CardHeader from '../header'; +import CardMedia from '../media'; +import { getCardStoryProps } from './_utils'; + +export default { title: 'Components|Card/Media', component: CardMedia }; + +const DummyImage = () => ( + <img + src="https://images.unsplash.com/photo-1570776765652-4ce2a88cc1f9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80" + alt="SMELLING MARSHMELLOW ICECREAM CONE" + /> +); + +export const _default = () => { + const mediaOnTop = boolean( 'Example: On Top', true ); + const props = getCardStoryProps(); + + const showTopContent = ! mediaOnTop; + const showBottomContent = ! showTopContent; + const content = text( 'Body: children', 'Content' ); + + return ( + <Card { ...props }> + { showTopContent && <CardBody>{ content }</CardBody> } + <CardMedia> + <DummyImage /> + </CardMedia> + { showBottomContent && <CardBody>{ content }</CardBody> } + </Card> + ); +}; + +export const onTop = () => { + const props = getCardStoryProps(); + + return ( + <Card { ...props }> + <CardMedia> + <DummyImage /> + </CardMedia> + <CardBody> + <div>Content</div> + </CardBody> + </Card> + ); +}; + +export const onBottom = () => { + const props = getCardStoryProps(); + + return ( + <Card { ...props }> + <CardBody>Content</CardBody> + <CardMedia> + <DummyImage /> + </CardMedia> + </Card> + ); +}; + +export const headerAndFooter = () => { + const props = getCardStoryProps(); + + return ( + <Card { ...props }> + <CardHeader>Header Content</CardHeader> + <CardMedia> + <DummyImage /> + </CardMedia> + <CardFooter isShady>Caption</CardFooter> + </Card> + ); +}; + +export const iframeEmbed = () => { + const props = getCardStoryProps(); + + return ( + <Card { ...props }> + <CardHeader>Header Content</CardHeader> + <CardMedia> + <iframe + width="560" + height="315" + src="https://www.youtube.com/embed/zGP6zk7jcrQ" + frameBorder="0" + allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" + allowFullScreen + title="Corgi" + ></iframe> + </CardMedia> + </Card> + ); +}; + +/** + * Referring to other styled components: + * https://www.styled-components.com/docs/advanced#referring-to-other-components + * https://www.styled-components.com/docs/advanced#caveat + */ +const StyledCardMedia = styled( CardMedia )( '' ); +const StyledCardBody = styled( CardBody )( '' ); + +const HorizontallyAlignedCard = styled( Card )` + display: flex; + + &.is-right { + flex-direction: row-reverse; + } + + ${ StyledCardBody } { + flex: 1; + } + + ${ StyledCardMedia } { + &.is-left { + border-radius: 3px 0 0 3px; + } + &.is-right { + border-radius: 0 3px 3px 0; + } + } +`; + +export const horizontallyAligned = () => { + const align = select( + 'Example: Align', + { + left: 'left', + right: 'right', + }, + 'left' + ); + const maxWidth = number( 'Example: Media Width', 200 ); + const content = text( 'Body: children', 'Content' ); + const classes = `is-${ align }`; + + return ( + <> + <p> + Note: This story demonstrates how this UI may be created. It + requires extra styling. + </p> + <HorizontallyAlignedCard className={ classes }> + <StyledCardMedia className={ classes } style={ { maxWidth } }> + <DummyImage /> + </StyledCardMedia> + <StyledCardBody>{ content }</StyledCardBody> + </HorizontallyAlignedCard> + </> + ); +}; diff --git a/packages/components/src/card/styles/card-styles.js b/packages/components/src/card/styles/card-styles.js new file mode 100644 index 00000000000000..150ff2ba1f57c9 --- /dev/null +++ b/packages/components/src/card/styles/card-styles.js @@ -0,0 +1,156 @@ +/** + * External dependencies + */ +import styled from '@emotion/styled'; + +/** + * Internal dependencies + */ +import { HorizontalRule } from '../../primitives'; +import { color } from '../../utils/colors'; + +export const styleProps = { + borderColor: color( 'lightGray.500' ), + borderRadius: '3px', + backgroundShady: color( 'lightGray.200' ), +}; + +const { borderColor, borderRadius, backgroundShady } = styleProps; + +export const CardUI = styled.div` + background: ${ color( 'white' ) }; + box-sizing: border-box; + border-radius: ${ borderRadius }; + border: 1px solid ${ borderColor }; + + ${ handleBorderless }; + + &.is-elevated { + box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2), + 0px 1px 1px 0px rgba(0, 0, 0, 0.14), + 0px 2px 1px -1px rgba(0, 0, 0, 0.12); + } +`; + +export const HeaderUI = styled.div` + border-bottom: 1px solid ${ borderColor }; + border-top-left-radius: ${ borderRadius }; + border-top-right-radius: ${ borderRadius }; + box-sizing: border-box; + + &:last-child { + border-bottom: none; + } + + ${ headerFooterSizes }; + ${ handleBorderless }; + ${ handleShady }; +`; + +export const MediaUI = styled.div` + box-sizing: border-box; + overflow: hidden; + + & > img, + & > iframe { + display: block; + height: auto; + max-width: 100%; + width: 100%; + } + + &:first-of-type { + border-top-left-radius: ${ borderRadius }; + border-top-right-radius: ${ borderRadius }; + } + + &:last-of-type { + border-bottom-left-radius: ${ borderRadius }; + border-bottom-right-radius: ${ borderRadius }; + } +`; + +export const BodyUI = styled.div` + box-sizing: border-box; + + ${ bodySize }; + ${ handleShady }; +`; + +export const FooterUI = styled.div` + border-top: 1px solid ${ borderColor }; + border-bottom-left-radius: ${ borderRadius }; + border-bottom-right-radius: ${ borderRadius }; + box-sizing: border-box; + + &:first-of-type { + border-top: none; + } + + ${ headerFooterSizes }; + ${ handleBorderless }; + ${ handleShady }; +`; + +export const DividerUI = styled( HorizontalRule )` + all: unset; + border-top: 1px solid ${ borderColor }; + box-sizing: border-box; + display: block; + height: 0; + width: 100%; +`; + +export function bodySize() { + return ` + &.is-size { + &-large { + padding: 28px; + } + &-medium { + padding: 20px; + } + &-small { + padding: 12px; + } + &-extraSmall { + padding: 8px; + } + } + `; +} + +export function headerFooterSizes() { + return ` + &.is-size { + &-large { + padding: 20px 28px; + } + &-medium { + padding: 12px 20px; + } + &-small { + padding: 8px 12px; + } + &-extraSmall { + padding: 4px 8px; + } + } + `; +} + +export function handleBorderless() { + return ` + &.is-borderless { + border: none; + } + `; +} + +export function handleShady() { + return ` + &.is-shady { + background: ${ backgroundShady }; + } + `; +} diff --git a/packages/components/src/card/test/body.js b/packages/components/src/card/test/body.js new file mode 100644 index 00000000000000..dbbe21cdd6028a --- /dev/null +++ b/packages/components/src/card/test/body.js @@ -0,0 +1,48 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import CardBody from '../body'; + +describe( 'CardBody', () => { + describe( 'basic rendering', () => { + test( 'should have components-card className', () => { + const wrapper = shallow( <CardBody /> ); + const cardBody = wrapper.find( '.components-card__body' ); + + expect( cardBody.length ).toBe( 1 ); + } ); + + test( 'should be able to render content', () => { + const cardBody = shallow( + <CardBody> + <div className="content">Hello</div> + </CardBody> + ); + const content = cardBody.find( '.content' ); + + expect( content.length ).toBe( 1 ); + expect( content.text() ).toBe( 'Hello' ); + } ); + } ); + + describe( 'modifiers', () => { + test( 'should be able to render size modifier', () => { + const wrapper = shallow( <CardBody size="large" /> ); + const cardBody = wrapper.find( '.components-card__body' ); + + expect( cardBody.hasClass( 'is-size-large' ) ).toBe( true ); + } ); + + test( 'should be able to render shady modifier', () => { + const wrapper = shallow( <CardBody isShady /> ); + const cardBody = wrapper.find( '.components-card__body' ); + + expect( cardBody.hasClass( 'is-shady' ) ).toBe( true ); + } ); + } ); +} ); diff --git a/packages/components/src/card/test/divider.js b/packages/components/src/card/test/divider.js new file mode 100644 index 00000000000000..81f6012c9cd59a --- /dev/null +++ b/packages/components/src/card/test/divider.js @@ -0,0 +1,38 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import CardDivider from '../divider'; + +describe( 'CardDivider', () => { + describe( 'basic rendering', () => { + test( 'should have components-card className', () => { + const wrapper = shallow( <CardDivider /> ); + const cardDivider = wrapper.find( '.components-card__divider' ); + + expect( cardDivider.length ).toBe( 1 ); + } ); + + test( 'should not render child content', () => { + const cardDivider = shallow( + <CardDivider> + <div className="content">Hello</div> + </CardDivider> + ); + const content = cardDivider.find( '.content' ); + + expect( content.length ).toBe( 0 ); + } ); + + test( 'should have role of separator', () => { + const wrapper = shallow( <CardDivider /> ); + const cardDivider = wrapper.find( '.components-card__divider' ); + + expect( cardDivider.prop( 'role' ) ).toBe( 'separator' ); + } ); + } ); +} ); diff --git a/packages/components/src/card/test/footer.js b/packages/components/src/card/test/footer.js new file mode 100644 index 00000000000000..bcd4308782437a --- /dev/null +++ b/packages/components/src/card/test/footer.js @@ -0,0 +1,55 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import CardFooter from '../footer'; + +describe( 'CardFooter', () => { + describe( 'basic rendering', () => { + test( 'should have components-card className', () => { + const wrapper = shallow( <CardFooter /> ); + const cardFooter = wrapper.find( '.components-card__footer' ); + + expect( cardFooter.length ).toBe( 1 ); + } ); + + test( 'should be able to render content', () => { + const cardFooter = shallow( + <CardFooter> + <div className="content">Hello</div> + </CardFooter> + ); + const content = cardFooter.find( '.content' ); + + expect( content.length ).toBe( 1 ); + expect( content.text() ).toBe( 'Hello' ); + } ); + } ); + + describe( 'modifiers', () => { + test( 'should be able to render size modifier', () => { + const wrapper = shallow( <CardFooter size="large" /> ); + const cardFooter = wrapper.find( '.components-card__footer' ); + + expect( cardFooter.hasClass( 'is-size-large' ) ).toBe( true ); + } ); + + test( 'should be able to render shady modifier', () => { + const wrapper = shallow( <CardFooter isShady /> ); + const cardFooter = wrapper.find( '.components-card__footer' ); + + expect( cardFooter.hasClass( 'is-shady' ) ).toBe( true ); + } ); + + test( 'should be able to render borderless modifier', () => { + const wrapper = shallow( <CardFooter isBorderless /> ); + const cardFooter = wrapper.find( '.components-card__footer' ); + + expect( cardFooter.hasClass( 'is-borderless' ) ).toBe( true ); + } ); + } ); +} ); diff --git a/packages/components/src/card/test/header.js b/packages/components/src/card/test/header.js new file mode 100644 index 00000000000000..3323658aaa7b6b --- /dev/null +++ b/packages/components/src/card/test/header.js @@ -0,0 +1,55 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import CardHeader from '../header'; + +describe( 'CardHeader', () => { + describe( 'basic rendering', () => { + test( 'should have components-card className', () => { + const wrapper = shallow( <CardHeader /> ); + const cardHeader = wrapper.find( '.components-card__header' ); + + expect( cardHeader.length ).toBe( 1 ); + } ); + + test( 'should be able to render content', () => { + const cardHeader = shallow( + <CardHeader> + <div className="content">Hello</div> + </CardHeader> + ); + const content = cardHeader.find( '.content' ); + + expect( content.length ).toBe( 1 ); + expect( content.text() ).toBe( 'Hello' ); + } ); + } ); + + describe( 'modifiers', () => { + test( 'should be able to render size modifier', () => { + const wrapper = shallow( <CardHeader size="large" /> ); + const cardHeader = wrapper.find( '.components-card__header' ); + + expect( cardHeader.hasClass( 'is-size-large' ) ).toBe( true ); + } ); + + test( 'should be able to render shady modifier', () => { + const wrapper = shallow( <CardHeader isShady /> ); + const cardHeader = wrapper.find( '.components-card__header' ); + + expect( cardHeader.hasClass( 'is-shady' ) ).toBe( true ); + } ); + + test( 'should be able to render borderless modifier', () => { + const wrapper = shallow( <CardHeader isBorderless /> ); + const cardHeader = wrapper.find( '.components-card__header' ); + + expect( cardHeader.hasClass( 'is-borderless' ) ).toBe( true ); + } ); + } ); +} ); diff --git a/packages/components/src/card/test/index.js b/packages/components/src/card/test/index.js new file mode 100644 index 00000000000000..16b4d3158971a0 --- /dev/null +++ b/packages/components/src/card/test/index.js @@ -0,0 +1,195 @@ +/** + * External dependencies + */ +import { mount, shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import Card from '../'; +import CardBody from '../body'; +import CardDivider from '../divider'; +import CardFooter from '../footer'; +import CardHeader from '../header'; +import CardMedia from '../media'; + +describe( 'Card', () => { + describe( 'basic rendering', () => { + test( 'should have components-card className', () => { + const wrapper = shallow( <Card /> ); + const card = wrapper.find( '.components-card' ); + + expect( card.length ).toBeTruthy(); + } ); + + test( 'should be able to render content', () => { + const card = shallow( + <Card> + <div className="content">Hello</div> + </Card> + ); + const content = card.find( '.content' ); + + expect( content.length ).toBe( 1 ); + expect( content.text() ).toBe( 'Hello' ); + } ); + } ); + + describe( 'styles', () => { + test( 'should render borderless', () => { + const wrapper = shallow( <Card isBorderless /> ); + const card = wrapper.find( '.components-card' ); + + expect( card.hasClass( 'is-borderless' ) ).toBe( true ); + } ); + + test( 'should render elevated styles', () => { + const wrapper = shallow( <Card isElevated /> ); + const card = wrapper.find( '.components-card' ); + + expect( card.hasClass( 'is-elevated' ) ).toBe( true ); + } ); + } ); + + describe( 'CardBody', () => { + test( 'should be able to render', () => { + const card = mount( + <Card> + <CardBody>Hello</CardBody> + </Card> + ); + const cardBody = card.find( 'CardBody' ); + + expect( cardBody.length ).toBe( 1 ); + expect( cardBody.text() ).toBe( 'Hello' ); + } ); + + test( 'should receive modifier props from context', () => { + const card = mount( + <Card size="extraSmall"> + <CardBody>Hello</CardBody> + </Card> + ); + const cardBody = card.find( '.components-card__body' ).first(); + + expect( cardBody.hasClass( 'is-size-extraSmall' ) ).toBe( true ); + } ); + + test( 'should be able to override props from context', () => { + const card = mount( + <Card size="extraSmall"> + <CardBody size="large">Hello</CardBody> + </Card> + ); + const cardBody = card.find( '.components-card__body' ).first(); + + expect( cardBody.hasClass( 'is-size-large' ) ).toBe( true ); + } ); + } ); + + describe( 'CardHeader', () => { + test( 'should be able to render', () => { + const card = mount( + <Card> + <CardHeader>Hello</CardHeader> + </Card> + ); + const cardHeader = card.find( 'CardHeader' ); + + expect( cardHeader.length ).toBe( 1 ); + expect( cardHeader.text() ).toBe( 'Hello' ); + } ); + + test( 'should receive modifier props from context', () => { + const card = mount( + <Card size="extraSmall" isBorderless={ true }> + <CardHeader>Hello</CardHeader> + </Card> + ); + const cardHeader = card.find( '.components-card__header' ).first(); + + expect( cardHeader.hasClass( 'is-size-extraSmall' ) ).toBe( true ); + expect( cardHeader.hasClass( 'is-borderless' ) ).toBe( true ); + } ); + + test( 'should be able to override props from context', () => { + const card = mount( + <Card size="extraSmall" isBorderless={ true }> + <CardHeader size="large" isBorderless={ false }> + Hello + </CardHeader> + </Card> + ); + const cardHeader = card.find( '.components-card__header' ).first(); + + expect( cardHeader.hasClass( 'is-size-large' ) ).toBe( true ); + expect( cardHeader.hasClass( 'is-borderless' ) ).toBe( false ); + } ); + } ); + + describe( 'CardFooter', () => { + test( 'should be able to render', () => { + const card = mount( + <Card> + <CardFooter>Hello</CardFooter> + </Card> + ); + const cardFooter = card.find( 'CardFooter' ); + + expect( cardFooter.length ).toBe( 1 ); + expect( cardFooter.text() ).toBe( 'Hello' ); + } ); + + test( 'should receive modifier props from context', () => { + const card = mount( + <Card size="extraSmall" isBorderless={ true }> + <CardFooter>Hello</CardFooter> + </Card> + ); + const cardFooter = card.find( '.components-card__footer' ).first(); + + expect( cardFooter.hasClass( 'is-size-extraSmall' ) ).toBe( true ); + expect( cardFooter.hasClass( 'is-borderless' ) ).toBe( true ); + } ); + + test( 'should be able to override props from context', () => { + const card = mount( + <Card size="extraSmall" isBorderless={ true }> + <CardFooter size="large" isBorderless={ false }> + Hello + </CardFooter> + </Card> + ); + const cardFooter = card.find( '.components-card__footer' ).first(); + + expect( cardFooter.hasClass( 'is-size-large' ) ).toBe( true ); + expect( cardFooter.hasClass( 'is-borderless' ) ).toBe( false ); + } ); + } ); + + describe( 'CardDivider', () => { + test( 'should be able to render', () => { + const card = mount( + <Card> + <CardDivider /> + </Card> + ); + const cardBody = card.find( 'CardDivider' ); + + expect( cardBody.length ).toBe( 1 ); + } ); + } ); + + describe( 'CardMedia', () => { + test( 'should be able to render', () => { + const card = mount( + <Card> + <CardMedia /> + </Card> + ); + const cardBody = card.find( 'CardMedia' ); + + expect( cardBody.length ).toBe( 1 ); + } ); + } ); +} ); diff --git a/packages/components/src/card/test/media.js b/packages/components/src/card/test/media.js new file mode 100644 index 00000000000000..935134d281dc21 --- /dev/null +++ b/packages/components/src/card/test/media.js @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import CardMedia from '../media'; + +describe( 'CardMedia', () => { + describe( 'basic rendering', () => { + test( 'should have components-card className', () => { + const wrapper = shallow( <CardMedia /> ); + const cardMedia = wrapper.find( '.components-card__media' ); + + expect( cardMedia.length ).toBe( 1 ); + } ); + + test( 'should be able to render content', () => { + const cardBody = shallow( + <CardMedia> + <div className="content">Hello</div> + </CardMedia> + ); + const content = cardBody.find( '.content' ); + + expect( content.length ).toBe( 1 ); + expect( content.text() ).toBe( 'Hello' ); + } ); + } ); +} ); diff --git a/packages/components/src/checkbox-control/README.md b/packages/components/src/checkbox-control/README.md index 43165e8ae1b59c..b6245fbd036cd0 100644 --- a/packages/components/src/checkbox-control/README.md +++ b/packages/components/src/checkbox-control/README.md @@ -56,17 +56,16 @@ If only a few child checkboxes are checked, the parent checkbox becomes a mixed Render an is author checkbox: ```jsx import { CheckboxControl } from '@wordpress/components'; -import { withState } from '@wordpress/compose'; +import { useState } from '@wordpress/element'; -const MyCheckboxControl = withState( { - isChecked: true, -} )( ( { isChecked, setState } ) => ( +const MyCheckboxControl = () => ( + const [ isChecked, setChecked ] = useState( true ); <CheckboxControl heading="User" label="Is author" help="Is the user a author or not?" checked={ isChecked } - onChange={ ( isChecked ) => { setState( { isChecked } ) } } + onChange={ setChecked } /> ) ); ``` diff --git a/packages/components/src/checkbox-control/stories/index.js b/packages/components/src/checkbox-control/stories/index.js new file mode 100644 index 00000000000000..fbb8235ff6d39d --- /dev/null +++ b/packages/components/src/checkbox-control/stories/index.js @@ -0,0 +1,54 @@ +/** + * External dependencies + */ +import { text } from '@storybook/addon-knobs'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import CheckboxControl from '../'; + +export default { title: 'Components|CheckboxControl', component: CheckboxControl }; + +const CheckboxControlWithState = ( { checked, ...props } ) => { + const [ isChecked, setChecked ] = useState( checked ); + + return ( + <CheckboxControl + { ...props } + checked={ isChecked } + onChange={ setChecked } + /> + ); +}; + +export const _default = () => { + const label = text( 'Label', 'Is author' ); + + return ( + <CheckboxControlWithState + label={ label } + checked + /> + ); +}; + +export const all = () => { + const heading = text( 'Heading', 'User' ); + const label = text( 'Label', 'Is author' ); + const help = text( 'Help', 'Is the user an author or not?' ); + + return ( + <CheckboxControlWithState + heading={ heading } + label={ label } + help={ help } + checked + /> + ); +}; diff --git a/packages/components/src/circular-option-picker/index.js b/packages/components/src/circular-option-picker/index.js index 1d1b7635f9f589..97e048b25ca7e6 100644 --- a/packages/components/src/circular-option-picker/index.js +++ b/packages/components/src/circular-option-picker/index.js @@ -18,13 +18,11 @@ function Option( { ...additionalProps } ) { const optionButton = ( - <button - type="button" - aria-pressed={ isSelected } + <Button + isPressed={ isSelected } className={ classnames( className, 'components-circular-option-picker__option', - { 'is-active': isSelected } ) } { ...additionalProps } /> @@ -78,7 +76,6 @@ function ButtonAction( { 'components-circular-option-picker__clear', className ) } - type="button" isSmall isDefault { ...additionalProps } @@ -92,10 +89,12 @@ export default function CircularOptionPicker( { actions, className, options, + children, } ) { return ( <div className={ classnames( 'components-circular-option-picker', className ) }> { options } + { children } { actions && ( <div className="components-circular-option-picker__custom-clear-wrapper"> { actions } diff --git a/packages/components/src/circular-option-picker/style.scss b/packages/components/src/circular-option-picker/style.scss index 4098ca3248d0d8..deee7969feb612 100644 --- a/packages/components/src/circular-option-picker/style.scss +++ b/packages/components/src/circular-option-picker/style.scss @@ -1,12 +1,12 @@ $color-palette-circle-size: 28px; -$color-palette-circle-spacing: 14px; +$color-palette-circle-spacing: 12px; .components-circular-option-picker { - margin-right: -14px; - width: calc(100% + 14px); + display: inline-block; + margin-top: 0.6rem; + width: 100%; .components-circular-option-picker__custom-clear-wrapper { - width: calc(100% - 14px); display: flex; justify-content: flex-end; } @@ -22,6 +22,7 @@ $color-palette-circle-spacing: 14px; transform: scale(1); transition: 100ms transform ease; @include reduce-motion("transition"); + &:hover { transform: scale(1.2); } @@ -31,6 +32,10 @@ $color-palette-circle-spacing: 14px; height: 100%; width: 100%; } + + &:nth-child(6n+6) { + margin-right: 0; + } } .components-circular-option-picker__option-wrapper::before { @@ -61,17 +66,17 @@ $color-palette-circle-spacing: 14px; @include reduce-motion("transition"); cursor: pointer; - &.is-active { + &.is-pressed { box-shadow: inset 0 0 0 4px; position: relative; - z-index: z-index(".components-circular-option-picker__option.is-active"); + z-index: z-index(".components-circular-option-picker__option.is-pressed"); & + .dashicons-saved { position: absolute; left: 4px; top: 4px; border-radius: 50%; - z-index: z-index(".components-circular-option-picker__option.is-active + .dashicons-saved"); + z-index: z-index(".components-circular-option-picker__option.is-pressed + .dashicons-saved"); background: $white; pointer-events: none; } @@ -91,7 +96,6 @@ $color-palette-circle-spacing: 14px; } &:focus { - outline: none; &::after { content: ""; border: #{ $border-width * 2 } solid $dark-gray-400; @@ -104,6 +108,12 @@ $color-palette-circle-spacing: 14px; box-shadow: inset 0 0 0 2px $white; } } + + &.components-button:focus { + background-color: transparent; + box-shadow: inset 0 0 0 ($color-palette-circle-size / 2); + outline: none; + } } .components-circular-option-picker__button-action .components-circular-option-picker__option { diff --git a/packages/components/src/clipboard-button/index.js b/packages/components/src/clipboard-button/index.js index cd6e9a6472bff1..596750e41b1d50 100644 --- a/packages/components/src/clipboard-button/index.js +++ b/packages/components/src/clipboard-button/index.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { Component, createRef } from '@wordpress/element'; /** * Internal dependencies @@ -19,16 +19,16 @@ class ClipboardButton extends Component { constructor() { super( ...arguments ); - this.bindContainer = this.bindContainer.bind( this ); + this.containerRef = createRef(); this.onCopy = this.onCopy.bind( this ); this.getText = this.getText.bind( this ); } componentDidMount() { - const { container, getText, onCopy } = this; - const button = container.firstChild; + const { getText, onCopy } = this; + const container = this.containerRef.current; - this.clipboard = new Clipboard( button, { + this.clipboard = new Clipboard( container.firstChild, { text: getText, container, } ); @@ -42,10 +42,6 @@ class ClipboardButton extends Component { clearTimeout( this.onCopyTimeout ); } - bindContainer( container ) { - this.container = container; - } - onCopy( args ) { // Clearing selection will move focus back to the triggering button, // ensuring that it is not reset to the body, and further that it is @@ -93,7 +89,7 @@ class ClipboardButton extends Component { }; return ( - <span ref={ this.bindContainer } onCopy={ focusOnCopyEventTarget }> + <span ref={ this.containerRef } onCopy={ focusOnCopyEventTarget }> <ComponentToUse { ...buttonProps } className={ classes }> { children } </ComponentToUse> diff --git a/packages/components/src/clipboard-button/stories/index.js b/packages/components/src/clipboard-button/stories/index.js new file mode 100644 index 00000000000000..82861f1a1d8828 --- /dev/null +++ b/packages/components/src/clipboard-button/stories/index.js @@ -0,0 +1,42 @@ +/** + * External dependencies + */ +import { boolean, text } from '@storybook/addon-knobs'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import ClipboardButton from '../'; + +export default { title: 'Components|ClipboardButton', component: ClipboardButton }; + +const ClipboardButtonWithState = ( { copied, ...props } ) => { + const [ isCopied, setCopied ] = useState( copied ); + + return ( + <ClipboardButton + { ...props } + onCopy={ () => setCopied( true ) } + onFinishCopy={ () => setCopied( false ) } + > + { isCopied ? 'Copied!' : `Copy "${ props.text }"` } + </ClipboardButton> + ); +}; + +export const _default = () => { + const isPrimary = boolean( 'Is primary', true ); + const copyText = text( 'Text', 'Text' ); + + return ( + <ClipboardButtonWithState + isPrimary={ isPrimary } + text={ copyText } + /> + ); +}; diff --git a/packages/components/src/color-indicator/README.md b/packages/components/src/color-indicator/README.md index 185e4961af7116..02d356b62e448e 100644 --- a/packages/components/src/color-indicator/README.md +++ b/packages/components/src/color-indicator/README.md @@ -6,6 +6,6 @@ import { ColorIndicator } from '@wordpress/components'; const MyColorIndicator = () => ( - <ColorIndicator colorValue="#f00" /> + <ColorIndicator colorValue="#0073aa" /> ); ``` diff --git a/packages/components/src/color-indicator/stories/index.js b/packages/components/src/color-indicator/stories/index.js new file mode 100644 index 00000000000000..c81ed107fb0e8b --- /dev/null +++ b/packages/components/src/color-indicator/stories/index.js @@ -0,0 +1,21 @@ +/** + * External dependencies + */ +import { text } from '@storybook/addon-knobs'; + +/** + * Internal dependencies + */ +import ColorIndicator from '../'; + +export default { + title: 'Components|ColorIndicator', + component: ColorIndicator, +}; + +export const _default = () => { + const color = text( 'Color', '#0073aa' ); + return ( + <ColorIndicator colorValue={ color } /> + ); +}; diff --git a/packages/components/src/color-palette/stories/index.js b/packages/components/src/color-palette/stories/index.js new file mode 100644 index 00000000000000..3694f6903eae4b --- /dev/null +++ b/packages/components/src/color-palette/stories/index.js @@ -0,0 +1,55 @@ +/** + * External dependencies + */ +import { object } from '@storybook/addon-knobs'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import ColorPalette from '../'; + +export default { title: 'Components|ColorPalette', component: ColorPalette }; + +const ColorPaletteWithState = ( props ) => { + const [ color, setColor ] = useState( '#F00' ); + return ( + <ColorPalette + { ...props } + value={ color } + onChange={ setColor } + /> + ); +}; + +export const _default = () => { + const colors = [ + { name: 'red', color: '#f00' }, + { name: 'white', color: '#fff' }, + { name: 'blue', color: '#00f' }, + ]; + + return ( + <ColorPaletteWithState + colors={ colors } + /> + ); +}; + +export const withKnobs = () => { + const colors = [ + object( 'Red', { name: 'red', color: '#f00' } ), + object( 'White', { name: 'white', color: '#fff' } ), + object( 'Blue', { name: 'blue', color: '#00f' } ), + ]; + + return ( + <ColorPaletteWithState + colors={ colors } + /> + ); +}; diff --git a/packages/components/src/color-palette/test/__snapshots__/index.js.snap b/packages/components/src/color-palette/test/__snapshots__/index.js.snap index ea834490fd18cf..4f182217c8a13e 100644 --- a/packages/components/src/color-palette/test/__snapshots__/index.js.snap +++ b/packages/components/src/color-palette/test/__snapshots__/index.js.snap @@ -106,7 +106,7 @@ exports[`ColorPalette Dropdown should render it correctly 1`] = ` renderToggle={[Function]} > <div - className="components-circular-option-picker__dropdown-link-action" + className="components-dropdown components-circular-option-picker__dropdown-link-action" > <ForwardRef(Button) aria-expanded={false} @@ -283,10 +283,10 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = ` <Tooltip text="red" > - <button + <ForwardRef(Button) aria-label="Color: red" - aria-pressed={true} - className="components-circular-option-picker__option is-active" + className="components-circular-option-picker__option" + isPressed={true} onBlur={[Function]} onClick={[Function]} onFocus={[Function]} @@ -298,8 +298,25 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = ` "color": "#f00", } } - type="button" - /> + > + <button + aria-label="Color: red" + aria-pressed={true} + className="components-button components-circular-option-picker__option is-pressed" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + style={ + Object { + "color": "#f00", + } + } + type="button" + /> + </ForwardRef(Button)> </Tooltip> <Dashicon icon="saved" @@ -354,10 +371,10 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = ` <Tooltip text="white" > - <button + <ForwardRef(Button) aria-label="Color: white" - aria-pressed={false} className="components-circular-option-picker__option" + isPressed={false} onBlur={[Function]} onClick={[Function]} onFocus={[Function]} @@ -369,8 +386,25 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = ` "color": "#fff", } } - type="button" - /> + > + <button + aria-label="Color: white" + aria-pressed={false} + className="components-button components-circular-option-picker__option" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + style={ + Object { + "color": "#fff", + } + } + type="button" + /> + </ForwardRef(Button)> </Tooltip> </div> </Option> @@ -392,10 +426,10 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = ` <Tooltip text="blue" > - <button + <ForwardRef(Button) aria-label="Color: blue" - aria-pressed={false} className="components-circular-option-picker__option" + isPressed={false} onBlur={[Function]} onClick={[Function]} onFocus={[Function]} @@ -407,8 +441,25 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = ` "color": "#00f", } } - type="button" - /> + > + <button + aria-label="Color: blue" + aria-pressed={false} + className="components-button components-circular-option-picker__option" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + style={ + Object { + "color": "#00f", + } + } + type="button" + /> + </ForwardRef(Button)> </Tooltip> </div> </Option> @@ -436,7 +487,7 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = ` renderToggle={[Function]} > <div - className="components-circular-option-picker__dropdown-link-action" + className="components-dropdown components-circular-option-picker__dropdown-link-action" > <ForwardRef(Button) aria-expanded={false} @@ -465,7 +516,6 @@ exports[`ColorPalette should render a dynamic toolbar of colors 1`] = ` isDefault={true} isSmall={true} onClick={[Function]} - type="button" > <button className="components-button components-circular-option-picker__clear is-button is-default is-small" diff --git a/packages/components/src/color-palette/test/index.js b/packages/components/src/color-palette/test/index.js index 241f048aae96d8..c4022ba48c4c7b 100644 --- a/packages/components/src/color-palette/test/index.js +++ b/packages/components/src/color-palette/test/index.js @@ -29,7 +29,7 @@ describe( 'ColorPalette', () => { } ); test( 'should call onClick on an active button with undefined', () => { - const activeButton = buttons.findWhere( ( button ) => button.hasClass( 'is-active' ) ); + const activeButton = buttons.findWhere( ( button ) => button.hasClass( 'is-pressed' ) ); activeButton.simulate( 'click' ); expect( onChange ).toHaveBeenCalledTimes( 1 ); @@ -37,7 +37,7 @@ describe( 'ColorPalette', () => { } ); test( 'should call onClick on an inactive button', () => { - const inactiveButton = buttons.findWhere( ( button ) => ! button.hasClass( 'is-active' ) ).first(); + const inactiveButton = buttons.findWhere( ( button ) => ! button.hasClass( 'is-pressed' ) ).first(); inactiveButton.simulate( 'click' ); expect( onChange ).toHaveBeenCalledTimes( 1 ); diff --git a/packages/components/src/color-picker/hue.js b/packages/components/src/color-picker/hue.js index 214df7e725bdc1..0c97020d89efdc 100644 --- a/packages/components/src/color-picker/hue.js +++ b/packages/components/src/color-picker/hue.js @@ -43,6 +43,7 @@ import { TAB } from '@wordpress/keycodes'; */ import { calculateHueChange } from './utils'; import KeyboardShortcuts from '../keyboard-shortcuts'; +import VisuallyHidden from '../visually-hidden'; export class Hue extends Component { constructor() { @@ -157,12 +158,11 @@ export class Hue extends Component { style={ pointerLocation } onKeyDown={ this.preventKeyEvents } /> - <p - className="components-color-picker__hue-description screen-reader-text" + <VisuallyHidden as="p" id={ `components-color-picker__hue-description-${ instanceId }` } > { __( 'Move the arrow left or right to change hue.' ) } - </p> + </VisuallyHidden> </div> { /* eslint-enable jsx-a11y/no-static-element-interactions */ } </div> diff --git a/packages/components/src/color-picker/inputs.js b/packages/components/src/color-picker/inputs.js index 39817d13edb74f..9f2f95c39adaa5 100644 --- a/packages/components/src/color-picker/inputs.js +++ b/packages/components/src/color-picker/inputs.js @@ -17,6 +17,7 @@ import { pure } from '@wordpress/compose'; */ import IconButton from '../icon-button'; import TextControl from '../text-control'; +import VisuallyHidden from '../visually-hidden'; import { isValidHex } from './utils'; /* Wrapper for TextControl, only used to handle intermediate state while typing. */ @@ -141,7 +142,7 @@ export class Inputs extends Component { return value; } - if ( value > 0 ) { + if ( value < 0 ) { return 0; } else if ( value > 1 ) { return 1; @@ -175,9 +176,9 @@ export class Inputs extends Component { } else if ( this.state.view === 'rgb' ) { return ( <fieldset> - <legend className="screen-reader-text"> + <VisuallyHidden as="legend"> { __( 'Color value in RGB' ) } - </legend> + </VisuallyHidden> <div className="components-color-picker__inputs-fields"> <Input source={ this.state.view } @@ -228,9 +229,9 @@ export class Inputs extends Component { } else if ( this.state.view === 'hsl' ) { return ( <fieldset> - <legend className="screen-reader-text"> + <VisuallyHidden as="legend"> { __( 'Color value in HSL' ) } - </legend> + </VisuallyHidden> <div className="components-color-picker__inputs-fields"> <Input source={ this.state.view } diff --git a/packages/components/src/color-picker/saturation.js b/packages/components/src/color-picker/saturation.js index 3bdd10bfcdfe28..d2e495923b617d 100644 --- a/packages/components/src/color-picker/saturation.js +++ b/packages/components/src/color-picker/saturation.js @@ -42,7 +42,9 @@ import { compose, pure, withInstanceId } from '@wordpress/compose'; * Internal dependencies */ import { calculateSaturationChange } from './utils'; +import Button from '../button'; import KeyboardShortcuts from '../keyboard-shortcuts'; +import VisuallyHidden from '../visually-hidden'; export class Saturation extends Component { constructor( props ) { @@ -164,20 +166,19 @@ export class Saturation extends Component { > <div className="components-color-picker__saturation-white" /> <div className="components-color-picker__saturation-black" /> - <button + <Button aria-label={ __( 'Choose a shade' ) } aria-describedby={ `color-picker-saturation-${ instanceId }` } className="components-color-picker__saturation-pointer" style={ pointerLocation } onKeyDown={ this.preventKeyEvents } /> - <div - className="screen-reader-text" + <VisuallyHidden id={ `color-picker-saturation-${ instanceId }` }> { __( 'Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation.' ) } - </div> + </VisuallyHidden> </div> </KeyboardShortcuts> ); diff --git a/packages/components/src/color-picker/stories/index.js b/packages/components/src/color-picker/stories/index.js new file mode 100644 index 00000000000000..1ad10680bf6e35 --- /dev/null +++ b/packages/components/src/color-picker/stories/index.js @@ -0,0 +1,39 @@ + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import ColorPicker from '../'; + +export default { title: 'Components|ColorPicker', component: ColorPicker }; + +const ColorPickerWithState = ( { ...props } ) => { + const [ color, setColor ] = useState( '#f00' ); + return ( + <ColorPicker + { ...props } + color={ color } + onChangeComplete={ ( value ) => setColor( value.hex ) } + /> + ); +}; + +export const _default = () => { + return ( + <ColorPickerWithState + disableAlpha + /> + ); +}; + +export const alphaEnabled = () => { + return ( + <ColorPickerWithState + disableAlpha={ false } + /> + ); +}; diff --git a/packages/components/src/color-picker/style.scss b/packages/components/src/color-picker/style.scss index 3736be0d5f9d4f..ad5779c8d9a7b0 100644 --- a/packages/components/src/color-picker/style.scss +++ b/packages/components/src/color-picker/style.scss @@ -28,18 +28,30 @@ .components-color-picker { width: 100%; overflow: hidden; + + * { + box-sizing: border-box; + } + + .components-icon-button { + padding: 6px; + } } + .components-color-picker__saturation { width: 100%; padding-bottom: 55%; position: relative; } + .components-color-picker__body { padding: $grid-size-large $grid-size-large #{ $grid-size-small * 3 }; } + .components-color-picker__controls { display: flex; } + .components-color-picker__saturation-pointer, .components-color-picker__hue-pointer, .components-color-picker__alpha-pointer { @@ -72,6 +84,7 @@ margin-top: 0; } } + .components-color-picker__active { position: absolute; top: 0; @@ -93,16 +106,20 @@ right: 0; bottom: 0; } + .components-color-picker__saturation-color { overflow: hidden; } + .components-color-picker__saturation-white { /*rtl:ignore*/ background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0)); } + .components-color-picker__saturation-black { background: linear-gradient(to top, #000, rgba(0, 0, 0, 0)); } + .components-color-picker__saturation-pointer { width: 8px; height: 8px; @@ -119,6 +136,7 @@ .components-color-picker__toggles { flex: 1; } + .components-color-picker__alpha { background-image: linear-gradient(45deg, #ddd 25%, transparent 25%), @@ -128,6 +146,7 @@ background-size: 10px 10px; background-position: 0 0, 0 5px, 5px -5px, -5px 0; } + .components-color-picker__hue-gradient, .components-color-picker__alpha-gradient { position: absolute; @@ -136,14 +155,17 @@ right: 0; bottom: 0; } + .components-color-picker__hue, .components-color-picker__alpha { height: 12px; position: relative; } + .is-alpha-enabled .components-color-picker__hue { margin-bottom: 8px; } + .components-color-picker__hue-bar, .components-color-picker__alpha-bar { position: relative; @@ -151,10 +173,12 @@ height: 100%; padding: 0 2px; } + .components-color-picker__hue-gradient { /*rtl:ignore*/ background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%); } + .components-color-picker__hue-pointer, .components-color-picker__alpha-pointer { /*rtl:ignore*/ @@ -192,7 +216,6 @@ outline-offset: -2px; } - /* INPUTS COMPONENT */ .components-color-picker__inputs-wrapper { margin: 0 -4px; @@ -202,16 +225,29 @@ fieldset { flex: 1; + border: none; + margin: 0; + padding: 0; } .components-color-picker__inputs-fields .components-text-control__input[type="number"] { - padding: 2px; + padding: 6px 8px; } } + +.components-color-picker__inputs-field { + width: 100%; +} + .components-color-picker__inputs-fields { display: flex; /*rtl:ignore*/ direction: ltr; + flex-grow: 1; + + .components-base-control + .components-base-control { + margin-bottom: 0; + } .components-base-control__field { margin: 0 4px; diff --git a/packages/components/src/color-picker/test/__snapshots__/index.js.snap b/packages/components/src/color-picker/test/__snapshots__/index.js.snap index 5d61a24e5ac9e6..317a7db3538a70 100644 --- a/packages/components/src/color-picker/test/__snapshots__/index.js.snap +++ b/packages/components/src/color-picker/test/__snapshots__/index.js.snap @@ -29,7 +29,7 @@ exports[`ColorPicker should commit changes to all views on blur 1`] = ` <button aria-describedby="color-picker-saturation-2" aria-label="Choose a shade" - className="components-color-picker__saturation-pointer" + className="components-button components-color-picker__saturation-pointer" onKeyDown={[Function]} style={ Object { @@ -37,9 +37,10 @@ exports[`ColorPicker should commit changes to all views on blur 1`] = ` "top": "20%", } } + type="button" /> <div - className="screen-reader-text" + className="components-visually-hidden" id="color-picker-saturation-2" > Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. @@ -99,7 +100,7 @@ exports[`ColorPicker should commit changes to all views on blur 1`] = ` tabIndex="0" /> <p - className="components-color-picker__hue-description screen-reader-text" + className="components-visually-hidden" id="components-color-picker__hue-description-2" > Move the arrow left or right to change hue. @@ -203,7 +204,7 @@ exports[`ColorPicker should commit changes to all views on keyDown = DOWN 1`] = <button aria-describedby="color-picker-saturation-4" aria-label="Choose a shade" - className="components-color-picker__saturation-pointer" + className="components-button components-color-picker__saturation-pointer" onKeyDown={[Function]} style={ Object { @@ -211,9 +212,10 @@ exports[`ColorPicker should commit changes to all views on keyDown = DOWN 1`] = "top": "20%", } } + type="button" /> <div - className="screen-reader-text" + className="components-visually-hidden" id="color-picker-saturation-4" > Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. @@ -273,7 +275,7 @@ exports[`ColorPicker should commit changes to all views on keyDown = DOWN 1`] = tabIndex="0" /> <p - className="components-color-picker__hue-description screen-reader-text" + className="components-visually-hidden" id="components-color-picker__hue-description-4" > Move the arrow left or right to change hue. @@ -377,7 +379,7 @@ exports[`ColorPicker should commit changes to all views on keyDown = ENTER 1`] = <button aria-describedby="color-picker-saturation-5" aria-label="Choose a shade" - className="components-color-picker__saturation-pointer" + className="components-button components-color-picker__saturation-pointer" onKeyDown={[Function]} style={ Object { @@ -385,9 +387,10 @@ exports[`ColorPicker should commit changes to all views on keyDown = ENTER 1`] = "top": "20%", } } + type="button" /> <div - className="screen-reader-text" + className="components-visually-hidden" id="color-picker-saturation-5" > Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. @@ -447,7 +450,7 @@ exports[`ColorPicker should commit changes to all views on keyDown = ENTER 1`] = tabIndex="0" /> <p - className="components-color-picker__hue-description screen-reader-text" + className="components-visually-hidden" id="components-color-picker__hue-description-5" > Move the arrow left or right to change hue. @@ -551,7 +554,7 @@ exports[`ColorPicker should commit changes to all views on keyDown = UP 1`] = ` <button aria-describedby="color-picker-saturation-3" aria-label="Choose a shade" - className="components-color-picker__saturation-pointer" + className="components-button components-color-picker__saturation-pointer" onKeyDown={[Function]} style={ Object { @@ -559,9 +562,10 @@ exports[`ColorPicker should commit changes to all views on keyDown = UP 1`] = ` "top": "20%", } } + type="button" /> <div - className="screen-reader-text" + className="components-visually-hidden" id="color-picker-saturation-3" > Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. @@ -621,7 +625,7 @@ exports[`ColorPicker should commit changes to all views on keyDown = UP 1`] = ` tabIndex="0" /> <p - className="components-color-picker__hue-description screen-reader-text" + className="components-visually-hidden" id="components-color-picker__hue-description-3" > Move the arrow left or right to change hue. @@ -725,7 +729,7 @@ exports[`ColorPicker should only update input view for draft changes 1`] = ` <button aria-describedby="color-picker-saturation-1" aria-label="Choose a shade" - className="components-color-picker__saturation-pointer" + className="components-button components-color-picker__saturation-pointer" onKeyDown={[Function]} style={ Object { @@ -733,9 +737,10 @@ exports[`ColorPicker should only update input view for draft changes 1`] = ` "top": "0%", } } + type="button" /> <div - className="screen-reader-text" + className="components-visually-hidden" id="color-picker-saturation-1" > Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. @@ -795,7 +800,7 @@ exports[`ColorPicker should only update input view for draft changes 1`] = ` tabIndex="0" /> <p - className="components-color-picker__hue-description screen-reader-text" + className="components-visually-hidden" id="components-color-picker__hue-description-1" > Move the arrow left or right to change hue. @@ -899,7 +904,7 @@ exports[`ColorPicker should render color picker 1`] = ` <button aria-describedby="color-picker-saturation-0" aria-label="Choose a shade" - className="components-color-picker__saturation-pointer" + className="components-button components-color-picker__saturation-pointer" onKeyDown={[Function]} style={ Object { @@ -907,9 +912,10 @@ exports[`ColorPicker should render color picker 1`] = ` "top": "0%", } } + type="button" /> <div - className="screen-reader-text" + className="components-visually-hidden" id="color-picker-saturation-0" > Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. @@ -969,7 +975,7 @@ exports[`ColorPicker should render color picker 1`] = ` tabIndex="0" /> <p - className="components-color-picker__hue-description screen-reader-text" + className="components-visually-hidden" id="components-color-picker__hue-description-0" > Move the arrow left or right to change hue. diff --git a/packages/components/src/custom-gradient-picker/constants.js b/packages/components/src/custom-gradient-picker/constants.js new file mode 100644 index 00000000000000..234fd270874c32 --- /dev/null +++ b/packages/components/src/custom-gradient-picker/constants.js @@ -0,0 +1,11 @@ +export const INSERT_POINT_WIDTH = 23; +export const GRADIENT_MARKERS_WIDTH = 18; +export const MINIMUM_DISTANCE_BETWEEN_INSERTER_AND_MARKER = ( INSERT_POINT_WIDTH + GRADIENT_MARKERS_WIDTH ) / 2; +export const MINIMUM_ABSOLUTE_LEFT_POSITION = 5; +export const MINIMUM_DISTANCE_BETWEEN_POINTS = 9; +export const MINIMUM_SIGNIFICANT_MOVE = 5; +export const DEFAULT_GRADIENT = 'linear-gradient(135deg, rgba(6, 147, 227, 1) 0%, rgb(155, 81, 224) 100%)'; +export const COLOR_POPOVER_PROPS = { + className: 'components-custom-gradient-picker__color-picker-popover', + position: 'top', +}; diff --git a/packages/components/src/custom-gradient-picker/control-points.js b/packages/components/src/custom-gradient-picker/control-points.js new file mode 100644 index 00000000000000..79c7e0017e3deb --- /dev/null +++ b/packages/components/src/custom-gradient-picker/control-points.js @@ -0,0 +1,246 @@ + +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { Component, useEffect, useRef } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; +import { withInstanceId } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import Button from '../button'; +import ColorPicker from '../color-picker'; +import Dropdown from '../dropdown'; +import { + getGradientWithColorAtIndexChanged, + getGradientWithControlPointRemoved, + getGradientWithPositionAtIndexChanged, + getGradientWithPositionAtIndexDecreased, + getGradientWithPositionAtIndexIncreased, + getHorizontalRelativeGradientPosition, + isControlPointOverlapping, +} from './utils'; +import { + COLOR_POPOVER_PROPS, + GRADIENT_MARKERS_WIDTH, + MINIMUM_SIGNIFICANT_MOVE, +} from './constants'; +import KeyboardShortcuts from '../keyboard-shortcuts'; + +class ControlPointKeyboardMove extends Component { + constructor() { + super( ...arguments ); + this.increase = this.increase.bind( this ); + this.decrease = this.decrease.bind( this ); + this.shortcuts = { + right: this.increase, + left: this.decrease, + }; + } + increase( event ) { + // Stop propagation of the key press event to avoid focus moving + // to another editor area. + event.stopPropagation(); + const { gradientIndex, onChange, gradientAST } = this.props; + onChange( getGradientWithPositionAtIndexIncreased( gradientAST, gradientIndex ) ); + } + + decrease( event ) { + // Stop propagation of the key press event to avoid focus moving + // to another editor area. + event.stopPropagation(); + const { gradientIndex, onChange, gradientAST } = this.props; + onChange( getGradientWithPositionAtIndexDecreased( gradientAST, gradientIndex ) ); + } + render() { + const { children } = this.props; + return ( + <KeyboardShortcuts shortcuts={ this.shortcuts }> + { children } + </KeyboardShortcuts> + ); + } +} + +const ControlPointButton = withInstanceId( + function( { + instanceId, + isOpen, + position, + color, + onChange, + gradientIndex, + gradientAST, + ...additionalProps + } ) { + const descriptionId = `components-custom-gradient-picker__control-point-button-description-${ instanceId }`; + return ( + <ControlPointKeyboardMove + onChange={ onChange } + gradientIndex={ gradientIndex } + gradientAST={ gradientAST } + > + <Button + aria-label={ + sprintf( + // translators: %1$s: gradient position e.g: 70%, %2$s: gradient color code e.g: rgb(52,121,151). + __( 'Gradient control point at position %1$s with color code %2$s.' ), + position, + color + ) + } + aria-describedby={ descriptionId } + aria-expanded={ isOpen } + className={ + classnames( + 'components-custom-gradient-picker__control-point-button', + { 'is-active': isOpen } + ) + } + style={ { + left: position, + } } + { ...additionalProps } + /> + <div + className="screen-reader-text" + id={ descriptionId }> + { __( + 'Use your left or right arrow keys or drag and drop with the mouse to change the gradient position. Press the button to change the color or remove the control point.' + ) } + </div> + </ControlPointKeyboardMove> + ); + } +); + +export default function ControlPoints( { + gradientPickerDomRef, + ignoreMarkerPosition, + markerPoints, + onChange, + gradientAST, + onStartControlPointChange, + onStopControlPointChange, +} ) { + const controlPointMoveState = useRef(); + + const onMouseMove = ( event ) => { + const relativePosition = getHorizontalRelativeGradientPosition( + event.clientX, + gradientPickerDomRef.current, + GRADIENT_MARKERS_WIDTH, + ); + const { + gradientAST: referenceGradientAST, + position, + significantMoveHappened, + } = controlPointMoveState.current; + if ( ! significantMoveHappened ) { + const initialPosition = referenceGradientAST.colorStops[ position ].length.value; + if ( Math.abs( initialPosition - relativePosition ) >= MINIMUM_SIGNIFICANT_MOVE ) { + controlPointMoveState.current.significantMoveHappened = true; + } + } + + if ( ! isControlPointOverlapping( referenceGradientAST, relativePosition, position ) ) { + onChange( + getGradientWithPositionAtIndexChanged( referenceGradientAST, position, relativePosition ) + ); + } + }; + + const cleanEventListeners = () => { + if ( + window && window.removeEventListener && + controlPointMoveState.current && controlPointMoveState.current.listenersActivated + ) { + window.removeEventListener( 'mousemove', onMouseMove ); + window.removeEventListener( 'mouseup', cleanEventListeners ); + onStopControlPointChange(); + controlPointMoveState.current.listenersActivated = false; + } + }; + + useEffect( () => { + return () => { + cleanEventListeners(); + }; + }, [] ); + + return markerPoints.map( + ( point, index ) => ( + point && ignoreMarkerPosition !== point.positionValue && ( + <Dropdown + key={ index } + onClose={ onStopControlPointChange } + renderToggle={ ( { isOpen, onToggle } ) => ( + <ControlPointButton + key={ index } + onClick={ () => { + if ( + controlPointMoveState.current && + controlPointMoveState.current.significantMoveHappened + ) { + return; + } + onStartControlPointChange(); + onToggle(); + } } + onMouseDown={ () => { + if ( window && window.addEventListener ) { + controlPointMoveState.current = { + gradientAST, + position: index, + significantMoveHappened: false, + listenersActivated: true, + }; + onStartControlPointChange(); + window.addEventListener( 'mousemove', onMouseMove ); + window.addEventListener( 'mouseup', cleanEventListeners ); + } + } } + isOpen={ isOpen } + position={ point.position } + color={ point.color } + onChange={ onChange } + gradientAST={ gradientAST } + gradientIndex={ index } + /> + ) } + renderContent={ ( { onClose } ) => ( + <> + <ColorPicker + color={ point.color } + onChangeComplete={ ( { rgb } ) => { + onChange( + getGradientWithColorAtIndexChanged( gradientAST, index, rgb ) + ); + } } + /> + <Button + className="components-custom-gradient-picker__remove-control-point" + onClick={ () => { + onChange( + getGradientWithControlPointRemoved( gradientAST, index ) + ); + onClose(); + } } + isLink + > + { __( 'Remove Control Point' ) } + </Button> + </> + ) } + popoverProps={ COLOR_POPOVER_PROPS } + /> + ) + ) + ); +} diff --git a/packages/components/src/custom-gradient-picker/index.js b/packages/components/src/custom-gradient-picker/index.js new file mode 100644 index 00000000000000..4286e587e91371 --- /dev/null +++ b/packages/components/src/custom-gradient-picker/index.js @@ -0,0 +1,234 @@ + +/** + * External dependencies + */ +import gradientParser from 'gradient-parser'; +import { some } from 'lodash'; +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { useRef, useReducer, useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import IconButton from '../icon-button'; +import ColorPicker from '../color-picker'; +import Dropdown from '../dropdown'; +import ControlPoints from './control-points'; +import { + DEFAULT_GRADIENT, + INSERT_POINT_WIDTH, + COLOR_POPOVER_PROPS, + MINIMUM_DISTANCE_BETWEEN_POINTS, +} from './constants'; +import { serializeGradient } from './serializer'; +import { + getGradientWithColorAtPositionChanged, + getGradientWithColorStopAdded, + getHorizontalRelativeGradientPosition, + getMarkerPoints, +} from './utils'; + +function InsertPoint( { + onChange, + gradientAST, + onOpenInserter, + onCloseInserter, + insertPosition, +} ) { + const [ alreadyInsertedPoint, setAlreadyInsertedPoint ] = useState( false ); + return ( + <Dropdown + className="components-custom-gradient-picker__inserter" + onClose={ () => { + onCloseInserter(); + } } + renderToggle={ ( { isOpen, onToggle } ) => ( + <IconButton + aria-expanded={ isOpen } + onClick={ () => { + setAlreadyInsertedPoint( false ); + onOpenInserter(); + onToggle(); + } } + className="components-custom-gradient-picker__insert-point" + icon="insert" + style={ { + left: insertPosition !== null ? `${ insertPosition }%` : undefined, + } } + /> + ) } + renderContent={ () => ( + <ColorPicker + onChangeComplete={ ( { rgb } ) => { + let newGradient; + if ( alreadyInsertedPoint ) { + newGradient = getGradientWithColorAtPositionChanged( gradientAST, insertPosition, rgb ); + } else { + newGradient = getGradientWithColorStopAdded( gradientAST, insertPosition, rgb ); + setAlreadyInsertedPoint( true ); + } + onChange( newGradient ); + } } + /> + ) } + popoverProps={ COLOR_POPOVER_PROPS } + /> + ); +} + +function customGradientBarReducer( state, action ) { + switch ( action.type ) { + case 'MOVE_INSERTER': + if ( state.id === 'IDLE' || state.id === 'MOVING_INSERTER' ) { + return { + id: 'MOVING_INSERTER', + insertPosition: action.insertPosition, + }; + } + break; + case 'STOP_INSERTER_MOVE': + if ( state.id === 'MOVING_INSERTER' ) { + return { + id: 'IDLE', + }; + } + break; + case 'OPEN_INSERTER': + if ( state.id === 'MOVING_INSERTER' ) { + return { + id: 'INSERTING_CONTROL_POINT', + insertPosition: state.insertPosition, + }; + } + break; + case 'CLOSE_INSERTER': + if ( state.id === 'INSERTING_CONTROL_POINT' ) { + return { + id: 'IDLE', + }; + } + break; + case 'START_CONTROL_CHANGE': + if ( state.id === 'IDLE' ) { + return { + id: 'MOVING_CONTROL_POINT', + }; + } + break; + case 'STOP_CONTROL_CHANGE': + if ( state.id === 'MOVING_CONTROL_POINT' ) { + return { + id: 'IDLE', + }; + } + break; + } + return state; +} +const customGradientBarReducerInitialState = { id: 'IDLE' }; + +export default function CustomGradientPicker( { value, onChange } ) { + let hasGradient = !! value; + // gradientAST will contain the gradient AST as parsed by gradient-parser npm module. + // More information of its structure available at. + let gradientAST; + let gradientValueUsed; + try { + gradientAST = gradientParser.parse( value || DEFAULT_GRADIENT )[ 0 ]; + gradientValueUsed = value || DEFAULT_GRADIENT; + } catch ( error ) { + hasGradient = false; + gradientAST = gradientParser.parse( DEFAULT_GRADIENT )[ 0 ]; + gradientValueUsed = DEFAULT_GRADIENT; + } + + const onGradientStructureChange = ( newGradientStructure ) => { + onChange( serializeGradient( newGradientStructure ) ); + }; + + const gradientPickerDomRef = useRef(); + const markerPoints = getMarkerPoints( gradientAST ); + + const [ gradientBarState, gradientBarStateDispatch ] = useReducer( + customGradientBarReducer, + customGradientBarReducerInitialState + ); + const onMouseEnterAndMove = ( event ) => { + const insertPosition = getHorizontalRelativeGradientPosition( + event.clientX, + gradientPickerDomRef.current, + INSERT_POINT_WIDTH, + ); + + // If the insert point is close to an existing control point don't show it. + if ( some( + markerPoints, + ( { positionValue } ) => { + return Math.abs( insertPosition - positionValue ) < MINIMUM_DISTANCE_BETWEEN_POINTS; + } + ) ) { + if ( gradientBarState.id === 'MOVING_INSERTER' ) { + gradientBarStateDispatch( { type: 'STOP_INSERTER_MOVE' } ); + } + return; + } + + gradientBarStateDispatch( { type: 'MOVE_INSERTER', insertPosition } ); + }; + + const onMouseLeave = () => { + gradientBarStateDispatch( { type: 'STOP_INSERTER_MOVE' } ); + }; + + const isMovingInserter = gradientBarState.id === 'MOVING_INSERTER'; + const isInsertingControlPoint = gradientBarState.id === 'INSERTING_CONTROL_POINT'; + + return ( + <div + ref={ gradientPickerDomRef } + className={ classnames( + 'components-custom-gradient-picker', + { 'has-gradient': hasGradient } + ) } + onMouseEnter={ onMouseEnterAndMove } + onMouseMove={ onMouseEnterAndMove } + style={ { + background: gradientValueUsed, + } } + onMouseLeave={ onMouseLeave } + > + <div className="components-custom-gradient-picker__markers-container"> + { ( isMovingInserter || isInsertingControlPoint ) && ( + <InsertPoint + insertPosition={ gradientBarState.insertPosition } + onChange={ onGradientStructureChange } + gradientAST={ gradientAST } + onOpenInserter={ () => { + gradientBarStateDispatch( { type: 'OPEN_INSERTER' } ); + } } + onCloseInserter={ () => { + gradientBarStateDispatch( { type: 'CLOSE_INSERTER' } ); + } } + /> + ) } + <ControlPoints + gradientPickerDomRef={ gradientPickerDomRef } + ignoreMarkerPosition={ isInsertingControlPoint ? gradientBarState.insertPosition : undefined } + markerPoints={ markerPoints } + onChange={ onGradientStructureChange } + gradientAST={ gradientAST } + onStartControlPointChange={ () => { + gradientBarStateDispatch( { type: 'START_CONTROL_CHANGE' } ); + } } + onStopControlPointChange={ () => { + gradientBarStateDispatch( { type: 'STOP_CONTROL_CHANGE' } ); + } } + /> + </div> + </div> + ); +} diff --git a/packages/components/src/custom-gradient-picker/serializer.js b/packages/components/src/custom-gradient-picker/serializer.js new file mode 100644 index 00000000000000..a76191e7ec4101 --- /dev/null +++ b/packages/components/src/custom-gradient-picker/serializer.js @@ -0,0 +1,31 @@ +/** + * External dependencies + */ +import { compact, get } from 'lodash'; + +export function serializeGradientColor( { type, value } ) { + return `${ type }(${ value.join( ',' ) })`; +} + +export function serializeGradientPosition( { type, value } ) { + return `${ value }${ type }`; +} + +export function serializeGradientColorStop( { type, value, length } ) { + return `${ serializeGradientColor( { type, value } ) } ${ serializeGradientPosition( length ) }`; +} + +export function serializeGradientOrientation( orientation ) { + if ( ! orientation || orientation.type !== 'angular' ) { + return; + } + return `${ orientation.value }deg`; +} + +export function serializeGradient( { type, orientation, colorStops } ) { + const serializedOrientation = serializeGradientOrientation( orientation ); + const serializedColorStops = colorStops.sort( ( colorStop1, colorStop2 ) => { + return get( colorStop1, [ 'length', 'value' ], 0 ) - get( colorStop2, [ 'length', 'value' ], 0 ); + } ).map( serializeGradientColorStop ); + return `${ type }(${ compact( [ serializedOrientation, ...serializedColorStops ] ).join( ',' ) })`; +} diff --git a/packages/components/src/custom-gradient-picker/style.scss b/packages/components/src/custom-gradient-picker/style.scss new file mode 100644 index 00000000000000..1590a8a6c07375 --- /dev/null +++ b/packages/components/src/custom-gradient-picker/style.scss @@ -0,0 +1,57 @@ +$components-custom-gradient-picker__padding: 3px; // 24px container, 18px handles inside, that leaves 6px padding, half of which is 3. + +.components-custom-gradient-picker:not(.has-gradient) { + opacity: 0.4; +} + +.components-custom-gradient-picker { + width: 100%; + height: $icon-button-size-small; + border-radius: $icon-button-size-small; + margin-bottom: $grid-size; + padding-left: $components-custom-gradient-picker__padding; + padding-right: $icon-button-size-small - $components-custom-gradient-picker__padding; + + .components-custom-gradient-picker__markers-container { + position: relative; + } + + .components-custom-gradient-picker__insert-point { + border-radius: 50%; + background: $white; + padding: 2px; + width: $icon-button-size-small; + height: $icon-button-size-small; + position: relative; + } + + .components-custom-gradient-picker__control-point-button { + border: 2px solid $white; + border-radius: 50%; + height: 18px; + position: absolute; + width: 18px; + top: $components-custom-gradient-picker__padding; + + &.is-active { + background: #fafafa; + color: #23282d; + border-color: #999; + box-shadow: + inset 0 -1px 0 #999, + 0 0 0 1px $white, + 0 0 0 3px $blue-medium-focus; + } + } +} + +.components-custom-gradient-picker__color-picker-popover .components-custom-gradient-picker__remove-control-point { + margin-left: auto; + margin-right: auto; + display: block; + margin-bottom: 8px; +} + +.components-custom-gradient-picker__inserter { + width: 100%; +} diff --git a/packages/components/src/custom-gradient-picker/test/serializer.js b/packages/components/src/custom-gradient-picker/test/serializer.js new file mode 100644 index 00000000000000..fd2ab6f6bd421f --- /dev/null +++ b/packages/components/src/custom-gradient-picker/test/serializer.js @@ -0,0 +1,96 @@ +/** + * Internal dependencies + */ +import { + serializeGradientColor, + serializeGradientPosition, + serializeGradientColorStop, + serializeGradientOrientation, + serializeGradient, +} from '../serializer'; + +describe( 'It should serialize a gradient', () => { + test( 'serializeGradientColor', () => { + expect( serializeGradientColor( + { type: 'rgba', value: [ 1, 2, 3, 0.5 ] } + ) ).toBe( 'rgba(1,2,3,0.5)' ); + + expect( serializeGradientColor( + { type: 'rgb', value: [ 255, 0, 0 ] } + ) ).toBe( 'rgb(255,0,0)' ); + } ); + + test( 'serializeGradientPosition', () => { + expect( serializeGradientPosition( + { type: '%', value: 70 } + ) ).toBe( '70%' ); + + expect( serializeGradientPosition( + { type: '%', value: 0 } + ) ).toBe( '0%' ); + + expect( serializeGradientPosition( + { type: 'px', value: 4 } + ) ).toBe( '4px' ); + } ); + + test( 'serializeGradientColorStop', () => { + expect( serializeGradientColorStop( + { type: 'rgba', value: [ 1, 2, 3, 0.5 ], length: { type: '%', value: 70 } } + ) ).toBe( 'rgba(1,2,3,0.5) 70%' ); + + expect( serializeGradientColorStop( + { type: 'rgb', value: [ 255, 0, 0 ], length: { type: '%', value: 0 } } + ) ).toBe( 'rgb(255,0,0) 0%' ); + + expect( serializeGradientColorStop( + { type: 'rgba', value: [ 1, 2, 3, 0.5 ], length: { type: 'px', value: 100 } } + ) ).toBe( 'rgba(1,2,3,0.5) 100px' ); + } ); + + test( 'serializeGradientOrientation', () => { + expect( serializeGradientOrientation( + { type: 'angular', value: 40 } + ) ).toBe( '40deg' ); + + expect( serializeGradientOrientation( + { type: 'angular', value: 0 } + ) ).toBe( '0deg' ); + } ); + + test( 'serializeGradient', () => { + expect( serializeGradient( + { + type: 'linear-gradient', + orientation: { type: 'angular', value: 40 }, + colorStops: [ + { type: 'rgba', value: [ 1, 2, 3, 0.5 ], length: { type: '%', value: 70 } }, + { type: 'rgba', value: [ 255, 1, 1, 0.9 ], length: { type: '%', value: 40 } }, + ], + } + ) ).toBe( 'linear-gradient(40deg,rgba(255,1,1,0.9) 40%,rgba(1,2,3,0.5) 70%)' ); + + expect( serializeGradient( + { + type: 'linear-gradient', + colorStops: [ + { type: 'rgba', value: [ 1, 2, 3, 0.5 ], length: { type: '%', value: 70 } }, + { type: 'rgba', value: [ 255, 1, 1, 0.9 ], length: { type: '%', value: 40 } }, + ], + } + ) ).toBe( 'linear-gradient(rgba(255,1,1,0.9) 40%,rgba(1,2,3,0.5) 70%)' ); + + expect( serializeGradient( + { + type: 'linear-gradient', + orientation: { type: 'angular', value: 0 }, + colorStops: [ + { type: 'rgba', value: [ 1, 2, 3, 0.5 ], length: { type: '%', value: 0 } }, + { type: 'rgba', value: [ 255, 1, 1, 0.9 ], length: { type: '%', value: 40 } }, + { type: 'rgba', value: [ 1, 2, 3, 0.5 ], length: { type: '%', value: 100 } }, + { type: 'rgba', value: [ 10, 20, 30, 0.5 ], length: { type: '%', value: 20 } }, + ], + } + ) ).toBe( 'linear-gradient(0deg,rgba(1,2,3,0.5) 0%,rgba(10,20,30,0.5) 20%,rgba(255,1,1,0.9) 40%,rgba(1,2,3,0.5) 100%)' ); + } ); +} ); diff --git a/packages/components/src/custom-gradient-picker/utils.js b/packages/components/src/custom-gradient-picker/utils.js new file mode 100644 index 00000000000000..0ea49d4b311e63 --- /dev/null +++ b/packages/components/src/custom-gradient-picker/utils.js @@ -0,0 +1,176 @@ +/** + * External dependencies + */ +import { findIndex, map, some } from 'lodash'; + +/** + * Internal dependencies + */ +import { + INSERT_POINT_WIDTH, + MINIMUM_ABSOLUTE_LEFT_POSITION, + MINIMUM_DISTANCE_BETWEEN_POINTS, +} from './constants'; +import { + serializeGradientColor, + serializeGradientPosition, +} from './serializer'; + +function tinyColorRgbToGradientColorStop( { r, g, b, a } ) { + if ( a === 1 ) { + return { + type: 'rgb', + value: [ r, g, b ], + }; + } + return { + type: 'rgba', + value: [ r, g, b, a ], + }; +} + +export function getGradientWithColorStopAdded( gradientAST, relativePosition, rgbaColor ) { + const colorStop = tinyColorRgbToGradientColorStop( rgbaColor ); + colorStop.length = { + type: '%', + value: relativePosition, + }; + return { + ...gradientAST, + colorStops: [ + ...gradientAST.colorStops, + colorStop, + ], + }; +} + +export function getGradientWithPositionAtIndexChanged( gradientAST, index, relativePosition ) { + return { + ...gradientAST, + colorStops: gradientAST.colorStops.map( + ( colorStop, colorStopIndex ) => { + if ( colorStopIndex !== index ) { + return colorStop; + } + return { + ...colorStop, + length: { + ...colorStop.length, + value: relativePosition, + }, + }; + } + ), + }; +} + +export function isControlPointOverlapping( gradientAST, position, initialIndex ) { + const initialPosition = parseInt( gradientAST.colorStops[ initialIndex ].length.value ); + const minPosition = Math.min( initialPosition, position ); + const maxPosition = Math.max( initialPosition, position ); + + return some( + gradientAST.colorStops, + ( { length }, index ) => { + const itemPosition = parseInt( length.value ); + return index !== initialIndex && ( + Math.abs( itemPosition - position ) < MINIMUM_DISTANCE_BETWEEN_POINTS || + ( minPosition < itemPosition && itemPosition < maxPosition ) + ); + } + ); +} + +function getGradientWithPositionAtIndexSummed( gradientAST, index, valueToSum ) { + const currentPosition = gradientAST.colorStops[ index ].length.value; + const newPosition = Math.max( 0, Math.min( 100, parseInt( currentPosition ) + valueToSum ) ); + if ( isControlPointOverlapping( gradientAST, newPosition, index ) ) { + return gradientAST; + } + return getGradientWithPositionAtIndexChanged( gradientAST, index, newPosition ); +} + +export function getGradientWithPositionAtIndexIncreased( gradientAST, index ) { + return getGradientWithPositionAtIndexSummed( gradientAST, index, MINIMUM_DISTANCE_BETWEEN_POINTS ); +} + +export function getGradientWithPositionAtIndexDecreased( gradientAST, index ) { + return getGradientWithPositionAtIndexSummed( gradientAST, index, -MINIMUM_DISTANCE_BETWEEN_POINTS ); +} + +export function getGradientWithColorAtIndexChanged( gradientAST, index, rgbaColor ) { + return { + ...gradientAST, + colorStops: gradientAST.colorStops.map( + ( colorStop, colorStopIndex ) => { + if ( colorStopIndex !== index ) { + return colorStop; + } + return { + ...colorStop, + ...tinyColorRgbToGradientColorStop( rgbaColor ), + }; + } + ), + }; +} + +export function getGradientWithColorAtPositionChanged( gradientAST, relativePositionValue, rgbaColor ) { + const index = findIndex( gradientAST.colorStops, ( colorStop ) => { + return ( + colorStop && + colorStop.length && + colorStop.length.type === '%' && + colorStop.length.value === relativePositionValue.toString() + ); + } ); + return getGradientWithColorAtIndexChanged( gradientAST, index, rgbaColor ); +} + +export function getGradientWithControlPointRemoved( gradientAST, index ) { + return { + ...gradientAST, + colorStops: gradientAST.colorStops.filter( ( elem, elemIndex ) => { + return elemIndex !== index; + } ), + }; +} + +export function getHorizontalRelativeGradientPosition( mouseXCoordinate, containerElement, positionedElementWidth ) { + if ( ! containerElement ) { + return; + } + const { x, width } = containerElement.getBoundingClientRect(); + const absolutePositionValue = mouseXCoordinate - x - MINIMUM_ABSOLUTE_LEFT_POSITION - ( positionedElementWidth / 2 ); + const availableWidth = width - MINIMUM_ABSOLUTE_LEFT_POSITION - INSERT_POINT_WIDTH; + return Math.round( + Math.min( Math.max( ( absolutePositionValue * 100 ) / availableWidth, 0 ), 100 ) + ); +} + +/** + * Returns the marker points from a gradient AST. + * + * @param {Object} gradientAST An object representing the gradient AST. + * + * @return {Array.<{color: string, position: string, positionValue: number}>} + * An array of markerPoint objects. + * color: A string with the color code ready to be used in css style e.g: "rgba( 1, 2 , 3, 0.5)". + * position: A string with the position ready to be used in css style e.g: "70%". + * positionValue: A number with the relative position value e.g: 70. + */ +export function getMarkerPoints( gradientAST ) { + if ( ! gradientAST ) { + return []; + } + return map( gradientAST.colorStops, ( colorStop ) => { + if ( ! colorStop || ! colorStop.length || colorStop.length.type !== '%' ) { + return null; + } + return { + color: serializeGradientColor( colorStop ), + position: serializeGradientPosition( colorStop.length ), + positionValue: parseInt( colorStop.length.value ), + }; + } ); +} diff --git a/packages/components/src/custom-select-control/index.js b/packages/components/src/custom-select-control/index.js new file mode 100644 index 00000000000000..0d49c08e4de137 --- /dev/null +++ b/packages/components/src/custom-select-control/index.js @@ -0,0 +1,140 @@ +/** + * External dependencies + */ +import { useSelect } from 'downshift'; +import classnames from 'classnames'; + +/** + * Internal dependencies + */ +import { Button, Dashicon } from '../'; + +const itemToString = ( item ) => item && item.name; +// This is needed so that in Windows, where +// the menu does not necessarily open on +// key up/down, you can still switch between +// options with the menu closed. +const stateReducer = ( + { selectedItem }, + { type, changes, props: { items } } +) => { + switch ( type ) { + case useSelect.stateChangeTypes.ToggleButtonKeyDownArrowDown: + // If we already have a selected item, try to select the next one, + // without circular navigation. Otherwise, select the first item. + return { + selectedItem: + items[ + selectedItem ? + Math.min( items.indexOf( selectedItem ) + 1, items.length - 1 ) : + 0 + ], + }; + case useSelect.stateChangeTypes.ToggleButtonKeyDownArrowUp: + // If we already have a selected item, try to select the previous one, + // without circular navigation. Otherwise, select the last item. + return { + selectedItem: + items[ + selectedItem ? + Math.max( items.indexOf( selectedItem ) - 1, 0 ) : + items.length - 1 + ], + }; + default: + return changes; + } +}; +export default function CustomSelectControl( { + className, + hideLabelFromVision, + label, + options: items, + onChange: onSelectedItemChange, + value: _selectedItem, +} ) { + const { + getLabelProps, + getToggleButtonProps, + getMenuProps, + getItemProps, + isOpen, + highlightedIndex, + selectedItem, + } = useSelect( { + initialSelectedItem: items[ 0 ], + items, + itemToString, + onSelectedItemChange, + selectedItem: _selectedItem, + stateReducer, + } ); + const menuProps = getMenuProps( { + className: 'components-custom-select-control__menu', + } ); + // We need this here, because the null active descendant is not + // fully ARIA compliant. + if ( + menuProps[ 'aria-activedescendant' ] && + menuProps[ 'aria-activedescendant' ].slice( 0, 'downshift-null'.length ) === + 'downshift-null' + ) { + delete menuProps[ 'aria-activedescendant' ]; + } + return ( + <div className={ classnames( 'components-custom-select-control', className ) }> + { /* eslint-disable-next-line jsx-a11y/label-has-associated-control, jsx-a11y/label-has-for */ } + <label + { ...getLabelProps( { + className: classnames( 'components-custom-select-control__label', { + 'screen-reader-text': hideLabelFromVision, + } ), + } ) } + > + { label } + </label> + <Button + { ...getToggleButtonProps( { + // This is needed because some speech recognition software don't support `aria-labelledby`. + 'aria-label': label, + 'aria-labelledby': undefined, + className: 'components-custom-select-control__button', + } ) } + > + { itemToString( selectedItem ) } + <Dashicon + icon="arrow-down-alt2" + className="components-custom-select-control__button-icon" + /> + </Button> + <ul { ...menuProps }> + { isOpen && + items.map( ( item, index ) => ( + // eslint-disable-next-line react/jsx-key + <li + { ...getItemProps( { + item, + index, + key: item.key, + className: classnames( + 'components-custom-select-control__item', + { + 'is-highlighted': index === highlightedIndex, + } + ), + style: item.style, + } ) } + > + { item === selectedItem && ( + <Dashicon + icon="saved" + className="components-custom-select-control__item-icon" + /> + ) } + { item.name } + </li> + ) ) } + </ul> + </div> + ); +} diff --git a/packages/components/src/custom-select-control/stories/index.js b/packages/components/src/custom-select-control/stories/index.js new file mode 100644 index 00000000000000..4313e736a24617 --- /dev/null +++ b/packages/components/src/custom-select-control/stories/index.js @@ -0,0 +1,32 @@ +/** + * Internal dependencies + */ +import CustomSelectControl from '../'; + +export default { title: 'CustomSelectControl', component: CustomSelectControl }; + +const options = [ + { + key: 'small', + name: 'Small', + style: { fontSize: '50%' }, + }, + { + key: 'normal', + name: 'Normal', + style: { fontSize: '100%' }, + }, + { + key: 'large', + name: 'Large', + style: { fontSize: '200%' }, + }, + { + key: 'huge', + name: 'Huge', + style: { fontSize: '300%' }, + }, +]; +export const _default = () => ( + <CustomSelectControl label="Font Size" options={ options } /> +); diff --git a/packages/components/src/custom-select-control/style.scss b/packages/components/src/custom-select-control/style.scss new file mode 100644 index 00000000000000..fbd2d46bb5cb4e --- /dev/null +++ b/packages/components/src/custom-select-control/style.scss @@ -0,0 +1,56 @@ +.components-custom-select-control { + color: $dark-gray-500; + position: relative; +} + +.components-custom-select-control__label { + display: block; + margin-bottom: 5px; +} + +.components-custom-select-control__button { + border: 1px solid $dark-gray-200; + border-radius: 4px; + color: $dark-gray-500; + display: inline; + min-height: 30px; + min-width: 130px; + position: relative; + text-align: left; + + &:focus { + border-color: $blue-medium-500; + } + + &-icon { + height: 100%; + padding: 0 4px; + position: absolute; + right: 0; + top: 0; + } +} + +.components-custom-select-control__menu { + background: $white; + padding: 0; + position: absolute; + width: 100%; + z-index: z-index(".components-popover"); +} + +.components-custom-select-control__item { + align-items: center; + display: flex; + list-style-type: none; + padding: 10px 5px 10px 25px; + + &.is-highlighted { + background: $light-gray-500; + } + + &-icon { + margin-left: -20px; + margin-right: 0; + } +} diff --git a/packages/components/src/dashicon/stories/index.js b/packages/components/src/dashicon/stories/index.js new file mode 100644 index 00000000000000..1baa2a69b3f5a4 --- /dev/null +++ b/packages/components/src/dashicon/stories/index.js @@ -0,0 +1,25 @@ +/** + * External dependencies + */ +import { number, text } from '@storybook/addon-knobs'; + +/** + * Internal dependencies + */ +import Dashicon from '../'; + +export default { title: 'Components|Dashicon', component: Dashicon }; + +export const _default = () => { + const icon = text( 'Icon', 'wordpress' ); + const color = text( 'Color', '#0079AA' ); + const size = number( 'Size', 20 ); + + return ( + <Dashicon + icon={ icon } + color={ color } + size={ size } + /> + ); +}; diff --git a/packages/components/src/date-time/date.js b/packages/components/src/date-time/date.js index 7361494a61e688..7b1accff50227d 100644 --- a/packages/components/src/date-time/date.js +++ b/packages/components/src/date-time/date.js @@ -65,7 +65,7 @@ class DatePicker extends Component { * object representing now. If a null value is passed, return a null value. * * @param {?string} currentDate Date representing the currently selected date or null to signify no selection. - * @return {?Moment} Moment object for selected date or null. + * @return {?moment.Moment} Moment object for selected date or null. */ getMomentDate( currentDate ) { if ( null === currentDate ) { diff --git a/packages/components/src/date-time/index.js b/packages/components/src/date-time/index.js index 97bd891cac9a7c..bb7434d2e82ce3 100644 --- a/packages/components/src/date-time/index.js +++ b/packages/components/src/date-time/index.js @@ -34,7 +34,7 @@ export class DateTimePicker extends Component { } render() { - const { currentDate, is12Hour, onChange } = this.props; + const { currentDate, is12Hour, isInvalidDate, onChange } = this.props; return ( <div className="components-datetime"> @@ -48,6 +48,7 @@ export class DateTimePicker extends Component { <DatePicker currentDate={ currentDate } onChange={ onChange } + isInvalidDate={ isInvalidDate } /> </> ) } diff --git a/packages/components/src/date-time/style.scss b/packages/components/src/date-time/style.scss index 2863fb908ff107..d797a695f28e25 100644 --- a/packages/components/src/date-time/style.scss +++ b/packages/components/src/date-time/style.scss @@ -5,8 +5,10 @@ /*rtl:end:ignore*/ .components-datetime { + padding: $grid-size-large; + .components-datetime__calendar-help { - padding: $grid-size; + padding: $grid-size-large; h4 { margin: 0; @@ -16,8 +18,6 @@ .components-datetime__date-help-button { display: block; margin-left: auto; - margin-right: $grid-size; - margin-top: 0.5em; } fieldset { @@ -28,21 +28,33 @@ select, input { - box-sizing: border-box; - height: 28px; - vertical-align: middle; - padding: 0; @include input-style__neutral(); } + + // Override inherited conflicting styles to be consistent. + select, + input[type="number"], + .components-button { + height: 30px; + margin-top: 0; + margin-bottom: 0; + } } .components-datetime__date { min-height: 236px; border-top: 1px solid $light-gray-500; - margin-left: -$grid-size; - margin-right: -$grid-size; // Override external DatePicker styles. + .CalendarMonthGrid { + // The included component contains an arbitrary 13px padding that misaligns things. + margin-left: -13px; + } + + .DayPickerNavigation_leftButton__horizontalDefault { + left: 0; + } + .CalendarMonth_caption { font-size: $default-font-size; } @@ -73,6 +85,11 @@ .DayPicker_weekHeader { top: 50px; + .DayPicker_weekHeader_ul { + margin: 1px 0; + padding-left: 0; + padding-right: 0; + } } &.is-description-visible .DayPicker, @@ -82,11 +99,15 @@ } .components-datetime__time { - margin-bottom: 1em; + padding-bottom: $grid-size-large; fieldset { - margin-top: 0.5em; position: relative; + margin-bottom: 0.5em; + } + + fieldset + fieldset { + margin-bottom: 0; } .components-datetime__time-field-am-pm fieldset { @@ -119,8 +140,8 @@ z-index: 1; } - .components-datetime__time-am-button.is-toggled, - .components-datetime__time-pm-button.is-toggled { + .components-datetime__time-am-button.is-pressed, + .components-datetime__time-pm-button.is-pressed { background: $light-gray-300; border-color: $dark-gray-100; box-shadow: inset 0 2px 5px -3px $dark-gray-500; @@ -216,9 +237,3 @@ .components-popover .components-datetime__date { padding-left: 4px; } - -// Used to prevent z-index issues on mobile. -// See: https://github.com/WordPress/gutenberg/pull/7621#issuecomment-424322735 -.components-popover.edit-post-post-schedule__dialog.is-bottom.is-left { - z-index: 100000; -} diff --git a/packages/components/src/date-time/test/__snapshots__/time.js.snap b/packages/components/src/date-time/test/__snapshots__/time.js.snap index d67e11da54846e..b0e421cea3ff5f 100644 --- a/packages/components/src/date-time/test/__snapshots__/time.js.snap +++ b/packages/components/src/date-time/test/__snapshots__/time.js.snap @@ -321,19 +321,17 @@ exports[`TimePicker matches the snapshot when the is12hour prop is specified 1`] className="components-datetime__time-field components-datetime__time-field-am-pm" > <ForwardRef(Button) - aria-pressed={false} className="components-datetime__time-am-button" isDefault={true} - isToggled={false} + isPressed={false} onClick={[Function]} > AM </ForwardRef(Button)> <ForwardRef(Button) - aria-pressed={true} className="components-datetime__time-pm-button" isDefault={true} - isToggled={true} + isPressed={true} onClick={[Function]} > PM @@ -504,19 +502,17 @@ exports[`TimePicker matches the snapshot when the is12hour prop is true 1`] = ` className="components-datetime__time-field components-datetime__time-field-am-pm" > <ForwardRef(Button) - aria-pressed={false} className="components-datetime__time-am-button" isDefault={true} - isToggled={false} + isPressed={false} onClick={[Function]} > AM </ForwardRef(Button)> <ForwardRef(Button) - aria-pressed={true} className="components-datetime__time-pm-button" isDefault={true} - isToggled={true} + isPressed={true} onClick={[Function]} > PM diff --git a/packages/components/src/date-time/time.js b/packages/components/src/date-time/time.js index 0cfc8c7dc8ea68..e2dc04386618e1 100644 --- a/packages/components/src/date-time/time.js +++ b/packages/components/src/date-time/time.js @@ -301,19 +301,17 @@ class TimePicker extends Component { { is12Hour && ( <div className="components-datetime__time-field components-datetime__time-field-am-pm"> <Button - aria-pressed={ am === 'AM' } isDefault className="components-datetime__time-am-button" - isToggled={ am === 'AM' } + isPressed={ am === 'AM' } onClick={ this.updateAmPm( 'AM' ) } > { __( 'AM' ) } </Button> <Button - aria-pressed={ am === 'PM' } isDefault className="components-datetime__time-pm-button" - isToggled={ am === 'PM' } + isPressed={ am === 'PM' } onClick={ this.updateAmPm( 'PM' ) } > { __( 'PM' ) } diff --git a/packages/components/src/dimension-control/README.md b/packages/components/src/dimension-control/README.md new file mode 100644 index 00000000000000..435fdf4e73b046 --- /dev/null +++ b/packages/components/src/dimension-control/README.md @@ -0,0 +1,120 @@ +DimensionControl +============================= + +`DimensionControl` is a component designed to provide a UI to control spacing and/or dimensions. + +## Usage + +In a block's `edit` implementation, render a `<DimensionControl />` component. + + +```jsx +import { registerBlockType } from '@wordpress/blocks'; +import { __ } from '@wordpress/i18n'; +import { + DimensionControl, +} from '@wordpress/block-editor'; + +registerBlockType( 'my-plugin/my-block', { + // ... + + attributes: { + // other attributes here + // ... + + paddingSize: { + type: 'string', + }, + }, + + edit( { attributes, setAttributes, clientId } ) { + + const { paddingSize } = attributes; + + + const updateSpacing = ( dimension, size, device = '' ) => { + setAttributes( { + [ `${ dimension }${ device }` ]: size, + } ); + }; + + return ( + <DimensionControl + label={ __( 'Padding' ) } + icon={ 'desktop' } + onChange={ partialRight( updateSpacing, 'paddingSize' ) } + value={ paddingSize } + /> + ); + } +} ); +``` + +_Note:_ it is recommended to partially apply the value of the Block attribute to be updated (eg: `paddingSize`, `marginSize`...etc) to your callback functions. This avoids the need to unnecessarily couple the component to the Block attribute schema. + +_Note:_ by default, if you do not provide an initial `value` prop for the current dimension value, then no value will be selected (ie: there is no default dimension set). + +## Props + +### `label` +* **Type:** `String` +* **Default:** `undefined` +* **Required:** Yes + +The human readable label for the control. + +### `value` +* **Type:** `String` +* **Default:** `''` +* **Required:** No + +The current value of the dimension UI control. If provided the UI with automatically select the value. + +### `sizes` +* **Type:** `Array` +* **Default:** See `packages/block-editor/src/components/dimension-control/sizes.js` +* **Required:** No + +An optional array of size objects in the following shape: + +``` +[ + { + name: __( 'Small' ), + slug: 'small', + }, + { + name: __( 'Medium' ), + slug: 'small', + }, + // ...etc +] +``` + +By default a set of relative sizes (`small`, `medium`...etc) are provided. See `packages/block-editor/src/components/dimension-control/sizes.js`. + +### `icon` +* **Type:** `String` +* **Default:** `undefined` +* **Required:** No + +An optional dashicon to display before to the control label. + +### `onChange` +* **Type:** `Function` +* **Default:** `undefined` +* **Required:** No +* **Arguments:**: + - `size` - a string representing the selected size (eg: `medium`) + +A callback which is triggered when a spacing size value changes (is selected/clicked). + + +### `className` +* **Type:** `String` +* **Default:** `''` +* **Required:** No + +A string of classes to be added to the control component. + + diff --git a/packages/components/src/dimension-control/index.js b/packages/components/src/dimension-control/index.js new file mode 100644 index 00000000000000..c69b80f5c49e02 --- /dev/null +++ b/packages/components/src/dimension-control/index.js @@ -0,0 +1,76 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { isFunction } from 'lodash'; + +/** + * WordPress dependencies + */ +/** + * Internal dependencies + */ +import { + Icon, + SelectControl, +} from '../'; +import { __ } from '@wordpress/i18n'; + +import { + Fragment, +} from '@wordpress/element'; + +/** + * Internal dependencies + */ +import sizesTable, { findSizeBySlug } from './sizes'; + +export function DimensionControl( props ) { + const { label, value, sizes = sizesTable, icon, onChange, className = '' } = props; + + const onChangeSpacingSize = ( val ) => { + const theSize = findSizeBySlug( sizes, val ); + + if ( ! theSize || value === theSize.slug ) { + onChange( undefined ); + } else if ( isFunction( onChange ) ) { + onChange( theSize.slug ); + } + }; + + const formatSizesAsOptions = ( theSizes ) => { + const options = theSizes.map( ( { name, slug } ) => ( { + label: name, + value: slug, + } ) ); + + return [ { + label: __( 'Default' ), + value: '', + } ].concat( options ); + }; + + const selectLabel = ( + <Fragment> + { icon && ( + <Icon + icon={ icon } + /> + ) } + { label } + </Fragment> + ); + + return ( + <SelectControl + className={ classnames( className, 'block-editor-dimension-control' ) } + label={ selectLabel } + hideLabelFromVision={ false } + value={ value } + onChange={ onChangeSpacingSize } + options={ formatSizesAsOptions( sizes ) } + /> + ); +} + +export default DimensionControl; diff --git a/packages/components/src/dimension-control/sizes.js b/packages/components/src/dimension-control/sizes.js new file mode 100644 index 00000000000000..41fa0717028725 --- /dev/null +++ b/packages/components/src/dimension-control/sizes.js @@ -0,0 +1,45 @@ +/** + * Sizes + * + * defines the sizes used in dimension controls + * all hardcoded `size` values are based on the value of + * the Sass variable `$block-padding` from + * `packages/block-editor/src/components/dimension-control/sizes.js`. + */ + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Finds the correct size object from the provided sizes + * table by size slug (eg: `medium`) + * + * @param {Array} sizes containing objects for each size definition + * @param {string} slug a string representation of the size (eg: `medium`) + * @return {Object} the matching size definition + */ +export const findSizeBySlug = ( sizes, slug ) => sizes.find( ( size ) => slug === size.slug ); + +export default [ + { + name: __( 'None' ), + slug: 'none', + }, + { + name: __( 'Small' ), + slug: 'small', + }, + { + name: __( 'Medium' ), + slug: 'medium', + }, + { + name: __( 'Large' ), + slug: 'large', + }, { + name: __( 'Extra Large' ), + slug: 'xlarge', + }, +]; diff --git a/packages/components/src/dimension-control/style.scss b/packages/components/src/dimension-control/style.scss new file mode 100644 index 00000000000000..7f1481747dfe1a --- /dev/null +++ b/packages/components/src/dimension-control/style.scss @@ -0,0 +1,22 @@ +.block-editor-dimension-control { + + .components-base-control__field { + display: flex; + align-items: center; + } + + .components-base-control__label { + display: flex; + align-items: center; + margin-right: 1em; + margin-bottom: 0; + + .dashicon { + margin-right: 0.5em; + } + } + + &.is-manual .components-base-control__label { + width: 10em; + } +} diff --git a/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap b/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap new file mode 100644 index 00000000000000..c06662ee862c99 --- /dev/null +++ b/packages/components/src/dimension-control/test/__snapshots__/index.test.js.snap @@ -0,0 +1,163 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DimensionControl rendering renders with custom sizes 1`] = ` +<WithInstanceId(SelectControl) + className="block-editor-dimension-control" + hideLabelFromVision={false} + label={ + <React.Fragment> + Custom Dimension + </React.Fragment> + } + onChange={[Function]} + options={ + Array [ + Object { + "label": "Default", + "value": "", + }, + Object { + "label": "Mini", + "value": "mini", + }, + Object { + "label": "Middle", + "value": "middle", + }, + Object { + "label": "Giant", + "value": "giant", + }, + ] + } +/> +`; + +exports[`DimensionControl rendering renders with defaults 1`] = ` +<WithInstanceId(SelectControl) + className="block-editor-dimension-control" + hideLabelFromVision={false} + label={ + <React.Fragment> + Padding + </React.Fragment> + } + onChange={[Function]} + options={ + Array [ + Object { + "label": "Default", + "value": "", + }, + Object { + "label": "None", + "value": "none", + }, + Object { + "label": "Small", + "value": "small", + }, + Object { + "label": "Medium", + "value": "medium", + }, + Object { + "label": "Large", + "value": "large", + }, + Object { + "label": "Extra Large", + "value": "xlarge", + }, + ] + } +/> +`; + +exports[`DimensionControl rendering renders with icon and custom icon label 1`] = ` +<WithInstanceId(SelectControl) + className="block-editor-dimension-control" + hideLabelFromVision={false} + label={ + <React.Fragment> + <Icon + icon="tablet" + /> + Margin + </React.Fragment> + } + onChange={[Function]} + options={ + Array [ + Object { + "label": "Default", + "value": "", + }, + Object { + "label": "None", + "value": "none", + }, + Object { + "label": "Small", + "value": "small", + }, + Object { + "label": "Medium", + "value": "medium", + }, + Object { + "label": "Large", + "value": "large", + }, + Object { + "label": "Extra Large", + "value": "xlarge", + }, + ] + } +/> +`; + +exports[`DimensionControl rendering renders with icon and default icon label 1`] = ` +<WithInstanceId(SelectControl) + className="block-editor-dimension-control" + hideLabelFromVision={false} + label={ + <React.Fragment> + <Icon + icon="tablet" + /> + Margin + </React.Fragment> + } + onChange={[Function]} + options={ + Array [ + Object { + "label": "Default", + "value": "", + }, + Object { + "label": "None", + "value": "none", + }, + Object { + "label": "Small", + "value": "small", + }, + Object { + "label": "Medium", + "value": "medium", + }, + Object { + "label": "Large", + "value": "large", + }, + Object { + "label": "Extra Large", + "value": "xlarge", + }, + ] + } +/> +`; diff --git a/packages/components/src/dimension-control/test/index.test.js b/packages/components/src/dimension-control/test/index.test.js new file mode 100644 index 00000000000000..0b053a493ee123 --- /dev/null +++ b/packages/components/src/dimension-control/test/index.test.js @@ -0,0 +1,128 @@ +/** + * External dependencies + */ +import { shallow, mount } from 'enzyme'; +import { uniqueId } from 'lodash'; + +/** + * Internal dependencies + */ +import { DimensionControl } from '../'; + +describe( 'DimensionControl', () => { + const onChangeHandler = jest.fn(); + + afterEach( () => { + onChangeHandler.mockClear(); + } ); + + describe( 'rendering', () => { + it( 'renders with defaults', () => { + const wrapper = shallow( + <DimensionControl + instanceId={ uniqueId() } + label={ 'Padding' } + /> + ); + expect( wrapper ).toMatchSnapshot(); + } ); + + it( 'renders with icon and default icon label', () => { + const wrapper = shallow( + <DimensionControl + instanceId={ uniqueId() } + label={ 'Margin' } + icon={ 'tablet' } + /> + ); + expect( wrapper ).toMatchSnapshot(); + } ); + + it( 'renders with icon and custom icon label', () => { + const wrapper = shallow( + <DimensionControl + instanceId={ uniqueId() } + label={ 'Margin' } + icon={ 'tablet' } + iconLabel={ 'Tablet Devices' } + /> + ); + expect( wrapper ).toMatchSnapshot(); + } ); + + it( 'renders with custom sizes', () => { + const customSizes = [ + { + name: 'Mini', + size: 1, + slug: 'mini', + }, + { + name: 'Middle', + size: 5, + slug: 'middle', + }, + { + name: 'Giant', + size: 10, + slug: 'giant', + }, + ]; + + const wrapper = shallow( + <DimensionControl + instanceId={ uniqueId() } + label={ 'Custom Dimension' } + sizes={ customSizes } + /> + ); + expect( wrapper ).toMatchSnapshot(); + } ); + } ); + + describe( 'callbacks', () => { + it( 'should call onChange handler with correct args on size change', () => { + const wrapper = mount( + <DimensionControl + instanceId={ uniqueId() } + label={ 'Padding' } + onChange={ onChangeHandler } + /> + ); + + wrapper.find( 'select' ).at( 0 ).simulate( 'change', { + target: { + value: 'small', + }, + } ); + + wrapper.find( 'select' ).at( 0 ).simulate( 'change', { + target: { + value: 'medium', + }, + } ); + + expect( onChangeHandler ).toHaveBeenCalledTimes( 2 ); + expect( onChangeHandler.mock.calls[ 0 ][ 0 ] ).toEqual( 'small' ); + expect( onChangeHandler.mock.calls[ 1 ][ 0 ] ).toEqual( 'medium' ); + } ); + + it( 'should call onChange handler with undefined value when no size is provided on change', () => { + const wrapper = mount( + <DimensionControl + instanceId={ uniqueId() } + label={ 'Padding' } + onChange={ onChangeHandler } + /> + ); + + wrapper.find( 'select' ).at( 0 ).simulate( 'change', { + target: { + value: '', // this happens when you select the "default" <option /> + }, + } ); + + expect( onChangeHandler ).toHaveBeenNthCalledWith( 1, undefined ); + } ); + } ); +} ); diff --git a/packages/components/src/draggable/README.md b/packages/components/src/draggable/README.md index 34d4fbecea28f9..f76c983a8d999e 100644 --- a/packages/components/src/draggable/README.md +++ b/packages/components/src/draggable/README.md @@ -51,16 +51,17 @@ const MyDraggable = () => ( elementId="draggable-panel" transferData={ { } } > - { - ( { onDraggableStart, onDraggableEnd } ) => ( - <Dashicon - icon="move" - onDragStart={ onDraggableStart } - onDragEnd={ onDraggableEnd } - draggable - /> - ) - } + { + ( { onDraggableStart, onDraggableEnd } ) => ( + <div className="example-drag-handle" draggable> + <Dashicon + icon="move" + onDragStart={ onDraggableStart } + onDragEnd={ onDraggableEnd } + /> + </div> + ) + } </Draggable> </PanelBody> </Panel> @@ -83,16 +84,17 @@ const MyDraggable = ( { onDragStart, onDragEnd } ) => ( onDragStart={ onDragStart } onDragEnd={ onDragEnd } > - { - ( { onDraggableStart, onDraggableEnd } ) => ( - <Dashicon - icon="move" - onDragStart={ onDraggableStart } - onDragEnd={ onDraggableEnd } - draggable - /> - ) - } + { + ( { onDraggableStart, onDraggableEnd } ) => ( + <div className="example-drag-handle" draggable> + <Dashicon + icon="move" + onDragStart={ onDraggableStart } + onDragEnd={ onDraggableEnd } + /> + </div> + ) + } </Draggable> </PanelBody> </Panel> diff --git a/packages/components/src/draggable/stories/index.js b/packages/components/src/draggable/stories/index.js new file mode 100644 index 00000000000000..24db493a4616ff --- /dev/null +++ b/packages/components/src/draggable/stories/index.js @@ -0,0 +1,69 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import Draggable from '../'; +import Dashicon from '../../dashicon'; + +export default { title: 'Components|Draggable', component: Draggable }; + +const Box = ( props ) => { + return ( + <div + { ...props } + style={ { + alignItems: 'center', + display: 'flex', + justifyContent: 'center', + width: 100, + height: 100, + background: '#ddd', + } } + /> + ); +}; + +const DraggalbeExample = () => { + const [ isDragging, setDragging ] = useState( false ); + + // Allow for the use of ID in the example + /* eslint-disable no-restricted-syntax */ + return ( + <div> + <p>Is Dragging? { isDragging ? 'Yes' : 'No' }</p> + <div id="draggable-example-box" style={ { display: 'inline-flex' } }> + <Draggable elementId="draggable-example-box"> + { ( { onDraggableStart, onDraggableEnd } ) => { + const handleOnDragStart = ( event ) => { + setDragging( true ); + onDraggableStart( event ); + }; + const handleOnDragEnd = ( event ) => { + setDragging( false ); + onDraggableEnd( event ); + }; + + return ( + <Box + onDragStart={ handleOnDragStart } + onDragEnd={ handleOnDragEnd } + draggable + > + <Dashicon icon="move" /> + </Box> + ); + } } + </Draggable> + </div> + </div> + ); + /* eslint-enable no-restricted-syntax */ +}; + +export const _default = () => { + return <DraggalbeExample />; +}; diff --git a/packages/components/src/drop-zone/index.js b/packages/components/src/drop-zone/index.js index 07e384e22f9476..0f9adbc296ec00 100644 --- a/packages/components/src/drop-zone/index.js +++ b/packages/components/src/drop-zone/index.js @@ -58,10 +58,14 @@ class DropZoneComponent extends Component { } render() { - const { className, label } = this.props; + const { className, label, onFilesDrop, onHTMLDrop, onDrop } = this.props; const { isDraggingOverDocument, isDraggingOverElement, position, type } = this.state; const classes = classnames( 'components-drop-zone', className, { - 'is-active': isDraggingOverDocument || isDraggingOverElement, + 'is-active': ( isDraggingOverDocument || isDraggingOverElement ) && ( + ( type === 'file' && onFilesDrop ) || + ( type === 'html' && onHTMLDrop ) || + ( type === 'default' && onDrop ) + ), 'is-dragging-over-document': isDraggingOverDocument, 'is-dragging-over-element': isDraggingOverElement, 'is-close-to-top': position && position.y === 'top', diff --git a/packages/components/src/dropdown-menu/index.js b/packages/components/src/dropdown-menu/index.js index e94b246231ad1b..768e2edff5209d 100644 --- a/packages/components/src/dropdown-menu/index.js +++ b/packages/components/src/dropdown-menu/index.js @@ -98,8 +98,18 @@ function DropdownMenu( { <IconButton { ...mergedToggleProps } icon={ icon } - onClick={ onToggle } - onKeyDown={ openOnArrowDown } + onClick={ ( event ) => { + onToggle( event ); + if ( mergedToggleProps.onClick ) { + mergedToggleProps.onClick( event ); + } + } } + onKeyDown={ ( event ) => { + openOnArrowDown( event ); + if ( mergedToggleProps.onKeyDown ) { + mergedToggleProps.onKeyDown( event ); + } + } } aria-haspopup="true" aria-expanded={ isOpen } label={ label } diff --git a/packages/components/src/dropdown-menu/style.scss b/packages/components/src/dropdown-menu/style.scss index 9efe547e19d807..dbd307873dfcf9 100644 --- a/packages/components/src/dropdown-menu/style.scss +++ b/packages/components/src/dropdown-menu/style.scss @@ -1,40 +1,5 @@ -.components-dropdown-menu { - padding: 3px; - display: flex; - - .components-dropdown-menu__toggle { - width: auto; - margin: 0; - padding: 4px; - border: $border-width solid transparent; - display: flex; - flex-direction: row; - - &.is-active, - &.is-active:hover { - box-shadow: none; - background-color: $dark-gray-500; - color: $white; - } - - &:focus::before { - top: -3px; - right: -3px; - bottom: -3px; - left: -3px; - } - - &:hover, - &:focus, - &:not(:disabled):not([aria-disabled="true"]):not(.is-default):hover { - @include formatting-button-style__hover(); - } - - // Add a dropdown arrow indicator. - .components-dropdown-menu__indicator::after { - @include dropdown-arrow(); - } - } +.components-dropdown-menu__indicator::after { + @include dropdown-arrow(); } .components-dropdown-menu__popover .components-popover__content { diff --git a/packages/components/src/dropdown/index.js b/packages/components/src/dropdown/index.js index 6c5cffa6e5e4b7..02de89eda69143 100644 --- a/packages/components/src/dropdown/index.js +++ b/packages/components/src/dropdown/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -62,6 +67,9 @@ class Dropdown extends Component { } close() { + if ( this.props.onClose ) { + this.props.onClose(); + } this.setState( { isOpen: false } ); } @@ -82,7 +90,7 @@ class Dropdown extends Component { const args = { isOpen, onToggle: this.toggle, onClose: this.close }; return ( - <div className={ className } ref={ this.containerRef }> + <div className={ classnames( 'components-dropdown', className ) } ref={ this.containerRef }> { renderToggle( args ) } { isOpen && ( <Popover diff --git a/packages/components/src/dropdown/stories/index.js b/packages/components/src/dropdown/stories/index.js new file mode 100644 index 00000000000000..ce24a8c2be6b75 --- /dev/null +++ b/packages/components/src/dropdown/stories/index.js @@ -0,0 +1,86 @@ +/** + * Internal dependencies + */ +import Dropdown from '../'; +import IconButton from '../../icon-button'; +import MenuGroup from '../../menu-group'; +import MenuItem from '../../menu-item'; +import DropdownMenu from '../../dropdown-menu'; + +export default { title: 'Components|Dropdown', component: Dropdown }; + +const DropdownAndDropdownMenuExample = () => { + return ( + <> + <div> + <p>This is a DropdownMenu component:</p> + <DropdownMenu + icon="move" + label="Select a direction" + controls={ [ + { + title: 'Up', + icon: 'arrow-up-alt', + }, + { + title: 'Right', + icon: 'arrow-right-alt', + }, + { + title: 'Down', + icon: 'arrow-down-alt', + }, + { + title: 'Left', + icon: 'arrow-left-alt', + }, + ] } + /> + </div> + <div> + <p>This is an assembled Dropdown component:</p> + <Dropdown + className="my-container-class-name" + contentClassName="my-popover-content-classname" + position="bottom right" + renderToggle={ ( { isOpen, onToggle } ) => ( + <IconButton + icon="move" + onClick={ onToggle } + aria-expanded={ isOpen } + label="Select a direction" + /> + ) } + renderContent={ () => ( + <MenuGroup> + <MenuItem + icon="arrow-up-alt" + > + Up + </MenuItem> + <MenuItem + icon="arrow-down-alt" + > + Down + </MenuItem> + <MenuItem + icon="arrow-left-alt" + > + Left + </MenuItem> + <MenuItem + icon="arrow-right-alt" + > + Right + </MenuItem> + </MenuGroup> + ) } + /> + </div> + </> + ); +}; + +export const _default = () => { + return <DropdownAndDropdownMenuExample />; +}; diff --git a/packages/components/src/dropdown/style.scss b/packages/components/src/dropdown/style.scss new file mode 100644 index 00000000000000..8aeea6338019a3 --- /dev/null +++ b/packages/components/src/dropdown/style.scss @@ -0,0 +1,3 @@ +.components-dropdown { + display: inline-block; +} diff --git a/packages/components/src/external-link/index.js b/packages/components/src/external-link/index.js index 68bf56394b8f86..cc9750a4b779c9 100644 --- a/packages/components/src/external-link/index.js +++ b/packages/components/src/external-link/index.js @@ -14,6 +14,7 @@ import { forwardRef } from '@wordpress/element'; * Internal dependencies */ import Dashicon from '../dashicon'; +import VisuallyHidden from '../visually-hidden'; export function ExternalLink( { href, children, className, rel = '', ...additionalProps }, ref ) { rel = uniq( compact( [ @@ -27,12 +28,12 @@ export function ExternalLink( { href, children, className, rel = '', ...addition // eslint-disable-next-line react/jsx-no-target-blank <a { ...additionalProps } className={ classes } href={ href } target="_blank" rel={ rel } ref={ ref }> { children } - <span className="screen-reader-text"> + <VisuallyHidden as="span"> { /* translators: accessibility text */ __( '(opens in a new tab)' ) } - </span> + </VisuallyHidden> <Dashicon icon="external" className="components-external-link__icon" /> </a> ); diff --git a/packages/components/src/external-link/stories/index.js b/packages/components/src/external-link/stories/index.js new file mode 100644 index 00000000000000..29a8e921c46bdf --- /dev/null +++ b/packages/components/src/external-link/stories/index.js @@ -0,0 +1,17 @@ +/** + * External dependencies + */ +import { text } from '@storybook/addon-knobs'; +/** + * Internal dependencies + */ +import ExternalLink from '../'; + +export default { title: 'Components|ExternalLink', component: ExternalLink }; + +export const _default = () => { + const title = text( 'children', 'WordPress' ); + const href = text( 'href', 'https://wordpress.org' ); + + return <ExternalLink href={ href }>{ title }</ExternalLink>; +}; diff --git a/packages/components/src/focal-point-picker/index.js b/packages/components/src/focal-point-picker/index.js index d502ebbded0e03..3faa87ccdf6455 100644 --- a/packages/components/src/focal-point-picker/index.js +++ b/packages/components/src/focal-point-picker/index.js @@ -102,8 +102,8 @@ export class FocalPointPicker extends Component { ) ); const percentages = { - x: ( left - bounds.left ) / ( pickerDimensions.width - ( bounds.left * 2 ) ), - y: ( top - bounds.top ) / ( pickerDimensions.height - ( bounds.top * 2 ) ), + x: ( ( left - bounds.left ) / ( pickerDimensions.width - ( bounds.left * 2 ) ) ).toFixed( 2 ), + y: ( ( top - bounds.top ) / ( pickerDimensions.height - ( bounds.top * 2 ) ) ).toFixed( 2 ), }; this.setState( { percentages }, function() { onChange( { @@ -126,7 +126,7 @@ export class FocalPointPicker extends Component { const { onChange } = this.props; const { percentages } = this.state; const cleanValue = Math.max( Math.min( parseInt( value ), 100 ), 0 ); - percentages[ axis ] = cleanValue ? cleanValue / 100 : 0; + percentages[ axis ] = ( cleanValue ? cleanValue / 100 : 0 ).toFixed( 2 ); this.setState( { percentages }, function() { onChange( { x: this.state.percentages.x, diff --git a/packages/components/src/font-size-picker/index.js b/packages/components/src/font-size-picker/index.js index a52f6ccd841d2f..429e6fd8a10fa5 100644 --- a/packages/components/src/font-size-picker/index.js +++ b/packages/components/src/font-size-picker/index.js @@ -4,13 +4,14 @@ */ import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +import { withInstanceId } from '@wordpress/compose'; /** * Internal dependencies */ import Button from '../button'; import RangeControl from '../range-control'; -import SelectControl from '../select-control'; +import CustomSelectControl from '../custom-select-control'; function getSelectValueFromFontSize( fontSizes, value ) { if ( value ) { @@ -20,11 +21,18 @@ function getSelectValueFromFontSize( fontSizes, value ) { return 'normal'; } -function getSelectOptions( optionsArray ) { - return [ - ...optionsArray.map( ( option ) => ( { value: option.slug, label: option.name } ) ), - { value: 'custom', label: __( 'Custom' ) }, - ]; +function getSelectOptions( optionsArray, disableCustomFontSizes ) { + if ( ! disableCustomFontSizes ) { + optionsArray = [ + ...optionsArray, + { slug: 'custom', name: __( 'Custom' ) }, + ]; + } + return optionsArray.map( ( option ) => ( { + key: option.slug, + name: option.name, + style: { fontSize: option.size }, + } ) ); } function FontSizePicker( { @@ -34,6 +42,7 @@ function FontSizePicker( { onChange, value, withSlider = false, + instanceId, } ) { const [ currentSelectValue, setCurrentSelectValue ] = useState( getSelectValueFromFontSize( fontSizes, value ) ); @@ -51,42 +60,51 @@ function FontSizePicker( { onChange( Number( newValue ) ); }; - const onSelectChangeValue = ( eventValue ) => { - setCurrentSelectValue( eventValue ); - const selectedFont = fontSizes.find( ( font ) => font.slug === eventValue ); - if ( selectedFont ) { - onChange( selectedFont.size ); - } + const onSelectChangeValue = ( { selectedItem } ) => { + setCurrentSelectValue( selectedItem.key ); + onChange( selectedItem.style && selectedItem.style.fontSize ); + }; + + const onSliderChangeValue = ( sliderValue ) => { + onChange( sliderValue ); + setCurrentSelectValue( getSelectValueFromFontSize( fontSizes, sliderValue ) ); }; + const options = getSelectOptions( fontSizes, disableCustomFontSizes ); + const rangeControlNumberId = `components-range-control__number#${ instanceId }`; return ( - <fieldset> - <legend> + <fieldset className="components-font-size-picker"> + <legend className="screen-reader-text"> { __( 'Font Size' ) } </legend> <div className="components-font-size-picker__controls"> - { ( fontSizes.length > 0 ) && - <SelectControl + { fontSizes.length > 0 && ( + <CustomSelectControl className={ 'components-font-size-picker__select' } - label={ 'Choose preset' } - hideLabelFromVision={ true } - value={ currentSelectValue } + label={ __( 'Preset Size' ) } + options={ options } + value={ + options.find( ( option ) => option.key === currentSelectValue ) || + options[ 0 ] + } onChange={ onSelectChangeValue } - options={ getSelectOptions( fontSizes ) } /> - } + ) } { ( ! withSlider && ! disableCustomFontSizes ) && - <input - className="components-range-control__number" - type="number" - onChange={ onChangeValue } - aria-label={ __( 'Custom' ) } - value={ value || '' } - /> + <div className="components-range-control__number-container"> + <label htmlFor={ rangeControlNumberId }>{ __( 'Custom' ) }</label> + <input + id={ rangeControlNumberId } + className="components-range-control__number" + type="number" + onChange={ onChangeValue } + aria-label={ __( 'Custom' ) } + value={ value || '' } + /> + </div> } <Button className="components-color-palette__clear" - type="button" disabled={ value === undefined } onClick={ () => { onChange( undefined ); @@ -104,7 +122,7 @@ function FontSizePicker( { label={ __( 'Custom Size' ) } value={ value || '' } initialPosition={ fallbackFontSize } - onChange={ onChange } + onChange={ onSliderChangeValue } min={ 12 } max={ 100 } beforeIcon="editor-textcolor" @@ -115,4 +133,4 @@ function FontSizePicker( { ); } -export default FontSizePicker; +export default withInstanceId( FontSizePicker ); diff --git a/packages/components/src/font-size-picker/stories/index.js b/packages/components/src/font-size-picker/stories/index.js new file mode 100644 index 00000000000000..6661c0211eda04 --- /dev/null +++ b/packages/components/src/font-size-picker/stories/index.js @@ -0,0 +1,107 @@ +/** + * External dependencies + */ +import { number, object } from '@storybook/addon-knobs'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import FontSizePicker from '../'; + +export default { title: 'Components|FontSizePicker', component: FontSizePicker }; + +const FontSizePickerWithState = ( { ...props } ) => { + const [ fontSize, setFontSize ] = useState( 16 ); + + return ( + <FontSizePicker + { ...props } + value={ fontSize } + onChange={ setFontSize } + /> + ); +}; + +export const _default = () => { + const fontSizes = object( 'Font Sizes', [ + { + name: 'Small', + slug: 'small', + size: 12, + }, + { + name: 'Normal', + slug: 'normal', + size: 16, + }, + { + name: 'Big', + slug: 'big', + size: 26, + }, + ] ); + return ( + <FontSizePickerWithState + fontSizes={ fontSizes } + /> + ); +}; + +export const withSlider = () => { + const fontSizes = object( 'Font Sizes', [ + { + name: 'Small', + slug: 'small', + size: 12, + }, + { + name: 'Normal', + slug: 'normal', + size: 16, + }, + { + name: 'Big', + slug: 'big', + size: 26, + }, + ] ); + const fallbackFontSize = number( 'Fallback Font Size - Slider Only', 16 ); + return ( + <FontSizePickerWithState + fontSizes={ fontSizes } + fallbackFontSize={ fallbackFontSize } + withSlider + /> + ); +}; + +export const withoutCustomSizes = () => { + const fontSizes = object( 'Font Sizes', [ + { + name: 'Small', + slug: 'small', + size: 12, + }, + { + name: 'Normal', + slug: 'normal', + size: 16, + }, + { + name: 'Big', + slug: 'big', + size: 26, + }, + ] ); + return ( + <FontSizePickerWithState + fontSizes={ fontSizes } + disableCustomFontSizes + /> + ); +}; diff --git a/packages/components/src/font-size-picker/style.scss b/packages/components/src/font-size-picker/style.scss index 2444087dcf30e3..123713e6e83b1b 100644 --- a/packages/components/src/font-size-picker/style.scss +++ b/packages/components/src/font-size-picker/style.scss @@ -7,6 +7,8 @@ // Apply the same height as the isSmall Reset button. .components-range-control__number { height: 30px; + margin-bottom: 0; + margin-top: 5px; margin-left: 0; margin-right: $grid-size; @@ -16,6 +18,11 @@ opacity: 0.3; pointer-events: none; } + + &-container { + display: flex; + flex-direction: column; + } } // Allow the font-size picker dropdown to grow in width. @@ -23,12 +30,11 @@ margin-right: $grid-size; flex-grow: 1; } -} -// Needed to override CSS set in https://github.com/WordPress/gutenberg/blob/9c438f93a7215d50d1efc0492c308e4cbaa59c52/packages/edit-post/src/components/sidebar/settings-sidebar/style.scss#L6. -.components-font-size-picker__select.components-font-size-picker__select.components-font-size-picker__select.components-font-size-picker__select, -.components-font-size-picker__select .components-base-control__field { - margin-bottom: 0; + .components-color-palette__clear { + height: 30px; + margin-top: 23px; + } } .components-font-size-picker__custom-input { @@ -38,3 +44,9 @@ } } +.components-font-size-picker { + border: 0; + padding: 0; + margin: 0; +} + diff --git a/packages/components/src/form-token-field/token.js b/packages/components/src/form-token-field/token.js index 93af65a064fd8a..0e83faa1733093 100644 --- a/packages/components/src/form-token-field/token.js +++ b/packages/components/src/form-token-field/token.js @@ -14,6 +14,7 @@ import { __, sprintf } from '@wordpress/i18n'; * Internal dependencies */ import IconButton from '../icon-button'; +import VisuallyHidden from '../visually-hidden'; function Token( { value, @@ -60,7 +61,7 @@ function Token( { className="components-form-token-field__token-text" id={ `components-form-token-field__token-text-${ instanceId }` } > - <span className="screen-reader-text">{ termPositionAndCount }</span> + <VisuallyHidden as="span">{ termPositionAndCount }</VisuallyHidden> <span aria-hidden="true">{ transformedValue }</span> </span> diff --git a/packages/components/src/gradient-picker/index.js b/packages/components/src/gradient-picker/index.js index 1a60de751646f3..e8ab40f07ada2b 100644 --- a/packages/components/src/gradient-picker/index.js +++ b/packages/components/src/gradient-picker/index.js @@ -19,6 +19,7 @@ export default function GradientPicker( { gradients, onChange, value, + clearable = true, } ) { const clearGradient = useCallback( () => onChange( undefined ), @@ -56,7 +57,7 @@ export default function GradientPicker( { <CircularOptionPicker className={ className } options={ gradientOptions } - actions={ ( + actions={ clearable && ( <CircularOptionPicker.ButtonAction onClick={ clearGradient }> { __( 'Clear' ) } </CircularOptionPicker.ButtonAction> diff --git a/packages/components/src/guide/README.md b/packages/components/src/guide/README.md new file mode 100644 index 00000000000000..0c9fdacaf784d4 --- /dev/null +++ b/packages/components/src/guide/README.md @@ -0,0 +1,56 @@ +Guide +======== + +`Guide` is a React component that renders a _user guide_ in a modal. The guide consists of several `GuidePage` components which the user can step through one by one. The guide is finished when the modal is closed or when the user clicks _Finish_ on the last page of the guide. + +## Usage + +```jsx +function MyTutorial() { + const [ isOpen, setIsOpen ] = useState( true ); + + if ( ! isOpen ) { + return null; + } + + <Guide onFinish={ () => setIsOpen( false ) }> + <GuidePage> + <p>Welcome to the ACME Store! Select a category to begin browsing our wares.</p> + </GuidePage> + <GuidePage> + <p>When you find something you love, click <i>Add to Cart</i> to add the product to your shopping cart.</p> + </GuidePage> + </Guide> +} +``` + +## Props + +The component accepts the following props: + +### onFinish + +A function which is called when the guide is finished. The guide is finished when the modal is closed or when the user clicks _Finish_ on the last page of the guide. + +- Type: `function` +- Required: Yes + +### children + +A list of `GuidePage` components. One page is shown at a time. + +- Required: Yes + +### className + +A custom class to add to the modal. + +- Type: `string` +- Required: No + +### finishButtonText + +Use this to customize the label of the _Finish_ button shown at the end of the guide. + +- Type: `string` +- Required: No diff --git a/packages/components/src/guide/finish-button.js b/packages/components/src/guide/finish-button.js new file mode 100644 index 00000000000000..baf1fccaff480a --- /dev/null +++ b/packages/components/src/guide/finish-button.js @@ -0,0 +1,33 @@ +/** + * WordPress dependencies + */ +import { useRef, useLayoutEffect } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import Button from '../button'; + +export default function FinishButton( { className, onClick, children } ) { + const button = useRef( null ); + + // Focus the button on mount if nothing else is focused. This prevents a + // focus loss when the 'Next' button is swapped out. + useLayoutEffect( () => { + if ( document.activeElement === document.body ) { + button.current.focus(); + } + }, [ button ] ); + + return ( + <Button + ref={ button } + className={ className } + isPrimary + isLarge + onClick={ onClick } + > + { children } + </Button> + ); +} diff --git a/packages/components/src/guide/icons.js b/packages/components/src/guide/icons.js new file mode 100644 index 00000000000000..02638ff58ec331 --- /dev/null +++ b/packages/components/src/guide/icons.js @@ -0,0 +1,24 @@ +/** + * Internal dependencies + */ +import { SVG, Path, Circle } from '../primitives/svg'; + +export const BackButtonIcon = () => ( + <SVG xmlns="http://www.w3.org/2000/svg" width="24" height="24"> + <Path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" /> + <Path d="M0 0h24v24H0z" fill="none" /> + </SVG> +); + +export const ForwardButtonIcon = () => ( + <SVG xmlns="http://www.w3.org/2000/svg" width="24" height="24"> + <Path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" /> + <Path d="M0 0h24v24H0z" fill="none" /> + </SVG> +); + +export const PageControlIcon = ( { isSelected } ) => ( + <SVG width="12" height="12" fill="none" xmlns="http://www.w3.org/2000/svg"> + <Circle cx="6" cy="6" r="6" fill={ isSelected ? '#419ECD' : '#E1E3E6' } /> + </SVG> +); diff --git a/packages/components/src/guide/index.js b/packages/components/src/guide/index.js new file mode 100644 index 00000000000000..3d0a905741e14b --- /dev/null +++ b/packages/components/src/guide/index.js @@ -0,0 +1,107 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { useState, Children } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import Modal from '../modal'; +import KeyboardShortcuts from '../keyboard-shortcuts'; +import IconButton from '../icon-button'; +import PageControl from './page-control'; +import { BackButtonIcon, ForwardButtonIcon } from './icons'; +import FinishButton from './finish-button'; + +export default function Guide( { children, className, finishButtonText, onFinish } ) { + const [ currentPage, setCurrentPage ] = useState( 0 ); + + const numberOfPages = Children.count( children ); + const canGoBack = currentPage > 0; + const canGoForward = currentPage < numberOfPages - 1; + + const goBack = () => { + if ( canGoBack ) { + setCurrentPage( currentPage - 1 ); + } + }; + + const goForward = () => { + if ( canGoForward ) { + setCurrentPage( currentPage + 1 ); + } + }; + + if ( numberOfPages === 0 ) { + return null; + } + + return ( + <Modal + className={ classnames( 'components-guide', className ) } + onRequestClose={ onFinish } + > + + <KeyboardShortcuts key={ currentPage } shortcuts={ { + left: goBack, + right: goForward, + } } /> + + <div className="components-guide__container"> + + { children[ currentPage ] } + + { ! canGoForward && ( + <FinishButton + className="components-guide__inline-finish-button" + onClick={ onFinish } + > + { finishButtonText || __( 'Finish' ) } + </FinishButton> + ) } + + <div className="components-guide__footer"> + { canGoBack && ( + <IconButton + className="components-guide__back-button" + icon={ <BackButtonIcon /> } + onClick={ goBack } + > + { __( 'Previous' ) } + </IconButton> + ) } + <PageControl + currentPage={ currentPage } + numberOfPages={ numberOfPages } + setCurrentPage={ setCurrentPage } + /> + { canGoForward && ( + <IconButton + className="components-guide__forward-button" + icon={ <ForwardButtonIcon /> } + onClick={ goForward } + > + { __( 'Next' ) } + </IconButton> + ) } + { ! canGoForward && ( + <FinishButton + className="components-guide__finish-button" + onClick={ onFinish } + > + { finishButtonText || __( 'Finish' ) } + </FinishButton> + ) } + </div> + + </div> + + </Modal> + ); +} diff --git a/packages/components/src/guide/page-control.js b/packages/components/src/guide/page-control.js new file mode 100644 index 00000000000000..6a5a451efd6b1d --- /dev/null +++ b/packages/components/src/guide/page-control.js @@ -0,0 +1,33 @@ +/** + * External dependencies + */ +import { times } from 'lodash'; + +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import IconButton from '../icon-button'; +import { PageControlIcon } from './icons'; + +export default function PageControl( { currentPage, numberOfPages, setCurrentPage } ) { + return ( + <ul className="components-guide__page-control" aria-label={ __( 'Guide controls' ) }> + { times( numberOfPages, ( page ) => ( + <li key={ page }> + <IconButton + key={ page } + icon={ <PageControlIcon isSelected={ page === currentPage } /> } + /* translators: %1$d: current page number %2$d: total number of pages */ + aria-label={ sprintf( __( 'Page %1$d of %2$d' ), page + 1, numberOfPages ) } + onClick={ () => setCurrentPage( page ) } + /> + </li> + ) ) } + </ul> + ); +} diff --git a/packages/components/src/guide/page.js b/packages/components/src/guide/page.js new file mode 100644 index 00000000000000..d8f0b582e8f9b7 --- /dev/null +++ b/packages/components/src/guide/page.js @@ -0,0 +1,3 @@ +export default function GuidePage( props ) { + return <div { ...props } />; +} diff --git a/packages/components/src/guide/stories/index.js b/packages/components/src/guide/stories/index.js new file mode 100644 index 00000000000000..3a96e1031e3968 --- /dev/null +++ b/packages/components/src/guide/stories/index.js @@ -0,0 +1,56 @@ +/** + * External dependencies + */ +import { times } from 'lodash'; +import { text, number } from '@storybook/addon-knobs'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import Button from '../../button'; +import Guide from '../'; +import GuidePage from '../page'; + +export default { title: 'Components|Guide', component: Guide }; + +const ModalExample = ( { numberOfPages, ...props } ) => { + const [ isOpen, setOpen ] = useState( false ); + + const openGuide = () => setOpen( true ); + const closeGuide = () => setOpen( false ); + + const loremIpsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; + + return ( + <> + <Button isDefault onClick={ openGuide }>Open Guide</Button> + { isOpen && ( + <Guide { ...props } onFinish={ closeGuide }> + { times( numberOfPages, ( page ) => ( + <GuidePage key={ page }> + <h1>Page { page + 1 } of { numberOfPages }</h1> + <p>{ loremIpsum }</p> + </GuidePage> + ) ) } + </Guide> + ) } + </> + ); +}; + +export const _default = () => { + const finishButtonText = text( 'finishButtonText', 'Finish' ); + const numberOfPages = number( 'numberOfPages', 3, { range: true, min: 1, max: 10, step: 1 } ); + + const modalProps = { + finishButtonText, + numberOfPages, + }; + + return <ModalExample { ...modalProps } />; +}; diff --git a/packages/components/src/guide/style.scss b/packages/components/src/guide/style.scss new file mode 100644 index 00000000000000..92044211ce3cd4 --- /dev/null +++ b/packages/components/src/guide/style.scss @@ -0,0 +1,119 @@ +.components-guide { + .components-modal__header { + background: none; + border-bottom: none; + + .components-icon-button { + align-self: flex-start; + margin-top: $grid-size-xlarge; + position: static; + } + } + + &__container { + align-items: center; + display: flex; + flex-direction: column; + margin-top: -$header-height; + min-height: 100%; + } + + &__footer { + align-content: center; + display: flex; + height: 30px; + justify-content: center; + margin: auto 0 $grid-size-xlarge 0; + position: relative; + width: 100%; + + @include break-small() { + margin: $grid-size-xlarge 0 0; + } + } + + &__page-control { + margin: 0; + + li { + display: inline-block; + margin: 0 2px; + } + + .components-icon-button { + height: 30px; + } + } +} + +.components-modal__frame.components-guide { + @media (max-width: $break-small) { + bottom: 15%; + left: $grid-size-xlarge; + right: $grid-size-xlarge; + top: 15%; + } +} + +.components-button { + &.components-guide__back-button, + &.components-guide__forward-button, + &.components-guide__finish-button { + height: 30px; + position: absolute; + } + + &.components-guide__back-button, + &.components-guide__forward-button { + font-size: 0; + padding: 4px 2px; + + &.has-text svg { + margin: 0; + } + + @include break-small() { + font-size: $default-font-size; + } + } + + &.components-guide__back-button { + left: 0; + + @include break-small() { + padding: 4px 8px 4px 2px; + + &.has-text svg { + margin-right: 4px; + } + } + } + + &.components-guide__forward-button { + right: 0; + + @include break-small() { + padding: 4px 2px 4px 8px; + + &.has-text svg { + margin-left: 4px; + order: 1; + } + } + } + + &.components-guide__finish-button { + display: none; + right: 0; + + @include break-small() { + display: unset; + } + } + + &.components-guide__inline-finish-button { + @include break-small() { + display: none; + } + } +} diff --git a/packages/components/src/guide/test/finish-button.js b/packages/components/src/guide/test/finish-button.js new file mode 100644 index 00000000000000..86e2d659f21b10 --- /dev/null +++ b/packages/components/src/guide/test/finish-button.js @@ -0,0 +1,49 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; +import { create } from 'react-test-renderer'; + +/** + * WordPress dependencies + */ +import { Button } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import FinishButton from '../finish-button'; + +describe( 'FinishButton', () => { + it( 'renders a button', () => { + const wrapper = shallow( <FinishButton /> ); + expect( wrapper.find( Button ) ).toHaveLength( 1 ); + } ); + + it( 'calls onClick when the button is clicked', () => { + const onClick = jest.fn(); + const wrapper = shallow( <FinishButton onClick={ onClick } /> ); + wrapper.find( Button ).prop( 'onClick' )(); + expect( onClick ).toHaveBeenCalled(); + } ); + + it( 'receives focus on mount when nothing is focused', () => { + const focus = jest.fn(); + create( <FinishButton />, { + createNodeMock: () => ( { focus } ), + } ); + expect( focus ).toHaveBeenCalled(); + } ); + + it( 'does not receive focus on mount when something is already focused', () => { + const button = document.createElement( 'button' ); + document.body.append( button ); + button.focus(); + + const focus = jest.fn(); + create( <FinishButton />, { + createNodeMock: () => ( { focus } ), + } ); + expect( focus ).not.toHaveBeenCalled(); + } ); +} ); diff --git a/packages/components/src/guide/test/index.js b/packages/components/src/guide/test/index.js new file mode 100644 index 00000000000000..bb1fad16aa3a7a --- /dev/null +++ b/packages/components/src/guide/test/index.js @@ -0,0 +1,78 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import Guide from '../'; +import GuidePage from '../page'; +import PageControl from '../page-control'; +import Modal from '../../modal'; + +describe( 'Guide', () => { + it( 'renders nothing when there are no pages', () => { + const wrapper = shallow( <Guide /> ); + expect( wrapper.isEmptyRender() ).toBe( true ); + } ); + + it( 'renders one page at a time', () => { + const wrapper = shallow( + <Guide> + <GuidePage>Page 1</GuidePage> + <GuidePage>Page 2</GuidePage> + </Guide> + ); + expect( wrapper.find( GuidePage ) ).toHaveLength( 1 ); + } ); + + it( 'hides back button and shows forward button on the first page', () => { + const wrapper = shallow( + <Guide> + <GuidePage>Page 1</GuidePage> + <GuidePage>Page 2</GuidePage> + </Guide> + ); + expect( wrapper.find( PageControl ).prop( 'currentPage' ) ).toBe( 0 ); + expect( wrapper.find( '.components-guide__back-button' ) ).toHaveLength( 0 ); + expect( wrapper.find( '.components-guide__forward-button' ) ).toHaveLength( 1 ); + expect( wrapper.find( '.components-guide__finish-button' ) ).toHaveLength( 0 ); + } ); + + it( 'shows back button and shows finish button on the last page', () => { + const wrapper = shallow( + <Guide> + <GuidePage>Page 1</GuidePage> + <GuidePage>Page 2</GuidePage> + </Guide> + ); + wrapper.find( '.components-guide__forward-button' ).simulate( 'click' ); + expect( wrapper.find( PageControl ).prop( 'currentPage' ) ).toBe( 1 ); + expect( wrapper.find( '.components-guide__back-button' ) ).toHaveLength( 1 ); + expect( wrapper.find( '.components-guide__forward-button' ) ).toHaveLength( 0 ); + expect( wrapper.find( '.components-guide__finish-button' ) ).toHaveLength( 1 ); + } ); + + it( 'calls onFinish when the finish button is clicked', () => { + const onFinish = jest.fn(); + const wrapper = shallow( + <Guide onFinish={ onFinish }> + <GuidePage>Page 1</GuidePage> + </Guide> + ); + wrapper.find( '.components-guide__finish-button' ).simulate( 'click' ); + expect( onFinish ).toHaveBeenCalled(); + } ); + + it( 'calls onFinish when the modal is closed', () => { + const onFinish = jest.fn(); + const wrapper = shallow( + <Guide onFinish={ onFinish }> + <GuidePage>Page 1</GuidePage> + </Guide> + ); + wrapper.find( Modal ).prop( 'onRequestClose' )(); + expect( onFinish ).toHaveBeenCalled(); + } ); +} ); diff --git a/packages/components/src/guide/test/page-control.js b/packages/components/src/guide/test/page-control.js new file mode 100644 index 00000000000000..6490dc1cfaa021 --- /dev/null +++ b/packages/components/src/guide/test/page-control.js @@ -0,0 +1,39 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * WordPress dependencies + */ +import { IconButton } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import PageControl from '../page-control'; + +describe( 'PageControl', () => { + it( 'renders an empty list when there are no pages', () => { + const wrapper = shallow( <PageControl currentPage={ 0 } numberOfPages={ 0 } /> ); + expect( wrapper.find( IconButton ) ).toHaveLength( 0 ); + } ); + + it( 'renders a button for each page', () => { + const wrapper = shallow( <PageControl currentPage={ 0 } numberOfPages={ 5 } /> ); + expect( wrapper.find( IconButton ) ).toHaveLength( 5 ); + } ); + + it( 'sets the current page when a button is clicked', () => { + const setCurrentPage = jest.fn(); + const wrapper = shallow( + <PageControl + currentPage={ 0 } + numberOfPages={ 2 } + setCurrentPage={ setCurrentPage } + /> + ); + wrapper.find( IconButton ).at( 1 ).simulate( 'click' ); + expect( setCurrentPage ).toHaveBeenCalledWith( 1 ); + } ); +} ); diff --git a/packages/components/src/higher-order/navigate-regions/index.js b/packages/components/src/higher-order/navigate-regions/index.js index b8b010a7414f45..973dfe7505e4c2 100644 --- a/packages/components/src/higher-order/navigate-regions/index.js +++ b/packages/components/src/higher-order/navigate-regions/index.js @@ -48,11 +48,16 @@ export default createHigherOrderComponent( } nextRegion.focus(); - this.setState( { isFocusingRegions: true } ); + + if ( ! this.state.isFocusingRegions ) { + this.setState( { isFocusingRegions: true } ); + } } onClick() { - this.setState( { isFocusingRegions: false } ); + if ( this.state.isFocusingRegions ) { + this.setState( { isFocusingRegions: false } ); + } } render() { diff --git a/packages/components/src/higher-order/navigate-regions/style.scss b/packages/components/src/higher-order/navigate-regions/style.scss index 4653a08ee3862b..bd65bfb64135c1 100644 --- a/packages/components/src/higher-order/navigate-regions/style.scss +++ b/packages/components/src/higher-order/navigate-regions/style.scss @@ -1,4 +1,6 @@ .components-navigate-regions.is-focusing-regions [role="region"] { + position: relative; + // For browsers that don't support outline-offset (IE11). &:focus::after { content: ""; diff --git a/packages/components/src/higher-order/with-focus-return/index.js b/packages/components/src/higher-order/with-focus-return/index.js index 2b504878bdf528..6a411ad909af9f 100644 --- a/packages/components/src/higher-order/with-focus-return/index.js +++ b/packages/components/src/higher-order/with-focus-return/index.js @@ -36,11 +36,11 @@ function isComponentLike( object ) { * when the component is unmounted. * * @param {(WPComponent|Object)} options The component to be enhanced with - * focus return behavior, or an object - * describing the component and the - * focus return characteristics. + * focus return behavior, or an object + * describing the component and the + * focus return characteristics. * - * @return {Component} Component with the focus restauration behaviour. + * @return {WPComponent} Component with the focus restauration behaviour. */ function withFocusReturn( options ) { // Normalize as overloaded form `withFocusReturn( options )( Component )` diff --git a/packages/components/src/higher-order/with-notices/index.js b/packages/components/src/higher-order/with-notices/index.js index 49a44608039ce1..9097728b2921a8 100644 --- a/packages/components/src/higher-order/with-notices/index.js +++ b/packages/components/src/higher-order/with-notices/index.js @@ -17,8 +17,9 @@ import NoticeList from '../../notice/list'; /** * Override the default edit UI to include notices if supported. * - * @param {Function|Component} OriginalComponent Original component. - * @return {Component} Wrapped component. + * @param {WPComponent} OriginalComponent Original component. + * + * @return {WPComponent} Wrapped component. */ export default createHigherOrderComponent( ( OriginalComponent ) => { return class WrappedBlockEdit extends Component { diff --git a/packages/components/src/higher-order/with-spoken-messages/index.js b/packages/components/src/higher-order/with-spoken-messages/index.js index de17dc62aeb575..406d10bf0a245a 100644 --- a/packages/components/src/higher-order/with-spoken-messages/index.js +++ b/packages/components/src/higher-order/with-spoken-messages/index.js @@ -14,9 +14,9 @@ import { createHigherOrderComponent } from '@wordpress/compose'; * A Higher Order Component used to be provide a unique instance ID by * component. * - * @param {WPElement} WrappedComponent The wrapped component. + * @param {WPComponent} WrappedComponent The wrapped component. * - * @return {Component} Component with an instanceId prop. + * @return {WPComponent} The component to be rendered. */ export default createHigherOrderComponent( ( WrappedComponent ) => { return class extends Component { diff --git a/packages/components/src/icon-button/index.js b/packages/components/src/icon-button/index.js index 8c2e361eb2a371..b41b3335b0c3a6 100644 --- a/packages/components/src/icon-button/index.js +++ b/packages/components/src/icon-button/index.js @@ -2,7 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { isArray, isString } from 'lodash'; +import { isArray } from 'lodash'; /** * WordPress dependencies @@ -25,6 +25,7 @@ function IconButton( props, ref ) { tooltip, shortcut, labelPosition, + size, ...additionalProps } = props; const classes = classnames( 'components-icon-button', className, { @@ -55,7 +56,7 @@ function IconButton( props, ref ) { className={ classes } ref={ ref } > - { isString( icon ) ? <Icon icon={ icon } /> : icon } + <Icon icon={ icon } size={ size } /> { children } </Button> ); diff --git a/packages/components/src/icon-button/stories/index.js b/packages/components/src/icon-button/stories/index.js new file mode 100644 index 00000000000000..6b6861afd71c6a --- /dev/null +++ b/packages/components/src/icon-button/stories/index.js @@ -0,0 +1,39 @@ +/** + * External dependencies + */ +import { number, text } from '@storybook/addon-knobs'; + +/** + * Internal dependencies + */ +import IconButton from '../'; + +export default { title: 'Components|IconButton', component: IconButton }; + +export const _default = () => { + const icon = text( 'Icon', 'ellipsis' ); + const label = text( 'Label', 'More' ); + const size = number( 'Size' ); + + return ( + <IconButton + icon={ icon } + label={ label } + size={ size } + /> + ); +}; + +export const grouped = () => { + const GroupContainer = ( { children } ) => ( + <div style={ { display: 'inline-flex' } }>{ children }</div> + ); + + return ( + <GroupContainer> + <IconButton icon="editor-bold" label="Bold" /> + <IconButton icon="editor-italic" label="Italic" /> + <IconButton icon="editor-underline" label="Underline" /> + </GroupContainer> + ); +}; diff --git a/packages/components/src/icon/stories/index.js b/packages/components/src/icon/stories/index.js new file mode 100644 index 00000000000000..4add6e69b4ae8e --- /dev/null +++ b/packages/components/src/icon/stories/index.js @@ -0,0 +1,95 @@ +/** + * External dependencies + */ +import { number, text } from '@storybook/addon-knobs'; + +/** + * Internal dependencies + */ +import Icon from '../'; +import { SVG, Path } from '../../primitives/svg'; + +export default { title: 'Components|Icon', component: Icon }; + +const IconSizeLabel = ( { size } ) => <div style={ { fontSize: 12 } }>{ size }px</div>; + +export const _default = () => { + const icon = text( 'Icon', 'screenoptions' ); + const size = number( 'Size', '24' ); + + return ( + <div> + <Icon icon={ icon } size={ size } /> + <IconSizeLabel size={ size } /> + </div> + ); +}; + +export const sizes = () => { + const iconSizes = [ 14, 16, 20, 24, 28, 32, 40, 48, 56 ]; + + return ( + <> + { iconSizes.map( ( size ) => ( + <div key={ size } style={ { padding: 20, display: 'inline-block' } }> + <Icon icon="screenoptions" size={ size } /> + <IconSizeLabel size={ size } /> + </div> + ) ) } + </> + ); +}; + +export const colors = () => { + const iconColors = [ 'blue', 'purple', 'green' ]; + + /** + * The SVG icon inherits the color from a parent selector. + */ + + return ( + <> + { iconColors.map( ( color ) => ( + <div + key={ color } + style={ { padding: 20, display: 'inline-block', color } } + > + <Icon icon="screenoptions" /> + <IconSizeLabel size={ 24 } /> + </div> + ) ) } + </> + ); +}; + +export const withAFunction = () => ( + <Icon + icon={ () => ( + <SVG> + <Path d="M5 4v3h5.5v12h3V7H19V4z" /> + </SVG> + ) } + /> +); + +export const withAComponent = () => { + const MyIconComponent = () => ( + <SVG> + <Path d="M5 4v3h5.5v12h3V7H19V4z" /> + </SVG> + ); + + return <Icon icon={ MyIconComponent } />; +}; + +export const withAnSVG = () => { + return ( + <Icon + icon={ + <SVG> + <Path d="M5 4v3h5.5v12h3V7H19V4z" /> + </SVG> + } + /> + ); +}; diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 8542a86ba9b659..02a2f62492ae2c 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -5,13 +5,21 @@ export { default as Autocomplete } from './autocomplete'; export { default as BaseControl } from './base-control'; export { default as Button } from './button'; export { default as ButtonGroup } from './button-group'; +export { default as Card } from './card'; +export { default as CardBody } from './card/body'; +export { default as CardDivider } from './card/divider'; +export { default as CardFooter } from './card/footer'; +export { default as CardHeader } from './card/header'; +export { default as CardMedia } from './card/media'; export { default as CheckboxControl } from './checkbox-control'; export { default as ClipboardButton } from './clipboard-button'; export { default as ColorIndicator } from './color-indicator'; export { default as ColorPalette } from './color-palette'; export { default as ColorPicker } from './color-picker'; +export { default as CustomSelectControl } from './custom-select-control'; export { default as Dashicon } from './dashicon'; export { DateTimePicker, DatePicker, TimePicker } from './date-time'; +export { default as __experimentalDimensionControl } from './dimension-control'; export { default as Disabled } from './disabled'; export { default as Draggable } from './draggable'; export { default as DropZone } from './drop-zone'; @@ -26,6 +34,9 @@ export { default as FormFileUpload } from './form-file-upload'; export { default as FormToggle } from './form-toggle'; export { default as FormTokenField } from './form-token-field'; export { default as __experimentalGradientPicker } from './gradient-picker'; +export { default as __experimentalCustomGradientPicker } from './custom-gradient-picker'; +export { default as Guide } from './guide'; +export { default as GuidePage } from './guide/page'; export { default as Icon } from './icon'; export { default as IconButton } from './icon-button'; export { default as KeyboardShortcuts } from './keyboard-shortcuts'; @@ -56,21 +67,39 @@ export { default as Spinner } from './spinner'; export { default as TabPanel } from './tab-panel'; export { default as TextControl } from './text-control'; export { default as TextareaControl } from './textarea-control'; +export { default as TextHighlight } from './text-highlight'; export { default as Tip } from './tip'; export { default as ToggleControl } from './toggle-control'; export { default as Toolbar } from './toolbar'; export { default as ToolbarButton } from './toolbar-button'; +export { default as ToolbarGroup } from './toolbar-group'; export { default as Tooltip } from './tooltip'; export { default as TreeSelect } from './tree-select'; +export { default as VisuallyHidden } from './visually-hidden'; export { default as IsolatedEventContainer } from './isolated-event-container'; -export { createSlotFill, Slot, Fill, Provider as SlotFillProvider } from './slot-fill'; +export { + createSlotFill, + Slot, + Fill, + Provider as SlotFillProvider, + Consumer as __experimentalSlotFillConsumer, +} from './slot-fill'; // Higher-Order Components export { default as navigateRegions } from './higher-order/navigate-regions'; -export { default as withConstrainedTabbing } from './higher-order/with-constrained-tabbing'; -export { default as withFallbackStyles } from './higher-order/with-fallback-styles'; +export { + default as withConstrainedTabbing, +} from './higher-order/with-constrained-tabbing'; +export { + default as withFallbackStyles, +} from './higher-order/with-fallback-styles'; export { default as withFilters } from './higher-order/with-filters'; export { default as withFocusOutside } from './higher-order/with-focus-outside'; -export { default as withFocusReturn, Provider as FocusReturnProvider } from './higher-order/with-focus-return'; +export { + default as withFocusReturn, + Provider as FocusReturnProvider, +} from './higher-order/with-focus-return'; export { default as withNotices } from './higher-order/with-notices'; -export { default as withSpokenMessages } from './higher-order/with-spoken-messages'; +export { + default as withSpokenMessages, +} from './higher-order/with-spoken-messages'; diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js index c2c52ba08f0e58..7cda193234fcde 100644 --- a/packages/components/src/index.native.js +++ b/packages/components/src/index.native.js @@ -1,9 +1,12 @@ // Components export * from './primitives'; +export { default as ColorIndicator } from './color-indicator'; +export { default as ColorPalette } from './color-palette'; export { default as Dashicon } from './dashicon'; export { default as Dropdown } from './dropdown'; export { default as Toolbar } from './toolbar'; export { default as ToolbarButton } from './toolbar-button'; +export { default as ToolbarGroup } from './toolbar-group'; export { default as Icon } from './icon'; export { default as IconButton } from './icon-button'; export { default as Spinner } from './spinner'; @@ -11,10 +14,12 @@ export { createSlotFill, Slot, Fill, Provider as SlotFillProvider } from './slot export { default as BaseControl } from './base-control'; export { default as TextareaControl } from './textarea-control'; export { default as PanelBody } from './panel/body'; +export { default as PanelActions } from './panel/actions'; export { default as Button } from './button'; export { default as TextControl } from './text-control'; export { default as ToggleControl } from './toggle-control'; export { default as SelectControl } from './select-control'; +export { default as RangeControl } from './range-control'; // Higher-Order Components export { default as withConstrainedTabbing } from './higher-order/with-constrained-tabbing'; diff --git a/packages/components/src/isolated-event-container/index.js b/packages/components/src/isolated-event-container/index.js index 1f305071c95cf7..74fc49276575ff 100644 --- a/packages/components/src/isolated-event-container/index.js +++ b/packages/components/src/isolated-event-container/index.js @@ -1,35 +1,24 @@ /** * WordPress dependencies */ -import { Component } from '@wordpress/element'; +import { forwardRef } from '@wordpress/element'; -class IsolatedEventContainer extends Component { - constructor( props ) { - super( props ); - - this.stopEventPropagationOutsideContainer = this.stopEventPropagationOutsideContainer.bind( this ); - } - - stopEventPropagationOutsideContainer( event ) { - event.stopPropagation(); - } - - render() { - const { children, ... props } = this.props; - - // Disable reason: this stops certain events from propagating outside of the component. - // - onMouseDown is disabled as this can cause interactions with other DOM elements - /* eslint-disable jsx-a11y/no-static-element-interactions */ - return ( - <div - { ... props } - onMouseDown={ this.stopEventPropagationOutsideContainer } - > - { children } - </div> - ); - /* eslint-enable jsx-a11y/no-static-element-interactions */ - } +function stopPropagation( event ) { + event.stopPropagation(); } -export default IsolatedEventContainer; +export default forwardRef( ( { children, ...props }, ref ) => { + // Disable reason: this stops certain events from propagating outside of the component. + // - onMouseDown is disabled as this can cause interactions with other DOM elements + /* eslint-disable jsx-a11y/no-static-element-interactions */ + return ( + <div + { ...props } + ref={ ref } + onMouseDown={ stopPropagation } + > + { children } + </div> + ); + /* eslint-enable jsx-a11y/no-static-element-interactions */ +} ); diff --git a/packages/components/src/keyboard-shortcuts/index.native.js b/packages/components/src/keyboard-shortcuts/index.native.js new file mode 100644 index 00000000000000..fab7b99261e3ae --- /dev/null +++ b/packages/components/src/keyboard-shortcuts/index.native.js @@ -0,0 +1,2 @@ +const KeyboardShortcuts = () => null; +export default KeyboardShortcuts; diff --git a/packages/components/src/menu-item/index.js b/packages/components/src/menu-item/index.js index b7cc7c8ca7a0bd..3227659e6d10a8 100644 --- a/packages/components/src/menu-item/index.js +++ b/packages/components/src/menu-item/index.js @@ -18,7 +18,7 @@ import IconButton from '../icon-button'; /** * Renders a generic menu item for use inside the more menu. * - * @return {WPElement} More menu item. + * @return {WPComponent} The component to be rendered. */ export function MenuItem( { children, diff --git a/packages/components/src/menu-items-choice/index.js b/packages/components/src/menu-items-choice/index.js index 2311fd3a0435dd..8412c64e4141ff 100644 --- a/packages/components/src/menu-items-choice/index.js +++ b/packages/components/src/menu-items-choice/index.js @@ -17,6 +17,7 @@ export default function MenuItemsChoice( { icon={ isSelected && 'yes' } isSelected={ isSelected } shortcut={ item.shortcut } + className="components-menu-items-choice" onClick={ () => { if ( ! isSelected ) { onSelect( item.value ); diff --git a/packages/components/src/menu-items-choice/style.scss b/packages/components/src/menu-items-choice/style.scss new file mode 100644 index 00000000000000..2d933a77a13a98 --- /dev/null +++ b/packages/components/src/menu-items-choice/style.scss @@ -0,0 +1,12 @@ +.components-menu-items-choice, +.components-menu-items-choice.components-icon-button { + padding-left: 2rem; + + &.has-icon { + padding-left: 0.5rem; + } + + .dashicon { + margin-right: 4px; + } +} diff --git a/packages/components/src/mobile/bottom-sheet/borderStyles.android.scss b/packages/components/src/mobile/bottom-sheet/borderStyles.android.scss new file mode 100644 index 00000000000000..e96055892d0803 --- /dev/null +++ b/packages/components/src/mobile/bottom-sheet/borderStyles.android.scss @@ -0,0 +1,8 @@ +.borderStyle { + border-bottom-width: 1px; +} + +.isSelected { + border-bottom-width: 2px; + border-color: $blue-wordpress; +} diff --git a/packages/components/src/mobile/bottom-sheet/borderStyles.ios.scss b/packages/components/src/mobile/bottom-sheet/borderStyles.ios.scss new file mode 100644 index 00000000000000..0d36f6d51973e3 --- /dev/null +++ b/packages/components/src/mobile/bottom-sheet/borderStyles.ios.scss @@ -0,0 +1,9 @@ +.isSelected { + border-width: 2px; + border-color: $blue-wordpress; +} + +.borderStyle { + border-width: 1px; + border-radius: 4px; +} diff --git a/packages/components/src/mobile/bottom-sheet/cell.native.js b/packages/components/src/mobile/bottom-sheet/cell.native.js index af74a25e2cee82..f8ab0b445be771 100644 --- a/packages/components/src/mobile/bottom-sheet/cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/cell.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { TouchableOpacity, Text, View, TextInput, I18nManager } from 'react-native'; +import { TouchableOpacity, Text, View, TextInput, I18nManager, AccessibilityInfo } from 'react-native'; import { isEmpty } from 'lodash'; /** @@ -23,7 +23,10 @@ class BottomSheetCell extends Component { super( ...arguments ); this.state = { isEditingValue: props.autoFocus || false, + isScreenReaderEnabled: false, }; + + this.handleScreenReaderToggled = this.handleScreenReaderToggled.bind( this ); } componentDidUpdate() { @@ -32,8 +35,43 @@ class BottomSheetCell extends Component { } } + componentDidMount() { + AccessibilityInfo.addEventListener( + 'screenReaderChanged', + this.handleScreenReaderToggled, + ); + + AccessibilityInfo.isScreenReaderEnabled().then( ( isScreenReaderEnabled ) => { + this.setState( { isScreenReaderEnabled } ); + } ); + } + + componentWillUnmount() { + AccessibilityInfo.removeEventListener( + 'screenReaderChanged', + this.handleScreenReaderToggled, + ); + } + + handleScreenReaderToggled( isScreenReaderEnabled ) { + this.setState( { isScreenReaderEnabled } ); + } + + typeToKeyboardType( type, step ) { + let keyboardType = `default`; + if ( type === `number` ) { + if ( step && Math.abs( step ) < 1 ) { + keyboardType = `decimal-pad`; + } else { + keyboardType = `number-pad`; + } + } + return keyboardType; + } + render() { const { + accessible, accessibilityLabel, accessibilityHint, accessibilityRole, @@ -42,14 +80,20 @@ class BottomSheetCell extends Component { value, valuePlaceholder = '', icon, + leftAlign, labelStyle = {}, valueStyle = {}, + cellContainerStyle = {}, + cellRowContainerStyle = {}, onChangeValue, children, editable = true, separatorType, style = {}, getStylesFromColorScheme, + customActionButton, + type, + step, ...valueProps } = this.props; @@ -57,8 +101,15 @@ class BottomSheetCell extends Component { const isValueEditable = editable && onChangeValue !== undefined; const cellLabelStyle = getStylesFromColorScheme( styles.cellLabel, styles.cellTextDark ); const cellLabelCenteredStyle = getStylesFromColorScheme( styles.cellLabelCentered, styles.cellTextDark ); - const defaultLabelStyle = showValue || icon !== undefined ? cellLabelStyle : cellLabelCenteredStyle; + const cellLabelLeftAlignNoIconStyle = getStylesFromColorScheme( styles.cellLabelLeftAlignNoIcon, styles.cellTextDark ); + const defaultMissingIconAndValue = leftAlign ? cellLabelLeftAlignNoIconStyle : cellLabelCenteredStyle; + const defaultLabelStyle = showValue || icon !== undefined || customActionButton ? cellLabelStyle : defaultMissingIconAndValue; + const drawSeparator = ( separatorType && separatorType !== 'none' ) || separatorStyle === undefined; + const drawTopSeparator = drawSeparator && separatorType === 'topFullWidth'; + + const cellContainerStyles = [ styles.cellContainer, cellContainerStyle ]; + const rowContainerStyles = [ styles.cellRowContainer, cellRowContainerStyle ]; const onCellPress = () => { if ( isValueEditable ) { @@ -87,6 +138,7 @@ class BottomSheetCell extends Component { case 'leftMargin': return leftMarginStyle; case 'fullWidth': + case 'topFullWidth': return defaultSeparatorStyle; case 'none': return undefined; @@ -118,6 +170,7 @@ class BottomSheetCell extends Component { pointerEvents={ this.state.isEditingValue ? 'auto' : 'none' } onFocus={ startEditing } onBlur={ finishEditing } + keyboardType={ this.typeToKeyboardType( type, step ) } { ...valueProps } /> ) : ( @@ -151,10 +204,13 @@ class BottomSheetCell extends Component { }; const iconStyle = getStylesFromColorScheme( styles.icon, styles.iconDark ); + const resetButtonStyle = getStylesFromColorScheme( styles.resetButton, styles.resetButtonDark ); + const containerPointerEvents = this.state.isScreenReaderEnabled && accessible ? 'none' : 'auto'; + const { title, handler } = customActionButton || {}; return ( <TouchableOpacity - accessible={ ! this.state.isEditingValue } + accessible={ accessible !== undefined ? accessible : ! this.state.isEditingValue } accessibilityLabel={ getAccessibilityLabel() } accessibilityRole={ accessibilityRole || 'button' } accessibilityHint={ isValueEditable ? @@ -163,24 +219,33 @@ class BottomSheetCell extends Component { accessibilityHint } onPress={ onCellPress } - style={ { ...styles.clipToBounds, ...style } } + style={ [ styles.clipToBounds, style ] } > - <View style={ styles.cellContainer }> - <View style={ styles.cellRowContainer }> - { icon && ( - <View style={ styles.cellRowContainer }> - <Dashicon icon={ icon } size={ 24 } color={ iconStyle.color } /> - <View style={ platformStyles.labelIconSeparator } /> - </View> - ) } - <Text numberOfLines={ 1 } style={ { ...defaultLabelStyle, ...labelStyle } }> - { label } - </Text> + { drawTopSeparator && ( + <View style={ separatorStyle() } /> + ) } + <View style={ cellContainerStyles } pointerEvents={ containerPointerEvents }> + <View style={ rowContainerStyles }> + <View style={ styles.cellRowContainer }> + { icon && ( + <View style={ styles.cellRowContainer }> + <Dashicon icon={ icon } size={ 24 } color={ iconStyle.color } /> + <View style={ platformStyles.labelIconSeparator } /> + </View> + ) } + <Text style={ [ defaultLabelStyle, labelStyle ] }> + { label } + </Text> + </View> + { customActionButton && <TouchableOpacity onPress={ handler } accessibilityRole={ 'button' }> + <Text style={ resetButtonStyle }>{ title } + </Text> + </TouchableOpacity> } </View> { showValue && getValueComponent() } { children } </View> - { drawSeparator && ( + { ! drawTopSeparator && ( <View style={ separatorStyle() } /> ) } </TouchableOpacity> diff --git a/packages/components/src/mobile/bottom-sheet/index.native.js b/packages/components/src/mobile/bottom-sheet/index.native.js index f17284b49e7306..449bc5dc05059e 100644 --- a/packages/components/src/mobile/bottom-sheet/index.native.js +++ b/packages/components/src/mobile/bottom-sheet/index.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { Text, View, Platform, PanResponder, Dimensions, Easing } from 'react-native'; +import { Text, View, Platform, PanResponder, Dimensions } from 'react-native'; import Modal from 'react-native-modal'; import SafeArea from 'react-native-safe-area'; @@ -19,6 +19,7 @@ import Button from './button'; import Cell from './cell'; import PickerCell from './picker-cell'; import SwitchCell from './switch-cell'; +import RangeCell from './range-cell'; import KeyboardAvoidingView from './keyboard-avoiding-view'; class BottomSheet extends Component { @@ -97,38 +98,13 @@ class BottomSheet extends Component { </View> ); - const { height } = Dimensions.get( 'window' ); - const easing = Easing.bezier( 0.450, 0.000, 0.160, 1.020 ); - - const animationIn = { - easing, - from: { - translateY: height, - }, - to: { - translateY: 0, - }, - }; - - const animationOut = { - easing, - from: { - translateY: 0, - }, - to: { - translateY: height, - }, - }; - const backgroundStyle = getStylesFromColorScheme( styles.background, styles.backgroundDark ); return ( <Modal isVisible={ isVisible } style={ styles.bottomModal } - animationIn={ animationIn } animationInTiming={ 600 } - animationOut={ animationOut } animationOutTiming={ 250 } backdropTransitionInTiming={ 50 } backdropTransitionOutTiming={ 50 } @@ -136,6 +112,8 @@ class BottomSheet extends Component { onBackdropPress={ this.props.onClose } onBackButtonPress={ this.props.onClose } onSwipe={ this.props.onClose } + onDismiss={ Platform.OS === 'ios' ? this.props.onDismiss : undefined } + onModalHide={ Platform.OS === 'android' ? this.props.onDismiss : undefined } swipeDirection="down" onMoveShouldSetResponder={ panResponder.panHandlers.onMoveShouldSetResponder } onMoveShouldSetResponderCapture={ panResponder.panHandlers.onMoveShouldSetResponderCapture } @@ -171,5 +149,6 @@ ThemedBottomSheet.Button = Button; ThemedBottomSheet.Cell = Cell; ThemedBottomSheet.PickerCell = PickerCell; ThemedBottomSheet.SwitchCell = SwitchCell; +ThemedBottomSheet.RangeCell = RangeCell; export default ThemedBottomSheet; diff --git a/packages/components/src/mobile/bottom-sheet/picker-cell.native.js b/packages/components/src/mobile/bottom-sheet/picker-cell.native.js index 50a775f25728b1..12eae94c7d8919 100644 --- a/packages/components/src/mobile/bottom-sheet/picker-cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/picker-cell.native.js @@ -7,6 +7,7 @@ import Picker from '../picker'; export default function BottomSheetPickerCell( props ) { const { options, + hideCancelButton, onChangeValue, ...cellProps } = props; @@ -24,6 +25,8 @@ export default function BottomSheetPickerCell( props ) { return ( <Cell onPress={ onCellPress } editable={ false } { ...cellProps } > <Picker + leftAlign + hideCancelButton={ hideCancelButton } ref={ ( instance ) => picker = instance } options={ options } onChange={ onChange } diff --git a/packages/components/src/mobile/bottom-sheet/range-cell.native.js b/packages/components/src/mobile/bottom-sheet/range-cell.native.js new file mode 100644 index 00000000000000..b228975c852fe9 --- /dev/null +++ b/packages/components/src/mobile/bottom-sheet/range-cell.native.js @@ -0,0 +1,215 @@ +/** + * External dependencies + */ +import { Platform, AccessibilityInfo, findNodeHandle, TextInput, View, PixelRatio, AppState } from 'react-native'; +import Slider from '@react-native-community/slider'; + +/** + * WordPress dependencies + */ +import { _x, __, sprintf } from '@wordpress/i18n'; +import { Component } from '@wordpress/element'; +import { withPreferredColorScheme } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import Cell from './cell'; +import styles from './range-cell.scss'; +import borderStyles from './borderStyles.scss'; + +class BottomSheetRangeCell extends Component { + constructor( props ) { + super( props ); + this.handleToggleFocus = this.handleToggleFocus.bind( this ); + this.handleChange = this.handleChange.bind( this ); + this.handleValueSave = this.handleValueSave.bind( this ); + this.handleReset = this.handleReset.bind( this ); + this.onChangeValue = this.onChangeValue.bind( this ); + this.onCellPress = this.onCellPress.bind( this ); + this.handleChangePixelRatio = this.handleChangePixelRatio.bind( this ); + + const initialValue = this.validateInput( props.value || props.defaultValue || props.minimumValue ); + const fontScale = this.getFontScale(); + + this.state = { accessible: true, sliderValue: initialValue, initialValue, hasFocus: false, fontScale }; + } + + componentDidUpdate( ) { + const reset = this.props.value === null; + if ( reset ) { + this.handleReset(); + } + } + + componentDidMount() { + AppState.addEventListener( 'change', this.handleChangePixelRatio ); + } + + componentWillUnmount() { + this.handleToggleFocus(); + AppState.removeEventListener( 'change', this.handleChangePixelRatio ); + } + + getFontScale() { + return PixelRatio.getFontScale() < 1 ? 1 : PixelRatio.getFontScale(); + } + + handleChangePixelRatio( nextAppState ) { + if ( nextAppState === 'active' ) { + this.setState( { fontScale: this.getFontScale() } ); + } + } + + handleChange( text ) { + if ( ! isNaN( Number( text ) ) ) { + this.setState( { sliderValue: text } ); + this.announceCurrentValue( text ); + } + } + + handleReset() { + this.handleValueSave( this.props.defaultValue || this.state.initialValue ); + } + + handleToggleFocus( validateInput = true ) { + const newState = { hasFocus: ! this.state.hasFocus }; + + if ( validateInput ) { + const sliderValue = this.validateInput( this.state.sliderValue ); + this.handleValueSave( sliderValue ); + } + + this.setState( newState ); + } + + validateInput( text ) { + const { minimumValue, maximumValue } = this.props; + if ( ! text ) { + return minimumValue; + } + if ( typeof text === 'number' ) { + return Math.min( Math.max( text, minimumValue ), maximumValue ); + } + return Math.min( Math.max( text.replace( /[^0-9]/g, '' ).replace( /^0+(?=\d)/, '' ), minimumValue ), maximumValue ); + } + + handleValueSave( text ) { + if ( ! isNaN( Number( text ) ) ) { + this.onChangeValue( text ); + this.setState( { sliderValue: text } ); + this.announceCurrentValue( text ); + } + } + + onChangeValue( initialValue ) { + const { minimumValue, maximumValue, onChange } = this.props; + + let sliderValue = initialValue; + if ( sliderValue < minimumValue ) { + sliderValue = minimumValue; + } else if ( sliderValue > maximumValue ) { + sliderValue = maximumValue; + } + onChange( sliderValue ); + } + + onCellPress() { + this.setState( { accessible: false } ); + if ( this.sliderRef ) { + const reactTag = findNodeHandle( this.sliderRef ); + AccessibilityInfo.setAccessibilityFocus( reactTag ); + } + } + + announceCurrentValue( value ) { + const announcement = sprintf( __( 'Current value is %s' ), value ); + AccessibilityInfo.announceForAccessibility( announcement ); + } + + render() { + const { + value, + defaultValue, + minimumValue = 0, + maximumValue = 10, + disabled, + step = 1, + preferredColorScheme, + minimumTrackTintColor = preferredColorScheme === 'light' ? '#00669b' : '#5198d9', + maximumTrackTintColor = Platform.OS === 'ios' ? '#e9eff3' : '#909090', + thumbTintColor = Platform.OS === 'android' && '#00669b', + getStylesFromColorScheme, + allowReset = true, + ...cellProps + } = this.props; + + const { hasFocus, sliderValue, accessible, fontScale } = this.state; + + const accessibilityLabel = + sprintf( + /* translators: accessibility text. Inform about current value. %1$s: Control label %2$s: Current value. */ + _x( '%1$s. Current value is %2$s', 'Slider for picking a number inside a range' ), + cellProps.label, value + ); + + const defaultSliderStyle = getStylesFromColorScheme( styles.sliderTextInput, styles.sliderDarkTextInput ); + const resetButtonText = Platform.OS === 'ios' ? __( 'Reset' ) : __( 'RESET' ); + const resetButton = { title: resetButtonText, handler: this.handleReset }; + + return ( + <Cell + { ...cellProps } + cellContainerStyle={ styles.cellContainerStyles } + cellRowContainerStyle={ styles.cellRowStyles } + accessibilityRole={ 'none' } + editable={ false } + accessible={ accessible } + onPress={ this.onCellPress } + accessibilityLabel={ accessibilityLabel } + customActionButton={ allowReset ? resetButton : undefined } + accessibilityHint={ + /* translators: accessibility text (hint for focusing a slider) */ + __( 'Double tap to change the value using slider' ) + } + > + <View style={ styles.container }> + <Slider + value={ this.validateInput( sliderValue ) } + defaultValue={ defaultValue } + disabled={ disabled } + step={ step } + minimumValue={ minimumValue } + maximumValue={ maximumValue } + minimumTrackTintColor={ minimumTrackTintColor } + maximumTrackTintColor={ maximumTrackTintColor } + thumbTintColor={ thumbTintColor } + onValueChange={ this.handleChange } + onSlidingComplete={ this.handleValueSave } + ref={ ( slider ) => { + this.sliderRef = slider; + } } + style={ styles.slider } + accessibilityRole={ 'adjustable' } + /> + <TextInput + style={ [ + defaultSliderStyle, + borderStyles.borderStyle, + hasFocus && borderStyles.isSelected, + { width: 40 * fontScale }, + ] } + onChangeText={ this.handleChange } + onFocus={ this.handleToggleFocus } + onBlur={ this.handleToggleFocus } + keyboardType="number-pad" + returnKeyType="done" + value={ `${ sliderValue }` } + /> + </View> + </Cell> + ); + } +} + +export default withPreferredColorScheme( BottomSheetRangeCell ); diff --git a/packages/components/src/mobile/bottom-sheet/range-cell.native.scss b/packages/components/src/mobile/bottom-sheet/range-cell.native.scss new file mode 100644 index 00000000000000..21a7051d20ced9 --- /dev/null +++ b/packages/components/src/mobile/bottom-sheet/range-cell.native.scss @@ -0,0 +1,40 @@ +.slider { + flex-grow: 1; +} + +.sliderTextInput { + min-height: 25px; + align-self: center; + margin-left: 10px; + border-color: $light-gray-400; + padding-top: 0; + padding-bottom: 0; + text-align: center; +} + +.sliderDarkTextInput { + border-color: $gray-70; +} + +.isSelected { + border-width: 2px; + border-color: $blue-wordpress; +} + +.container { + flex-direction: row; + align-items: center; + min-height: 48px; +} + +.cellContainerStyles { + flex-direction: column; + align-items: flex-start; +} + +.cellRowStyles { + min-height: 48px; + margin-bottom: -13px; + width: 100%; + justify-content: space-between; +} diff --git a/packages/components/src/mobile/bottom-sheet/styles.native.scss b/packages/components/src/mobile/bottom-sheet/styles.native.scss index 8f153715c16705..b098b4e5bdbf79 100644 --- a/packages/components/src/mobile/bottom-sheet/styles.native.scss +++ b/packages/components/src/mobile/bottom-sheet/styles.native.scss @@ -76,6 +76,15 @@ padding: 5px; } +.resetButton { + font-size: 17px; + color: $blue-wordpress; +} + +.resetButtonDark { + color: $blue-30; +} + // Cell .cellContainer { @@ -101,6 +110,7 @@ .cellRowContainer { flex-direction: row; align-items: center; + flex-shrink: 1; } .cellIcon { @@ -120,6 +130,13 @@ text-align: center; } +.cellLabelLeftAlignNoIcon { + font-size: 17px; + color: #2e4453; + flex: 1; + margin-left: 12px; +} + .cellValue { font-size: 17px; color: #2e4453; diff --git a/packages/components/src/mobile/html-text-input/index.native.js b/packages/components/src/mobile/html-text-input/index.native.js index 7b7c648216ef33..f71efdf1490ef2 100644 --- a/packages/components/src/mobile/html-text-input/index.native.js +++ b/packages/components/src/mobile/html-text-input/index.native.js @@ -10,6 +10,7 @@ import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { parse } from '@wordpress/blocks'; import { withDispatch, withSelect } from '@wordpress/data'; +import { addAction, removeAction } from '@wordpress/hooks'; import { withInstanceId, compose, withPreferredColorScheme } from '@wordpress/compose'; /** @@ -24,11 +25,9 @@ export class HTMLTextInput extends Component { this.edit = this.edit.bind( this ); this.stopEditing = this.stopEditing.bind( this ); + addAction( 'native-editor.persist-html', 'core/editor', this.stopEditing ); - this.state = { - isDirty: false, - value: '', - }; + this.state = {}; } static getDerivedStateFromProps( props, state ) { @@ -43,6 +42,7 @@ export class HTMLTextInput extends Component { } componentWillUnmount() { + removeAction( 'native-editor.persist-html', 'core/editor' ); //TODO: Blocking main thread this.stopEditing(); } @@ -107,8 +107,7 @@ export default compose( [ }; } ), withDispatch( ( dispatch ) => { - const { resetBlocks } = dispatch( 'core/block-editor' ); - const { editPost } = dispatch( 'core/editor' ); + const { editPost, resetEditorBlocks } = dispatch( 'core/editor' ); return { editTitle( title ) { editPost( { title } ); @@ -117,7 +116,8 @@ export default compose( [ editPost( { content } ); }, onPersist( content ) { - resetBlocks( parse( content ) ); + const blocks = parse( content ); + resetEditorBlocks( blocks ); }, }; } ), diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js index 52a3b98269e932..0d22e649d6787e 100644 --- a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js +++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js @@ -9,10 +9,11 @@ export const KeyboardAwareFlatList = ( { shouldPreventAutomaticScroll, innerRef, autoScroll, + scrollViewStyle, ...listProps } ) => ( <KeyboardAwareScrollView - style={ { flex: 1, overflow: 'visible' } } + style={ [ { flex: 1 }, scrollViewStyle ] } keyboardDismissMode="none" enableResetScrollToCoords={ false } keyboardShouldPersistTaps="handled" @@ -32,7 +33,9 @@ export const KeyboardAwareFlatList = ( { this.latestContentOffsetY !== undefined && ! shouldPreventAutomaticScroll() ) { // Reset the content position if keyboard is still closed - this.scrollViewRef.props.scrollToPosition( 0, this.latestContentOffsetY, true ); + if ( this.scrollViewRef ) { + this.scrollViewRef.props.scrollToPosition( 0, this.latestContentOffsetY, true ); + } } }, 50 ); } } diff --git a/packages/components/src/mobile/picker/index.android.js b/packages/components/src/mobile/picker/index.android.js index 3fad1cc2b23bf0..372378e0a2b3ed 100644 --- a/packages/components/src/mobile/picker/index.android.js +++ b/packages/components/src/mobile/picker/index.android.js @@ -52,6 +52,7 @@ export default class Picker extends Component { <BottomSheet.Cell icon={ option.icon } key={ index } + leftAlign={ this.props.leftAlign } label={ option.label } separatorType={ 'none' } onPress={ () => this.onCellPress( option.value ) } diff --git a/packages/components/src/mobile/picker/index.ios.js b/packages/components/src/mobile/picker/index.ios.js index 2cf9798b28e9c0..9de17d1ff7060a 100644 --- a/packages/components/src/mobile/picker/index.ios.js +++ b/packages/components/src/mobile/picker/index.ios.js @@ -11,12 +11,13 @@ import { Component } from '@wordpress/element'; class Picker extends Component { presentPicker() { - const { options, onChange } = this.props; + const { options, onChange, title } = this.props; const labels = options.map( ( { label } ) => label ); const fullOptions = [ __( 'Cancel' ) ].concat( labels ); ActionSheetIOS.showActionSheetWithOptions( { + title, options: fullOptions, cancelButtonIndex: 0, }, diff --git a/packages/components/src/modal/README.md b/packages/components/src/modal/README.md index 4a2aed1aab4238..ad75e3ee656b29 100644 --- a/packages/components/src/modal/README.md +++ b/packages/components/src/modal/README.md @@ -120,24 +120,28 @@ The following example shows you how to properly implement a modal. For the modal ```jsx import { Button, Modal } from '@wordpress/components'; -import { withState } from '@wordpress/compose'; - -const MyModal = withState( { - isOpen: false, -} )( ( { isOpen, setState } ) => ( - <div> - <Button isDefault onClick={ () => setState( { isOpen: true } ) }>Open Modal</Button> - { isOpen && ( - <Modal - title="This is my modal" - onRequestClose={ () => setState( { isOpen: false } ) }> - <Button isDefault onClick={ () => setState( { isOpen: false } ) }> - My custom close button - </Button> - </Modal> - ) } - </div> -) ); +import { useState } from '@wordpress/element'; + +const MyModal = () => { + const [ isOpen, setOpen ] = useState( false ); + const openModal = () => setOpen( true ); + const closeModal = () => setOpen( false ); + + return ( + <> + <Button isDefault onClick={ openModal }>Open Modal</Button> + { isOpen && ( + <Modal + title="This is my modal" + onRequestClose={ closeModal }> + <Button isDefault onClick={ closeModal }> + My custom close button + </Button> + </Modal> + ) } + </> + ) +} ``` ### Props diff --git a/packages/components/src/modal/index.js b/packages/components/src/modal/index.js index 66d3421ee6ff5c..f4a64c289b4e7d 100644 --- a/packages/components/src/modal/index.js +++ b/packages/components/src/modal/index.js @@ -3,7 +3,7 @@ */ import { Component, createPortal } from '@wordpress/element'; import { withInstanceId } from '@wordpress/compose'; -import { deprecated } from '@wordpress/deprecated'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies diff --git a/packages/components/src/modal/stories/index.js b/packages/components/src/modal/stories/index.js new file mode 100644 index 00000000000000..043a9126561f7a --- /dev/null +++ b/packages/components/src/modal/stories/index.js @@ -0,0 +1,64 @@ +/** + * External dependencies + */ +import { boolean, text } from '@storybook/addon-knobs'; + +/** + * Internal dependencies + */ +import Button from '../../button'; +import Dashicon from '../../dashicon'; +import Modal from '../'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +export default { title: 'Components|Modal', component: Modal }; + +const ModalExample = ( props ) => { + const [ isOpen, setOpen ] = useState( false ); + const openModal = () => setOpen( true ); + const closeModal = () => setOpen( false ); + + return ( + <> + <Button isDefault onClick={ openModal }> + Open Modal + </Button> + { isOpen && ( + <Modal { ...props } onRequestClose={ closeModal }> + <Button isDefault onClick={ closeModal }> + Close Modal + </Button> + </Modal> + ) } + </> + ); +}; + +export const _default = () => { + const title = text( 'title', 'Title' ); + const icon = text( 'icon', '' ); + const isDismissible = boolean( 'isDismissible', true ); + const focusOnMount = boolean( 'focusOnMount', true ); + const shouldCloseOnEsc = boolean( 'shouldCloseOnEsc', true ); + const shouldCloseOnClickOutside = boolean( + 'shouldCloseOnClickOutside', + true + ); + + const iconComponent = icon ? <Dashicon icon={ icon } /> : null; + + const modalProps = { + icon: iconComponent, + focusOnMount, + isDismissible, + shouldCloseOnEsc, + shouldCloseOnClickOutside, + title, + }; + + return <ModalExample { ...modalProps } />; +}; diff --git a/packages/components/src/panel/actions.native.js b/packages/components/src/panel/actions.native.js new file mode 100644 index 00000000000000..02031bf53cb857 --- /dev/null +++ b/packages/components/src/panel/actions.native.js @@ -0,0 +1,36 @@ +/** + * External dependencies + */ +import { View } from 'react-native'; + +/** + * WordPress dependencies + */ +import { + TextControl, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import styles from './actions.scss'; + +function PanelActions( { actions } ) { + return ( + <View style={ styles.panelActionsContainer }> + { actions.map( ( { label, onPress } ) => { + return ( + <TextControl + label={ label } + separatorType="topFullWidth" + onPress={ onPress } + labelStyle={ styles.defaultLabelStyle } + key={ label } + /> + ); + } ) } + </View> + ); +} + +export default PanelActions; diff --git a/packages/components/src/panel/actions.native.scss b/packages/components/src/panel/actions.native.scss new file mode 100644 index 00000000000000..ed226c13883be9 --- /dev/null +++ b/packages/components/src/panel/actions.native.scss @@ -0,0 +1,7 @@ +.panelActionsContainer { + padding-top: 24; +} + +.defaultLabelStyle { + color: $alert-red; +} diff --git a/packages/components/src/panel/body.native.js b/packages/components/src/panel/body.native.js index aeeccc77a06c19..314650f945278e 100644 --- a/packages/components/src/panel/body.native.js +++ b/packages/components/src/panel/body.native.js @@ -1,12 +1,16 @@ /** * External dependencies */ -import { View } from 'react-native'; +import { Text, View } from 'react-native'; /** * WordPress dependencies */ import { Component } from '@wordpress/element'; +/** + * Internal dependencies + */ +import styles from './body.scss'; export class PanelBody extends Component { constructor( ) { @@ -15,9 +19,11 @@ export class PanelBody extends Component { } render() { - const { children } = this.props; + const { children, title } = this.props; + return ( - <View > + <View style={ styles.panelContainer }> + { title && <Text style={ styles.sectionHeaderText }>{ title }</Text> } { children } </View> ); diff --git a/packages/components/src/panel/body.native.scss b/packages/components/src/panel/body.native.scss new file mode 100644 index 00000000000000..a30c4df4c15dfc --- /dev/null +++ b/packages/components/src/panel/body.native.scss @@ -0,0 +1,11 @@ +.panelContainer { + padding: 0 16px; +} + +.sectionHeaderText { + color: #87a6bc; + padding-top: 24; + padding-bottom: 8; + font-size: 14; + font-weight: 500; +} diff --git a/packages/components/src/placeholder/README.md b/packages/components/src/placeholder/README.md index 2a12363e7fde01..9e5bf053417d0d 100644 --- a/packages/components/src/placeholder/README.md +++ b/packages/components/src/placeholder/README.md @@ -16,7 +16,7 @@ const MyPlaceholder = () => ( Name | Type | Default | Description --- | --- | --- | --- -`icon` | `string, ReactElement` | `undefined` | If provided, renders an icon next to the label. +`icon` | `string, WPElement` | `undefined` | If provided, renders an icon next to the label. `label` | `string` | `undefined` | Renders a label for the placeholder. `instructions` | `string` | `undefined` | Renders instruction text below label. `isColumnLayout` | `bool` | `false` | Changes placeholder children layout from flex-row to flex-column. diff --git a/packages/components/src/placeholder/index.js b/packages/components/src/placeholder/index.js index 3a7c40c993001f..b2f41381246eed 100644 --- a/packages/components/src/placeholder/index.js +++ b/packages/components/src/placeholder/index.js @@ -2,12 +2,11 @@ * External dependencies */ import classnames from 'classnames'; -import { isString } from 'lodash'; /** * Internal dependencies */ -import Dashicon from '../dashicon'; +import Icon from '../icon'; /** * Renders a placeholder. Normally used by blocks to render their empty state. @@ -27,7 +26,7 @@ function Placeholder( { icon, children, label, instructions, className, notices, </div> } <div className="components-placeholder__label"> - { isString( icon ) ? <Dashicon icon={ icon } /> : icon } + <Icon icon={ icon } /> { label } </div> { !! instructions && <div className="components-placeholder__instructions">{ instructions }</div> } diff --git a/packages/components/src/placeholder/style.scss b/packages/components/src/placeholder/style.scss index 20c5299624d0ae..f1f3b7f779da70 100644 --- a/packages/components/src/placeholder/style.scss +++ b/packages/components/src/placeholder/style.scss @@ -1,14 +1,18 @@ .components-placeholder { margin-bottom: $default-block-margin; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; padding: 1em; min-height: 200px; width: 100%; text-align: center; + // IE11 doesn't read rules inside this query. They are applied only to modern browsers. + @supports (position: sticky) { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + // Use opacity to work in various editor styles. background: $dark-opacity-light-200; @@ -73,3 +77,25 @@ margin: 3%; width: 50%; } + +.components-placeholder__fieldset .components-button { + margin-right: 4px; + margin-bottom: 10px; // if buttons wrap we need vertical space between + + &:last-child { + margin-right: 0; + } +} + +// Any `<Button />` component with `isLink` prop will need extra spacing if placed +// immediately after a button which is *not* an `isLink` style button. This is because +// `isLink` has no padding so we need to account for this to avoid the buttons butting +// up against each other. If it's the last item we don't need a right margin. +.components-placeholder__fieldset .components-button:not(.is-link) ~ .components-button.is-link { + margin-left: 10px; // equal to standard button inner padding + margin-right: 10px; // equal to standard button inner padding + + &:last-child { + margin-right: 0; + } +} diff --git a/packages/components/src/placeholder/test/index.js b/packages/components/src/placeholder/test/index.js index 54f9f847f9750b..ed27f2834c3d42 100644 --- a/packages/components/src/placeholder/test/index.js +++ b/packages/components/src/placeholder/test/index.js @@ -26,19 +26,19 @@ describe( 'Placeholder', () => { expect( placeholderFieldset.exists() ).toBe( true ); } ); - it( 'should render a Dashicon in the label section', () => { + it( 'should render an Icon in the label section', () => { const placeholder = shallow( <Placeholder icon="wordpress" /> ); const placeholderLabel = placeholder.find( '.components-placeholder__label' ); expect( placeholderLabel.exists() ).toBe( true ); - expect( placeholderLabel.find( 'Dashicon' ).exists() ).toBe( true ); + expect( placeholderLabel.find( 'Icon' ).exists() ).toBe( true ); } ); it( 'should render a label section', () => { const label = 'WordPress'; const placeholder = shallow( <Placeholder label={ label } /> ); const placeholderLabel = placeholder.find( '.components-placeholder__label' ); - const child = placeholderLabel.childAt( 0 ); + const child = placeholderLabel.childAt( 1 ); expect( child.text() ).toBe( label ); } ); diff --git a/packages/components/src/popover/index.js b/packages/components/src/popover/index.js index 420ac78b76bdbf..e9887a8dd69d06 100644 --- a/packages/components/src/popover/index.js +++ b/packages/components/src/popover/index.js @@ -7,10 +7,10 @@ import classnames from 'classnames'; * WordPress dependencies */ import { useRef, useState, useEffect } from '@wordpress/element'; -import { focus } from '@wordpress/dom'; +import { focus, getRectangleFromRange } from '@wordpress/dom'; import { ESCAPE } from '@wordpress/keycodes'; -import isShallowEqual from '@wordpress/is-shallow-equal'; import deprecated from '@wordpress/deprecated'; +import { useViewportMatch } from '@wordpress/compose'; /** * Internal dependencies @@ -34,179 +34,99 @@ const FocusManaged = withConstrainedTabbing( withFocusReturn( ( { children } ) = */ const SLOT_NAME = 'Popover'; -/** - * Hook used trigger an event handler once the window is resized or scrolled. - * - * @param {Function} handler Event handler. - * @param {Object} ignoredScrollableRef scroll events inside this element are ignored. - */ -function useThrottledWindowScrollOrResize( handler, ignoredScrollableRef ) { - // Refresh anchor rect on resize - useEffect( () => { - let refreshHandle; - const throttledRefresh = ( event ) => { - window.cancelAnimationFrame( refreshHandle ); - if ( ignoredScrollableRef && event && event.type === 'scroll' && ignoredScrollableRef.current.contains( event.target ) ) { - return; - } - refreshHandle = window.requestAnimationFrame( handler ); - }; +function computeAnchorRect( + anchorRefFallback, + anchorRect, + getAnchorRect, + anchorRef = false, + shouldAnchorIncludePadding +) { + if ( anchorRect ) { + return anchorRect; + } - window.addEventListener( 'resize', throttledRefresh ); - window.addEventListener( 'scroll', throttledRefresh, true ); + if ( getAnchorRect ) { + if ( ! anchorRefFallback.current ) { + return; + } - return () => { - window.removeEventListener( 'resize', throttledRefresh ); - window.removeEventListener( 'scroll', throttledRefresh, true ); - }; - }, [] ); -} + return getAnchorRect( anchorRefFallback.current ); + } -/** - * Hook used to compute and update the anchor position properly. - * - * @param {Object} anchorRef reference to the popover anchor element. - * @param {Object} contentRef reference to the popover content element. - * @param {Object} anchorRect anchor Rect prop used to override the computed value. - * @param {Function} getAnchorRect function used to override the anchor value computation algorithm. - * - * @return {Object} Anchor position. - */ -function useAnchor( anchorRef, contentRef, anchorRect, getAnchorRect ) { - const [ anchor, setAnchor ] = useState( null ); - const refreshAnchorRect = () => { - if ( ! anchorRef.current ) { + if ( anchorRef !== false ) { + if ( ! anchorRef ) { return; } - let newAnchor; - if ( anchorRect ) { - newAnchor = anchorRect; - } else if ( getAnchorRect ) { - newAnchor = getAnchorRect( anchorRef.current ); - } else { - const rect = anchorRef.current.parentNode.getBoundingClientRect(); - // subtract padding - const { paddingTop, paddingBottom } = window.getComputedStyle( anchorRef.current.parentNode ); - const topPad = parseInt( paddingTop, 10 ); - const bottomPad = parseInt( paddingBottom, 10 ); - newAnchor = { - x: rect.left, - y: rect.top + topPad, - width: rect.width, - height: rect.height - topPad - bottomPad, - left: rect.left, - right: rect.right, - top: rect.top + topPad, - bottom: rect.bottom - bottomPad, - }; + if ( anchorRef instanceof window.Range ) { + return getRectangleFromRange( anchorRef ); } - const didAnchorRectChange = ! isShallowEqual( newAnchor, anchor ); - if ( didAnchorRectChange ) { - setAnchor( newAnchor ); - } - }; - useEffect( refreshAnchorRect, [ anchorRect, getAnchorRect ] ); - useEffect( () => { - if ( ! anchorRect ) { - /* - * There are sometimes we need to reposition or resize the popover that are not - * handled by the resize/scroll window events (i.e. CSS changes in the layout - * that changes the position of the anchor). - * - * For these situations, we refresh the popover every 0.5s - */ - const intervalHandle = setInterval( refreshAnchorRect, 500 ); - - return () => clearInterval( intervalHandle ); + const rect = anchorRef.getBoundingClientRect(); + + if ( shouldAnchorIncludePadding ) { + return rect; } - }, [ anchorRect ] ); - useThrottledWindowScrollOrResize( refreshAnchorRect, contentRef ); + return withoutPadding( rect, anchorRef ); + } - return anchor; -} + if ( ! anchorRefFallback.current ) { + return; + } -/** - * Hook used to compute the initial size of an element. - * The popover applies styling to limit the height of the element, - * we only care about the initial size. - * - * @param {Object} ref Reference to the popover content element. - * - * @return {Object} Content size. - */ -function useInitialContentSize( ref ) { - const [ contentSize, setContentSize ] = useState( null ); - useEffect( () => { - const contentRect = ref.current.getBoundingClientRect(); - setContentSize( { - width: contentRect.width, - height: contentRect.height, - } ); - }, [] ); + const { parentNode } = anchorRefFallback.current; + const rect = parentNode.getBoundingClientRect(); - return contentSize; -} + if ( shouldAnchorIncludePadding ) { + return rect; + } -/** - * Hook used to compute and update the position of the popover - * based on the anchor position and the content size. - * - * @param {Object} anchor Anchor Position. - * @param {Object} contentSize Content Size. - * @param {string} position Position prop. - * @param {boolean} expandOnMobile Whether to show the popover full width on mobile. - * @param {Object} contentRef Reference to the popover content element. - * - * @return {Object} Popover position. - */ -function usePopoverPosition( anchor, contentSize, position, expandOnMobile, contentRef ) { - const [ popoverPosition, setPopoverPosition ] = useState( { - popoverLeft: null, - popoverTop: null, - yAxis: 'top', - xAxis: 'center', - contentHeight: null, - contentWidth: null, - isMobile: false, - } ); - const refreshPopoverPosition = () => { - if ( ! anchor || ! contentSize ) { - return; - } + return withoutPadding( rect, parentNode ); +} - const newPopoverPosition = computePopoverPosition( - anchor, - contentSize, - position, - expandOnMobile - ); - - if ( - popoverPosition.yAxis !== newPopoverPosition.yAxis || - popoverPosition.xAxis !== newPopoverPosition.xAxis || - popoverPosition.popoverLeft !== newPopoverPosition.popoverLeft || - popoverPosition.popoverTop !== newPopoverPosition.popoverTop || - popoverPosition.contentHeight !== newPopoverPosition.contentHeight || - popoverPosition.contentWidth !== newPopoverPosition.contentWidth || - popoverPosition.isMobile !== newPopoverPosition.isMobile - ) { - setPopoverPosition( newPopoverPosition ); - } +function addBuffer( rect, verticalBuffer = 0, horizontalBuffer = 0 ) { + return { + x: rect.left - horizontalBuffer, + y: rect.top - verticalBuffer, + width: rect.width + ( 2 * horizontalBuffer ), + height: rect.height + ( 2 * verticalBuffer ), + left: rect.left - horizontalBuffer, + right: rect.right + horizontalBuffer, + top: rect.top - verticalBuffer, + bottom: rect.bottom + verticalBuffer, }; - useEffect( refreshPopoverPosition, [ anchor, contentSize ] ); - useThrottledWindowScrollOrResize( refreshPopoverPosition, contentRef ); +} - return popoverPosition; +function withoutPadding( rect, element ) { + const { + paddingTop, + paddingBottom, + paddingLeft, + paddingRight, + } = window.getComputedStyle( element ); + const top = paddingTop ? parseInt( paddingTop, 10 ) : 0; + const bottom = paddingBottom ? parseInt( paddingBottom, 10 ) : 0; + const left = paddingLeft ? parseInt( paddingLeft, 10 ) : 0; + const right = paddingRight ? parseInt( paddingRight, 10 ) : 0; + + return { + x: rect.left + left, + y: rect.top + top, + width: rect.width - left - right, + height: rect.height - top - bottom, + left: rect.left + left, + right: rect.right - right, + top: rect.top + top, + bottom: rect.bottom - bottom, + }; } /** * Hook used to focus the first tabbable element on mount. * * @param {boolean|string} focusOnMount Focus on mount mode. - * @param {Object} contentRef Reference to the popover content element. + * @param {Object} contentRef Reference to the popover content element. */ function useFocusContentOnMount( focusOnMount, contentRef ) { // Focus handling @@ -226,6 +146,7 @@ function useFocusContentOnMount( focusOnMount, contentRef ) { // Find first tabbable node within content and shift focus, falling // back to the popover panel itself. const firstTabbable = focus.tabbable.find( contentRef.current )[ 0 ]; + if ( firstTabbable ) { firstTabbable.focus(); } else { @@ -246,6 +167,55 @@ function useFocusContentOnMount( focusOnMount, contentRef ) { }, [] ); } +/** + * Sets or removes an element attribute. + * + * @param {Element} element The element to modify. + * @param {string} name The attribute name to set or remove. + * @param {?string} value The value to set. A falsy value will remove the + * attribute. + */ +function setAttribute( element, name, value ) { + if ( ! value ) { + if ( element.hasAttribute( name ) ) { + element.removeAttribute( name ); + } + } else if ( element.getAttribute( name ) !== value ) { + element.setAttribute( name, value ); + } +} + +/** + * Sets or removes an element style property. + * + * @param {Element} element The element to modify. + * @param {string} property The property to set or remove. + * @param {?string} value The value to set. A falsy value will remove the + * property. + */ +function setStyle( element, property, value = '' ) { + if ( element.style[ property ] !== value ) { + element.style[ property ] = value; + } +} + +/** + * Sets or removes an element class. + * + * @param {Element} element The element to modify. + * @param {string} name The class to set or remove. + * @param {boolean} toggle True to set the class, false to remove. + */ +function setClass( element, name, toggle ) { + if ( toggle ) { + if ( ! element.classList.contains( name ) ) { + element.classList.add( name ); + } + } else if ( element.classList.contains( name ) ) { + element.classList.remove( name ); + } +} + const Popover = ( { headerTitle, onClose, @@ -259,6 +229,10 @@ const Popover = ( { position = 'top', range, focusOnMount = 'firstElement', + anchorRef, + shouldAnchorIncludePadding, + anchorVerticalBuffer, + anchorHorizontalBuffer, anchorRect, getAnchorRect, expandOnMobile, @@ -268,31 +242,116 @@ const Popover = ( { /* eslint-enable no-unused-vars */ ...contentProps } ) => { - const anchorRef = useRef( null ); + const anchorRefFallback = useRef( null ); const contentRef = useRef( null ); + const containerRef = useRef(); + const contentRect = useRef(); + const isMobileViewport = useViewportMatch( 'medium', '<' ); + const [ animateOrigin, setAnimateOrigin ] = useState(); + const isExpanded = expandOnMobile && isMobileViewport; - // Animation - const [ isReadyToAnimate, setIsReadyToAnimate ] = useState( false ); - - // Anchor position - const anchor = useAnchor( anchorRef, contentRef, anchorRect, getAnchorRect ); + noArrow = isExpanded || noArrow; - // Content size - const contentSize = useInitialContentSize( contentRef ); useEffect( () => { - if ( contentSize ) { - setIsReadyToAnimate( true ); + const containerEl = containerRef.current; + const contentEl = contentRef.current; + + if ( isExpanded ) { + setClass( containerEl, 'is-without-arrow', noArrow ); + setAttribute( containerEl, 'data-x-axis' ); + setAttribute( containerEl, 'data-y-axis' ); + setStyle( containerEl, 'top' ); + setStyle( containerEl, 'left' ); + setStyle( contentEl, 'maxHeight' ); + setStyle( contentEl, 'maxWidth' ); + return; } - }, [ contentSize ] ); - // Compute the position - const popoverPosition = usePopoverPosition( - anchor, - contentSize, + const refresh = () => { + let anchor = computeAnchorRect( + anchorRefFallback, + anchorRect, + getAnchorRect, + anchorRef, + shouldAnchorIncludePadding + ); + + if ( ! anchor ) { + return; + } + + anchor = addBuffer( + anchor, + anchorVerticalBuffer, + anchorHorizontalBuffer + ); + + if ( ! contentRect.current ) { + contentRect.current = contentEl.getBoundingClientRect(); + } + + const { + popoverTop, + popoverLeft, + xAxis, + yAxis, + contentHeight, + contentWidth, + } = computePopoverPosition( anchor, contentRect.current, position ); + + setClass( containerEl, 'is-without-arrow', noArrow || ( xAxis === 'center' && yAxis === 'middle' ) ); + setAttribute( containerEl, 'data-x-axis', xAxis ); + setAttribute( containerEl, 'data-y-axis', yAxis ); + setStyle( containerEl, 'top', typeof popoverTop === 'number' ? popoverTop + 'px' : '' ); + setStyle( containerEl, 'left', typeof popoverLeft === 'number' ? popoverLeft + 'px' : '' ); + setStyle( contentEl, 'maxHeight', typeof contentHeight === 'number' ? contentHeight + 'px' : '' ); + setStyle( contentEl, 'maxWidth', typeof contentWidth === 'number' ? contentWidth + 'px' : '' ); + + // Compute the animation position + const yAxisMapping = { + top: 'bottom', + bottom: 'top', + }; + const xAxisMapping = { + left: 'right', + right: 'left', + }; + const animateYAxis = yAxisMapping[ yAxis ] || 'middle'; + const animateXAxis = xAxisMapping[ xAxis ] || 'center'; + + setAnimateOrigin( animateXAxis + ' ' + animateYAxis ); + }; + + // Height may still adjust between now and the next tick. + const timeoutId = window.setTimeout( refresh ); + + /* + * There are sometimes we need to reposition or resize the popover that + * are not handled by the resize/scroll window events (i.e. CSS changes + * in the layout that changes the position of the anchor). + * + * For these situations, we refresh the popover every 0.5s + */ + const intervalHandle = window.setInterval( refresh, 500 ); + window.addEventListener( 'resize', refresh ); + window.addEventListener( 'scroll', refresh, true ); + + return () => { + window.clearTimeout( timeoutId ); + window.clearInterval( intervalHandle ); + window.removeEventListener( 'resize', refresh ); + window.removeEventListener( 'scroll', refresh, true ); + }; + }, [ + isExpanded, + anchorRect, + getAnchorRect, + anchorRef, + shouldAnchorIncludePadding, + anchorVerticalBuffer, + anchorHorizontalBuffer, position, - expandOnMobile, - contentRef - ); + ] ); useFocusContentOnMount( focusOnMount, contentRef ); @@ -351,53 +410,31 @@ const Popover = ( { onClickOutside( clickEvent ); } - // Compute the animation position - const yAxisMapping = { - top: 'bottom', - bottom: 'top', - }; - const xAxisMapping = { - left: 'right', - right: 'left', - }; - const animateYAxis = yAxisMapping[ popoverPosition.yAxis ] || 'middle'; - const animateXAxis = xAxisMapping[ popoverPosition.xAxis ] || 'center'; - - const classes = classnames( - 'components-popover', - className, - 'is-' + popoverPosition.yAxis, - 'is-' + popoverPosition.xAxis, - { - 'is-mobile': popoverPosition.isMobile, - 'is-without-arrow': noArrow || ( - popoverPosition.xAxis === 'center' && - popoverPosition.yAxis === 'middle' - ), - } - ); - // Disable reason: We care to capture the _bubbled_ events from inputs // within popover as inferring close intent. let content = ( <PopoverDetectOutside onFocusOutside={ handleOnFocusOutside }> <Animate - type={ animate && isReadyToAnimate ? 'appear' : null } - options={ { origin: animateYAxis + ' ' + animateXAxis } } + type={ animate && animateOrigin ? 'appear' : null } + options={ { origin: animateOrigin } } > { ( { className: animateClassName } ) => ( <IsolatedEventContainer - className={ classnames( classes, animateClassName ) } - style={ { - top: ! popoverPosition.isMobile && popoverPosition.popoverTop ? popoverPosition.popoverTop + 'px' : undefined, - left: ! popoverPosition.isMobile && popoverPosition.popoverLeft ? popoverPosition.popoverLeft + 'px' : undefined, - visibility: contentSize ? undefined : 'hidden', - } } + className={ classnames( + 'components-popover', + className, + animateClassName, + { + 'is-expanded': isExpanded, + 'is-without-arrow': noArrow, + } + ) } { ...contentProps } onKeyDown={ maybeClose } + ref={ containerRef } > - { popoverPosition.isMobile && ( + { isExpanded && ( <div className="components-popover__header"> <span className="components-popover__header-title"> { headerTitle } @@ -408,10 +445,6 @@ const Popover = ( { <div ref={ contentRef } className="components-popover__content" - style={ { - maxHeight: ! popoverPosition.isMobile && popoverPosition.contentHeight ? popoverPosition.contentHeight + 'px' : undefined, - maxWidth: ! popoverPosition.isMobile && popoverPosition.contentWidth ? popoverPosition.contentWidth + 'px' : undefined, - } } tabIndex="-1" > { children } @@ -438,9 +471,9 @@ const Popover = ( { } return ( - <span ref={ anchorRef }> + <span ref={ anchorRefFallback }> { content } - { popoverPosition.isMobile && expandOnMobile && <ScrollLock /> } + { isMobileViewport && expandOnMobile && <ScrollLock /> } </span> ); } } diff --git a/packages/components/src/popover/stories/_utils.js b/packages/components/src/popover/stories/_utils.js new file mode 100644 index 00000000000000..c99c23190e3603 --- /dev/null +++ b/packages/components/src/popover/stories/_utils.js @@ -0,0 +1,64 @@ +/** + * WordPress dependencies + */ +import { useCallback, useEffect, useState } from '@wordpress/element'; + +export const DraggableWrapper = ( + props = { role: 'presentation', style: {} } +) => { + const { role, style, ...restProps } = props; + const [ position, setPosition ] = useState( { x: 0, y: 0 } ); + const [ isDragging, setDragging ] = useState( false ); + + const updatePosition = useCallback( + ( event ) => { + if ( ! isDragging ) { + return false; + } + const { movementX, movementY } = event; + setPosition( { + ...position, + x: position.x + movementX, + y: position.y + movementY, + } ); + }, + [ isDragging, position ] + ); + + const startDragging = useCallback( () => setDragging( true ), [] ); + const stopDragging = useCallback( () => setDragging( false ), [] ); + + useEffect( () => { + document.addEventListener( 'mousemove', updatePosition ); + document.addEventListener( 'mouseup', stopDragging ); + return () => { + document.removeEventListener( 'mousemove', updatePosition ); + document.removeEventListener( 'mouseup', stopDragging ); + }; + }, [ updatePosition, stopDragging ] ); + + const { x, y } = position; + + /** + * Ideally, the x and y coordinates would be applied transform: translate. + * However, using transform is causing layout rendering issues when + * combined with components like Popover (due to their positioning + * strategy). + */ + const componentStyle = { + ...style, + position: 'relative', + top: y, + left: x, + }; + + return ( + <div + { ...restProps } + onMouseUp={ stopDragging } + onMouseDown={ startDragging } + role={ role } + style={ componentStyle } + /> + ); +}; diff --git a/packages/components/src/popover/stories/index.js b/packages/components/src/popover/stories/index.js new file mode 100644 index 00000000000000..ea401bd2174cc8 --- /dev/null +++ b/packages/components/src/popover/stories/index.js @@ -0,0 +1,100 @@ +/** + * External dependencies + */ +import { boolean, select, text } from '@storybook/addon-knobs'; + +/** + * Internal dependencies + */ +import { DraggableWrapper } from './_utils'; +import Popover from '../'; + +export default { title: 'Components|Popover', component: Popover }; + +export const _default = () => { + const show = boolean( 'Example: Show', true ); + const children = text( 'children', 'Popover Content' ); + const animate = boolean( 'animate', false ); + const focusOnMount = select( + 'focusOnMount', + { + firstElement: 'firstElement', + container: 'container', + }, + 'firstElement' + ); + const noArrow = boolean( 'noArrow', false ); + + const props = { + animate, + children, + focusOnMount, + noArrow, + }; + + if ( ! show ) { + return null; + } + + return <Popover { ...props } />; +}; + +const DragExample = ( props ) => { + const { label, content, ...restProps } = props; + + return ( + <div> + <div style={ { position: 'absolute', color: '#555' } }> + <p>Move the gray box around.</p> + <p> + The{ ' ' } + <strong style={ { background: 'pink' } }> + pink bordered + </strong>{ ' ' } + element is a parent. + </p> + <p> + The{ ' ' } + <strong style={ { background: 'cyan' } }> + cyan bordered + </strong>{ ' ' } + element is a sibling to <strong>Popover</strong>. + </p> + <p> + <strong>Popover</strong> aligns to the content within + parent. + </p> + </div> + <div + style={ { + height: '100vh', + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + } } + > + <DraggableWrapper + style={ { + background: '#ddd', + border: '2px solid pink', + borderRadius: 4, + padding: 10, + userSelect: 'none', + } } + > + <div style={ { border: '2px solid cyan' } }>{ label }</div> + <Popover { ...restProps }>{ content }</Popover> + </DraggableWrapper> + </div> + </div> + ); +}; + +export const positioning = () => { + const label = text( 'Example: Label', 'Drag Me!' ); + const content = text( 'Example: Content', 'Popover' ); + const noArrow = boolean( 'noArrow', false ); + + return <DragExample label={ label } content={ content } noArrow={ noArrow } />; +}; diff --git a/packages/components/src/popover/style.scss b/packages/components/src/popover/style.scss index 4312f89fe84f9c..106fbf59f74f82 100644 --- a/packages/components/src/popover/style.scss +++ b/packages/components/src/popover/style.scss @@ -6,14 +6,25 @@ $arrow-size: 8px; z-index: z-index(".components-popover"); left: 50%; - &.is-mobile { + // Hide the popover element until the position has been calculated. The position + // cannot be calculated until the popover element is rendered because the + // position depends on the size of the popover. + opacity: 0; + + &.is-expanded, + &[data-x-axis][data-y-axis] { + opacity: 1; + } + + &.is-expanded { top: 0; left: 0; right: 0; bottom: 0; + z-index: z-index(".components-popover") !important; } - &:not(.is-without-arrow):not(.is-mobile) { + &:not(.is-without-arrow) { margin-left: 2px; &::before { @@ -33,7 +44,7 @@ $arrow-size: 8px; line-height: 0; } - &.is-top { + &[data-y-axis="top"] { margin-top: - $arrow-size; &::before { @@ -54,7 +65,7 @@ $arrow-size: 8px; } } - &.is-bottom { + &[data-y-axis="bottom"] { margin-top: $arrow-size; &::before { @@ -75,7 +86,7 @@ $arrow-size: 8px; } } - &.is-middle.is-left { + &[data-y-axis="middle"][data-x-axis="left"] { margin-left: -$arrow-size; &::before { @@ -95,7 +106,7 @@ $arrow-size: 8px; } } - &.is-middle.is-right { + &[data-y-axis="middle"][data-x-axis="right"] { margin-left: $arrow-size; &::before { @@ -116,19 +127,17 @@ $arrow-size: 8px; } } - &:not(.is-mobile) { - &.is-top { - bottom: 100%; - } + &[data-y-axis="top"] { + bottom: 100%; + } - &.is-bottom { - top: 100%; - } + &[data-y-axis="bottom"] { + top: 100%; + } - &.is-middle { - align-items: center; - display: flex; - } + &[data-y-axis="middle"] { + align-items: center; + display: flex; } } @@ -138,42 +147,46 @@ $arrow-size: 8px; background: $white; height: 100%; - .components-popover.is-mobile & { - height: calc(100% - #{ $panel-header-height }); - border-top: 0; - } - - .components-popover:not(.is-mobile) & { + .components-popover & { position: absolute; height: auto; overflow-y: auto; min-width: 260px; } - .components-popover:not(.is-mobile).is-top & { + .components-popover.is-expanded & { + position: static; + height: calc(100% - #{ $panel-header-height }); + overflow-y: visible; + min-width: auto; + border: none; + border-top: $border-width solid $light-gray-500; + } + + .components-popover[data-y-axis="top"] & { bottom: 100%; } - .components-popover:not(.is-mobile).is-center & { + .components-popover[data-x-axis="center"] & { left: 50%; transform: translateX(-50%); } - .components-popover:not(.is-mobile).is-right & { + .components-popover[data-x-axis="right"] & { position: absolute; left: 100%; } - .components-popover:not(.is-mobile):not(.is-middle).is-right & { + .components-popover:not([data-y-axis="middle"])[data-x-axis="right"] & { margin-left: -24px; } - .components-popover:not(.is-mobile).is-left & { + .components-popover[data-x-axis="left"] & { position: absolute; right: 100%; } - .components-popover:not(.is-mobile):not(.is-middle).is-left & { + .components-popover:not([data-y-axis="middle"])[data-x-axis="left"] & { margin-right: -24px; } } @@ -186,7 +199,6 @@ $arrow-size: 8px; .components-popover__header { align-items: center; background: $white; - border: $border-width solid $light-gray-500; display: flex; height: $panel-header-height; justify-content: space-between; diff --git a/packages/components/src/popover/test/__snapshots__/index.js.snap b/packages/components/src/popover/test/__snapshots__/index.js.snap index 614f2dd55e5ee5..236ae2a7132936 100644 --- a/packages/components/src/popover/test/__snapshots__/index.js.snap +++ b/packages/components/src/popover/test/__snapshots__/index.js.snap @@ -8,9 +8,8 @@ exports[`Popover should pass additional props to portaled element 1`] = ` <div> <div> <div - class="components-popover is-bottom is-center components-animate__appear is-from-top" + class="components-popover" role="tooltip" - style="" > <div class="components-popover__content" @@ -33,8 +32,7 @@ exports[`Popover should render content 1`] = ` <div> <div> <div - class="components-popover is-bottom is-center components-animate__appear is-from-top" - style="" + class="components-popover" > <div class="components-popover__content" diff --git a/packages/components/src/popover/test/utils.js b/packages/components/src/popover/test/utils.js index 3c491fa98d9a54..670d1ac2e2624b 100644 --- a/packages/components/src/popover/test/utils.js +++ b/packages/components/src/popover/test/utils.js @@ -188,29 +188,6 @@ describe( 'computePopoverPosition', () => { contentHeight: null, popoverTop: 30, yAxis: 'bottom', - isMobile: false, } ); } ); - - it( 'should set isMobile to true on small viewPosts', () => { - const originalWidth = window.innerWidth; - - const anchorRect = { - top: 10, - left: 10, - bottom: 30, - right: 30, - width: 20, - height: 20, - }; - - const contentSize = { - width: 200, - height: 300, - }; - - window.innerWidth = 200; - expect( computePopoverPosition( anchorRect, contentSize, 'bottom right', true ).isMobile ).toBe( true ); - window.innerWidth = originalWidth; - } ); } ); diff --git a/packages/components/src/popover/utils.js b/packages/components/src/popover/utils.js index 1b71fab09aa574..9379e6d2786c09 100644 --- a/packages/components/src/popover/utils.js +++ b/packages/components/src/popover/utils.js @@ -3,7 +3,6 @@ * Module constants */ const HEIGHT_OFFSET = 10; // used by the arrow and a bit of empty space -const isMobileViewport = () => window.innerWidth < 782; const isRTL = () => document.documentElement.dir === 'rtl'; /** @@ -144,18 +143,16 @@ export function computePopoverYAxisPosition( anchorRect, contentSize, yAxis ) { * @param {Object} anchorRect Anchor Rect. * @param {Object} contentSize Content Size. * @param {string} position Position. - * @param {boolean} expandOnMobile Whether to expand the popover on mobile or not. * * @return {Object} Popover position and constraints. */ -export function computePopoverPosition( anchorRect, contentSize, position = 'top', expandOnMobile = false ) { +export function computePopoverPosition( anchorRect, contentSize, position = 'top' ) { const [ yAxis, xAxis = 'center' ] = position.split( ' ' ); const yAxisPosition = computePopoverYAxisPosition( anchorRect, contentSize, yAxis ); const xAxisPosition = computePopoverXAxisPosition( anchorRect, contentSize, xAxis, yAxisPosition.yAxis ); return { - isMobile: isMobileViewport() && expandOnMobile, ...xAxisPosition, ...yAxisPosition, }; diff --git a/packages/components/src/primitives/block-quotation/README.md b/packages/components/src/primitives/block-quotation/README.md index 83d9f9057609ed..30510083a866b4 100644 --- a/packages/components/src/primitives/block-quotation/README.md +++ b/packages/components/src/primitives/block-quotation/README.md @@ -1,4 +1,4 @@ -# HorizontalRule +# Block Quotation A drop-in replacement for the HTML [blockquote](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote) element that works both on the web and in the mobile apps. It indicates that the enclosed text is an extended quotation. diff --git a/packages/components/src/primitives/svg/index.js b/packages/components/src/primitives/svg/index.js index abb15e121dc2b0..efebe49b2896b0 100644 --- a/packages/components/src/primitives/svg/index.js +++ b/packages/components/src/primitives/svg/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -9,9 +14,10 @@ export const Path = ( props ) => createElement( 'path', props ); export const Polygon = ( props ) => createElement( 'polygon', props ); export const Rect = ( props ) => createElement( 'rect', props ); -export const SVG = ( props ) => { +export const SVG = ( { className, isPressed, ...props } ) => { const appliedProps = { ...props, + className: classnames( className, { 'is-pressed': isPressed } ) || undefined, role: 'img', 'aria-hidden': 'true', focusable: 'false', diff --git a/packages/components/src/primitives/svg/index.native.js b/packages/components/src/primitives/svg/index.native.js index 2226b5fdf3fc6b..aa619dc61e4913 100644 --- a/packages/components/src/primitives/svg/index.native.js +++ b/packages/components/src/primitives/svg/index.native.js @@ -16,21 +16,21 @@ export { Rect, } from 'react-native-svg'; -export const SVG = ( props ) => { +export const SVG = ( { className = '', isPressed, ...props } ) => { const colorScheme = props.colorScheme || 'light'; - const stylesFromClasses = ( props.className || '' ).split( ' ' ).map( ( element ) => styles[ element ] ).filter( Boolean ); - const defaultStyle = props.active ? styles[ 'is-active' ] : styles[ 'components-toolbar__control-' + colorScheme ]; + const stylesFromClasses = className.split( ' ' ).map( ( element ) => styles[ element ] ).filter( Boolean ); + const defaultStyle = isPressed ? styles[ 'is-pressed' ] : styles[ 'components-toolbar__control-' + colorScheme ]; const styleValues = Object.assign( {}, props.style, defaultStyle, ...stylesFromClasses ); - const safeProps = { ...props, style: styleValues }; + const appliedProps = { ...props, style: styleValues }; return ( <Svg //We want to re-render when style color is changed - key={ safeProps.style.color } + key={ appliedProps.style.color } height="100%" width="100%" - { ...safeProps } + { ...appliedProps } /> ); }; diff --git a/packages/components/src/primitives/svg/style.native.scss b/packages/components/src/primitives/svg/style.native.scss index b5216fa98040ce..674c01c3bea4c5 100644 --- a/packages/components/src/primitives/svg/style.native.scss +++ b/packages/components/src/primitives/svg/style.native.scss @@ -11,7 +11,7 @@ } .dashicon-active, -.is-active { +.is-pressed { color: #fff; fill: currentColor; } diff --git a/packages/components/src/radio-control/stories/index.js b/packages/components/src/radio-control/stories/index.js new file mode 100644 index 00000000000000..f90dd4d4938ddd --- /dev/null +++ b/packages/components/src/radio-control/stories/index.js @@ -0,0 +1,49 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import RadioControl from '../'; + +export default { title: 'Components|RadioControl', component: RadioControl }; + +const RadioControlWithState = ( props ) => { + const [ option, setOption ] = useState( 'public' ); + + return ( + <RadioControl + { ...props } + selected={ option } + onChange={ setOption } + /> + ); +}; + +const options = [ + { label: 'Public', value: 'public' }, + { label: 'Private', value: 'private' }, + { label: 'Password Protected', value: 'password' }, +]; + +export const _default = () => { + return ( + <RadioControlWithState + label="Post visibility" + options={ options } + /> + ); +}; + +export const withHelp = () => { + return ( + <RadioControlWithState + help="The visibility level for the current post" + label="Post visibility" + options={ options } + /> + ); +}; + diff --git a/packages/components/src/radio-control/style.scss b/packages/components/src/radio-control/style.scss index f765cdd34798dd..6d93d4bf146bb7 100644 --- a/packages/components/src/radio-control/style.scss +++ b/packages/components/src/radio-control/style.scss @@ -1,6 +1,10 @@ .components-radio-control { display: flex; flex-direction: column; + + .components-base-control__help { + margin-top: 0; + } } .components-radio-control__option:not(:last-child) { diff --git a/packages/components/src/range-control/README.md b/packages/components/src/range-control/README.md index b0511fdaec2d7d..e88ec3b9d44103 100644 --- a/packages/components/src/range-control/README.md +++ b/packages/components/src/range-control/README.md @@ -118,6 +118,7 @@ If this property is added, a label will be generated using label property as the - Type: `String` - Required: No +- Platform: Web | Mobile #### help @@ -125,6 +126,7 @@ If this property is added, a help text will be generated using help property as - Type: `String|WPElement` - Required: No +- Platform: Web #### beforeIcon @@ -132,6 +134,7 @@ If this property is added, a DashIcon component will be rendered before the slid - Type: `String` - Required: No +- Platform: Web #### afterIcon @@ -139,6 +142,7 @@ If this property is added, a DashIcon component will be rendered after the slide - Type: `String` - Required: No +- Platform: Web #### allowReset @@ -146,6 +150,7 @@ If this property is true, a button to reset the the slider is rendered. - Type: `Boolean` - Required: No +- Platform: Web | Mobile #### initialPosition @@ -153,6 +158,7 @@ If no value exists this prop contains the slider starting position. - Type: `Number` - Required: No +- Platform: Web | Mobile #### value @@ -160,6 +166,7 @@ The current value of the range slider. - Type: `Number` - Required: Yes +- Platform: Web | Mobile #### onChange @@ -168,6 +175,7 @@ If allowReset is true, when onChange is called without any parameter passed it s - Type: `function` - Required: Yes +- Platform: Web | Mobile #### min @@ -175,7 +183,7 @@ The minimum value accepted. If smaller values are inserted onChange will not be - Type: `Number` - Required: No - +- Platform: Web | Mobile #### max @@ -183,6 +191,24 @@ The maximum value accepted. If higher values are inserted onChange will not be c - Type: `Number` - Required: No +- Platform: Web | Mobile + +#### icon + +An icon to be shown above the slider next to it's container title. + +- Type: `String` +- Required: No +- Platform: Mobile + +#### separatorType + +Define if separator line under/above control row should be disabled or full width. By default it is placed below excluding underline the control icon. + +- Type: `String Enum` +- Values: `none` | `fullWidth` | `topFullWidth` +- Required: No +- Platform: Mobile ## Related components diff --git a/packages/components/src/range-control/index.native.js b/packages/components/src/range-control/index.native.js new file mode 100644 index 00000000000000..59c439c04d3f14 --- /dev/null +++ b/packages/components/src/range-control/index.native.js @@ -0,0 +1,48 @@ +/** + * Internal dependencies + */ +import RangeCell from '../mobile/bottom-sheet/range-cell'; + +function RangeControl( { + className, + currentInput, + label, + value, + instanceId, + onChange, + beforeIcon, + afterIcon, + help, + allowReset, + initialPosition, + min, + max, + ...props +} ) { + const id = `inspector-range-control-${ instanceId }`; + + const currentInputValue = currentInput === null ? value : currentInput; + + const initialSliderValue = isFinite( currentInputValue ) ? currentInputValue : initialPosition; + + return ( + <RangeCell + label={ label } + id={ id } + help={ help } + className={ className } + onChange={ onChange } + aria-describedby={ !! help ? `${ id }__help` : undefined } + minimumValue={ min } + maximumValue={ max } + value={ value } + beforeIcon={ beforeIcon } + afterIcon={ afterIcon } + allowReset={ allowReset } + defaultValue={ initialSliderValue } + { ...props } + /> + ); +} + +export default RangeControl; diff --git a/packages/components/src/range-control/stories/index.js b/packages/components/src/range-control/stories/index.js new file mode 100644 index 00000000000000..397b974d8da206 --- /dev/null +++ b/packages/components/src/range-control/stories/index.js @@ -0,0 +1,99 @@ +/** + * External dependencies + */ +import { number, text } from '@storybook/addon-knobs'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import RangeControl from '../'; + +export default { title: 'Components|RangeControl', component: RangeControl }; + +const RangeControlWithState = ( props ) => { + const [ value, setValue ] = useState( 5 ); + + return ( + <RangeControl + { ...props } + value={ value } + onChange={ setValue } + /> + ); +}; + +export const _default = () => { + const label = text( 'Label', 'How many columns should this use?' ); + + return ( + <RangeControlWithState + label={ label } + /> + ); +}; + +export const withHelp = () => { + const label = text( 'Label', 'How many columns should this use?' ); + const help = text( 'Help Text', 'Please select the number of columns you would like this to contain.' ); + + return ( + <RangeControlWithState + label={ label } + help={ help } + /> + ); +}; + +export const withMinimumAndMaximumLimits = () => { + const label = text( 'Label', 'How many columns should this use?' ); + const min = number( 'Min Value', 2 ); + const max = number( 'Max Value', 10 ); + + return ( + <RangeControlWithState + label={ label } + min={ min } + max={ max } + /> + ); +}; + +export const withIconBefore = () => { + const label = text( 'Label', 'How many columns should this use?' ); + const icon = text( 'Icon', 'wordpress' ); + + return ( + <RangeControlWithState + label={ label } + beforeIcon={ icon } + /> + ); +}; + +export const withIconAfter = () => { + const label = text( 'Label', 'How many columns should this use?' ); + const icon = text( 'Icon', 'wordpress' ); + + return ( + <RangeControlWithState + label={ label } + afterIcon={ icon } + /> + ); +}; + +export const withReset = () => { + const label = text( 'Label', 'How many columns should this use?' ); + + return ( + <RangeControlWithState + label={ label } + allowReset + /> + ); +}; diff --git a/packages/components/src/resizable-box/README.md b/packages/components/src/resizable-box/README.md index 42302f93b08b06..7cc42ce60603bf 100644 --- a/packages/components/src/resizable-box/README.md +++ b/packages/components/src/resizable-box/README.md @@ -53,3 +53,11 @@ const Edit = ( props ) => { ); } ``` + +### Props + +Name | Type | Default | Description +--- | --- | --- | --- +`showHandle` | `bool` | `false` | Determines of the resize handles are visible. + +For additional props, check out [re-resizable](https://github.com/bokuweb/re-resizable#props). \ No newline at end of file diff --git a/packages/components/src/resizable-box/index.js b/packages/components/src/resizable-box/index.js index fc63dd126a24e8..1426f0b48b83e9 100644 --- a/packages/components/src/resizable-box/index.js +++ b/packages/components/src/resizable-box/index.js @@ -4,7 +4,7 @@ import classnames from 'classnames'; import { Resizable } from 're-resizable'; -function ResizableBox( { className, ...props } ) { +function ResizableBox( { className, showHandle = false, ...props } ) { // Removes the inline styles in the drag handles. const handleStylesOverrides = { width: null, @@ -23,7 +23,8 @@ function ResizableBox( { className, ...props } ) { <Resizable className={ classnames( 'components-resizable-box__container', - className, + showHandle && 'has-show-handle', + className ) } handleClasses={ { top: classnames( diff --git a/packages/components/src/resizable-box/stories/index.js b/packages/components/src/resizable-box/stories/index.js new file mode 100644 index 00000000000000..68742c08c22b27 --- /dev/null +++ b/packages/components/src/resizable-box/stories/index.js @@ -0,0 +1,83 @@ +/** + * External dependencies + */ +import { boolean, number, text } from '@storybook/addon-knobs'; + +/** + * Internal dependencies + */ +import ResizableBox from '../'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +export default { title: 'Components|ResizableBox', component: ResizableBox }; + +const Example = ( props ) => { + const [ attributes, setAttributes ] = useState( { height: 200, width: 400 } ); + const { height, width } = attributes; + const { children, ...restProps } = props; + + return ( + <div style={ { margin: 30 } }> + <ResizableBox + { ...restProps } + size={ { + height, + width, + } } + onResizeStop={ ( event, direction, elt, delta ) => { + setAttributes( { + height: parseInt( height + delta.height, 10 ), + width: parseInt( width + delta.width, 10 ), + } ); + } } + > + { children } + </ResizableBox> + </div> + ); +}; + +export const _default = () => { + const content = text( 'Example: Content', 'Resize' ); + const showHandle = boolean( 'showHandle', true ); + const minHeight = number( 'minHeight', 50 ); + const minWidth = number( 'minWidth', 50 ); + const enable = { + top: boolean( 'enable.top', false ), + right: boolean( 'enable.right', true ), + bottom: boolean( 'enable.bottom', true ), + left: boolean( 'enable.left', false ), + topRight: boolean( 'enable.topRight', false ), + bottomRight: boolean( 'enable.bottomRight', true ), + bottomLeft: boolean( 'enable.bottomLeft', false ), + topLeft: boolean( 'enable.topLeft', false ), + }; + + const props = { + enable, + minHeight, + minWidth, + showHandle, + }; + + return ( + <Example { ...props }> + <div + style={ { + background: '#eee', + display: 'flex', + height: '100%', + width: '100%', + alignItems: 'center', + justifyContent: 'center', + } } + > + <span>{ content }</span> + </div> + </Example> + ); +}; diff --git a/packages/components/src/resizable-box/style.scss b/packages/components/src/resizable-box/style.scss index 4297038291124a..7231ef4326a7da 100644 --- a/packages/components/src/resizable-box/style.scss +++ b/packages/components/src/resizable-box/style.scss @@ -5,8 +5,10 @@ width: $resize-handler-container-size; height: $resize-handler-container-size; - // Show the resize handle when selected. - .components-resizable-box__container.is-selected & { + // Show the resize handle when selected OR + // Show the resize handle if set in props + .components-resizable-box__container.is-selected &, + .components-resizable-box__container.has-show-handle & { display: block; } } diff --git a/packages/components/src/responsive-wrapper/index.js b/packages/components/src/responsive-wrapper/index.js index 7236f5eff1dc34..4c5dfca6ec363b 100644 --- a/packages/components/src/responsive-wrapper/index.js +++ b/packages/components/src/responsive-wrapper/index.js @@ -8,20 +8,26 @@ import classnames from 'classnames'; */ import { cloneElement, Children } from '@wordpress/element'; -function ResponsiveWrapper( { naturalWidth, naturalHeight, children } ) { +function ResponsiveWrapper( { + naturalWidth, + naturalHeight, + children, + isInline = false, +} ) { if ( Children.count( children ) !== 1 ) { return null; } const imageStyle = { paddingBottom: ( naturalHeight / naturalWidth * 100 ) + '%', }; + const TagName = isInline ? 'span' : 'div'; return ( - <div className="components-responsive-wrapper"> - <div style={ imageStyle } /> + <TagName className="components-responsive-wrapper"> + <TagName style={ imageStyle } /> { cloneElement( children, { className: classnames( 'components-responsive-wrapper__content', children.props.className ), } ) } - </div> + </TagName> ); } diff --git a/packages/components/src/responsive-wrapper/style.scss b/packages/components/src/responsive-wrapper/style.scss index ec89002f9b1a42..c51288172fbcba 100644 --- a/packages/components/src/responsive-wrapper/style.scss +++ b/packages/components/src/responsive-wrapper/style.scss @@ -1,6 +1,10 @@ .components-responsive-wrapper { position: relative; max-width: 100%; + &, + & > span { + display: block; + } } .components-responsive-wrapper__content { diff --git a/packages/components/src/scroll-lock/index.js b/packages/components/src/scroll-lock/index.js index 0d600aac62f14d..1d6255d8c12ef9 100644 --- a/packages/components/src/scroll-lock/index.js +++ b/packages/components/src/scroll-lock/index.js @@ -12,7 +12,7 @@ import { Component } from '@wordpress/element'; * @param {Object} args Keyword args. * @param {HTMLDocument} args.htmlDocument The document to lock the scroll for. * @param {string} args.className The name of the class used to lock scrolling. - * @return {Component} The bound ScrollLock component. + * @return {WPComponent} The bound ScrollLock component. */ export function createScrollLockComponent( { htmlDocument = document, diff --git a/packages/components/src/scroll-lock/stories/index.js b/packages/components/src/scroll-lock/stories/index.js new file mode 100644 index 00000000000000..5d225a33c17337 --- /dev/null +++ b/packages/components/src/scroll-lock/stories/index.js @@ -0,0 +1,70 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import Button from '../../button'; +import ScrollLock from '../'; + +export default { title: 'Components|ScrollLock', component: ScrollLock }; + +const Example = () => { + const [ isScrollLocked, setScrollLocked ] = useState( false ); + const toggleLock = () => setScrollLocked( ! isScrollLocked ); + + return ( + <StripedBackground> + <div>Start scrolling down...</div> + <ToggleContainer> + <Button isDefault onClick={ toggleLock }> + Toggle Scroll Lock + </Button> + { isScrollLocked && <ScrollLock /> } + <p> + Scroll locked: <strong>{ isScrollLocked ? 'Yes' : 'No' }</strong> + </p> + </ToggleContainer> + </StripedBackground> + ); +}; + +function StripedBackground( props ) { + return ( + <div + style={ { + backgroundColor: '#fff', + backgroundImage: + 'linear-gradient(transparent 50%, rgba(0, 0, 0, 0.05) 50%)', + backgroundSize: '50px 50px', + height: 3000, + position: 'relative', + } } + { ...props } + /> + ); +} + +function ToggleContainer( props ) { + const { children } = props; + return ( + <div + style={ { + position: 'sticky', + top: 0, + padding: 40, + display: 'flex', + justifyContent: 'center', + textAlign: 'center', + } } + > + <div>{ children }</div> + </div> + ); +} + +export const _default = () => { + return <Example />; +}; diff --git a/packages/components/src/slot-fill/context.js b/packages/components/src/slot-fill/context.js index 587c1ccab5ccb8..b4229c71ffaad5 100644 --- a/packages/components/src/slot-fill/context.js +++ b/packages/components/src/slot-fill/context.js @@ -29,6 +29,7 @@ class SlotFillProvider extends Component { this.unregisterFill = this.unregisterFill.bind( this ); this.getSlot = this.getSlot.bind( this ); this.getFills = this.getFills.bind( this ); + this.hasFills = this.hasFills.bind( this ); this.subscribe = this.subscribe.bind( this ); this.slots = {}; @@ -41,6 +42,7 @@ class SlotFillProvider extends Component { unregisterFill: this.unregisterFill, getSlot: this.getSlot, getFills: this.getFills, + hasFills: this.hasFills, subscribe: this.subscribe, }; } @@ -105,6 +107,10 @@ class SlotFillProvider extends Component { return sortBy( this.fills[ name ], 'occurrence' ); } + hasFills( name ) { + return this.fills[ name ] && !! this.fills[ name ].length; + } + resetFillOccurrence( name ) { forEach( this.fills[ name ], ( instance ) => { instance.occurrence = undefined; diff --git a/packages/components/src/snackbar/README.md b/packages/components/src/snackbar/README.md index c25956fba04c81..8ae5c4ec0b2778 100644 --- a/packages/components/src/snackbar/README.md +++ b/packages/components/src/snackbar/README.md @@ -41,7 +41,7 @@ const MySnackbarNotice = () => ( The following props are used to control the display of the component. * `onRemove`: function called when dismissing the notice. -* `actions`: (array) an array of action objects. Each member object should contain a `label` and either a `url` link string or `onClick` callback function. A `className` property can be used to add custom classes to the button styles. +* `actions`: (array) an array of action objects. Each member object should contain a `label` and either a `url` link string or `onClick` callback function. ## Related components diff --git a/packages/components/src/snackbar/stories/index.js b/packages/components/src/snackbar/stories/index.js new file mode 100644 index 00000000000000..bcfb2afeb73169 --- /dev/null +++ b/packages/components/src/snackbar/stories/index.js @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import { text } from '@storybook/addon-knobs'; + +/** + * Internal dependencies + */ +import Snackbar from '../'; + +export default { title: 'Components|Snackbar', component: Snackbar }; + +export const _default = () => { + const content = text( 'Content', 'Use Snackbars to communicate low priority, non-interruptive messages to the user.' ); + + return ( + <Snackbar> + { content } + </Snackbar> + ); +}; + +export const withActions = () => { + const content = text( 'Content', 'Use Snackbars with an action link to an external page.' ); + const actions = [ { label: text( 'Label', 'Open WP.org' ), url: text( 'URL', 'https://wordpress.org' ) } ]; + + return ( + <Snackbar actions={ actions }> + { content } + </Snackbar> + ); +}; diff --git a/packages/components/src/snackbar/style.scss b/packages/components/src/snackbar/style.scss index 013d7abed012e8..4b4dc5341fd1d5 100644 --- a/packages/components/src/snackbar/style.scss +++ b/packages/components/src/snackbar/style.scss @@ -28,18 +28,25 @@ } .components-snackbar__action.components-button { - margin-left: 32px; + margin-left: $grid-size * 4; color: $white; height: auto; flex-shrink: 0; line-height: $default-line-height; + padding: 0; &:not(:disabled):not([aria-disabled="true"]):not(.is-default) { text-decoration: underline; + background-color: transparent; - &:hover { + &:focus { color: $white; - text-decoration: none; + box-shadow: none; + outline: 1px dotted $white; + } + + &:hover { + color: $blue-medium-400; } } } diff --git a/packages/components/src/spinner/stories/index.js b/packages/components/src/spinner/stories/index.js new file mode 100644 index 00000000000000..95ffcc24c99e07 --- /dev/null +++ b/packages/components/src/spinner/stories/index.js @@ -0,0 +1,12 @@ +/** + * Internal dependencies + */ +import Spinner from '../'; + +export default { title: 'Components|Spinner', component: Spinner }; + +export const _default = () => { + return ( + <Spinner /> + ); +}; diff --git a/packages/components/src/style.scss b/packages/components/src/style.scss index 6365bff68cf568..80ac1ac25c10e2 100644 --- a/packages/components/src/style.scss +++ b/packages/components/src/style.scss @@ -7,11 +7,15 @@ @import "./circular-option-picker/style.scss"; @import "./color-indicator/style.scss"; @import "./color-picker/style.scss"; +@import "./custom-gradient-picker/style.scss"; +@import "./custom-select-control/style.scss"; @import "./dashicon/style.scss"; @import "./date-time/style.scss"; +@import "./dimension-control/style.scss"; @import "./disabled/style.scss"; @import "./draggable/style.scss"; @import "./drop-zone/style.scss"; +@import "./dropdown/style.scss"; @import "./dropdown-menu/style.scss"; @import "./external-link/style.scss"; @import "./focal-point-picker/style.scss"; @@ -19,10 +23,12 @@ @import "./form-file-upload/style.scss"; @import "./form-toggle/style.scss"; @import "./form-token-field/style.scss"; +@import "./guide/style.scss"; @import "./higher-order/navigate-regions/style.scss"; @import "./icon-button/style.scss"; @import "./menu-group/style.scss"; @import "./menu-item/style.scss"; +@import "./menu-items-choice/style.scss"; @import "./modal/style.scss"; @import "./notice/style.scss"; @import "./panel/style.scss"; @@ -43,4 +49,6 @@ @import "./toggle-control/style.scss"; @import "./toolbar/style.scss"; @import "./toolbar-button/style.scss"; +@import "./toolbar-group/style.scss"; @import "./tooltip/style.scss"; +@import "./visually-hidden/style.scss"; diff --git a/packages/components/src/tab-panel/stories/index.js b/packages/components/src/tab-panel/stories/index.js new file mode 100644 index 00000000000000..8d045fb876f4c6 --- /dev/null +++ b/packages/components/src/tab-panel/stories/index.js @@ -0,0 +1,34 @@ +/** + * External dependencies + */ +import { text } from '@storybook/addon-knobs'; + +/** + * Internal dependencies + */ +import TabPanel from '../'; + +export default { title: 'Components|TabPanel', component: TabPanel }; + +export const _default = () => { + return ( + <TabPanel className="my-tab-panel" + activeClass="active-tab" + tabs={ [ + { + name: 'tab1', + title: text( 'Tab 1 title', 'Tab 1' ), + className: 'tab-one', + }, + { + name: 'tab2', + title: text( 'Tab 2 title', 'Tab 2' ), + className: 'tab-two', + }, + ] }> + { + ( tab ) => <p>Selected tab: { tab.title }</p> + } + </TabPanel> + ); +}; diff --git a/packages/components/src/text-highlight/README.md b/packages/components/src/text-highlight/README.md new file mode 100644 index 00000000000000..e8a1e4be5d8429 --- /dev/null +++ b/packages/components/src/text-highlight/README.md @@ -0,0 +1,39 @@ +# TextHighlight + +Highlights occurances of a given string within another string of text. Wraps each match with a [`<mark>` tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark) which provides browser default styling. + +## Usage + +Pass in the `text` and the `highlight` string to be matched against. + +In the example below, the string `Gutenberg` would be highlighted twice. + +```jsx +import { TextHighlight } from '@wordpress/components'; + +const MyTextHighlight = () => ( + <TextHighlight + text="Why do we like Gutenberg? Because Gutenberg is the best!" + highlight="Gutenberg" + /> +); +``` + +## Props + +The component accepts the following props. + +### text + +The string of text to be tested for occurances of then given `highlight`. + +- Type: `String` +- Required: Yes + + +### highlight + +The string to search for and highlight within the `text`. Case insensitive. Multiple matches. + +- Type: `String` +- Required: Yes diff --git a/packages/components/src/text-highlight/index.js b/packages/components/src/text-highlight/index.js new file mode 100644 index 00000000000000..5d92f42f5382dc --- /dev/null +++ b/packages/components/src/text-highlight/index.js @@ -0,0 +1,28 @@ +/** + * External dependencies + */ +import { escapeRegExp } from 'lodash'; + +/** + * WordPress dependencies + */ +import { + __experimentalCreateInterpolateElement, +} from '@wordpress/element'; + +const TextHighlight = ( { text = '', highlight = '' } ) => { + if ( ! highlight.trim() ) { + return text; + } + + const regex = new RegExp( `(${ escapeRegExp( highlight ) })`, 'gi' ); + + return __experimentalCreateInterpolateElement( + text.replace( regex, '<mark>$&</mark>' ), + { + mark: <mark />, + } + ); +}; + +export default TextHighlight; diff --git a/packages/components/src/text-highlight/stories/index.js b/packages/components/src/text-highlight/stories/index.js new file mode 100644 index 00000000000000..71bc81fc125032 --- /dev/null +++ b/packages/components/src/text-highlight/stories/index.js @@ -0,0 +1,21 @@ +/** + * External dependencies + */ +import { text } from '@storybook/addon-knobs'; + +/** + * Internal dependencies + */ +import TextHighlight from '../'; + +export default { title: 'Components|TextHighlight', component: TextHighlight }; + +export const _default = () => { + const textToMatch = text( 'Text', 'We call the new editor Gutenberg. The entire editing experience has been rebuilt for media rich pages and posts.' ); + + const textToHighlight = text( 'Text to be highlighted ', 'Gutenberg' ); + + return ( + <TextHighlight text={ textToMatch } highlight={ textToHighlight } /> + ); +}; diff --git a/packages/components/src/text-highlight/test/index.js b/packages/components/src/text-highlight/test/index.js new file mode 100644 index 00000000000000..a93f28fc826b5d --- /dev/null +++ b/packages/components/src/text-highlight/test/index.js @@ -0,0 +1,112 @@ +/** + * External dependencies + */ +import { render, unmountComponentAtNode } from 'react-dom'; +import { act } from 'react-dom/test-utils'; + +/** + * Internal dependencies + */ +import TextHighlight from '../index'; + +let container = null; + +beforeEach( () => { + // setup a DOM element as a render target + container = document.createElement( 'div' ); + document.body.appendChild( container ); +} ); + +afterEach( () => { + // cleanup on exiting + unmountComponentAtNode( container ); + container.remove(); + container = null; +} ); + +const defaultText = 'We call the new editor Gutenberg. The entire editing experience has been rebuilt for media rich pages and posts.'; + +describe( 'Basic rendering', () => { + it.each( [ + [ 'Gutenberg' ], + [ 'media' ], + ] )( 'should highlight the singular occurance of the text "%s" in the text if it exists', ( highlight ) => { + act( () => { + render( + <TextHighlight + text={ defaultText } + highlight={ highlight } + />, container + ); + } ); + + const highlightedEls = Array.from( container.querySelectorAll( 'mark' ) ); + + highlightedEls.forEach( ( el ) => { + expect( el.innerHTML ).toEqual( expect.stringContaining( highlight ) ); + } ); + } ); + + it( 'should highlight multiple occurances of the string every time it exists in the text', () => { + const highlight = 'edit'; + + act( () => { + render( + <TextHighlight + text={ defaultText } + highlight={ highlight } + />, container + ); + } ); + + const highlightedEls = Array.from( container.querySelectorAll( 'mark' ) ); + + expect( highlightedEls ).toHaveLength( 2 ); + + highlightedEls.forEach( ( el ) => { + expect( el.innerHTML ).toEqual( expect.stringContaining( highlight ) ); + } ); + } ); + + it( 'should highlight occurances of a string regardless of capitalisation', () => { + const highlight = 'The'; // note this occurs in both sentance of lowercase forms + + act( () => { + render( + <TextHighlight + text={ defaultText } + highlight={ highlight } + />, container + ); + } ); + + const highlightedEls = Array.from( container.querySelectorAll( 'mark' ) ); + + // Our component matcher is case insensitive so string.Containing will + // return a false failure + const regex = new RegExp( highlight, 'i' ); + + expect( highlightedEls ).toHaveLength( 2 ); + + highlightedEls.forEach( ( el ) => { + expect( el.innerHTML ).toMatch( regex ); + } ); + } ); + + it( 'should not highlight a string that is not in the text', () => { + const highlight = 'Antidisestablishmentarianism'; + + act( () => { + render( + <TextHighlight + text={ defaultText } + highlight={ highlight } + />, container + ); + } ); + + const highlightedEls = Array.from( container.querySelectorAll( 'mark' ) ); + + expect( highlightedEls ).toHaveLength( 0 ); + } ); +} ); diff --git a/packages/components/src/tip/stories/index.js b/packages/components/src/tip/stories/index.js new file mode 100644 index 00000000000000..41080be29275bd --- /dev/null +++ b/packages/components/src/tip/stories/index.js @@ -0,0 +1,20 @@ +/** + * External dependencies + */ +import { text } from '@storybook/addon-knobs'; + +/** + * Internal dependencies + */ +import Tip from '../'; + +export default { title: 'Components|Tip', component: Tip }; + +export const _default = () => { + const tipText = text( 'Text', 'An example tip' ); + return ( + <Tip> + <p>{ tipText }</p> + </Tip> + ); +}; diff --git a/packages/components/src/toggle-control/stories/index.js b/packages/components/src/toggle-control/stories/index.js new file mode 100644 index 00000000000000..789b50079b94ac --- /dev/null +++ b/packages/components/src/toggle-control/stories/index.js @@ -0,0 +1,52 @@ +/** + * External dependencies + */ +import { text } from '@storybook/addon-knobs'; + +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import ToggleControl from '../'; + +export default { title: 'Components|ToggleControl', component: ToggleControl }; + +const ToggleControlWithState = ( { helpTextChecked, helpTextUnchecked, ...props } ) => { + const [ hasFixedBackground, setHasFixedBackground ] = useState( true ); + return ( + <ToggleControl + { ...props } + help={ hasFixedBackground ? helpTextChecked : helpTextUnchecked } + checked={ hasFixedBackground } + onChange={ setHasFixedBackground } + /> + ); +}; + +export const _default = () => { + const label = text( 'Label', 'Does this have a fixed background?' ); + + return ( + <ToggleControlWithState + label={ label } + /> + ); +}; + +export const withHelpText = () => { + const label = text( 'Label', 'Does this have a fixed background?' ); + const helpTextChecked = text( 'Help When Checked', 'Has fixed background.' ); + const helpTextUnchecked = text( 'Help When Unchecked', 'No fixed background.' ); + + return ( + <ToggleControlWithState + label={ label } + helpTextChecked={ helpTextChecked } + helpTextUnchecked={ helpTextUnchecked } + /> + ); +}; diff --git a/packages/components/src/toggle-control/style.scss b/packages/components/src/toggle-control/style.scss index e4b3e89b3ab49f..8d9a5b18bb15c8 100644 --- a/packages/components/src/toggle-control/style.scss +++ b/packages/components/src/toggle-control/style.scss @@ -1,6 +1,8 @@ .components-toggle-control .components-base-control__field { display: flex; margin-bottom: $grid-size-small * 3; + line-height: initial; + align-items: center; .components-form-toggle { margin-right: $grid-size-large; @@ -8,6 +10,5 @@ .components-toggle-control__label { display: block; - margin-bottom: 4px; } } diff --git a/packages/components/src/toolbar-button/accessible-toolbar-button-container.js b/packages/components/src/toolbar-button/accessible-toolbar-button-container.js new file mode 100644 index 00000000000000..5d026c4b83e8c9 --- /dev/null +++ b/packages/components/src/toolbar-button/accessible-toolbar-button-container.js @@ -0,0 +1,26 @@ +/** + * External dependencies + */ +import { useToolbarItem } from 'reakit/Toolbar'; + +/** + * WordPress dependencies + */ +import { Children, cloneElement, useContext } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import ToolbarContext from '../toolbar-context'; + +function AccessibleToolbarButtonContainer( props ) { + const accessibleToolbarState = useContext( ToolbarContext ); + const childButton = Children.only( props.children ); + + // https://reakit.io/docs/composition/#props-hooks + const itemHTMLProps = useToolbarItem( accessibleToolbarState, childButton.props ); + + return <div { ...props }>{ cloneElement( childButton, itemHTMLProps ) }</div>; +} + +export default AccessibleToolbarButtonContainer; diff --git a/packages/components/src/toolbar-button/accessible-toolbar-button-container.native.js b/packages/components/src/toolbar-button/accessible-toolbar-button-container.native.js new file mode 100644 index 00000000000000..7fe5e6657bcd10 --- /dev/null +++ b/packages/components/src/toolbar-button/accessible-toolbar-button-container.native.js @@ -0,0 +1,6 @@ +/** + * Internal dependencies + */ +import ToolbarButtonContainer from './toolbar-button-container'; + +export default ToolbarButtonContainer; diff --git a/packages/components/src/toolbar-button/index.js b/packages/components/src/toolbar-button/index.js index 8c2374b0284d1d..2fb46662797ba5 100644 --- a/packages/components/src/toolbar-button/index.js +++ b/packages/components/src/toolbar-button/index.js @@ -3,10 +3,17 @@ */ import classnames from 'classnames'; +/** + * WordPress dependencies + */ +import { useContext } from '@wordpress/element'; + /** * Internal dependencies */ import IconButton from '../icon-button'; +import ToolbarContext from '../toolbar-context'; +import AccessibleToolbarButtonContainer from './accessible-toolbar-button-container'; import ToolbarButtonContainer from './toolbar-button-container'; function ToolbarButton( { @@ -22,26 +29,44 @@ function ToolbarButton( { extraProps, children, } ) { + // It'll contain state if `ToolbarButton` is being used within + // `<Toolbar __experimentalAccessibilityLabel="label" />` + const accessibleToolbarState = useContext( ToolbarContext ); + + const button = ( + <IconButton + icon={ icon } + label={ title } + shortcut={ shortcut } + data-subscript={ subscript } + onClick={ ( event ) => { + event.stopPropagation(); + if ( onClick ) { + onClick( event ); + } + } } + className={ classnames( + 'components-toolbar__control', + className, + ) } + isPressed={ isActive } + disabled={ isDisabled } + { ...extraProps } + /> + ); + + if ( accessibleToolbarState ) { + return ( + <AccessibleToolbarButtonContainer className={ containerClassName }> + { button } + </AccessibleToolbarButtonContainer> + ); + } + + // ToolbarButton is being used outside of the accessible Toolbar return ( <ToolbarButtonContainer className={ containerClassName }> - <IconButton - icon={ icon } - label={ title } - shortcut={ shortcut } - data-subscript={ subscript } - onClick={ ( event ) => { - event.stopPropagation(); - onClick(); - } } - className={ classnames( - 'components-toolbar__control', - className, - { 'is-active': isActive } - ) } - aria-pressed={ isActive } - disabled={ isDisabled } - { ...extraProps } - /> + { button } { children } </ToolbarButtonContainer> ); diff --git a/packages/components/src/toolbar-button/style.scss b/packages/components/src/toolbar-button/style.scss index cd434448784a20..f73782b4bab4f3 100644 --- a/packages/components/src/toolbar-button/style.scss +++ b/packages/components/src/toolbar-button/style.scss @@ -1,36 +1,7 @@ .components-toolbar__control.components-button { - display: inline-flex; - align-items: flex-end; - margin: 0; - padding: 3px; - outline: none; - cursor: pointer; - position: relative; width: $icon-button-size; height: $icon-button-size; - // Unset icon button styles - &:not([aria-disabled="true"]):not(.is-default):active, - &:not([aria-disabled="true"]):hover, - &:not([aria-disabled="true"]):focus { - outline: none; - box-shadow: none; - background: none; - border: none; - } - - // Disabled - &:disabled { - cursor: default; - } - - & > svg { - padding: 5px; - border-radius: $radius-round-rectangle; - height: 30px; - width: 30px; - } - // Subscript for numbered icon buttons, like headings &[data-subscript] svg { padding: 5px 10px 5px 0; @@ -47,43 +18,21 @@ bottom: 10px; } - // Assign hover style to child element, not the button itself - &:not(:disabled):not([aria-disabled="true"]):hover { - box-shadow: none; - } - - &:not(:disabled).is-active > svg, - &:not(:disabled):hover > svg { - @include formatting-button-style__hover; - } - - // Active & toggled style - &:not(:disabled).is-active > svg { - @include formatting-button-style__active; - } - - &:not(:disabled).is-active[data-subscript]::after { + &:not(:disabled).is-pressed[data-subscript]::after { color: $white; } - // Focus style - &:not(:disabled):focus > svg { - @include formatting-button-style__focus; - // Remove outline from SVG to apply on focused element - see below. - outline: 0; + &.is-pressed { + padding: 3px; + outline: none; } - // Microsoft Edge in high contrast mode only displays outlines on focused elements, not their children. - &:not(:disabled).is-active { - outline: 1px dotted transparent; - outline-offset: -2px; - } - // Microsoft Edge in high contrast mode only displays outlines on focused elements, not their children. - &:not(:disabled):focus { - outline: 2px solid transparent; + &.is-pressed > svg { + padding: 5px; + border-radius: $radius-round-rectangle; + height: 30px; + width: 30px; + box-sizing: border-box; + @include formatting-button-style__active; } } - -.components-toolbar__control .dashicon { - display: block; -} diff --git a/packages/components/src/toolbar-context/index.js b/packages/components/src/toolbar-context/index.js new file mode 100644 index 00000000000000..a5095c503f6ece --- /dev/null +++ b/packages/components/src/toolbar-context/index.js @@ -0,0 +1,8 @@ +/** + * WordPress dependencies + */ +import { createContext } from '@wordpress/element'; + +const ToolbarContext = createContext(); + +export default ToolbarContext; diff --git a/packages/components/src/toolbar-group/index.js b/packages/components/src/toolbar-group/index.js new file mode 100644 index 00000000000000..866a671875c18e --- /dev/null +++ b/packages/components/src/toolbar-group/index.js @@ -0,0 +1,112 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { flatMap } from 'lodash'; + +/** + * WordPress dependencies + */ +import { useContext } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import ToolbarButton from '../toolbar-button'; +import ToolbarGroupContainer from './toolbar-group-container'; +import ToolbarGroupCollapsed from './toolbar-group-collapsed'; +import ToolbarContext from '../toolbar-context'; + +/** + * Renders a collapsible group of controls + * + * The `controls` prop accepts an array of sets. A set is an array of controls. + * Controls have the following shape: + * + * ``` + * { + * icon: string, + * title: string, + * subscript: string, + * onClick: Function, + * isActive: boolean, + * isDisabled: boolean + * } + * ``` + * + * For convenience it is also possible to pass only an array of controls. It is + * then assumed this is the only set. + * + * Either `controls` or `children` is required, otherwise this components + * renders nothing. + * + * @param {Object} props Component props. + * @param {Array} [props.controls] The controls to render in this toolbar. + * @param {WPElement} [props.children] Any other things to render inside the toolbar besides the controls. + * @param {string} [props.className] Class to set on the container div. + * @param {boolean} [props.isCollapsed] Turns ToolbarGroup into a dropdown menu. + * @param {WPBlockTypeIconRender} [props.icon] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element. + * @param {string} [props.label] The menu item text. + */ +function ToolbarGroup( { + controls = [], + children, + className, + isCollapsed, + icon, + title, + ...otherProps +} ) { + // It'll contain state if `ToolbarGroup` is being used within + // `<Toolbar accessibilityLabel="label" />` + const accessibleToolbarState = useContext( ToolbarContext ); + + if ( ( ! controls || ! controls.length ) && ! children ) { + return null; + } + + const finalClassName = classnames( + // Unfortunately, there's legacy code referencing to `.components-toolbar` + // So we can't get rid of it + accessibleToolbarState ? 'components-toolbar-group' : 'components-toolbar', + className + ); + + // Normalize controls to nested array of objects (sets of controls) + let controlSets = controls; + if ( ! Array.isArray( controlSets[ 0 ] ) ) { + controlSets = [ controlSets ]; + } + + if ( isCollapsed ) { + return ( + <ToolbarGroupCollapsed + icon={ icon } + label={ title } + controls={ controlSets } + className={ finalClassName } + children={ children } + { ...otherProps } + /> + ); + } + + return ( + <ToolbarGroupContainer className={ finalClassName } { ...otherProps }> + { flatMap( controlSets, ( controlSet, indexOfSet ) => + controlSet.map( ( control, indexOfControl ) => ( + <ToolbarButton + key={ [ indexOfSet, indexOfControl ].join() } + containerClassName={ + indexOfSet > 0 && indexOfControl === 0 ? 'has-left-divider' : null + } + { ...control } + /> + ) ) + ) } + { children } + </ToolbarGroupContainer> + ); +} + +export default ToolbarGroup; diff --git a/packages/components/src/toolbar-group/stories/index.js b/packages/components/src/toolbar-group/stories/index.js new file mode 100644 index 00000000000000..3f969e1ee08c47 --- /dev/null +++ b/packages/components/src/toolbar-group/stories/index.js @@ -0,0 +1,28 @@ +/** + * Internal dependencies + */ +import { ToolbarButton, ToolbarGroup } from '../../'; + +export default { title: 'Components|ToolbarGroup', component: ToolbarGroup }; + +export const _default = () => { + return ( + <ToolbarGroup> + <ToolbarButton icon="editor-bold" title="Bold" isActive /> + <ToolbarButton icon="editor-italic" title="Italic" /> + <ToolbarButton icon="admin-links" title="Link" /> + </ToolbarGroup> + ); +}; + +export const withControlsProp = () => { + return ( + <ToolbarGroup + controls={ [ + { icon: 'editor-bold', title: 'Bold', isActive: true }, + { icon: 'editor-italic', title: 'Italic' }, + { icon: 'admin-links', title: 'Link' }, + ] } + /> + ); +}; diff --git a/packages/components/src/toolbar-group/style.native.scss b/packages/components/src/toolbar-group/style.native.scss new file mode 100644 index 00000000000000..e218aa37363e37 --- /dev/null +++ b/packages/components/src/toolbar-group/style.native.scss @@ -0,0 +1,11 @@ +.container { + flex-direction: row; + border-left-width: 1px; + border-color: #e9eff3; + padding-left: 5px; + padding-right: 5px; +} + +.containerDark { + border-color: #525354; +} diff --git a/packages/components/src/toolbar-group/style.scss b/packages/components/src/toolbar-group/style.scss new file mode 100644 index 00000000000000..e03bb5a1a7a640 --- /dev/null +++ b/packages/components/src/toolbar-group/style.scss @@ -0,0 +1,58 @@ +.components-toolbar-group { + border: $border-width solid $light-gray-500; + background-color: $white; + display: flex; + flex-shrink: 0; + margin-right: -$border-width; + + & & { + border-width: 0; + margin: 0; + } + + line-height: 0; +} + +// Legacy toolbar group +// External code references to it, so we can't change it? +.components-toolbar { + margin: 0; + border: $border-width solid $light-gray-500; + background-color: $white; + display: flex; + flex-shrink: 0; +} + +div.components-toolbar { + & > div { + // IE11 does not support `position: sticky`, or Flex very well, so use block. + display: block; + @supports (position: sticky) { + display: flex; + } + + margin: 0; + } + + & > div + div { + margin-left: -3px; + + &.has-left-divider { + margin-left: 6px; + position: relative; + overflow: visible; + } + + &.has-left-divider::before { + display: inline-block; + content: ""; + box-sizing: content-box; + background-color: $light-gray-500; + position: absolute; + top: 8px; + left: -3px; + width: 1px; + height: $icon-button-size - 16px; + } + } +} diff --git a/packages/components/src/toolbar-group/test/index.js b/packages/components/src/toolbar-group/test/index.js new file mode 100644 index 00000000000000..972dca89499f78 --- /dev/null +++ b/packages/components/src/toolbar-group/test/index.js @@ -0,0 +1,101 @@ +/** + * External dependencies + */ +import { mount } from 'enzyme'; + +/** + * Internal dependencies + */ +import ToolbarGroup from '../'; + +describe( 'ToolbarGroup', () => { + describe( 'basic rendering', () => { + it( 'should render an empty node, when controls are not passed', () => { + const wrapper = mount( <ToolbarGroup /> ); + expect( wrapper.html() ).toBeNull(); + } ); + + it( 'should render an empty node, when controls are empty', () => { + const wrapper = mount( <ToolbarGroup controls={ [] } /> ); + expect( wrapper.html() ).toBeNull(); + } ); + + it( 'should render a list of controls with buttons', () => { + const clickHandler = ( event ) => event; + const controls = [ + { + icon: 'wordpress', + title: 'WordPress', + onClick: clickHandler, + isActive: false, + }, + ]; + const wrapper = mount( <ToolbarGroup controls={ controls } /> ); + const button = wrapper.find( '[aria-label="WordPress"]' ).hostNodes(); + expect( button.props() ).toMatchObject( { + 'aria-label': 'WordPress', + 'aria-pressed': false, + type: 'button', + } ); + } ); + + it( 'should render a list of controls with buttons and active control', () => { + const clickHandler = ( event ) => event; + const controls = [ + { + icon: 'wordpress', + title: 'WordPress', + onClick: clickHandler, + isActive: true, + }, + ]; + const wrapper = mount( <ToolbarGroup controls={ controls } /> ); + const button = wrapper.find( '[aria-label="WordPress"]' ).hostNodes(); + expect( button.props() ).toMatchObject( { + 'aria-label': 'WordPress', + 'aria-pressed': true, + type: 'button', + } ); + } ); + + it( 'should render a nested list of controls with separator between', () => { + const controls = [ + [ // First set + { + icon: 'wordpress', + title: 'WordPress', + }, + ], + [ // Second set + { + icon: 'wordpress', + title: 'WordPress', + }, + ], + ]; + + const wrapper = mount( <ToolbarGroup controls={ controls } /> ); + const buttons = wrapper.find( 'button' ).hostNodes(); + const hasLeftDivider = wrapper.find( '.has-left-divider' ).hostNodes(); + expect( buttons ).toHaveLength( 2 ); + expect( hasLeftDivider ).toHaveLength( 1 ); + expect( hasLeftDivider.html() ).toContain( buttons.at( 1 ).html() ); + } ); + + it( 'should call the clickHandler on click.', () => { + const clickHandler = jest.fn(); + const controls = [ + { + icon: 'wordpress', + title: 'WordPress', + onClick: clickHandler, + isActive: true, + }, + ]; + const wrapper = mount( <ToolbarGroup controls={ controls } /> ); + const button = wrapper.find( '[aria-label="WordPress"]' ).hostNodes(); + button.simulate( 'click' ); + expect( clickHandler ).toHaveBeenCalledTimes( 1 ); + } ); + } ); +} ); diff --git a/packages/components/src/toolbar-group/toolbar-group-collapsed.js b/packages/components/src/toolbar-group/toolbar-group-collapsed.js new file mode 100644 index 00000000000000..8e684a7cae72ac --- /dev/null +++ b/packages/components/src/toolbar-group/toolbar-group-collapsed.js @@ -0,0 +1,52 @@ +/** + * External dependencies + */ +import { ToolbarItem } from 'reakit/Toolbar'; + +/** + * WordPress dependencies + */ +import { useContext } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import DropdownMenu from '../dropdown-menu'; +import ToolbarContext from '../toolbar-context'; + +function ToolbarGroupCollapsed( { + controls = [], + className, + icon, + label, + ...props +} ) { + // It'll contain state if `ToolbarGroup` is being used within + // `<Toolbar __experimentalAccessibilityLabel="label" />` + const accessibleToolbarState = useContext( ToolbarContext ); + + const renderDropdownMenu = ( toggleProps ) => ( + <DropdownMenu + hasArrowIndicator + icon={ icon } + label={ label } + controls={ controls } + className={ className } + toggleProps={ toggleProps } + { ...props } + /> + ); + + if ( accessibleToolbarState ) { + return ( + // https://reakit.io/docs/composition/#render-props + <ToolbarItem { ...accessibleToolbarState }> + { ( toolbarItemHTMLProps ) => renderDropdownMenu( toolbarItemHTMLProps ) } + </ToolbarItem> + ); + } + + return renderDropdownMenu(); +} + +export default ToolbarGroupCollapsed; diff --git a/packages/components/src/toolbar-group/toolbar-group-collapsed.native.js b/packages/components/src/toolbar-group/toolbar-group-collapsed.native.js new file mode 100644 index 00000000000000..718e1238792c43 --- /dev/null +++ b/packages/components/src/toolbar-group/toolbar-group-collapsed.native.js @@ -0,0 +1,18 @@ +/** + * Internal dependencies + */ +import DropdownMenu from '../dropdown-menu'; + +function ToolbarGroupCollapsed( { controls = [], className, icon, label } ) { + return ( + <DropdownMenu + hasArrowIndicator + icon={ icon } + label={ label } + controls={ controls } + className={ className } + /> + ); +} + +export default ToolbarGroupCollapsed; diff --git a/packages/components/src/toolbar-group/toolbar-group-container.js b/packages/components/src/toolbar-group/toolbar-group-container.js new file mode 100644 index 00000000000000..d86e066ce45a11 --- /dev/null +++ b/packages/components/src/toolbar-group/toolbar-group-container.js @@ -0,0 +1,6 @@ +const ToolbarGroupContainer = ( props ) => ( + <div className={ props.className }> + { props.children } + </div> +); +export default ToolbarGroupContainer; diff --git a/packages/components/src/toolbar-group/toolbar-group-container.native.js b/packages/components/src/toolbar-group/toolbar-group-container.native.js new file mode 100644 index 00000000000000..705c6b00806c38 --- /dev/null +++ b/packages/components/src/toolbar-group/toolbar-group-container.native.js @@ -0,0 +1,22 @@ +/** + * External dependencies + */ +import { View } from 'react-native'; + +/** + * WordPress dependencies + */ +import { withPreferredColorScheme } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import styles from './style.scss'; + +const ToolbarGroupContainer = ( { getStylesFromColorScheme, passedStyle, children } ) => ( + <View style={ [ getStylesFromColorScheme( styles.container, styles.containerDark ), passedStyle ] }> + { children } + </View> +); + +export default withPreferredColorScheme( ToolbarGroupContainer ); diff --git a/packages/components/src/toolbar/index.js b/packages/components/src/toolbar/index.js index ca41d32bb6e1b9..9d37a768cadc70 100644 --- a/packages/components/src/toolbar/index.js +++ b/packages/components/src/toolbar/index.js @@ -2,86 +2,34 @@ * External dependencies */ import classnames from 'classnames'; -import { flatMap } from 'lodash'; /** * Internal dependencies */ -import ToolbarButton from '../toolbar-button'; -import DropdownMenu from '../dropdown-menu'; +import ToolbarGroup from '../toolbar-group'; import ToolbarContainer from './toolbar-container'; /** - * Renders a toolbar with controls. + * Renders a toolbar. * - * The `controls` prop accepts an array of sets. A set is an array of controls. - * Controls have the following shape: + * To add controls, simply pass `ToolbarButton` components as children. * - * ``` - * { - * icon: string, - * title: string, - * subscript: string, - * onClick: Function, - * isActive: boolean, - * isDisabled: boolean - * } - * ``` - * - * For convenience it is also possible to pass only an array of controls. It is - * then assumed this is the only set. - * - * Either `controls` or `children` is required, otherwise this components - * renders nothing. - * - * @param {Object} props - * @param {Array} [props.controls] The controls to render in this toolbar. - * @param {ReactElement} [props.children] Any other things to render inside the - * toolbar besides the controls. - * @param {string} [props.className] Class to set on the container div. - * - * @return {ReactElement} The rendered toolbar. + * @param {Object} props Component props. + * @param {string} [props.className] Class to set on the container div. */ -function Toolbar( { controls = [], children, className, isCollapsed, icon, label, ...otherProps } ) { - if ( - ( ! controls || ! controls.length ) && - ! children - ) { - return null; - } - - // Normalize controls to nested array of objects (sets of controls) - let controlSets = controls; - if ( ! Array.isArray( controlSets[ 0 ] ) ) { - controlSets = [ controlSets ]; - } - - if ( isCollapsed ) { +function Toolbar( { className, __experimentalAccessibilityLabel, ...props } ) { + if ( __experimentalAccessibilityLabel ) { return ( - <DropdownMenu - hasArrowIndicator - icon={ icon } - label={ label } - controls={ controlSets } - className={ classnames( 'components-toolbar', className ) } + <ToolbarContainer + // `ToolbarGroup` already uses components-toolbar for compatibility reasons + className={ classnames( 'components-accessible-toolbar', className ) } + accessibilityLabel={ __experimentalAccessibilityLabel } + { ...props } /> ); } - return ( - <ToolbarContainer className={ classnames( 'components-toolbar', className ) } { ...otherProps }> - { flatMap( controlSets, ( controlSet, indexOfSet ) => ( - controlSet.map( ( control, indexOfControl ) => ( - <ToolbarButton - key={ [ indexOfSet, indexOfControl ].join() } - containerClassName={ indexOfSet > 0 && indexOfControl === 0 ? 'has-left-divider' : null } - { ...control } - /> - ) ) - ) ) } - { children } - </ToolbarContainer> - ); + return <ToolbarGroup { ...props } className={ className } />; } export default Toolbar; diff --git a/packages/components/src/toolbar/stories/index.js b/packages/components/src/toolbar/stories/index.js new file mode 100644 index 00000000000000..102f924f5a1a07 --- /dev/null +++ b/packages/components/src/toolbar/stories/index.js @@ -0,0 +1,75 @@ +/** + * Internal dependencies + */ +import Toolbar from '../'; +import { SVG, Path, ToolbarButton, ToolbarGroup } from '../../'; + +export default { title: 'Components|Toolbar', component: Toolbar }; + +function InlineImageIcon() { + return ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M4 16h10c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v9c0 1.1.9 2 2 2zM4 5h10v9H4V5zm14 9v2h4v-2h-4zM2 20h20v-2H2v2zm6.4-8.8L7 9.4 5 12h8l-2.6-3.4-2 2.6z" /> + </SVG> + ); +} + +/* eslint-disable no-restricted-syntax */ +export const _default = () => { + return ( + // id is required for server side rendering + <Toolbar __experimentalAccessibilityLabel="Options" id="options-toolbar"> + <ToolbarGroup> + <ToolbarButton icon="editor-paragraph" title="Paragraph" /> + </ToolbarGroup> + <ToolbarGroup + icon="editor-alignleft" + label="Change text alignment" + isCollapsed + controls={ [ + { icon: 'editor-alignleft', title: 'Align left', isActive: true }, + { icon: 'editor-aligncenter', title: 'Align center' }, + { icon: 'editor-alignright', title: 'Align right' }, + ] } + /> + <ToolbarGroup> + <ToolbarButton icon="editor-bold" title="Bold" /> + <ToolbarButton icon="editor-italic" title="Italic" /> + <ToolbarButton icon="admin-links" title="Link" /> + <ToolbarGroup + isCollapsed + icon={ false } + label="More rich text controls" + controls={ [ + { icon: 'editor-code', title: 'Inline code' }, + { icon: <InlineImageIcon />, title: 'Inline image' }, + { icon: 'editor-strikethrough', title: 'Strikethrough' }, + ] } + /> + </ToolbarGroup> + <ToolbarGroup + hasArrowIndicator={ false } + icon="ellipsis" + label="Change text alignment" + isCollapsed + controls={ [ + { icon: 'editor-alignleft', title: 'Align left', isActive: true }, + { icon: 'editor-aligncenter', title: 'Align center' }, + { icon: 'editor-alignright', title: 'Align right' }, + ] } + /> + </Toolbar> + ); +}; + +export const withoutGroup = () => { + return ( + // id is required for server side rendering + <Toolbar __experimentalAccessibilityLabel="Options" id="options-toolbar-without-group"> + <ToolbarButton icon="editor-bold" title="Bold" /> + <ToolbarButton icon="editor-italic" title="Italic" /> + <ToolbarButton icon="admin-links" title="Link" /> + </Toolbar> + ); +}; +/* eslint-enable no-restricted-syntax */ diff --git a/packages/components/src/toolbar/style.scss b/packages/components/src/toolbar/style.scss index 1356b9d83d4afd..29def9674f4854 100644 --- a/packages/components/src/toolbar/style.scss +++ b/packages/components/src/toolbar/style.scss @@ -1,41 +1,55 @@ -.components-toolbar { - margin: 0; - border: $border-width solid $light-gray-500; - background-color: $white; - display: flex; +.components-accessible-toolbar { + // Required for IE11. + display: inline-flex; + + // IE11 doesn't read rules inside this query. They are applied only to modern browsers. + @supports (position: sticky) { + display: flex; + } + flex-shrink: 0; } -div.components-toolbar { - & > div { - // IE11 does not support `position: sticky`, or Flex very well, so use block. - display: block; - @supports (position: sticky) { - display: flex; - } +// We should extract this to a separate component. +.components-tab-button { + padding: 3px; + color: $dark-gray-500; + height: $icon-button-size; + font-weight: 500; - margin: 0; + &.is-pressed, + &.is-pressed:hover { + color: $white; } - & > div + div { - margin-left: -3px; + &:disabled { + cursor: default; + } - &.has-left-divider { - margin-left: 6px; - position: relative; - overflow: visible; - } + & > span { + border: $border-width solid transparent; + padding: 0 6px; + box-sizing: content-box; + height: 28px; + line-height: 28px; + } + + &:hover > span, + &:focus > span { + color: $dark-gray-500; + } - &.has-left-divider::before { - display: inline-block; - content: ""; - box-sizing: content-box; - background-color: $light-gray-500; - position: absolute; - top: 8px; - left: -3px; - width: 1px; - height: $icon-button-size - 16px; + &:not(:disabled) { + &.is-pressed > span, + &:hover > span, + &:focus > span { + border: $border-width solid $dark-gray-500; } } + + &.is-pressed > span, + &.is-pressed:hover > span { + background-color: $dark-gray-500; + color: $white; + } } diff --git a/packages/components/src/toolbar/test/index.js b/packages/components/src/toolbar/test/index.js index 6abcad9c843440..2d454d1a8bff38 100644 --- a/packages/components/src/toolbar/test/index.js +++ b/packages/components/src/toolbar/test/index.js @@ -1,26 +1,42 @@ /** * External dependencies */ -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; /** * Internal dependencies */ import Toolbar from '../'; +import ToolbarButton from '../../toolbar-button'; describe( 'Toolbar', () => { describe( 'basic rendering', () => { + it( 'should render a toolbar with toolbar buttons', () => { + const wrapper = mount( + <Toolbar __experimentalAccessibilityLabel="blocks"> + <ToolbarButton title="control1" /> + <ToolbarButton title="control2" /> + </Toolbar> + ); + const control1 = wrapper.find( 'button[aria-label="control1"]' ); + const control2 = wrapper.find( 'button[aria-label="control1"]' ); + expect( control1 ).toHaveLength( 1 ); + expect( control2 ).toHaveLength( 1 ); + } ); + } ); + + describe( 'ToolbarGroup', () => { it( 'should render an empty node, when controls are not passed', () => { - const toolbar = shallow( <Toolbar /> ); - expect( toolbar.type() ).toBeNull(); + const wrapper = mount( <Toolbar /> ); + expect( wrapper.html() ).toBeNull(); } ); it( 'should render an empty node, when controls are empty', () => { - const toolbar = shallow( <Toolbar controls={ [] } /> ); - expect( toolbar.type() ).toBeNull(); + const wrapper = mount( <Toolbar controls={ [] } /> ); + expect( wrapper.html() ).toBeNull(); } ); - it( 'should render a list of controls with ToolbarButtons', () => { + it( 'should render a list of controls with buttons', () => { const clickHandler = ( event ) => event; const controls = [ { @@ -31,80 +47,14 @@ describe( 'Toolbar', () => { isActive: false, }, ]; - const toolbar = shallow( <Toolbar controls={ controls } /> ); - const listItem = toolbar.find( 'ToolbarButton' ); - expect( listItem.props() ).toMatchObject( { - containerClassName: null, - icon: 'wordpress', - title: 'WordPress', - subscript: 'wp', - onClick: clickHandler, - isActive: false, - } ); - } ); - - it( 'should render a list of controls with ToolbarButtons and active control', () => { - const clickHandler = ( event ) => event; - const controls = [ - { - icon: 'wordpress', - title: 'WordPress', - subscript: 'wp', - onClick: clickHandler, - isActive: true, - }, - ]; - const toolbar = shallow( <Toolbar controls={ controls } /> ); - const listItem = toolbar.find( 'ToolbarButton' ); - expect( listItem.props() ).toMatchObject( { - containerClassName: null, - icon: 'wordpress', - title: 'WordPress', - subscript: 'wp', - onClick: clickHandler, - isActive: true, + const wrapper = mount( <Toolbar controls={ controls } /> ); + const button = wrapper.find( '[aria-label="WordPress"]' ).hostNodes(); + expect( button.props() ).toMatchObject( { + 'aria-label': 'WordPress', + 'aria-pressed': false, + 'data-subscript': 'wp', + type: 'button', } ); } ); - - it( 'should render a nested list of controls with separator between', () => { - const controls = [ - [ // First set - { - icon: 'wordpress', - title: 'WordPress', - }, - ], - [ // Second set - { - icon: 'wordpress', - title: 'WordPress', - }, - ], - ]; - - const toolbar = shallow( <Toolbar controls={ controls } /> ); - expect( toolbar.children() ).toHaveLength( 2 ); - expect( toolbar.childAt( 0 ).prop( 'containerClassName' ) ).toBeNull(); - expect( toolbar.childAt( 1 ).prop( 'containerClassName' ) ).toBe( 'has-left-divider' ); - } ); - - it( 'should call the clickHandler on click.', () => { - const clickHandler = jest.fn(); - const event = { stopPropagation: () => undefined }; - const controls = [ - { - icon: 'wordpress', - title: 'WordPress', - subscript: 'wp', - onClick: clickHandler, - isActive: true, - }, - ]; - const toolbar = shallow( <Toolbar controls={ controls } /> ); - const listItem = toolbar.find( 'ToolbarButton' ); - listItem.simulate( 'click', event ); - expect( clickHandler ).toHaveBeenCalledTimes( 1 ); - expect( clickHandler ).toHaveBeenCalledWith( event ); - } ); } ); } ); diff --git a/packages/components/src/toolbar/toolbar-container.js b/packages/components/src/toolbar/toolbar-container.js index 9361c1fcf0bbbb..d40843ddab3f3f 100644 --- a/packages/components/src/toolbar/toolbar-container.js +++ b/packages/components/src/toolbar/toolbar-container.js @@ -1,6 +1,33 @@ -const ToolbarContainer = ( props ) => ( - <div className={ props.className }> - { props.children } - </div> -); -export default ToolbarContainer; +/** + * External dependencies + */ +import { useToolbarState, Toolbar } from 'reakit/Toolbar'; + +/** + * WordPress dependencies + */ +import { forwardRef } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import ToolbarContext from '../toolbar-context'; + +function ToolbarContainer( { accessibilityLabel, ...props }, ref ) { + // https://reakit.io/docs/basic-concepts/#state-hooks + const toolbarState = useToolbarState( { loop: true } ); + + return ( + // This will provide state for `ToolbarButton`'s + <ToolbarContext.Provider value={ toolbarState }> + <Toolbar + ref={ ref } + aria-label={ accessibilityLabel } + { ...toolbarState } + { ...props } + /> + </ToolbarContext.Provider> + ); +} + +export default forwardRef( ToolbarContainer ); diff --git a/packages/components/src/toolbar/toolbar-container.native.js b/packages/components/src/toolbar/toolbar-container.native.js index 33fe77d11db4c0..75a8a45ebb92ca 100644 --- a/packages/components/src/toolbar/toolbar-container.native.js +++ b/packages/components/src/toolbar/toolbar-container.native.js @@ -3,20 +3,8 @@ */ import { View } from 'react-native'; -/** - * WordPress dependencies - */ -import { withPreferredColorScheme } from '@wordpress/compose'; - -/** - * Internal dependencies - */ -import styles from './style.scss'; - -const ToolbarContainer = ( { getStylesFromColorScheme, passedStyle, children } ) => ( - <View style={ [ getStylesFromColorScheme( styles.container, styles.containerDark ), passedStyle ] }> - { children } - </View> +const ToolbarContainer = ( { children } ) => ( + <View>{ children }</View> ); -export default withPreferredColorScheme( ToolbarContainer ); +export default ToolbarContainer; diff --git a/packages/components/src/tooltip/style.scss b/packages/components/src/tooltip/style.scss index 93642af8cf2052..8fc6ccb6e73fef 100644 --- a/packages/components/src/tooltip/style.scss +++ b/packages/components/src/tooltip/style.scss @@ -5,15 +5,15 @@ border-color: transparent; } - &.is-top::after { + &[data-y-axis="top"]::after { border-top-color: $dark-gray-900; } - &.is-bottom::after { + &[data-y-axis="bottom"]::after { border-bottom-color: $dark-gray-900; } - &:not(.is-mobile) .components-popover__content { + .components-popover__content { min-width: 0; } } diff --git a/packages/components/src/utils/colors-values.js b/packages/components/src/utils/colors-values.js new file mode 100644 index 00000000000000..8dfca09543f5e9 --- /dev/null +++ b/packages/components/src/utils/colors-values.js @@ -0,0 +1,115 @@ +/** + * Internal dependencies + */ +import { rgba } from './colors'; +export const BASE = { + black: '#000', + white: '#fff', +}; + +export const DARK_GRAY = { + 900: '#191e23', + 800: '#23282d', + 700: '#32373c', + 600: '#40464d', + 500: '#555d66', // Use this most of the time for dark items. + 400: '#606a73', + 300: '#6c7781', // Lightest gray that can be used for AA text contrast. + 200: '#7e8993', + 150: '#8d96a0', // Lightest gray that can be used for AA non-text contrast. + 100: '#8f98a1', +}; + +export const DARK_OPACITY = { + 900: rgba( '#000510', 0.9 ), + 800: rgba( '#00000a', 0.85 ), + 700: rgba( '#06060b', 0.8 ), + 600: rgba( '#000913', 0.75 ), + 500: rgba( '#0a1829', 0.7 ), + 400: rgba( '#0a1829', 0.65 ), + 300: rgba( '#0e1c2e', 0.62 ), + 200: rgba( '#162435', 0.55 ), + 100: rgba( '#223443', 0.5 ), + backgroundFill: rgba( DARK_GRAY[ 700 ], 0.7 ), +}; + +export const DARK_OPACITY_LIGHT = { + 900: rgba( '#304455', 0.45 ), + 800: rgba( '#425863', 0.4 ), + 700: rgba( '#667886', 0.35 ), + 600: rgba( '#7b86a2', 0.3 ), + 500: rgba( '#9197a2', 0.25 ), + 400: rgba( '#95959c', 0.2 ), + 300: rgba( '#829493', 0.15 ), + 200: rgba( '#8b8b96', 0.1 ), + 100: rgba( '#747474', 0.05 ), +}; + +export const LIGHT_GRAY = { + 900: '#a2aab2', + 800: '#b5bcc2', + 700: '#ccd0d4', + 600: '#d7dade', + 500: '#e2e4e7', // Good for "grayed" items and borders. + 400: '#e8eaeb', // Good for "readonly" input fields and special text selection. + 300: '#edeff0', + 200: '#f3f4f5', + 100: '#f8f9f9', +}; + +export const LIGHT_OPACITY_LIGHT = { + 900: rgba( BASE.white, 0.5 ), + 800: rgba( BASE.white, 0.45 ), + 700: rgba( BASE.white, 0.4 ), + 600: rgba( BASE.white, 0.35 ), + 500: rgba( BASE.white, 0.3 ), + 400: rgba( BASE.white, 0.25 ), + 300: rgba( BASE.white, 0.2 ), + 200: rgba( BASE.white, 0.15 ), + 100: rgba( BASE.white, 0.1 ), + backgroundFill: rgba( LIGHT_GRAY[ 300 ], 0.8 ), +}; + +// Additional colors. +// Some are from https://make.wordpress.org/design/handbook/foundations/colors/. + +export const BLUE = { + wordpress: { + 700: '#00669b', + }, + dark: { + 900: '#0071a1', + }, + medium: { + 900: '#006589', + 800: '#00739c', + 700: '#007fac', + 600: '#008dbe', + 500: '#00a0d2', + 400: '#33b3db', + 300: '#66c6e4', + 200: '#bfe7f3', + 100: '#e5f5fa', + highlight: '#b3e7fe', + focus: '#007cba', + }, +}; + +export const ALERT = { + yellow: '#f0b849', + red: '#d94f4f', + green: '#4ab866', +}; + +export const COLORS = { + ...BASE, + darkGrey: DARK_GRAY, + darkOpacity: DARK_OPACITY, + darkOpacityLight: DARK_OPACITY_LIGHT, + lightGray: LIGHT_GRAY, + lightGrayLight: LIGHT_OPACITY_LIGHT, + blue: BLUE, + alert: ALERT, +}; + +export default COLORS; diff --git a/packages/components/src/utils/colors.js b/packages/components/src/utils/colors.js new file mode 100644 index 00000000000000..1de39eed4d2eb8 --- /dev/null +++ b/packages/components/src/utils/colors.js @@ -0,0 +1,40 @@ +/** + * External dependencies + */ +import { get } from 'lodash'; +import tinycolor from 'tinycolor2'; +/** + * Internal dependencies + */ +import { COLORS } from './colors-values'; + +/** + * Generating a CSS complient rgba() color value. + * + * @param {string} hexValue The hex value to convert to rgba(). + * @param {number} alpha The alpha value for opacity. + * @return {string} The converted rgba() color value. + * + * @example + * rgba( '#000000', 0.5 ) + * // rgba(0, 0, 0, 0.5) + */ +export function rgba( hexValue = '', alpha = 1 ) { + const { r, g, b } = tinycolor( hexValue ).toRgb(); + return `rgba(${ r }, ${ g }, ${ b }, ${ alpha })`; +} + +/** + * Retrieves a color from the color palette. + * + * @param {string} value The value to retrieve. + * @return {string} The color (or fallback, if not found). + * + * @example + * color( 'blue.wordpress.700' ) + * // #00669b + */ +export function color( value ) { + const fallbackColor = '#000'; + return get( COLORS, value, fallbackColor ); +} diff --git a/packages/components/src/visually-hidden/README.md b/packages/components/src/visually-hidden/README.md new file mode 100644 index 00000000000000..1f490f5134af26 --- /dev/null +++ b/packages/components/src/visually-hidden/README.md @@ -0,0 +1,9 @@ +# VisuallyHidden + +A component used to render text intended to be visually hidden, but will show for alternate devices, for example a screen reader. + +### Usage + +```jsx +<VisuallyHidden> Show text for screenreader. </VisuallyHidden> +``` diff --git a/packages/components/src/visually-hidden/index.js b/packages/components/src/visually-hidden/index.js new file mode 100644 index 00000000000000..b29c954c2cd9e1 --- /dev/null +++ b/packages/components/src/visually-hidden/index.js @@ -0,0 +1,22 @@ + +/** + * Internal dependencies + */ +import { renderAsRenderProps } from './utils'; + +/** + * VisuallyHidden component to render text out non-visually + * for use in devices such as a screen reader. + */ +function VisuallyHidden( { + as = 'div', + ...props +} ) { + return renderAsRenderProps( { + as, + className: 'components-visually-hidden', + ...props, + } ); +} +export default VisuallyHidden; + diff --git a/packages/components/src/visually-hidden/stories/index.js b/packages/components/src/visually-hidden/stories/index.js new file mode 100644 index 00000000000000..50ecae3ed1c51e --- /dev/null +++ b/packages/components/src/visually-hidden/stories/index.js @@ -0,0 +1,17 @@ +/** + * Internal dependencies + */ +import VisuallyHidden from '../'; + +export default { title: 'Components|VisuallyHidden', component: VisuallyHidden }; + +export const _default = () => ( + <> + <VisuallyHidden> + This should not show. + </VisuallyHidden> + <div> + This text will <VisuallyHidden as="span">but not inline </VisuallyHidden> always show. + </div> + </> +); diff --git a/packages/components/src/visually-hidden/style.scss b/packages/components/src/visually-hidden/style.scss new file mode 100644 index 00000000000000..02fec4486afa23 --- /dev/null +++ b/packages/components/src/visually-hidden/style.scss @@ -0,0 +1,30 @@ +.components-visually-hidden { + border: 0; + clip: rect(1px, 1px, 1px, 1px); + -webkit-clip-path: inset(50%); + clip-path: inset(50%); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + word-wrap: normal !important; +} + +.components-visually-hidden:focus { + background-color: $light-gray-500; + clip: auto !important; + clip-path: none; + color: #444; + display: block; + font-size: 1em; + height: auto; + left: 5px; + line-height: normal; + padding: 15px 23px 14px; + text-decoration: none; + top: 5px; + width: auto; + z-index: 100000; +} diff --git a/packages/components/src/visually-hidden/utils.js b/packages/components/src/visually-hidden/utils.js new file mode 100644 index 00000000000000..70bcfac3aeb7d7 --- /dev/null +++ b/packages/components/src/visually-hidden/utils.js @@ -0,0 +1,22 @@ +/** + * Utility Functions + */ + +/** + * renderAsRenderProps is used to wrap a component and convert + * the passed property "as" either a string or component, to the + * rendered tag if a string, or component. + * + * See VisuallyHidden hidden for example. + * + * @param {string|WPComponent} as A tag or component to render. + * @return {WPComponent} The rendered component. + */ +function renderAsRenderProps( { as: Component = 'div', ...props } ) { + if ( typeof props.children === 'function' ) { + return props.children( props ); + } + return <Component { ...props } />; +} + +export { renderAsRenderProps }; diff --git a/packages/components/storybook/.babelrc b/packages/components/storybook/.babelrc deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/packages/components/storybook/config.js b/packages/components/storybook/config.js deleted file mode 100644 index eace300f3fc963..00000000000000 --- a/packages/components/storybook/config.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * External dependencies - */ -import { addDecorator, configure } from '@storybook/react'; -import { withA11y } from '@storybook/addon-a11y'; - -/** - * Internal dependencies - */ -import '../build-style/style.css'; - -addDecorator( withA11y ); -configure( - [ - require.context( '../docs', true, /\/.+\.mdx$/ ), - require.context( '../src', true, /\/stories\/.+\.js$/ ), - ], - module -); diff --git a/packages/compose/README.md b/packages/compose/README.md index dc47f26e995652..dcd5c47411d74c 100644 --- a/packages/compose/README.md +++ b/packages/compose/README.md @@ -139,6 +139,26 @@ _Returns_ - `boolean`: Reduced motion preference value. +<a name="useViewportMatch" href="#useViewportMatch">#</a> **useViewportMatch** + +Returns true if the viewport matches the given query, or false otherwise. + +_Usage_ + +```js +useViewportMatch( 'huge', <' ); +useViewportMatch( 'medium' ); +``` + +_Parameters_ + +- _breakpoint_ `WPBreakpoint`: Breakpoint size name. +- _operator_ `[WPViewportOperator]`: Viewport operator. + +_Returns_ + +- `boolean`: Whether viewport matches query. + <a name="withGlobalEvents" href="#withGlobalEvents">#</a> **withGlobalEvents** Higher-order component creator which, given an object of DOM event types and @@ -163,11 +183,11 @@ component. _Parameters_ -- _WrappedComponent_ `WPElement`: The wrapped component. +- _WrappedComponent_ `WPComponent`: The wrapped component. _Returns_ -- `Component`: Component with an instanceId prop. +- `WPComponent`: Component with an instanceId prop. <a name="withSafeTimeout" href="#withSafeTimeout">#</a> **withSafeTimeout** @@ -176,11 +196,11 @@ that ought to be bound to a component's lifecycle. _Parameters_ -- _OriginalComponent_ `Component`: Component requiring setTimeout +- _OriginalComponent_ `WPComponent`: Component requiring setTimeout _Returns_ -- `Component`: Wrapped component. +- `WPComponent`: Wrapped component. <a name="withState" href="#withState">#</a> **withState** @@ -193,7 +213,7 @@ _Parameters_ _Returns_ -- `Component`: Wrapped component. +- `WPComponent`: Wrapped component. <!-- END TOKEN(Autogenerated API docs) --> diff --git a/packages/compose/package.json b/packages/compose/package.json index 827a0afb46eff7..0fbfcb1e435a90 100644 --- a/packages/compose/package.json +++ b/packages/compose/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/compose", - "version": "3.7.0", + "version": "3.8.0", "description": "WordPress higher-order components (HOCs).", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -21,6 +21,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "sideEffects": false, "dependencies": { "@babel/runtime": "^7.4.4", "@wordpress/element": "file:../element", diff --git a/packages/compose/src/higher-order/compose.js b/packages/compose/src/higher-order/compose.js new file mode 100644 index 00000000000000..a166387821e7b3 --- /dev/null +++ b/packages/compose/src/higher-order/compose.js @@ -0,0 +1,14 @@ +/** + * External dependencies + */ +import { flowRight as compose } from 'lodash'; + +/** + * Composes multiple higher-order components into a single higher-order component. Performs right-to-left function + * composition, where each successive invocation is supplied the return value of the previous. + * + * @param {...Function} hocs The HOC functions to invoke. + * + * @return {Function} Returns the new composite function. + */ +export default compose; diff --git a/packages/compose/src/higher-order/with-instance-id/index.js b/packages/compose/src/higher-order/with-instance-id/index.js index 53c09ec56f8882..960c6997e8671f 100644 --- a/packages/compose/src/higher-order/with-instance-id/index.js +++ b/packages/compose/src/higher-order/with-instance-id/index.js @@ -12,9 +12,9 @@ import createHigherOrderComponent from '../../utils/create-higher-order-componen * A Higher Order Component used to be provide a unique instance ID by * component. * - * @param {WPElement} WrappedComponent The wrapped component. + * @param {WPComponent} WrappedComponent The wrapped component. * - * @return {Component} Component with an instanceId prop. + * @return {WPComponent} Component with an instanceId prop. */ export default createHigherOrderComponent( ( WrappedComponent ) => { let instances = 0; diff --git a/packages/compose/src/higher-order/with-safe-timeout/index.js b/packages/compose/src/higher-order/with-safe-timeout/index.js index 910dd94fddc19d..43d4e9e780d5b7 100644 --- a/packages/compose/src/higher-order/with-safe-timeout/index.js +++ b/packages/compose/src/higher-order/with-safe-timeout/index.js @@ -17,9 +17,9 @@ import createHigherOrderComponent from '../../utils/create-higher-order-componen * A higher-order component used to provide and manage delayed function calls * that ought to be bound to a component's lifecycle. * - * @param {Component} OriginalComponent Component requiring setTimeout + * @param {WPComponent} OriginalComponent Component requiring setTimeout * - * @return {Component} Wrapped component. + * @return {WPComponent} Wrapped component. */ const withSafeTimeout = createHigherOrderComponent( ( OriginalComponent ) => { diff --git a/packages/compose/src/higher-order/with-state/index.js b/packages/compose/src/higher-order/with-state/index.js index 19e639043bd28b..02db9392b39631 100644 --- a/packages/compose/src/higher-order/with-state/index.js +++ b/packages/compose/src/higher-order/with-state/index.js @@ -14,7 +14,7 @@ import createHigherOrderComponent from '../../utils/create-higher-order-componen * * @param {?Object} initialState Optional initial state of the component. * - * @return {Component} Wrapped component. + * @return {WPComponent} Wrapped component. */ export default function withState( initialState = {} ) { return createHigherOrderComponent( ( OriginalComponent ) => { diff --git a/packages/compose/src/hooks/use-media-query/test/index.js b/packages/compose/src/hooks/use-media-query/test/index.js new file mode 100644 index 00000000000000..8b0017f083fb5a --- /dev/null +++ b/packages/compose/src/hooks/use-media-query/test/index.js @@ -0,0 +1,91 @@ +/** + * External dependencies + */ +import { create, act } from 'react-test-renderer'; + +/** + * Internal dependencies + */ +import useMediaQuery from '../'; + +describe( 'useMediaQuery', () => { + let addListener, removeListener; + + beforeAll( () => { + jest.spyOn( global, 'matchMedia' ); + + addListener = jest.fn(); + removeListener = jest.fn(); + } ); + + afterEach( () => { + global.matchMedia.mockClear(); + addListener.mockClear(); + removeListener.mockClear(); + } ); + + afterAll( () => { + global.matchMedia.mockRestore(); + } ); + + const TestComponent = ( { query } ) => { + const queryResult = useMediaQuery( query ); + return `useMediaQuery: ${ queryResult }`; + }; + + it( 'should return true when query matches', async () => { + global.matchMedia.mockReturnValue( { addListener, removeListener, matches: true } ); + + let root; + + await act( async () => { + root = create( <TestComponent query="(min-width: 782px)" /> ); + } ); + + expect( root.toJSON() ).toBe( 'useMediaQuery: true' ); + + await act( async () => { + root.unmount(); + } ); + expect( removeListener ).toHaveBeenCalled(); + } ); + + it( 'should correctly update the value when the query evaluation matches', async () => { + global.matchMedia.mockReturnValueOnce( { addListener, removeListener, matches: true } ); + global.matchMedia.mockReturnValueOnce( { addListener, removeListener, matches: true } ); + global.matchMedia.mockReturnValueOnce( { addListener, removeListener, matches: false } ); + + let root, updateMatchFunction; + await act( async () => { + root = create( <TestComponent query="(min-width: 782px)" /> ); + } ); + expect( root.toJSON() ).toBe( 'useMediaQuery: true' ); + + await act( async () => { + updateMatchFunction = addListener.mock.calls[ 0 ][ 0 ]; + updateMatchFunction(); + } ); + expect( root.toJSON() ).toBe( 'useMediaQuery: false' ); + + await act( async () => { + root.unmount(); + } ); + expect( removeListener.mock.calls ).toEqual( [ + [ updateMatchFunction ], + ] ); + } ); + + it( 'should return false when the query does not matches', async () => { + global.matchMedia.mockReturnValue( { addListener, removeListener, matches: false } ); + let root; + await act( async () => { + root = create( <TestComponent query="(min-width: 782px)" /> ); + } ); + expect( root.toJSON() ).toBe( 'useMediaQuery: false' ); + + await act( async () => { + root.unmount(); + } ); + expect( removeListener ).toHaveBeenCalled(); + } ); +} ); diff --git a/packages/compose/src/hooks/use-viewport-match/index.js b/packages/compose/src/hooks/use-viewport-match/index.js new file mode 100644 index 00000000000000..e27323f4af9304 --- /dev/null +++ b/packages/compose/src/hooks/use-viewport-match/index.js @@ -0,0 +1,60 @@ +/** + * Internal dependencies + */ +import useMediaQuery from '../use-media-query'; + +/** + * @typedef {"huge"|"wide"|"large"|"medium"|"small"|"mobile"} WPBreakpoint + */ + +/** + * Hash of breakpoint names with pixel width at which it becomes effective. + * + * @see _breakpoints.scss + * + * @type {Object<WPBreakpoint,number>} + */ +const BREAKPOINTS = { + huge: 1440, + wide: 1280, + large: 960, + medium: 782, + small: 600, + mobile: 480, +}; + +/** + * @typedef {">="|"<"} WPViewportOperator + */ + +/** + * Object mapping media query operators to the condition to be used. + * + * @type {Object<WPViewportOperator,string>} + */ +const CONDITIONS = { + '>=': 'min-width', + '<': 'max-width', +}; + +/** + * Returns true if the viewport matches the given query, or false otherwise. + * + * @param {WPBreakpoint} breakpoint Breakpoint size name. + * @param {WPViewportOperator} [operator=">="] Viewport operator. + * + * @example + * + * ```js + * useViewportMatch( 'huge', <' ); + * useViewportMatch( 'medium' ); + * ``` + * + * @return {boolean} Whether viewport matches query. + */ +const useViewportMatch = ( breakpoint, operator = '>=' ) => { + const mediaQuery = `(${ CONDITIONS[ operator ] }: ${ BREAKPOINTS[ breakpoint ] }px)`; + return useMediaQuery( mediaQuery ); +}; + +export default useViewportMatch; diff --git a/packages/compose/src/hooks/use-viewport-match/test/index.js b/packages/compose/src/hooks/use-viewport-match/test/index.js new file mode 100644 index 00000000000000..8fb97b98340576 --- /dev/null +++ b/packages/compose/src/hooks/use-viewport-match/test/index.js @@ -0,0 +1,82 @@ +/** + * External dependencies + */ +import { create, act } from 'react-test-renderer'; + +/** + * Internal dependencies + */ +import useViewportMatch from '../'; + +jest.mock( '../../use-media-query', () => { + return jest.fn(); +} ); + +import useMediaQueryMock from '../../use-media-query'; + +describe( 'useViewportMatch', () => { + afterEach( () => { + useMediaQueryMock.mockClear(); + } ); + + const TestComponent = ( { breakpoint, operator } ) => { + const result = useViewportMatch( breakpoint, operator ); + return `useViewportMatch: ${ result }`; + }; + + it( 'should return true when the viewport matches', async () => { + let root; + useMediaQueryMock.mockReturnValue( true ); + + await act( async () => { + root = create( <TestComponent breakpoint="wide" operator="<" /> ); + } ); + expect( root.toJSON() ).toBe( 'useViewportMatch: true' ); + + await act( async () => { + root.update( <TestComponent breakpoint="medium" operator=">=" /> ); + } ); + expect( root.toJSON() ).toBe( 'useViewportMatch: true' ); + + await act( async () => { + root.update( <TestComponent breakpoint="small" operator=">=" /> ); + } ); + expect( root.toJSON() ).toBe( 'useViewportMatch: true' ); + + expect( useMediaQueryMock.mock.calls ).toEqual( [ + [ '(max-width: 1280px)' ], + [ '(min-width: 782px)' ], + [ '(min-width: 600px)' ], + ] ); + + root.unmount(); + } ); + + it( 'should return false when the viewport matches', async () => { + let root; + useMediaQueryMock.mockReturnValue( false ); + + await act( async () => { + root = create( <TestComponent breakpoint="huge" operator=">=" /> ); + } ); + expect( root.toJSON() ).toBe( 'useViewportMatch: false' ); + + await act( async () => { + root.update( <TestComponent breakpoint="large" operator="<" /> ); + } ); + expect( root.toJSON() ).toBe( 'useViewportMatch: false' ); + + await act( async () => { + root.update( <TestComponent breakpoint="mobile" operator="<" /> ); + } ); + expect( root.toJSON() ).toBe( 'useViewportMatch: false' ); + + expect( useMediaQueryMock.mock.calls ).toEqual( [ + [ '(min-width: 1440px)' ], + [ '(max-width: 960px)' ], + [ '(max-width: 480px)' ], + ] ); + + root.unmount(); + } ); +} ); diff --git a/packages/compose/src/index.js b/packages/compose/src/index.js index 9ea31b2d351aba..aa1d03dcde2f7d 100644 --- a/packages/compose/src/index.js +++ b/packages/compose/src/index.js @@ -1,20 +1,8 @@ -/** - * External dependencies - */ -import { flowRight } from 'lodash'; - // Utils export { default as createHigherOrderComponent } from './utils/create-higher-order-component'; -/** - * Composes multiple higher-order components into a single higher-order component. Performs right-to-left function - * composition, where each successive invocation is supplied the return value of the previous. - * - * @param {...Function} hocs The HOC functions to invoke. - * - * @return {Function} Returns the new composite function. - */ -export { flowRight as compose }; +// Compose helper (aliased flowRight from Lodash) +export { default as compose } from './higher-order/compose'; // Higher-order components export { default as ifCondition } from './higher-order/if-condition'; @@ -27,3 +15,4 @@ export { default as withState } from './higher-order/with-state'; // Hooks export { default as useMediaQuery } from './hooks/use-media-query'; export { default as useReducedMotion } from './hooks/use-reduced-motion'; +export { default as useViewportMatch } from './hooks/use-viewport-match'; diff --git a/packages/core-data/README.md b/packages/core-data/README.md index 892a6e572a99be..a2bb3ccbe6deeb 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -106,7 +106,7 @@ a given URl has been received. _Parameters_ - _url_ `string`: URL to preview the embed for. -- _preview_ `Mixed`: Preview data. +- _preview_ `*`: Preview data. _Returns_ diff --git a/packages/core-data/package.json b/packages/core-data/package.json index 32fca25d8a2771..2cbc9de93ced58 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/core-data", - "version": "2.7.0", + "version": "2.8.0", "description": "Access to and manipulation of core WordPress entities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 5a318dad792154..66fbd292ec3403 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { castArray, get, merge, isEqual, find } from 'lodash'; +import { castArray, get, isEqual, find } from 'lodash'; /** * Internal dependencies @@ -109,8 +109,8 @@ export function receiveThemeSupports( themeSupports ) { * Returns an action object used in signalling that the preview data for * a given URl has been received. * - * @param {string} url URL to preview the embed for. - * @param {Mixed} preview Preview data. + * @param {string} url URL to preview the embed for. + * @param {*} preview Preview data. * * @return {Object} Action object. */ @@ -136,11 +136,11 @@ export function receiveEmbedPreview( url, preview ) { * @return {Object} Action object. */ export function* editEntityRecord( kind, name, recordId, edits, options = {} ) { - const { transientEdits = {}, mergedEdits = {} } = yield select( - 'getEntity', - kind, - name - ); + const entity = yield select( 'getEntity', kind, name ); + if ( ! entity ) { + throw new Error( `The entity being edited (${ kind }, ${ name }) does not have a loaded config.` ); + } + const { transientEdits = {}, mergedEdits = {} } = entity; const record = yield select( 'getRawEntityRecord', kind, name, recordId ); const editedRecord = yield select( 'getEditedEntityRecord', @@ -159,7 +159,7 @@ export function* editEntityRecord( kind, name, recordId, edits, options = {} ) { const recordValue = record[ key ]; const editedRecordValue = editedRecord[ key ]; const value = mergedEdits[ key ] ? - merge( {}, editedRecordValue, edits[ key ] ) : + { ...editedRecordValue, ...edits[ key ] } : edits[ key ]; acc[ key ] = isEqual( recordValue, value ) ? undefined : value; return acc; diff --git a/packages/core-data/src/controls.js b/packages/core-data/src/controls.js index 84644e76541ac9..fe163c118c6499 100644 --- a/packages/core-data/src/controls.js +++ b/packages/core-data/src/controls.js @@ -61,25 +61,7 @@ const controls = { RESOLVE_SELECT: createRegistryControl( ( registry ) => ( { selectorName, args } ) => { - return new Promise( ( resolve ) => { - const hasFinished = () => registry.select( 'core/data' ) - .hasFinishedResolution( 'core', selectorName, args ); - const getResult = () => registry.select( 'core' )[ selectorName ] - .apply( null, args ); - - // trigger the selector (to trigger the resolver) - const result = getResult(); - if ( hasFinished() ) { - return resolve( result ); - } - - const unsubscribe = registry.subscribe( () => { - if ( hasFinished() ) { - unsubscribe(); - resolve( getResult() ); - } - } ); - } ); + return registry.__experimentalResolveSelect( 'core' )[ selectorName ]( ...args ); } ), }; diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index 4ee2f6b715374f..ec4c3712da6d14 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -12,6 +12,7 @@ import { apiFetch, select } from './controls'; export const DEFAULT_ENTITY_KEY = 'id'; export const defaultEntities = [ + { name: 'site', kind: 'root', baseURL: '/wp/v2/settings' }, { name: 'postType', kind: 'root', key: 'slug', baseURL: '/wp/v2/types' }, { name: 'media', kind: 'root', baseURL: '/wp/v2/media', plural: 'mediaItems' }, { name: 'taxonomy', kind: 'root', key: 'slug', baseURL: '/wp/v2/taxonomies', plural: 'taxonomies' }, @@ -35,7 +36,11 @@ function* loadPostTypeEntities() { kind: 'postType', baseURL: '/wp/v2/' + postType.rest_base, name, - transientEdits: { blocks: true }, + transientEdits: { + blocks: true, + selectionStart: true, + selectionEnd: true, + }, mergedEdits: { meta: true }, }; } ); diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js index 7870e0217cc974..50439f1b57ad65 100644 --- a/packages/core-data/src/entity-provider.js +++ b/packages/core-data/src/entity-provider.js @@ -52,6 +52,17 @@ export default function EntityProvider( { kind, type, id, children } ) { return <Provider value={ id }>{ children }</Provider>; } +/** + * Hook that returns the ID for the nearest + * provided entity of the specified type. + * + * @param {string} kind The entity kind. + * @param {string} type The entity type. + */ +export function useEntityId( kind, type ) { + return useContext( getEntity( kind, type ).context ); +} + /** * Hook that returns the value and a setter for the * specified property of the nearest provided @@ -66,11 +77,13 @@ export default function EntityProvider( { kind, type, id, children } ) { * setter. */ export function useEntityProp( kind, type, prop ) { - const id = useContext( getEntity( kind, type ).context ); + const id = useEntityId( kind, type ); const value = useSelect( ( select ) => { - const entity = select( 'core' ).getEditedEntityRecord( kind, type, id ); + const { getEntityRecord, getEditedEntityRecord } = select( 'core' ); + getEntityRecord( kind, type, id ); // Trigger resolver. + const entity = getEditedEntityRecord( kind, type, id ); return entity && entity[ prop ]; }, [ kind, type, id, prop ] @@ -88,3 +101,62 @@ export function useEntityProp( kind, type, prop ) { return [ value, setValue ]; } + +/** + * Hook that returns whether the nearest provided + * entity of the specified type is dirty, saving, + * and a function to save it. + * + * The last, optional parameter is for scoping the + * selection to a single property or a list properties. + * + * By default, dirtyness detection and saving considers + * and handles all properties of an entity, but this + * last parameter lets you scope it to a single property + * or a list of properties for each instance of this hook. + * + * @param {string} kind The entity kind. + * @param {string} type The entity type. + * @param {string|[string]} [props] The property name or list of property names. + */ +export function __experimentalUseEntitySaving( kind, type, props ) { + const id = useEntityId( kind, type ); + + const [ isDirty, isSaving, edits ] = useSelect( + ( select ) => { + const { getEntityRecordNonTransientEdits, isSavingEntityRecord } = select( + 'core' + ); + const _edits = getEntityRecordNonTransientEdits( kind, type, id ); + const editKeys = Object.keys( _edits ); + return [ + props ? + editKeys.some( ( key ) => + typeof props === 'string' ? key === props : props.includes( key ) + ) : + editKeys.length > 0, + isSavingEntityRecord( kind, type, id ), + _edits, + ]; + }, + [ kind, type, id, props ] + ); + + const { saveEntityRecord } = useDispatch( 'core' ); + const save = useCallback( () => { + let filteredEdits = edits; + if ( typeof props === 'string' ) { + filteredEdits = { [ props ]: filteredEdits[ props ] }; + } else if ( props ) { + filteredEdits = filteredEdits.reduce( ( acc, key ) => { + if ( props.includes( key ) ) { + acc[ key ] = filteredEdits[ key ]; + } + return acc; + }, {} ); + } + saveEntityRecord( kind, type, { id, ...filteredEdits } ); + }, [ kind, type, id, props, edits ] ); + + return [ isDirty, isSaving, save ]; +} diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index 2cdddb960e448b..bf12ac2e5096fb 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -49,4 +49,9 @@ registerStore( REDUCER_KEY, { resolvers: { ...resolvers, ...entityResolvers }, } ); -export { default as EntityProvider, useEntityProp } from './entity-provider'; +export { + default as EntityProvider, + useEntityId, + useEntityProp, + __experimentalUseEntitySaving, +} from './entity-provider'; diff --git a/packages/core-data/src/queried-data/reducer.js b/packages/core-data/src/queried-data/reducer.js index 0c3256e9a8f987..36da4465bb922d 100644 --- a/packages/core-data/src/queried-data/reducer.js +++ b/packages/core-data/src/queried-data/reducer.js @@ -74,10 +74,10 @@ function items( state = {}, action ) { const key = action.key || DEFAULT_ENTITY_KEY; return { ...state, - ...action.items.reduce( ( acc, value ) => { + ...action.items.reduce( ( accumulator, value ) => { const itemId = value[ key ]; - acc[ itemId ] = conservativeMapItem( state[ itemId ], value ); - return acc; + accumulator[ itemId ] = conservativeMapItem( state[ itemId ], value ); + return accumulator; }, {} ), }; } diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js index 9a8eacad09aa5c..7cfb80e90ecb58 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.js @@ -309,17 +309,41 @@ export function undo( state = UNDO_INITIAL_STATE, action ) { switch ( action.type ) { case 'EDIT_ENTITY_RECORD': case 'CREATE_UNDO_LEVEL': - const isCreateUndoLevel = action.type === 'CREATE_UNDO_LEVEL'; + let isCreateUndoLevel = action.type === 'CREATE_UNDO_LEVEL'; + const isUndoOrRedo = + ! isCreateUndoLevel && ( action.meta.isUndo || action.meta.isRedo ); if ( isCreateUndoLevel ) { action = lastEditAction; - } else { - lastEditAction = action; + } else if ( ! isUndoOrRedo ) { + // Don't lose the last edit cache if the new one only has transient edits. + // Transient edits don't create new levels so updating the cache would make + // us skip an edit later when creating levels explicitly. + if ( Object.keys( action.edits ).some( ( key ) => ! action.transientEdits[ key ] ) ) { + lastEditAction = action; + } else { + lastEditAction = { + ...action, + edits: { ...( lastEditAction && lastEditAction.edits ), ...action.edits }, + }; + } } - if ( action.meta.isUndo || action.meta.isRedo ) { - const nextState = [ ...state ]; + let nextState; + if ( isUndoOrRedo ) { + nextState = [ ...state ]; nextState.offset = state.offset + ( action.meta.isUndo ? -1 : 1 ); - return nextState; + + if ( state.flattenedUndo ) { + // The first undo in a sequence of undos might happen while we have + // flattened undos in state. If this is the case, we want execution + // to continue as if we were creating an explicit undo level. This + // will result in an extra undo level being appended with the flattened + // undo values. + isCreateUndoLevel = true; + action = lastEditAction; + } else { + return nextState; + } } if ( ! action.meta.undo ) { @@ -329,16 +353,19 @@ export function undo( state = UNDO_INITIAL_STATE, action ) { // Transient edits don't create an undo level, but are // reachable in the next meaningful edit to which they // are merged. They are defined in the entity's config. - if ( ! isCreateUndoLevel && ! Object.keys( action.edits ).some( ( key ) => ! action.transientEdits[ key ] ) ) { - const nextState = [ ...state ]; + if ( + ! isCreateUndoLevel && + ! Object.keys( action.edits ).some( ( key ) => ! action.transientEdits[ key ] ) + ) { + nextState = [ ...state ]; nextState.flattenedUndo = { ...state.flattenedUndo, ...action.edits }; nextState.offset = state.offset; return nextState; } // Clear potential redos, because this only supports linear history. - const nextState = state.slice( 0, state.offset || undefined ); - nextState.offset = 0; + nextState = nextState || state.slice( 0, state.offset || undefined ); + nextState.offset = nextState.offset || 0; nextState.pop(); if ( ! isCreateUndoLevel ) { nextState.push( { @@ -350,14 +377,20 @@ export function undo( state = UNDO_INITIAL_STATE, action ) { } // When an edit is a function it's an optimization to avoid running some expensive operation. // We can't rely on the function references being the same so we opt out of comparing them here. - const comparisonUndoEdits = Object.values( action.meta.undo.edits ).filter( ( edit ) => typeof edit !== 'function' ); - const comparisonEdits = Object.values( action.edits ).filter( ( edit ) => typeof edit !== 'function' ); + const comparisonUndoEdits = Object.values( action.meta.undo.edits ).filter( + ( edit ) => typeof edit !== 'function' + ); + const comparisonEdits = Object.values( action.edits ).filter( + ( edit ) => typeof edit !== 'function' + ); if ( ! isShallowEqual( comparisonUndoEdits, comparisonEdits ) ) { nextState.push( { kind: action.kind, name: action.name, recordId: action.recordId, - edits: action.edits, + edits: isCreateUndoLevel ? + { ...state.flattenedUndo, ...action.edits } : + action.edits, } ); } return nextState; diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 8edfdbf895cded..670cb4adf2a750 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -47,7 +47,7 @@ export function* getCurrentUser() { * @param {string} name Entity name. * @param {number} key Record's key */ -export function* getEntityRecord( kind, name, key ) { +export function* getEntityRecord( kind, name, key = '' ) { const entities = yield getKindEntities( kind ); const entity = find( entities, { kind, name } ); if ( ! entity ) { diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js index 4c61d59220843f..1475ffd2562f8f 100644 --- a/packages/core-data/src/selectors.js +++ b/packages/core-data/src/selectors.js @@ -123,12 +123,12 @@ export const getRawEntityRecord = createSelector( const record = getEntityRecord( state, kind, name, key ); return ( record && - Object.keys( record ).reduce( ( acc, _key ) => { + Object.keys( record ).reduce( ( accumulator, _key ) => { // Because edits are the "raw" attribute values, // we return those from record selectors to make rendering, // comparisons, and joins with edits easier. - acc[ _key ] = get( record[ _key ], 'raw', record[ _key ] ); - return acc; + accumulator[ _key ] = get( record[ _key ], 'raw', record[ _key ] ); + return accumulator; }, {} ) ); }, @@ -183,8 +183,11 @@ export function getEntityRecordEdits( state, kind, name, recordId ) { */ export const getEntityRecordNonTransientEdits = createSelector( ( state, kind, name, recordId ) => { - const { transientEdits = {} } = getEntity( state, kind, name ); - const edits = getEntityRecordEdits( state, kind, name, recordId ) || []; + const { transientEdits } = getEntity( state, kind, name ) || {}; + const edits = getEntityRecordEdits( state, kind, name, recordId ) || {}; + if ( ! transientEdits ) { + return edits; + } return Object.keys( edits ).reduce( ( acc, key ) => { if ( ! transientEdits[ key ] ) { acc[ key ] = edits[ key ]; diff --git a/packages/core-data/src/test/actions.js b/packages/core-data/src/test/actions.js index cf839cf9a3493a..ffcee0efbca504 100644 --- a/packages/core-data/src/test/actions.js +++ b/packages/core-data/src/test/actions.js @@ -1,7 +1,34 @@ /** * Internal dependencies */ -import { saveEntityRecord, receiveEntityRecords, receiveUserPermission, receiveAutosaves, receiveCurrentUser } from '../actions'; +import { + editEntityRecord, + saveEntityRecord, + receiveEntityRecords, + receiveUserPermission, + receiveAutosaves, + receiveCurrentUser, +} from '../actions'; +import { select } from '../controls'; + +describe( 'editEntityRecord', () => { + it( 'throws when the edited entity does not have a loaded config.', () => { + const entity = { kind: 'someKind', name: 'someName', id: 'someId' }; + const fulfillment = editEntityRecord( + entity.kind, + entity.name, + entity.id, + {} + ); + expect( fulfillment.next().value ).toEqual( + select( 'getEntity', entity.kind, entity.name ) + ); + // Don't pass back an entity config. + expect( fulfillment.next.bind( fulfillment ) ).toThrow( + `The entity being edited (${ entity.kind }, ${ entity.name }) does not have a loaded config.` + ); + } ); +} ); describe( 'saveEntityRecord', () => { it( 'triggers a POST request for a new record', async () => { diff --git a/packages/core-data/src/test/reducer.js b/packages/core-data/src/test/reducer.js index eea3054bc64684..c585e4f4415a91 100644 --- a/packages/core-data/src/test/reducer.js +++ b/packages/core-data/src/test/reducer.js @@ -7,7 +7,15 @@ import { filter } from 'lodash'; /** * Internal dependencies */ -import { terms, entities, embedPreviews, userPermissions, autosaves, currentUser } from '../reducer'; +import { + terms, + entities, + undo, + embedPreviews, + userPermissions, + autosaves, + currentUser, +} from '../reducer'; describe( 'terms()', () => { it( 'returns an empty object by default', () => { @@ -99,6 +107,206 @@ describe( 'entities', () => { } ); } ); +describe( 'undo', () => { + let lastEdits; + let undoState; + let expectedUndoState; + const createEditActionPart = ( edits ) => ( { + kind: 'someKind', + name: 'someName', + recordId: 'someRecordId', + edits, + } ); + const createNextEditAction = ( edits, transientEdits = {} ) => { + let action = { + ...createEditActionPart( edits ), + transientEdits, + }; + action = { + type: 'EDIT_ENTITY_RECORD', + ...action, + meta: { + undo: { ...action, edits: lastEdits }, + }, + }; + lastEdits = { ...lastEdits, ...edits }; + return action; + }; + const createNextUndoState = ( ...args ) => { + let action = {}; + if ( args[ 0 ] === 'isUndo' || args[ 0 ] === 'isRedo' ) { + // We need to "apply" the undo level here and build + // the action to move the offset. + lastEdits = + undoState[ + undoState.length + undoState.offset - ( args[ 0 ] === 'isUndo' ? 2 : 0 ) + ].edits; + action = { + type: 'EDIT_ENTITY_RECORD', + meta: { + [ args[ 0 ] ]: true, + }, + }; + } else if ( args[ 0 ] === 'isCreate' ) { + action = { type: 'CREATE_UNDO_LEVEL' }; + } else if ( args.length ) { + action = createNextEditAction( ...args ); + } + return deepFreeze( undo( undoState, action ) ); + }; + beforeEach( () => { + lastEdits = {}; + undoState = undefined; + expectedUndoState = []; + expectedUndoState.offset = 0; + } ); + + it( 'initializes', () => { + expect( createNextUndoState() ).toEqual( expectedUndoState ); + } ); + + it( 'stacks undo levels', () => { + undoState = createNextUndoState(); + + // Check that the first edit creates an undo level for the current state and + // one for the new one. + undoState = createNextUndoState( { value: 1 } ); + expectedUndoState.push( + createEditActionPart( {} ), + createEditActionPart( { value: 1 } ) + ); + expect( undoState ).toEqual( expectedUndoState ); + + // Check that the second and third edits just create an undo level for + // themselves. + undoState = createNextUndoState( { value: 2 } ); + expectedUndoState.push( createEditActionPart( { value: 2 } ) ); + expect( undoState ).toEqual( expectedUndoState ); + undoState = createNextUndoState( { value: 3 } ); + expectedUndoState.push( createEditActionPart( { value: 3 } ) ); + expect( undoState ).toEqual( expectedUndoState ); + } ); + + it( 'handles undos/redos', () => { + undoState = createNextUndoState(); + undoState = createNextUndoState( { value: 1 } ); + undoState = createNextUndoState( { value: 2 } ); + undoState = createNextUndoState( { value: 3 } ); + expectedUndoState.push( + createEditActionPart( {} ), + createEditActionPart( { value: 1 } ), + createEditActionPart( { value: 2 } ), + createEditActionPart( { value: 3 } ) + ); + expect( undoState ).toEqual( expectedUndoState ); + + // Check that undoing and redoing an equal + // number of steps does not lose edits. + undoState = createNextUndoState( 'isUndo' ); + expectedUndoState.offset--; + expect( undoState ).toEqual( expectedUndoState ); + undoState = createNextUndoState( 'isUndo' ); + expectedUndoState.offset--; + expect( undoState ).toEqual( expectedUndoState ); + undoState = createNextUndoState( 'isRedo' ); + expectedUndoState.offset++; + expect( undoState ).toEqual( expectedUndoState ); + undoState = createNextUndoState( 'isRedo' ); + expectedUndoState.offset++; + expect( undoState ).toEqual( expectedUndoState ); + + // Check that another edit will go on top when there + // is no undo level offset. + undoState = createNextUndoState( { value: 4 } ); + expectedUndoState.push( createEditActionPart( { value: 4 } ) ); + expect( undoState ).toEqual( expectedUndoState ); + + // Check that undoing and editing will slice of + // all the levels after the current one. + undoState = createNextUndoState( 'isUndo' ); + undoState = createNextUndoState( 'isUndo' ); + undoState = createNextUndoState( { value: 5 } ); + expectedUndoState.pop(); + expectedUndoState.pop(); + expectedUndoState.push( createEditActionPart( { value: 5 } ) ); + expect( undoState ).toEqual( expectedUndoState ); + } ); + + it( 'handles flattened undos/redos', () => { + undoState = createNextUndoState(); + undoState = createNextUndoState( { value: 1 } ); + undoState = createNextUndoState( + { transientValue: 2 }, + { transientValue: true } + ); + undoState = createNextUndoState( { value: 3 } ); + expectedUndoState.push( + createEditActionPart( {} ), + createEditActionPart( { value: 1, transientValue: 2 } ), + createEditActionPart( { value: 3 } ) + ); + expect( undoState ).toEqual( expectedUndoState ); + } ); + + it( 'handles explicit undo level creation', () => { + undoState = createNextUndoState(); + + // Check that nothing happens if there are no pending + // transient edits. + undoState = createNextUndoState( { value: 1 } ); + undoState = createNextUndoState( 'isCreate' ); + expectedUndoState.push( + createEditActionPart( {} ), + createEditActionPart( { value: 1 } ) + ); + expect( undoState ).toEqual( expectedUndoState ); + + // Check that transient edits are merged into the last + // edits. + undoState = createNextUndoState( + { transientValue: 2 }, + { transientValue: true } + ); + undoState = createNextUndoState( 'isCreate' ); + expectedUndoState[ expectedUndoState.length - 1 ].edits.transientValue = 2; + expect( undoState ).toEqual( expectedUndoState ); + + // Check that undo levels are created with the latest action, + // even if undone. + undoState = createNextUndoState( { value: 3 } ); + undoState = createNextUndoState( 'isUndo' ); + undoState = createNextUndoState( 'isCreate' ); + expectedUndoState.pop(); + expectedUndoState.push( createEditActionPart( { value: 3 } ) ); + expect( undoState ).toEqual( expectedUndoState ); + } ); + + it( 'explicitly creates an undo level when undoing while there are pending transient edits', () => { + undoState = createNextUndoState(); + undoState = createNextUndoState( { value: 1 } ); + undoState = createNextUndoState( + { transientValue: 2 }, + { transientValue: true } + ); + undoState = createNextUndoState( 'isUndo' ); + expectedUndoState.push( + createEditActionPart( {} ), + createEditActionPart( { value: 1, transientValue: 2 } ) + ); + expectedUndoState.offset--; + expect( undoState ).toEqual( expectedUndoState ); + } ); + + it( 'does not create new levels for the same function edits', () => { + const value = () => {}; + undoState = createNextUndoState(); + undoState = createNextUndoState( { value } ); + undoState = createNextUndoState( { value: () => {} } ); + expectedUndoState.push( createEditActionPart( { value } ) ); + expect( undoState ).toEqual( expectedUndoState ); + } ); +} ); + describe( 'embedPreviews()', () => { it( 'returns an empty object by default', () => { const state = embedPreviews( undefined, {} ); diff --git a/packages/core-data/src/test/selectors.js b/packages/core-data/src/test/selectors.js index a52c13d99c7d16..13e1551f2f1d6e 100644 --- a/packages/core-data/src/test/selectors.js +++ b/packages/core-data/src/test/selectors.js @@ -9,6 +9,7 @@ import deepFreeze from 'deep-freeze'; import { getEntityRecord, getEntityRecords, + getEntityRecordNonTransientEdits, getEmbedPreview, isPreviewEmbedFallback, canUser, @@ -104,6 +105,17 @@ describe( 'getEntityRecords', () => { } ); } ); +describe( 'getEntityRecordNonTransientEdits', () => { + it( 'should return an empty object when the entity does not have a loaded config.', () => { + const state = deepFreeze( { + entities: { config: {}, data: {} }, + } ); + expect( + getEntityRecordNonTransientEdits( state, 'someKind', 'someName', 'someId' ) + ).toEqual( {} ); + } ); +} ); + describe( 'getEmbedPreview()', () => { it( 'returns preview stored for url', () => { let state = deepFreeze( { diff --git a/packages/data-controls/CHANGELOG.md b/packages/data-controls/CHANGELOG.md index ad84807c4ebebb..f92518a813a258 100644 --- a/packages/data-controls/CHANGELOG.md +++ b/packages/data-controls/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 1.4.0 (2019-11-14) ### Documentation diff --git a/packages/data-controls/package.json b/packages/data-controls/package.json index 8e74ef5a768b4c..7ce8eaa8a0dfb6 100644 --- a/packages/data-controls/package.json +++ b/packages/data-controls/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data-controls", - "version": "1.3.0", + "version": "1.4.0", "description": "A set of common controls for the @wordpress/data api.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/data-controls/src/index.js b/packages/data-controls/src/index.js index 5b5b5b40cb1fbd..e32f16392e5371 100644 --- a/packages/data-controls/src/index.js +++ b/packages/data-controls/src/index.js @@ -92,39 +92,6 @@ export function dispatch( storeKey, actionName, ...args ) { }; } -/** - * Utility for returning a promise that handles a selector with a resolver. - * - * @param {Object} registry The data registry. - * @param {Object} options - * @param {string} options.storeKey The store the selector belongs to - * @param {string} options.selectorName The selector name - * @param {Array} options.args The arguments fed to the selector - * - * @return {Promise} A promise for resolving the given selector. - */ -const resolveSelect = ( registry, { storeKey, selectorName, args } ) => { - return new Promise( ( resolve ) => { - const hasFinished = () => registry.select( 'core/data' ) - .hasFinishedResolution( storeKey, selectorName, args ); - const getResult = () => registry.select( storeKey )[ selectorName ] - .apply( null, args ); - - // trigger the selector (to trigger the resolver) - const result = getResult(); - if ( hasFinished() ) { - return resolve( result ); - } - - const unsubscribe = registry.subscribe( () => { - if ( hasFinished() ) { - unsubscribe(); - resolve( getResult() ); - } - } ); - } ); -}; - /** * The default export is what you use to register the controls with your custom * store. @@ -159,9 +126,11 @@ export const controls = { }, SELECT: createRegistryControl( ( registry ) => ( { storeKey, selectorName, args } ) => { - return registry.select( storeKey )[ selectorName ].hasResolver ? - resolveSelect( registry, { storeKey, selectorName, args } ) : - registry.select( storeKey )[ selectorName ]( ...args ); + return registry[ + registry.select( storeKey )[ selectorName ].hasResolver ? + '__experimentalResolveSelect' : + 'select' + ]( storeKey )[ selectorName ]( ...args ); } ), DISPATCH: createRegistryControl( diff --git a/packages/data-controls/src/test/index.js b/packages/data-controls/src/test/index.js index fcd6650352af18..a543e090d79a4c 100644 --- a/packages/data-controls/src/test/index.js +++ b/packages/data-controls/src/test/index.js @@ -31,28 +31,29 @@ describe( 'controls', () => { const selectorWithFalseResolver = jest.fn(); selectorWithFalseResolver.hasResolver = false; const hasFinishedResolution = jest.fn(); - const unsubscribe = jest.fn(); - let subscribedCallback; + + const mockStore = { + selectorWithResolver, + selectorWithUndefinedResolver, + selectorWithFalseResolver, + }; + const registryMock = { + __experimentalResolveSelect: ( storeKey ) => { + const stores = { + mockStore, + }; + return stores[ storeKey ]; + }, select: ( storeKey ) => { const stores = { - mockStore: { - selectorWithResolver, - selectorWithUndefinedResolver, - selectorWithFalseResolver, - }, + mockStore, 'core/data': { hasFinishedResolution, }, }; return stores[ storeKey ]; }, - subscribe: jest.fn( - ( subscribeCallback ) => { - subscribedCallback = subscribeCallback; - return unsubscribe; - } - ), }; const getSelectorArgs = ( storeKey, selectorName, ...args ) => ( { storeKey, selectorName, args } @@ -60,15 +61,14 @@ describe( 'controls', () => { beforeEach( () => { selectorWithUndefinedResolver.mockReturnValue( 'foo' ); selectorWithFalseResolver.mockReturnValue( 'bar' ); - selectorWithResolver.mockReturnValue( 'resolved' ); hasFinishedResolution.mockReturnValue( false ); + selectorWithResolver.mockResolvedValue( 'resolved' ); } ); afterEach( () => { selectorWithUndefinedResolver.mockClear(); selectorWithResolver.mockClear(); selectorWithFalseResolver.mockClear(); hasFinishedResolution.mockClear(); - unsubscribe.mockClear(); } ); it( 'invokes selector with undefined resolver', () => { const testControl = controls.SELECT( registryMock ); @@ -92,14 +92,13 @@ describe( 'controls', () => { } ); describe( 'invokes selector with resolver set to true', () => { const testControl = controls.SELECT( registryMock ); - it( 'returns a promise', () => { + it( 'returns a promise', async () => { const value = testControl( getSelectorArgs( 'mockStore', 'selectorWithResolver' ) ); - expect( value ).toBeInstanceOf( Promise ); - expect( hasFinishedResolution ).toHaveBeenCalled(); - expect( registryMock.subscribe ).toHaveBeenCalled(); + await expect( value ).resolves.toBe( 'resolved' ); + expect( selectorWithResolver ).toHaveBeenCalled(); } ); it( 'selector with resolver resolves to expected result when ' + 'finished', async () => { @@ -108,9 +107,8 @@ describe( 'controls', () => { 'selectorWithResolver' ) ); hasFinishedResolution.mockReturnValue( true ); - subscribedCallback(); + expect( selectorWithResolver ).toHaveBeenCalled(); await expect( value ).resolves.toBe( 'resolved' ); - expect( unsubscribe ).toHaveBeenCalled(); } ); } ); } ); diff --git a/packages/data/README.md b/packages/data/README.md index a76019371e725a..d0eeba0e08539d 100644 --- a/packages/data/README.md +++ b/packages/data/README.md @@ -251,6 +251,46 @@ Specific implementation differences from Redux and React Redux: <!-- START TOKEN(Autogenerated API docs) --> +<a name="AsyncModeProvider" href="#AsyncModeProvider">#</a> **AsyncModeProvider** + +Context Provider Component used to switch the data module component rerendering +between Sync and Async modes. + +_Usage_ + +```js +import { useSelect, AsyncModeProvider } from '@wordpress/data'; + +function BlockCount() { + const count = useSelect( ( select ) => { + return select( 'core/block-editor' ).getBlockCount() + }, [] ); + + return count; +} + +function App() { + return ( + <AsyncModeProvider value={ true }> + <BlockCount /> + </AsyncModeProvider> + ); +} +``` + +In this example, the BlockCount component is rerendered asynchronously. +It means if a more critical task is being performed (like typing in an input), +the rerendering is delayed until the browser becomes IDLE. +It is possible to nest multiple levels of AsyncModeProvider to fine-tune the rendering behavior. + +_Parameters_ + +- _props.value_ `boolean`: Enable Async Mode. + +_Returns_ + +- `WPComponent`: The component to be rendered. + <a name="combineReducers" href="#combineReducers">#</a> **combineReducers** The combineReducers helper function turns an object whose values are different @@ -348,7 +388,7 @@ dispatch( 'my-shop' ).setPrice( 'hammer', 9.75 ); _Parameters_ -- _name_ `string`: Store name +- _name_ `string`: Store name. _Returns_ @@ -445,7 +485,7 @@ select( 'my-shop' ).getPrice( 'hammer' ); _Parameters_ -- _name_ `string`: Store name +- _name_ `string`: Store name. _Returns_ @@ -508,7 +548,8 @@ function Button( { onClick, children } ) { const SaleButton = ( { children } ) => { const { stockNumber } = useSelect( - ( select ) => select( 'my-shop' ).getStockNumber() + ( select ) => select( 'my-shop' ).getStockNumber(), + [] ); const { startSale } = useDispatch( 'my-shop' ); const onClick = useCallback( () => { @@ -693,7 +734,7 @@ _Parameters_ _Returns_ -- `Component`: Enhanced component with merged dispatcher props. +- `WPComponent`: Enhanced component with merged dispatcher props. <a name="withRegistry" href="#withRegistry">#</a> **withRegistry** @@ -750,7 +791,7 @@ _Parameters_ _Returns_ -- `Component`: Enhanced component with merged state data props. +- `WPComponent`: Enhanced component with merged state data props. <!-- END TOKEN(Autogenerated API docs) --> diff --git a/packages/data/package.json b/packages/data/package.json index 9eb21a7608146d..4f00354f8932b3 100644 --- a/packages/data/package.json +++ b/packages/data/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/data", - "version": "4.9.0", + "version": "4.10.0", "description": "Data module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -32,6 +32,7 @@ "equivalent-key-map": "^0.2.2", "is-promise": "^2.1.0", "lodash": "^4.17.15", + "memize": "^1.0.5", "redux": "^4.0.0", "turbo-combine-reducers": "^1.0.2" }, diff --git a/packages/data/src/components/async-mode-provider/context.js b/packages/data/src/components/async-mode-provider/context.js index c0e59ef7138183..ff6528c324cc3d 100644 --- a/packages/data/src/components/async-mode-provider/context.js +++ b/packages/data/src/components/async-mode-provider/context.js @@ -9,4 +9,38 @@ const { Consumer, Provider } = Context; export const AsyncModeConsumer = Consumer; +/** + * Context Provider Component used to switch the data module component rerendering + * between Sync and Async modes. + * + * @example + * + * ```js + * import { useSelect, AsyncModeProvider } from '@wordpress/data'; + * + * function BlockCount() { + * const count = useSelect( ( select ) => { + * return select( 'core/block-editor' ).getBlockCount() + * }, [] ); + * + * return count; + * } + * + * function App() { + * return ( + * <AsyncModeProvider value={ true }> + * <BlockCount /> + * </AsyncModeProvider> + * ); + * } + * ``` + * + * In this example, the BlockCount component is rerendered asynchronously. + * It means if a more critical task is being performed (like typing in an input), + * the rerendering is delayed until the browser becomes IDLE. + * It is possible to nest multiple levels of AsyncModeProvider to fine-tune the rendering behavior. + * + * @param {boolean} props.value Enable Async Mode. + * @return {WPComponent} The component to be rendered. + */ export default Provider; diff --git a/packages/data/src/components/use-dispatch/use-dispatch.js b/packages/data/src/components/use-dispatch/use-dispatch.js index 985869d8057fb2..9add695434be11 100644 --- a/packages/data/src/components/use-dispatch/use-dispatch.js +++ b/packages/data/src/components/use-dispatch/use-dispatch.js @@ -29,7 +29,8 @@ import useRegistry from '../registry-provider/use-registry'; * * const SaleButton = ( { children } ) => { * const { stockNumber } = useSelect( - * ( select ) => select( 'my-shop' ).getStockNumber() + * ( select ) => select( 'my-shop' ).getStockNumber(), + * [] * ); * const { startSale } = useDispatch( 'my-shop' ); * const onClick = useCallback( () => { diff --git a/packages/data/src/components/with-dispatch/index.js b/packages/data/src/components/with-dispatch/index.js index 2247a253a189d5..3e6c2194dec5f4 100644 --- a/packages/data/src/components/with-dispatch/index.js +++ b/packages/data/src/components/with-dispatch/index.js @@ -85,7 +85,7 @@ import { useDispatchWithMap } from '../use-dispatch'; * returns an object with the same keys. For example, it should not contain * conditions under which a different value would be returned. * - * @return {Component} Enhanced component with merged dispatcher props. + * @return {WPComponent} Enhanced component with merged dispatcher props. */ const withDispatch = ( mapDispatchToProps ) => createHigherOrderComponent( ( WrappedComponent ) => ( ownProps ) => { diff --git a/packages/data/src/components/with-select/index.js b/packages/data/src/components/with-select/index.js index f9659e7e865a8f..aeb86761494e44 100644 --- a/packages/data/src/components/with-select/index.js +++ b/packages/data/src/components/with-select/index.js @@ -45,7 +45,7 @@ import useSelect from '../use-select'; * component and update automatically if the price of a hammer ever changes in * the store. * - * @return {Component} Enhanced component with merged state data props. + * @return {WPComponent} Enhanced component with merged state data props. */ const withSelect = ( mapSelectToProps ) => createHigherOrderComponent( ( WrappedComponent ) => pure( diff --git a/packages/data/src/factory.js b/packages/data/src/factory.js index 2a10c7a8e7afc1..2bb74724f56c92 100644 --- a/packages/data/src/factory.js +++ b/packages/data/src/factory.js @@ -3,9 +3,7 @@ */ import defaultRegistry from './default-registry'; -/** - * @typedef {import('./registry').WPDataRegistry} WPDataRegistry - */ +/** @typedef {import('./registry').WPDataRegistry} WPDataRegistry */ /** * Mark a selector as a registry selector. diff --git a/packages/data/src/index.js b/packages/data/src/index.js index 8dd6893c5d9e01..9631ff46548f27 100644 --- a/packages/data/src/index.js +++ b/packages/data/src/index.js @@ -19,9 +19,7 @@ export { } from './components/registry-provider'; export { default as useSelect } from './components/use-select'; export { useDispatch } from './components/use-dispatch'; -export { - AsyncModeProvider as __experimentalAsyncModeProvider, -} from './components/async-mode-provider'; +export { AsyncModeProvider } from './components/async-mode-provider'; export { createRegistry } from './registry'; export { createRegistrySelector, createRegistryControl } from './factory'; @@ -78,7 +76,7 @@ export { combineReducers }; * The selector functions are been pre-bound to pass the current state automatically. * As a consumer, you need only pass arguments of the selector, if applicable. * - * @param {string} name Store name + * @param {string} name Store name. * * @example * ```js @@ -91,6 +89,25 @@ export { combineReducers }; */ export const select = defaultRegistry.select; +/** + * Given the name of a registered store, returns an object containing the store's + * selectors pre-bound to state so that you only need to supply additional arguments, + * and modified so that they return promises that resolve to their eventual values, + * after any resolvers have ran. + * + * @param {string} name Store name. + * + * @example + * ```js + * const { __experimentalResolveSelect } = wp.data; + * + * __experimentalResolveSelect( 'my-shop' ).getPrice( 'hammer' ).then(console.log) + * ``` + * + * @return {Object} Object containing the store's promise-wrapped selectors. + */ +export const __experimentalResolveSelect = defaultRegistry.__experimentalResolveSelect; + /** * Given the name of a registered store, returns an object of the store's action creators. * Calling an action creator will cause it to be dispatched, updating the state value accordingly. @@ -98,7 +115,7 @@ export const select = defaultRegistry.select; * Note: Action creators returned by the dispatch will return a promise when * they are called. * - * @param {string} name Store name + * @param {string} name Store name. * * @example * ```js diff --git a/packages/data/src/namespace-store/index.js b/packages/data/src/namespace-store/index.js index b3bd639fcddfa3..bc41587569bbba 100644 --- a/packages/data/src/namespace-store/index.js +++ b/packages/data/src/namespace-store/index.js @@ -24,7 +24,7 @@ import * as metadataSelectors from './metadata/selectors'; import * as metadataActions from './metadata/actions'; /** - * @typedef {import('../registry').WPDataRegistry} WPDataRegistry + * @typedef {WPDataRegistry} WPDataRegistry */ /** diff --git a/packages/data/src/plugins/persistence/index.js b/packages/data/src/plugins/persistence/index.js index a0c910f8a73bca..49a49fc24302b2 100644 --- a/packages/data/src/plugins/persistence/index.js +++ b/packages/data/src/plugins/persistence/index.js @@ -9,6 +9,10 @@ import { merge, isPlainObject, get } from 'lodash'; import defaultStorage from './storage/default'; import { combineReducers } from '../../'; +/** @typedef {import('../../registry').WPDataRegistry} WPDataRegistry */ + +/** @typedef {import('../../registry').WPDataPlugin} WPDataPlugin */ + /** * @typedef {Object} WPDataPersistencePluginOptions Persistence plugin options. * @@ -138,7 +142,7 @@ const persistencePlugin = function( registry, pluginOptions ) { // to leverage its behavior of returning the same object when none // of the property values changes. This allows a strict reference // equality to bypass a persistence set on an unchanging state. - const reducers = keys.reduce( ( result, key ) => Object.assign( result, { + const reducers = keys.reduce( ( accumulator, key ) => Object.assign( accumulator, { [ key ]: ( state, action ) => action.nextState[ key ], } ), {} ); diff --git a/packages/data/src/registry.js b/packages/data/src/registry.js index ecaaa94c8e64d5..24950e68c4eae8 100644 --- a/packages/data/src/registry.js +++ b/packages/data/src/registry.js @@ -2,9 +2,11 @@ * External dependencies */ import { + omit, without, mapValues, } from 'lodash'; +import memize from 'memize'; /** * Internal dependencies @@ -88,6 +90,61 @@ export function createRegistry( storeConfigs = {}, parent = null ) { return parent && parent.select( reducerKey ); } + const getResolveSelectors = memize( + ( selectors ) => { + return mapValues( + omit( + selectors, + [ + 'getIsResolving', + 'hasStartedResolution', + 'hasFinishedResolution', + 'isResolving', + 'getCachedResolvers', + ] + ), + ( selector, selectorName ) => { + return ( ...args ) => { + return new Promise( ( resolve ) => { + const hasFinished = () => selectors + .hasFinishedResolution( selectorName, args ); + const getResult = () => selector.apply( null, args ); + + // trigger the selector (to trigger the resolver) + const result = getResult(); + if ( hasFinished() ) { + return resolve( result ); + } + + const unsubscribe = subscribe( () => { + if ( hasFinished() ) { + unsubscribe(); + resolve( getResult() ); + } + } ); + } ); + }; + } + ); + }, + { maxSize: 1 } + ); + + /** + * Given the name of a registered store, returns an object containing the store's + * selectors pre-bound to state so that you only need to supply additional arguments, + * and modified so that they return promises that resolve to their eventual values, + * after any resolvers have ran. + * + * @param {string} reducerKey Part of the state shape to register the + * selectors for. + * + * @return {Object} Each key of the object matches the name of a selector. + */ + function __experimentalResolveSelect( reducerKey ) { + return getResolveSelectors( select( reducerKey ) ); + } + /** * Returns the available actions for a part of the state. * @@ -146,6 +203,7 @@ export function createRegistry( storeConfigs = {}, parent = null ) { namespaces: stores, // TODO: Deprecate/remove this. subscribe, select, + __experimentalResolveSelect, dispatch, use, }; diff --git a/packages/data/src/resolvers-cache-middleware.js b/packages/data/src/resolvers-cache-middleware.js index e7e0a2ae7c6201..0a51fb02f2015b 100644 --- a/packages/data/src/resolvers-cache-middleware.js +++ b/packages/data/src/resolvers-cache-middleware.js @@ -3,6 +3,8 @@ */ import { get } from 'lodash'; +/** @typedef {import('./registry').WPDataRegistry} WPDataRegistry */ + /** * Creates a middleware handling resolvers cache invalidation. * diff --git a/packages/date/README.md b/packages/date/README.md index c4bad6ef1636e1..e45bb664c85519 100644 --- a/packages/date/README.md +++ b/packages/date/README.md @@ -23,7 +23,7 @@ Formats a date (like `date()` in PHP), in the site's timezone. _Parameters_ - _dateFormat_ `string`: PHP-style formatting string. See php.net/date. -- _dateValue_ `(Date|string|moment|null)`: Date object or string, parsable by moment.js. +- _dateValue_ `(Date|string|Moment|null)`: Date object or string, parsable by moment.js. _Returns_ @@ -36,7 +36,7 @@ Formats a date (like `date_i18n()` in PHP). _Parameters_ - _dateFormat_ `string`: PHP-style formatting string. See php.net/date. -- _dateValue_ `(Date|string|moment|null)`: Date object or string, parsable by moment.js. +- _dateValue_ `(Date|string|Moment|null)`: Date object or string, parsable by moment.js. - _gmt_ `boolean`: True for GMT/UTC, false for site's timezone. _Returns_ @@ -50,7 +50,7 @@ Formats a date. Does not alter the date's timezone. _Parameters_ - _dateFormat_ `string`: PHP-style formatting string. See php.net/date. -- _dateValue_ `(Date|string|moment|null)`: Date object or string, parsable by moment.js. +- _dateValue_ `(Date|string|Moment|null)`: Date object or string, parsable by moment.js. _Returns_ @@ -75,7 +75,7 @@ Formats a date (like `date()` in PHP), in the UTC timezone. _Parameters_ - _dateFormat_ `string`: PHP-style formatting string. See php.net/date. -- _dateValue_ `(Date|string|moment|null)`: Date object or string, parsable by moment.js. +- _dateValue_ `(Date|string|Moment|null)`: Date object or string, parsable by moment.js. _Returns_ diff --git a/packages/date/package.json b/packages/date/package.json index 978f7af656224b..3772c875e6f49f 100644 --- a/packages/date/package.json +++ b/packages/date/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/date", - "version": "3.5.0", + "version": "3.6.0", "description": "Date module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/date/src/index.js b/packages/date/src/index.js index 39b4425ea2da22..54fe5e633d8d29 100644 --- a/packages/date/src/index.js +++ b/packages/date/src/index.js @@ -5,6 +5,8 @@ import momentLib from 'moment'; import 'moment-timezone/moment-timezone'; import 'moment-timezone/moment-timezone-utils'; +/** @typedef {import('moment').Moment} Moment */ + const WP_ZONE = 'WP'; // Changes made here will likely need to be made in `lib/client-assets.php` as @@ -145,7 +147,7 @@ const formatMap = { /** * Gets the ordinal suffix. * - * @param {moment} momentDate Moment instance. + * @param {Moment} momentDate Moment instance. * * @return {string} Formatted date. */ @@ -160,7 +162,7 @@ const formatMap = { /** * Gets the day of the year (zero-indexed). * - * @param {moment} momentDate Moment instance. + * @param {Moment} momentDate Moment instance. * * @return {string} Formatted date. */ @@ -180,7 +182,7 @@ const formatMap = { /** * Gets the days in the month. * - * @param {moment} momentDate Moment instance. + * @param {Moment} momentDate Moment instance. * * @return {string} Formatted date. */ @@ -192,7 +194,7 @@ const formatMap = { /** * Gets whether the current year is a leap year. * - * @param {moment} momentDate Moment instance. + * @param {Moment} momentDate Moment instance. * * @return {string} Formatted date. */ @@ -209,7 +211,7 @@ const formatMap = { /** * Gets the current time in Swatch Internet Time (.beats). * - * @param {moment} momentDate Moment instance. + * @param {Moment} momentDate Moment instance. * * @return {string} Formatted date. */ @@ -240,7 +242,7 @@ const formatMap = { /** * Gets whether the timezone is in DST currently. * - * @param {moment} momentDate Moment instance. + * @param {Moment} momentDate Moment instance. * * @return {string} Formatted date. */ @@ -253,7 +255,7 @@ const formatMap = { /** * Gets the timezone offset in seconds. * - * @param {moment} momentDate Moment instance. + * @param {Moment} momentDate Moment instance. * * @return {string} Formatted date. */ @@ -273,10 +275,10 @@ const formatMap = { /** * Formats a date. Does not alter the date's timezone. * - * @param {string} dateFormat PHP-style formatting string. - * See php.net/date. - * @param {(Date|string|moment|null)} dateValue Date object or string, - * parsable by moment.js. + * @param {string} dateFormat PHP-style formatting string. + * See php.net/date. + * @param {(Date|string|Moment|null)} dateValue Date object or string, + * parsable by moment.js. * * @return {string} Formatted date. */ @@ -314,10 +316,10 @@ export function format( dateFormat, dateValue = new Date() ) { /** * Formats a date (like `date()` in PHP), in the site's timezone. * - * @param {string} dateFormat PHP-style formatting string. - * See php.net/date. - * @param {(Date|string|moment|null)} dateValue Date object or string, - * parsable by moment.js. + * @param {string} dateFormat PHP-style formatting string. + * See php.net/date. + * @param {(Date|string|Moment|null)} dateValue Date object or string, + * parsable by moment.js. * * @return {string} Formatted date. */ @@ -330,10 +332,10 @@ export function date( dateFormat, dateValue = new Date() ) { /** * Formats a date (like `date()` in PHP), in the UTC timezone. * - * @param {string} dateFormat PHP-style formatting string. - * See php.net/date. - * @param {(Date|string|moment|null)} dateValue Date object or string, - * parsable by moment.js. + * @param {string} dateFormat PHP-style formatting string. + * See php.net/date. + * @param {(Date|string|Moment|null)} dateValue Date object or string, + * parsable by moment.js. * * @return {string} Formatted date. */ @@ -345,12 +347,12 @@ export function gmdate( dateFormat, dateValue = new Date() ) { /** * Formats a date (like `date_i18n()` in PHP). * - * @param {string} dateFormat PHP-style formatting string. - * See php.net/date. - * @param {(Date|string|moment|null)} dateValue Date object or string, - * parsable by moment.js. - * @param {boolean} gmt True for GMT/UTC, false for - * site's timezone. + * @param {string} dateFormat PHP-style formatting string. + * See php.net/date. + * @param {(Date|string|Moment|null)} dateValue Date object or string, + * parsable by moment.js. + * @param {boolean} gmt True for GMT/UTC, false for + * site's timezone. * * @return {string} Formatted date. */ diff --git a/packages/dependency-extraction-webpack-plugin/package.json b/packages/dependency-extraction-webpack-plugin/package.json index 01ccab67c90f9a..1b2fc0100f848e 100644 --- a/packages/dependency-extraction-webpack-plugin/package.json +++ b/packages/dependency-extraction-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dependency-extraction-webpack-plugin", - "version": "2.0.0", + "version": "2.1.0", "description": "Extract WordPress script dependencies from webpack bundles.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/deprecated/package.json b/packages/deprecated/package.json index a225bd8fad4b14..7a0494fd4d54bb 100644 --- a/packages/deprecated/package.json +++ b/packages/deprecated/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/deprecated", - "version": "2.6.0", + "version": "2.6.1", "description": "Deprecation utility for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -20,6 +20,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "sideEffects": false, "dependencies": { "@babel/runtime": "^7.4.4", "@wordpress/hooks": "file:../hooks" diff --git a/packages/docgen/package.json b/packages/docgen/package.json index fd178278584abe..67126241f55751 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/docgen", - "version": "1.4.0", + "version": "1.5.0", "description": "Autogenerate public API documentation from exports and JSDoc comments.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/docgen/src/markdown/formatter.js b/packages/docgen/src/markdown/formatter.js index eaec51925f3062..2c99ad002ed9e1 100644 --- a/packages/docgen/src/markdown/formatter.js +++ b/packages/docgen/src/markdown/formatter.js @@ -109,6 +109,18 @@ module.exports = function( rootDir, docPath, symbols, headingTitle, headingStart ( tag ) => `\n- \`${ tag.type }\`: ${ cleanSpaces( tag.description ) }`, docs ); + formatTag( + 'Type Definition', + getSymbolTagsByName( symbol, 'typedef' ), + ( tag ) => `\n- *${ tag.name }* \`${ tag.type }\``, + docs + ); + formatTag( + 'Properties', + getSymbolTagsByName( symbol, 'property' ), + ( tag ) => `\n- *${ tag.name }* \`${ tag.type }\`: ${ cleanSpaces( tag.description ) }`, + docs + ); docs.push( '\n' ); docs.push( '\n' ); } ); diff --git a/packages/dom-ready/package.json b/packages/dom-ready/package.json index 19efb56d67eedd..ff4900eca80a02 100644 --- a/packages/dom-ready/package.json +++ b/packages/dom-ready/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom-ready", - "version": "2.5.0", + "version": "2.5.1", "description": "Execute callback after the DOM is loaded.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -20,6 +20,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "sideEffects": false, "dependencies": { "@babel/runtime": "^7.4.4" }, diff --git a/packages/dom/package.json b/packages/dom/package.json index 8d88d9607cd263..bf956f202c9ad4 100644 --- a/packages/dom/package.json +++ b/packages/dom/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/dom", - "version": "2.5.0", + "version": "2.6.0", "description": "DOM utilities module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -21,6 +21,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "sideEffects": false, "dependencies": { "@babel/runtime": "^7.4.4", "lodash": "^4.17.15" diff --git a/packages/e2e-test-utils/CHANGELOG.md b/packages/e2e-test-utils/CHANGELOG.md index df00ecbe7c0ce2..61335676dcfe0e 100644 --- a/packages/e2e-test-utils/CHANGELOG.md +++ b/packages/e2e-test-utils/CHANGELOG.md @@ -1,3 +1,19 @@ +## Master + +### Breaking Changes + +- The disableNavigationMode utility was removed. By default, the editor is in edit mode now. + +### Improvements + +- `setBrowserViewport` accepts an object of `width`, `height` values, to assign a viewport of arbitrary size. + +## 3.0.0 (2019-11-14) + +### Breaking Changes + +- The util function `enableExperimentalFeatures` was removed. It is now available for internal usage in the `e2e-tests` package. + ## 2.0.0 (2019-05-21) ### Requirements diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md index d8b3263c2e6e99..d397bce0bf6470 100644 --- a/packages/e2e-test-utils/README.md +++ b/packages/e2e-test-utils/README.md @@ -137,13 +137,9 @@ _Parameters_ - _slug_ `string`: Plugin slug. -<a name="disableNavigationMode" href="#disableNavigationMode">#</a> **disableNavigationMode** +<a name="disableFocusLossObservation" href="#disableFocusLossObservation">#</a> **disableFocusLossObservation** -Triggers edit mode if not already active. - -_Returns_ - -- `Promise`: Promise resolving after enabling the keyboard edit mode. +Removes the focus loss listener that `enableFocusLossObservation()` adds. <a name="disablePrePublishChecks" href="#disablePrePublishChecks">#</a> **disablePrePublishChecks** @@ -164,6 +160,11 @@ _Returns_ - `Promise`: Promise resolving when drag completes. +<a name="enableFocusLossObservation" href="#enableFocusLossObservation">#</a> **enableFocusLossObservation** + +Adds an event listener to the document which throws an error if there is a +loss of focus. + <a name="enablePageDialogAccept" href="#enablePageDialogAccept">#</a> **enablePageDialogAccept** Enables even listener which accepts a page dialog which @@ -203,7 +204,7 @@ _Parameters_ _Returns_ -- `?ElementHandle`: Object that represents an in-page DOM element. +- `Promise<(ElementHandle|undefined)>`: Object that represents an in-page DOM element. <a name="getAllBlockInserterItemTitles" href="#getAllBlockInserterItemTitles">#</a> **getAllBlockInserterItemTitles** @@ -327,11 +328,6 @@ _Returns_ - `Promise`: Promise that uses `mockCheck` to see if a request should be mocked with `mock`, and optionally transforms the response with `responseObjectTransform`. -<a name="observeFocusLoss" href="#observeFocusLoss">#</a> **observeFocusLoss** - -Binds to the document on page load which throws an error if a `focusout` -event occurs without a related target (i.e. focus loss). - <a name="openAllBlockInserterCategories" href="#openAllBlockInserterCategories">#</a> **openAllBlockInserterCategories** Opens all block inserter categories. @@ -416,7 +412,7 @@ Sets browser viewport to specified type. _Parameters_ -- _type_ `string`: String to represent dimensions type; can be either small or large. +- _viewport_ `WPViewport`: Viewport name or dimensions object to assign. <a name="setPostContent" href="#setPostContent">#</a> **setPostContent** @@ -529,7 +525,7 @@ without the new dimensions being applied. _Parameters_ - _width_ `number`: Width of the window. -- _height_ `height`: Height of the window. +- _height_ `number`: Height of the window. <!-- END TOKEN(Autogenerated API docs) --> diff --git a/packages/e2e-test-utils/package.json b/packages/e2e-test-utils/package.json index 7ddd91f1b73974..3584a56855a49a 100644 --- a/packages/e2e-test-utils/package.json +++ b/packages/e2e-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-test-utils", - "version": "2.4.0", + "version": "3.0.0", "description": "End-To-End (E2E) test utils for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/e2e-test-utils/src/create-new-post.js b/packages/e2e-test-utils/src/create-new-post.js index ddc2ad64ea6c1b..3abf89d0e8e63b 100644 --- a/packages/e2e-test-utils/src/create-new-post.js +++ b/packages/e2e-test-utils/src/create-new-post.js @@ -7,7 +7,6 @@ import { addQueryArgs } from '@wordpress/url'; * Internal dependencies */ import { visitAdminPage } from './visit-admin-page'; -import { disableNavigationMode } from './keyboard-mode'; /** * Creates new post. @@ -27,16 +26,17 @@ export async function createNewPost( { content, excerpt, } ).slice( 1 ); + await visitAdminPage( 'post-new.php', query ); - await page.evaluate( ( _enableTips ) => { - const action = _enableTips ? 'enableTips' : 'disableTips'; - wp.data.dispatch( 'core/nux' )[ action ](); - }, enableTips ); + const areTipsEnabled = await page.evaluate( () => wp.data.select( 'core/nux' ).areTipsEnabled() ); + + if ( enableTips !== areTipsEnabled ) { + await page.evaluate( ( _enableTips ) => { + const action = _enableTips ? 'enableTips' : 'disableTips'; + wp.data.dispatch( 'core/nux' )[ action ](); + }, enableTips ); - if ( enableTips ) { await page.reload(); } - - await disableNavigationMode(); } diff --git a/packages/e2e-test-utils/src/enable-page-dialog-accept.js b/packages/e2e-test-utils/src/enable-page-dialog-accept.js index f8f2976de67a76..d09fc7f5e046d0 100644 --- a/packages/e2e-test-utils/src/enable-page-dialog-accept.js +++ b/packages/e2e-test-utils/src/enable-page-dialog-accept.js @@ -1,7 +1,9 @@ +/** @typedef {import('puppeteer').Dialog} Dialog */ + /** * Callback which automatically accepts dialog. * - * @param {puppeteer.Dialog} dialog Dialog object dispatched by page via the 'dialog' event. + * @param {Dialog} dialog Dialog object dispatched by page via the 'dialog' event. */ async function acceptPageDialog( dialog ) { await dialog.accept(); diff --git a/packages/e2e-test-utils/src/find-sidebar-panel-toggle-button-with-title.js b/packages/e2e-test-utils/src/find-sidebar-panel-toggle-button-with-title.js index a4167fe6e2e6cd..916ddc53f4b6b4 100644 --- a/packages/e2e-test-utils/src/find-sidebar-panel-toggle-button-with-title.js +++ b/packages/e2e-test-utils/src/find-sidebar-panel-toggle-button-with-title.js @@ -3,6 +3,8 @@ */ import { first } from 'lodash'; +/** @typedef {import('puppeteer').ElementHandle} ElementHandle */ + /** * Finds a sidebar panel with the provided title. * diff --git a/packages/e2e-test-utils/src/find-sidebar-panel-with-title.js b/packages/e2e-test-utils/src/find-sidebar-panel-with-title.js index ff60dca5f00782..fea4bf968f0a46 100644 --- a/packages/e2e-test-utils/src/find-sidebar-panel-with-title.js +++ b/packages/e2e-test-utils/src/find-sidebar-panel-with-title.js @@ -3,16 +3,18 @@ */ import { first } from 'lodash'; +/** @typedef {import('puppeteer').ElementHandle} ElementHandle */ + /** * Finds the button responsible for toggling the sidebar panel with the provided title. * * @param {string} panelTitle The name of sidebar panel. * - * @return {?ElementHandle} Object that represents an in-page DOM element. + * @return {Promise<ElementHandle|undefined>} Object that represents an in-page DOM element. */ export async function findSidebarPanelWithTitle( panelTitle ) { const classSelect = ( className ) => `[contains(concat(" ", @class, " "), " ${ className } ")]`; const buttonSelector = `//div${ classSelect( 'edit-post-sidebar' ) }//button${ classSelect( 'components-button' ) }${ classSelect( 'components-panel__body-toggle' ) }[contains(text(),"${ panelTitle }")]`; const panelSelector = `${ buttonSelector }/ancestor::*[contains(concat(" ", @class, " "), " components-panel__body ")]`; - return first( await await page.$x( panelSelector ) ); + return first( await page.$x( panelSelector ) ); } diff --git a/packages/e2e-test-utils/src/index.js b/packages/e2e-test-utils/src/index.js index 8fcf2b400c5853..adb2b2867f737d 100644 --- a/packages/e2e-test-utils/src/index.js +++ b/packages/e2e-test-utils/src/index.js @@ -11,7 +11,6 @@ export { createURL } from './create-url'; export { deactivatePlugin } from './deactivate-plugin'; export { disablePrePublishChecks } from './disable-pre-publish-checks'; export { dragAndResize } from './drag-and-resize'; -export { enableExperimentalFeatures } from './enable-experimental-features'; export { enablePageDialogAccept } from './enable-page-dialog-accept'; export { enablePrePublishChecks } from './enable-pre-publish-checks'; export { ensureSidebarOpened } from './ensure-sidebar-opened'; @@ -28,7 +27,7 @@ export { installPlugin } from './install-plugin'; export { isCurrentURL } from './is-current-url'; export { isInDefaultBlock } from './is-in-default-block'; export { loginUser } from './login-user'; -export { observeFocusLoss } from './observe-focus-loss'; +export { enableFocusLossObservation, disableFocusLossObservation } from './observe-focus-loss'; export { openAllBlockInserterCategories } from './open-all-block-inserter-categories'; export { openDocumentSettingsSidebar } from './open-document-settings-sidebar'; export { openGlobalBlockInserter } from './open-global-block-inserter'; @@ -43,7 +42,6 @@ export { selectBlockByClientId } from './select-block-by-client-id'; export { setBrowserViewport } from './set-browser-viewport'; export { setPostContent } from './set-post-content'; export { switchEditorModeTo } from './switch-editor-mode-to'; -export { disableNavigationMode } from './keyboard-mode'; export { switchUserToAdmin } from './switch-user-to-admin'; export { switchUserToTest } from './switch-user-to-test'; export { toggleMoreMenu } from './toggle-more-menu'; diff --git a/packages/e2e-test-utils/src/is-current-url.js b/packages/e2e-test-utils/src/is-current-url.js index 6062f241d48548..80058d1225281f 100644 --- a/packages/e2e-test-utils/src/is-current-url.js +++ b/packages/e2e-test-utils/src/is-current-url.js @@ -20,5 +20,5 @@ export function isCurrentURL( WPPath, query = '' ) { currentURL.search = query; - return createURL( WPPath ) === currentURL.href; + return createURL( WPPath, query ) === currentURL.href; } diff --git a/packages/e2e-test-utils/src/keyboard-mode.js b/packages/e2e-test-utils/src/keyboard-mode.js index 8928c220b28f63..8b137891791fe9 100644 --- a/packages/e2e-test-utils/src/keyboard-mode.js +++ b/packages/e2e-test-utils/src/keyboard-mode.js @@ -1,12 +1 @@ -/** - * Triggers edit mode if not already active. - * - * @return {Promise} Promise resolving after enabling the keyboard edit mode. - */ -export async function disableNavigationMode() { - const focusedElement = await page.$( ':focus' ); - await page.click( '.editor-post-title' ); - if ( focusedElement ) { - await focusedElement.focus(); - } -} + diff --git a/packages/e2e-test-utils/src/observe-focus-loss.js b/packages/e2e-test-utils/src/observe-focus-loss.js index 13e541de27a8fe..1266d0997c1f29 100644 --- a/packages/e2e-test-utils/src/observe-focus-loss.js +++ b/packages/e2e-test-utils/src/observe-focus-loss.js @@ -1,15 +1,30 @@ /** - * Binds to the document on page load which throws an error if a `focusout` - * event occurs without a related target (i.e. focus loss). + * Adds an event listener to the document which throws an error if there is a + * loss of focus. */ -export function observeFocusLoss() { - page.on( 'load', () => { - page.evaluate( () => { - document.body.addEventListener( 'focusout', ( event ) => { - if ( ! event.relatedTarget ) { - throw new Error( 'Unexpected focus loss' ); - } - } ); - } ); +export async function enableFocusLossObservation() { + await page.evaluate( () => { + if ( window._detectFocusLoss ) { + document.body.removeEventListener( 'focusout', window._detectFocusLoss ); + } + + window._detectFocusLoss = ( event ) => { + if ( ! event.relatedTarget ) { + throw new Error( 'Unexpected focus loss' ); + } + }; + + document.body.addEventListener( 'focusout', window._detectFocusLoss ); + } ); +} + +/** + * Removes the focus loss listener that `enableFocusLossObservation()` adds. + */ +export async function disableFocusLossObservation() { + await page.evaluate( () => { + if ( window._detectFocusLoss ) { + document.body.removeEventListener( 'focusout', window._detectFocusLoss ); + } } ); } diff --git a/packages/e2e-test-utils/src/press-key-with-modifier.js b/packages/e2e-test-utils/src/press-key-with-modifier.js index 1989f0d7236ffd..b95123d4872201 100644 --- a/packages/e2e-test-utils/src/press-key-with-modifier.js +++ b/packages/e2e-test-utils/src/press-key-with-modifier.js @@ -80,6 +80,35 @@ async function emulateSelectAll() { } ); } +async function emulateClipboard( type ) { + await page.evaluate( ( _type ) => { + if ( _type !== 'paste' ) { + window._clipboardData = new DataTransfer(); + + const selection = window.getSelection(); + const plainText = selection.toString(); + let html = plainText; + + if ( selection.rangeCount ) { + const range = selection.getRangeAt( 0 ); + const fragment = range.cloneContents(); + + html = Array.from( fragment.childNodes ) + .map( ( node ) => node.outerHTML || node.nodeValue ) + .join( '' ); + } + + window._clipboardData.setData( 'text/plain', plainText ); + window._clipboardData.setData( 'text/html', html ); + } + + document.activeElement.dispatchEvent( new ClipboardEvent( _type, { + bubbles: true, + clipboardData: window._clipboardData, + } ) ); + }, type ); +} + /** * Performs a key press with modifier (Shift, Control, Meta, Alt), where each modifier * is normalized to platform-specific modifier. @@ -92,6 +121,18 @@ export async function pressKeyWithModifier( modifier, key ) { return await emulateSelectAll(); } + if ( modifier.toLowerCase() === 'primary' && key.toLowerCase() === 'c' ) { + return await emulateClipboard( 'copy' ); + } + + if ( modifier.toLowerCase() === 'primary' && key.toLowerCase() === 'x' ) { + return await emulateClipboard( 'cut' ); + } + + if ( modifier.toLowerCase() === 'primary' && key.toLowerCase() === 'v' ) { + return await emulateClipboard( 'paste' ); + } + const isAppleOS = () => process.platform === 'darwin'; const overWrittenModifiers = { ...modifiers, diff --git a/packages/e2e-test-utils/src/set-browser-viewport.js b/packages/e2e-test-utils/src/set-browser-viewport.js index b35eaa58910247..ddf65df5a83180 100644 --- a/packages/e2e-test-utils/src/set-browser-viewport.js +++ b/packages/e2e-test-utils/src/set-browser-viewport.js @@ -3,18 +3,50 @@ */ import { waitForWindowDimensions } from './wait-for-window-dimensions'; +/** + * Named viewport options. + * + * @typedef {"large"|"medium"|"small"} WPDimensionsName + */ + +/** + * Viewport dimensions object. + * + * @typedef {Object} WPViewportDimensions + * + * @property {number} width Width, in pixels. + * @property {number} height Height, in pixels. + */ + +/** + * Predefined viewport dimensions to reference by name. + * + * @enum {WPViewportDimensions} + * + * @type {Object<WPDimensionsName,WPViewportDimensions>} + */ +const PREDEFINED_DIMENSIONS = { + large: { width: 960, height: 700 }, + medium: { width: 768, height: 700 }, + small: { width: 600, height: 700 }, +}; + +/** + * Valid argument argument type from which to derive viewport dimensions. + * + * @typedef {WPDimensionsName|WPViewportDimensions} WPViewport + */ + /** * Sets browser viewport to specified type. * - * @param {string} type String to represent dimensions type; can be either small or large. + * @param {WPViewport} viewport Viewport name or dimensions object to assign. */ -export async function setBrowserViewport( type ) { - const allowedDimensions = { - large: { width: 960, height: 700 }, - medium: { width: 768, height: 700 }, - small: { width: 600, height: 700 }, - }; - const currentDimension = allowedDimensions[ type ]; - await page.setViewport( currentDimension ); - await waitForWindowDimensions( currentDimension.width, currentDimension.height ); +export async function setBrowserViewport( viewport ) { + const dimensions = typeof viewport === 'string' ? + PREDEFINED_DIMENSIONS[ viewport ] : + viewport; + + await page.setViewport( dimensions ); + await waitForWindowDimensions( dimensions.width, dimensions.height ); } diff --git a/packages/e2e-test-utils/src/transform-block-to.js b/packages/e2e-test-utils/src/transform-block-to.js index 9bb11b7bafc81a..5c105363ca74b5 100644 --- a/packages/e2e-test-utils/src/transform-block-to.js +++ b/packages/e2e-test-utils/src/transform-block-to.js @@ -1,28 +1,31 @@ +/** + * Internal dependencies + */ +import { pressKeyWithModifier } from './press-key-with-modifier'; + /** * Converts editor's block type. * * @param {string} name Block name. */ export async function transformBlockTo( name ) { - await page.mouse.move( 200, 300, { steps: 10 } ); - await page.mouse.move( 250, 350, { steps: 10 } ); - await page.click( '.block-editor-block-switcher__toggle' ); - // Close the "Block Styles" section if it is open. - // Having the section open may make the transform buttons hidden on the testing resolution. - const closeBlockStylesButton = await page.$x( - '//div[contains(@class,"block-editor-block-switcher__popover")]//button[contains(text(),"Block Styles")][@aria-expanded="true"]' - ); - if ( closeBlockStylesButton.length > 0 ) { - await closeBlockStylesButton[ 0 ].click(); - } - const insertButton = ( await page.$x( - `//button//span[contains(text(), '${ name }')]` - ) )[ 0 ]; + // Transition to block toolbar by key combination. + await pressKeyWithModifier( 'alt', 'F10' ); + + // Press Enter in the focused toggle button. + const switcherToggle = await page.waitForSelector( '.block-editor-block-switcher__toggle:focus' ); + await switcherToggle.press( 'Enter' ); + + // Find the block button option within the switcher popover. + const switcher = await page.$( '.block-editor-block-switcher__container' ); + const insertButton = ( await switcher.$x( `//button[.='${ name }']` ) )[ 0 ]; + + // Clicks may fail if the button is out of view. Assure it is before click. + await insertButton.evaluate( ( element ) => element.scrollIntoView() ); await insertButton.click(); + + // Wait for the transformed block to appear. const BLOCK_SELECTOR = '.block-editor-block-list__block'; const BLOCK_NAME_SELECTOR = `[aria-label="Block: ${ name }"]`; - // Wait for the transformed block to appear. - await page.waitForSelector( - `${ BLOCK_SELECTOR }${ BLOCK_NAME_SELECTOR }` - ); + await page.waitForSelector( `${ BLOCK_SELECTOR }${ BLOCK_NAME_SELECTOR }` ); } diff --git a/packages/e2e-test-utils/src/wait-for-window-dimensions.js b/packages/e2e-test-utils/src/wait-for-window-dimensions.js index 9b505cf3b12807..924a5c939b3da0 100644 --- a/packages/e2e-test-utils/src/wait-for-window-dimensions.js +++ b/packages/e2e-test-utils/src/wait-for-window-dimensions.js @@ -5,7 +5,7 @@ * https://github.com/GoogleChrome/puppeteer/issues/1751 * * @param {number} width Width of the window. - * @param {height} height Height of the window. + * @param {number} height Height of the window. */ export async function waitForWindowDimensions( width, height ) { await page diff --git a/packages/e2e-tests/CHANGELOG.md b/packages/e2e-tests/CHANGELOG.md index c9dcd799eb2a24..a8e8362a18d70e 100644 --- a/packages/e2e-tests/CHANGELOG.md +++ b/packages/e2e-tests/CHANGELOG.md @@ -1,3 +1,9 @@ +## Master + +### New Features + +- Added `THROTTLE_CPU` and `DOWNLOAD_THROUGHPUT` environment variable configuration options. + ## 1.7.0 (2019-09-16) ## 1.6.0 (2019-09-03) diff --git a/packages/e2e-tests/config/performance-reporter.js b/packages/e2e-tests/config/performance-reporter.js index 681bb60f41618e..f7bb05666054d5 100644 --- a/packages/e2e-tests/config/performance-reporter.js +++ b/packages/e2e-tests/config/performance-reporter.js @@ -6,7 +6,7 @@ function average( array ) { class PerformanceReporter { onRunComplete() { - const path = __dirname + '/../specs/results.json'; + const path = __dirname + '/../specs/performance/results.json'; if ( ! existsSync( path ) ) { return; diff --git a/packages/e2e-tests/config/setup-test-framework.js b/packages/e2e-tests/config/setup-test-framework.js index 74d8b108d81c76..dd9d390d98e2ca 100644 --- a/packages/e2e-tests/config/setup-test-framework.js +++ b/packages/e2e-tests/config/setup-test-framework.js @@ -18,9 +18,25 @@ import { } from '@wordpress/e2e-test-utils'; /** - * Environment variables + * Timeout, in seconds, that the test should be allowed to run. + * + * @type {string|undefined} + */ +const PUPPETEER_TIMEOUT = process.env.PUPPETEER_TIMEOUT; + +/** + * CPU slowdown factor, as a numeric multiplier. + * + * @type {string|undefined} + */ +const THROTTLE_CPU = process.env.THROTTLE_CPU; + +/** + * Network download speed, in bytes per second. + * + * @type {string|undefined} */ -const { PUPPETEER_TIMEOUT } = process.env; +const DOWNLOAD_THROUGHPUT = process.env.DOWNLOAD_THROUGHPUT; /** * Set of console logging types observed to protect against unexpected yet @@ -117,6 +133,16 @@ function observeConsoleLogging() { return; } + // A chrome advisory warning about SameSite cookies is informational + // about future changes, tracked separately for improvement in core. + // + // See: https://core.trac.wordpress.org/ticket/37000 + // See: https://www.chromestatus.com/feature/5088147346030592 + // See: https://www.chromestatus.com/feature/5633521622188032 + if ( text.includes( 'A cookie associated with a cross-site resource' ) ) { + return; + } + // Viewing posts on the front end can result in this error, which // has nothing to do with Gutenberg. if ( text.includes( 'net::ERR_UNKNOWN_URL_SCHEME' ) ) { @@ -129,19 +155,6 @@ function observeConsoleLogging() { return; } - // A bug present in WordPress 5.2 will produce console warnings when - // loading the Dashicons font. These can be safely ignored, as they do - // not otherwise regress on application behavior. This logic should be - // removed once the associated ticket has been closed. - // - // See: https://core.trac.wordpress.org/ticket/47183 - if ( - text.startsWith( 'Failed to decode downloaded font:' ) || - text.startsWith( 'OTS parsing error:' ) - ) { - return; - } - const logFunction = OBSERVED_CONSOLE_MESSAGE_TYPES[ type ]; // As of Puppeteer 1.6.1, `message.text()` wrongly returns an object of @@ -182,6 +195,8 @@ async function runAxeTestsForBlockEditor() { // See: https://github.com/WordPress/gutenberg/pull/15018. disabledRules: [ 'aria-allowed-role', + 'aria-hidden-focus', + 'aria-input-field-name', 'aria-valid-attr-value', 'button-name', 'color-contrast', @@ -201,6 +216,32 @@ async function runAxeTestsForBlockEditor() { } ); } +/** + * Simulate slow network or throttled CPU if provided via environment variables. + */ +async function simulateAdverseConditions() { + if ( ! DOWNLOAD_THROUGHPUT && ! THROTTLE_CPU ) { + return; + } + + const client = await page.target().createCDPSession(); + + if ( DOWNLOAD_THROUGHPUT ) { + // See: https://chromedevtools.github.io/devtools-protocol/tot/Network#method-emulateNetworkConditions + await client.send( 'Network.emulateNetworkConditions', { + // Simulated download speed (bytes/s) + downloadThroughput: Number( DOWNLOAD_THROUGHPUT ), + } ); + } + + if ( THROTTLE_CPU ) { + // See: https://chromedevtools.github.io/devtools-protocol/tot/Emulation#method-setCPUThrottlingRate + await client.send( 'Emulation.setCPUThrottlingRate', { + rate: Number( THROTTLE_CPU ), + } ); + } +} + // Before every test suite run, delete all content created by the test. This ensures // other posts/comments/etc. aren't dirtying tests and tests don't depend on // each other's side-effects. @@ -208,6 +249,7 @@ beforeAll( async () => { capturePageEventsForTearDown(); enablePageDialogAccept(); observeConsoleLogging(); + await simulateAdverseConditions(); await trashExistingPosts(); await setupBrowser(); diff --git a/packages/e2e-test-utils/src/enable-experimental-features.js b/packages/e2e-tests/experimental-features.js similarity index 60% rename from packages/e2e-test-utils/src/enable-experimental-features.js rename to packages/e2e-tests/experimental-features.js index be74f20f82fd92..4b06a761c865a0 100644 --- a/packages/e2e-test-utils/src/enable-experimental-features.js +++ b/packages/e2e-tests/experimental-features.js @@ -2,18 +2,9 @@ * WordPress dependencies */ import { addQueryArgs } from '@wordpress/url'; +import { visitAdminPage } from '@wordpress/e2e-test-utils'; -/** - * Internal dependencies - */ -import { visitAdminPage } from './visit-admin-page'; - -/** - * Enables experimental features from the plugin settings section. - * - * @param {Array} features Array of {string} selectors of settings to enable. Assumes they can be enabled with one click. - */ -export async function enableExperimentalFeatures( features ) { +async function setExperimentalFeaturesState( features, enable ) { const query = addQueryArgs( '', { page: 'gutenberg-experiments', } ); @@ -23,7 +14,7 @@ export async function enableExperimentalFeatures( features ) { await page.waitForSelector( feature ); const checkedSelector = `${ feature }[checked=checked]`; const isChecked = !! ( await page.$( checkedSelector ) ); - if ( ! isChecked ) { + if ( ( ! isChecked && enable ) || ( isChecked && ! enable ) ) { await page.click( feature ); } } ) ); @@ -32,3 +23,21 @@ export async function enableExperimentalFeatures( features ) { page.click( '#submit' ), ] ); } + +/** + * Enables experimental features from the plugin settings section. + * + * @param {Array} features Array of {string} selectors of settings to enable. Assumes they can be enabled with one click. + */ +export async function enableExperimentalFeatures( features ) { + await setExperimentalFeaturesState( features, true ); +} + +/** + * Disables experimental features from the plugin settings section. + * + * @param {Array} features Array of {string} selectors of settings to disable. Assumes they can be disabled with one click. + */ +export async function disableExperimentalFeatures( features ) { + await setExperimentalFeaturesState( features, false ); +} diff --git a/packages/e2e-tests/fixtures/block-transforms.js b/packages/e2e-tests/fixtures/block-transforms.js index ff297c84a5d3da..380d8f86cdf4cd 100644 --- a/packages/e2e-tests/fixtures/block-transforms.js +++ b/packages/e2e-tests/fixtures/block-transforms.js @@ -118,6 +118,28 @@ export const EXPECTED_TRANSFORMS = { ], originalBlock: 'Cover', }, + core__cover__gradient: { + availableTransforms: [ + 'Group', + 'Image', + 'Video', + ], + originalBlock: 'Cover', + }, + 'core__cover__gradient-image': { + availableTransforms: [ + 'Group', + 'Image', + ], + originalBlock: 'Cover', + }, + 'core__cover__gradient-video': { + availableTransforms: [ + 'Group', + 'Video', + ], + originalBlock: 'Cover', + }, core__cover__video: { availableTransforms: [ 'Group', @@ -325,14 +347,14 @@ export const EXPECTED_TRANSFORMS = { 'Group', ], }, - 'core__navigation-menu': { - originalBlock: 'Navigation Menu (Experimental)', + core__navigation: { + originalBlock: 'Navigation', availableTransforms: [ 'Group', ], }, - 'core__navigation-menu-item': { - originalBlock: 'Menu Item (Experimental)', + 'core__navigation-link': { + originalBlock: 'Navigation Link', availableTransforms: [ 'Group', ], @@ -354,6 +376,18 @@ export const EXPECTED_TRANSFORMS = { 'Verse', ], }, + 'core__post-content': { + availableTransforms: [ + 'Group', + ], + originalBlock: 'Post Content', + }, + 'core__post-title': { + availableTransforms: [ + 'Group', + ], + originalBlock: 'Post Title', + }, core__preformatted: { originalBlock: 'Preformatted', availableTransforms: [ @@ -368,6 +402,13 @@ export const EXPECTED_TRANSFORMS = { 'Group', ], }, + 'core__pullquote__main-color': { + originalBlock: 'Pullquote', + availableTransforms: [ + 'Quote', + 'Group', + ], + }, 'core__pullquote__multi-paragraph': { originalBlock: 'Pullquote', availableTransforms: [ @@ -425,6 +466,12 @@ export const EXPECTED_TRANSFORMS = { 'Group', ], }, + 'core__site-title': { + availableTransforms: [ + 'Group', + ], + originalBlock: 'Site Title', + }, 'core__social-link-amazon': { availableTransforms: [ 'Group', @@ -513,7 +560,7 @@ export const EXPECTED_TRANSFORMS = { availableTransforms: [ 'Group', ], - originalBlock: 'Github', + originalBlock: 'GitHub', }, 'core__social-link-goodreads': { availableTransforms: [ @@ -543,7 +590,7 @@ export const EXPECTED_TRANSFORMS = { availableTransforms: [ 'Group', ], - originalBlock: 'Linkedin', + originalBlock: 'LinkedIn', }, 'core__social-link-mail': { availableTransforms: [ @@ -677,6 +724,12 @@ export const EXPECTED_TRANSFORMS = { 'Group', ], }, + core__table__caption: { + originalBlock: 'Table', + availableTransforms: [ + 'Group', + ], + }, 'core__table__scope-attribute': { originalBlock: 'Table', availableTransforms: [ diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__gradient-image.html b/packages/e2e-tests/fixtures/blocks/core__cover__gradient-image.html new file mode 100644 index 00000000000000..6353eac851e882 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__gradient-image.html @@ -0,0 +1,10 @@ +<!-- wp:cover {"url":"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=","dimRatio":30,"customGradient":"linear-gradient(135deg,rgba(0,208,132,1) 0%,rgba(6,147,227,1) 100%)"} --> +<div class="wp-block-cover has-background-dim-30 has-background-dim has-background-gradient" style="background-image:url(data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=)"> + <span aria-hidden="true" class="wp-block-cover__gradient-background" style="background:linear-gradient(135deg,rgba(0,208,132,1) 0%,rgba(6,147,227,1) 100%)"></span> + <div class="wp-block-cover__inner-container"> + <!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> + <p class="has-text-align-center has-large-font-size"> Cover! </p> + <!-- /wp:paragraph --> + </div> +</div> +<!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__gradient-image.json b/packages/e2e-tests/fixtures/blocks/core__cover__gradient-image.json new file mode 100644 index 00000000000000..7752fa1da96f25 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__gradient-image.json @@ -0,0 +1,31 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/cover", + "isValid": true, + "attributes": { + "url": "data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=", + "hasParallax": false, + "dimRatio": 30, + "backgroundType": "image", + "customGradient": "linear-gradient(135deg,rgba(0,208,132,1) 0%,rgba(6,147,227,1) 100%)" + }, + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/paragraph", + "isValid": true, + "attributes": { + "align": "center", + "content": " Cover! ", + "dropCap": false, + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [], + "originalContent": "<p class=\"has-text-align-center has-large-font-size\"> Cover! </p>" + } + ], + "originalContent": "<div class=\"wp-block-cover has-background-dim-30 has-background-dim has-background-gradient\" style=\"background-image:url(data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=)\">\n\t<span aria-hidden=\"true\" class=\"wp-block-cover__gradient-background\" style=\"background:linear-gradient(135deg,rgba(0,208,132,1) 0%,rgba(6,147,227,1) 100%)\"></span>\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t\n\t</div>\n</div>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__gradient-image.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover__gradient-image.parsed.json new file mode 100644 index 00000000000000..07ab0aa6299bad --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__gradient-image.parsed.json @@ -0,0 +1,40 @@ +[ + { + "blockName": "core/cover", + "attrs": { + "url": "data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=", + "dimRatio": 30, + "customGradient": "linear-gradient(135deg,rgba(0,208,132,1) 0%,rgba(6,147,227,1) 100%)" + }, + "innerBlocks": [ + { + "blockName": "core/paragraph", + "attrs": { + "align": "center", + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [], + "innerHTML": "\n\t\t<p class=\"has-text-align-center has-large-font-size\"> Cover! </p>\n\t\t", + "innerContent": [ + "\n\t\t<p class=\"has-text-align-center has-large-font-size\"> Cover! </p>\n\t\t" + ] + } + ], + "innerHTML": "\n<div class=\"wp-block-cover has-background-dim-30 has-background-dim has-background-gradient\" style=\"background-image:url(data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=)\">\n\t<span aria-hidden=\"true\" class=\"wp-block-cover__gradient-background\" style=\"background:linear-gradient(135deg,rgba(0,208,132,1) 0%,rgba(6,147,227,1) 100%)\"></span>\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t\n\t</div>\n</div>\n", + "innerContent": [ + "\n<div class=\"wp-block-cover has-background-dim-30 has-background-dim has-background-gradient\" style=\"background-image:url(data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=)\">\n\t<span aria-hidden=\"true\" class=\"wp-block-cover__gradient-background\" style=\"background:linear-gradient(135deg,rgba(0,208,132,1) 0%,rgba(6,147,227,1) 100%)\"></span>\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t", + null, + "\n\t</div>\n</div>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__gradient-image.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__gradient-image.serialized.html new file mode 100644 index 00000000000000..0fda894680f92b --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__gradient-image.serialized.html @@ -0,0 +1,5 @@ +<!-- wp:cover {"url":"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=","dimRatio":30,"customGradient":"linear-gradient(135deg,rgba(0,208,132,1) 0%,rgba(6,147,227,1) 100%)"} --> +<div class="wp-block-cover has-background-dim-30 has-background-dim has-background-gradient" style="background-image:url(data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=)"><span aria-hidden="true" class="wp-block-cover__gradient-background" style="background:linear-gradient(135deg,rgba(0,208,132,1) 0%,rgba(6,147,227,1) 100%)"></span><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> +<p class="has-text-align-center has-large-font-size"> Cover! </p> +<!-- /wp:paragraph --></div></div> +<!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__gradient-video.html b/packages/e2e-tests/fixtures/blocks/core__cover__gradient-video.html new file mode 100644 index 00000000000000..8da6141aa041ab --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__gradient-video.html @@ -0,0 +1,11 @@ +<!-- wp:cover {"url":"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=","dimRatio":70,"backgroundType":"video","customGradient":"linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)"} --> +<div class="wp-block-cover has-background-dim-70 has-background-dim has-background-gradient"> + <span aria-hidden="true" class="wp-block-cover__gradient-background" style="background:linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)"></span> + <video class="wp-block-cover__video-background" autoplay muted loop src="data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE="></video> + <div class="wp-block-cover__inner-container"> + <!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> + <p class="has-text-align-center has-large-font-size">Cover!</p> + <!-- /wp:paragraph --> + </div> +</div> +<!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__gradient-video.json b/packages/e2e-tests/fixtures/blocks/core__cover__gradient-video.json new file mode 100644 index 00000000000000..84cb314d0fcca6 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__gradient-video.json @@ -0,0 +1,31 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/cover", + "isValid": true, + "attributes": { + "url": "data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=", + "hasParallax": false, + "dimRatio": 70, + "backgroundType": "video", + "customGradient": "linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)" + }, + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/paragraph", + "isValid": true, + "attributes": { + "align": "center", + "content": "Cover!", + "dropCap": false, + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [], + "originalContent": "<p class=\"has-text-align-center has-large-font-size\">Cover!</p>" + } + ], + "originalContent": "<div class=\"wp-block-cover has-background-dim-70 has-background-dim has-background-gradient\">\n\t<span aria-hidden=\"true\" class=\"wp-block-cover__gradient-background\" style=\"background:linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)\"></span>\n\t<video class=\"wp-block-cover__video-background\" autoplay muted loop src=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"></video>\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t\n\t</div>\n</div>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__gradient-video.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover__gradient-video.parsed.json new file mode 100644 index 00000000000000..3ad05dacbf1257 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__gradient-video.parsed.json @@ -0,0 +1,41 @@ +[ + { + "blockName": "core/cover", + "attrs": { + "url": "data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=", + "dimRatio": 70, + "backgroundType": "video", + "customGradient": "linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)" + }, + "innerBlocks": [ + { + "blockName": "core/paragraph", + "attrs": { + "align": "center", + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [], + "innerHTML": "\n\t\t<p class=\"has-text-align-center has-large-font-size\">Cover!</p>\n\t\t", + "innerContent": [ + "\n\t\t<p class=\"has-text-align-center has-large-font-size\">Cover!</p>\n\t\t" + ] + } + ], + "innerHTML": "\n<div class=\"wp-block-cover has-background-dim-70 has-background-dim has-background-gradient\">\n\t<span aria-hidden=\"true\" class=\"wp-block-cover__gradient-background\" style=\"background:linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)\"></span>\n\t<video class=\"wp-block-cover__video-background\" autoplay muted loop src=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"></video>\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t\n\t</div>\n</div>\n", + "innerContent": [ + "\n<div class=\"wp-block-cover has-background-dim-70 has-background-dim has-background-gradient\">\n\t<span aria-hidden=\"true\" class=\"wp-block-cover__gradient-background\" style=\"background:linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)\"></span>\n\t<video class=\"wp-block-cover__video-background\" autoplay muted loop src=\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\"></video>\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t", + null, + "\n\t</div>\n</div>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__gradient-video.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__gradient-video.serialized.html new file mode 100644 index 00000000000000..2bc567b4bf2e03 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__gradient-video.serialized.html @@ -0,0 +1,5 @@ +<!-- wp:cover {"url":"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=","dimRatio":70,"backgroundType":"video","customGradient":"linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)"} --> +<div class="wp-block-cover has-background-dim-70 has-background-dim has-background-gradient"><span aria-hidden="true" class="wp-block-cover__gradient-background" style="background:linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)"></span><video class="wp-block-cover__video-background" autoplay muted loop src="data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE="></video><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> +<p class="has-text-align-center has-large-font-size">Cover!</p> +<!-- /wp:paragraph --></div></div> +<!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__gradient.html b/packages/e2e-tests/fixtures/blocks/core__cover__gradient.html new file mode 100644 index 00000000000000..e3e7b9af44777a --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__gradient.html @@ -0,0 +1,9 @@ +<!-- wp:cover {"customGradient":"linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)"} --> +<div class="wp-block-cover has-background-dim has-background-gradient" style="background:linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)"> + <div class="wp-block-cover__inner-container"> + <!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> + <p class="has-text-align-center has-large-font-size">Cover!</p> + <!-- /wp:paragraph --> + </div> +</div> +<!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__gradient.json b/packages/e2e-tests/fixtures/blocks/core__cover__gradient.json new file mode 100644 index 00000000000000..c280fe63d55ab1 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__gradient.json @@ -0,0 +1,30 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/cover", + "isValid": true, + "attributes": { + "hasParallax": false, + "dimRatio": 50, + "backgroundType": "image", + "customGradient": "linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)" + }, + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/paragraph", + "isValid": true, + "attributes": { + "align": "center", + "content": "Cover!", + "dropCap": false, + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [], + "originalContent": "<p class=\"has-text-align-center has-large-font-size\">Cover!</p>" + } + ], + "originalContent": "<div class=\"wp-block-cover has-background-dim has-background-gradient\" style=\"background:linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)\">\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t\n\t</div>\n</div>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__gradient.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover__gradient.parsed.json new file mode 100644 index 00000000000000..423586ba2681e6 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__gradient.parsed.json @@ -0,0 +1,38 @@ +[ + { + "blockName": "core/cover", + "attrs": { + "customGradient": "linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)" + }, + "innerBlocks": [ + { + "blockName": "core/paragraph", + "attrs": { + "align": "center", + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [], + "innerHTML": "\n\t\t<p class=\"has-text-align-center has-large-font-size\">Cover!</p>\n\t\t", + "innerContent": [ + "\n\t\t<p class=\"has-text-align-center has-large-font-size\">Cover!</p>\n\t\t" + ] + } + ], + "innerHTML": "\n<div class=\"wp-block-cover has-background-dim has-background-gradient\" style=\"background:linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)\">\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t\n\t</div>\n</div>\n", + "innerContent": [ + "\n<div class=\"wp-block-cover has-background-dim has-background-gradient\" style=\"background:linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)\">\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t", + null, + "\n\t</div>\n</div>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__gradient.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__gradient.serialized.html new file mode 100644 index 00000000000000..92615e65131f9b --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__gradient.serialized.html @@ -0,0 +1,5 @@ +<!-- wp:cover {"customGradient":"linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)"} --> +<div class="wp-block-cover has-background-dim has-background-gradient" style="background:linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)"><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> +<p class="has-text-align-center has-large-font-size">Cover!</p> +<!-- /wp:paragraph --></div></div> +<!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__solid-color.html b/packages/e2e-tests/fixtures/blocks/core__cover__solid-color.html new file mode 100644 index 00000000000000..b9a4b31cf71738 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__solid-color.html @@ -0,0 +1,9 @@ +<!-- wp:cover {"overlayColor":"primary"} --> +<div class="wp-block-cover has-primary-background-color has-background-dim"> + <div class="wp-block-cover__inner-container"> + <!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> + <p class="has-text-align-center has-large-font-size"></p> + <!-- /wp:paragraph --> + </div> +</div> +<!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__solid-color.json b/packages/e2e-tests/fixtures/blocks/core__cover__solid-color.json new file mode 100644 index 00000000000000..ed8b8e8b97a9b8 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__solid-color.json @@ -0,0 +1,30 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/cover", + "isValid": true, + "attributes": { + "hasParallax": false, + "dimRatio": 50, + "overlayColor": "primary", + "backgroundType": "image" + }, + "innerBlocks": [ + { + "clientId": "_clientId_0", + "name": "core/paragraph", + "isValid": true, + "attributes": { + "align": "center", + "content": "", + "dropCap": false, + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [], + "originalContent": "<p class=\"has-text-align-center has-large-font-size\"></p>" + } + ], + "originalContent": "<div class=\"wp-block-cover has-primary-background-color has-background-dim\">\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t\n\t</div>\n</div>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__solid-color.parsed.json b/packages/e2e-tests/fixtures/blocks/core__cover__solid-color.parsed.json new file mode 100644 index 00000000000000..d6c34b1913417d --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__solid-color.parsed.json @@ -0,0 +1,38 @@ +[ + { + "blockName": "core/cover", + "attrs": { + "overlayColor": "primary" + }, + "innerBlocks": [ + { + "blockName": "core/paragraph", + "attrs": { + "align": "center", + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [], + "innerHTML": "\n\t\t<p class=\"has-text-align-center has-large-font-size\"></p>\n\t\t", + "innerContent": [ + "\n\t\t<p class=\"has-text-align-center has-large-font-size\"></p>\n\t\t" + ] + } + ], + "innerHTML": "\n<div class=\"wp-block-cover has-primary-background-color has-background-dim\">\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t\n\t</div>\n</div>\n", + "innerContent": [ + "\n<div class=\"wp-block-cover has-primary-background-color has-background-dim\">\n\t<div class=\"wp-block-cover__inner-container\">\n\t\t", + null, + "\n\t</div>\n</div>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__cover__solid-color.serialized.html b/packages/e2e-tests/fixtures/blocks/core__cover__solid-color.serialized.html new file mode 100644 index 00000000000000..d234e8512e986e --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__cover__solid-color.serialized.html @@ -0,0 +1,5 @@ +<!-- wp:cover {"overlayColor":"primary"} --> +<div class="wp-block-cover has-primary-background-color has-background-dim"><div class="wp-block-cover__inner-container"><!-- wp:paragraph {"align":"center","placeholder":"Write title…","fontSize":"large"} --> +<p class="has-text-align-center has-large-font-size"></p> +<!-- /wp:paragraph --></div></div> +<!-- /wp:cover --> diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation-link.html b/packages/e2e-tests/fixtures/blocks/core__navigation-link.html new file mode 100644 index 00000000000000..d696e0cc0248e1 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__navigation-link.html @@ -0,0 +1,2 @@ +<!-- wp:navigation-link {"label":"WordPress","url":"https://wordpress.org/"} --> +<!-- /wp:navigation-link --> diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.json b/packages/e2e-tests/fixtures/blocks/core__navigation-link.json similarity index 63% rename from packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.json rename to packages/e2e-tests/fixtures/blocks/core__navigation-link.json index eb71bb8b929c4b..f0a50359a7a355 100644 --- a/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.json +++ b/packages/e2e-tests/fixtures/blocks/core__navigation-link.json @@ -1,13 +1,13 @@ [ { "clientId": "_clientId_0", - "name": "core/navigation-menu-item", + "name": "core/navigation-link", "isValid": true, "attributes": { "label": "WordPress", - "destination": "https://wordpress.org/", "nofollow": false, - "opensInNewTab": false + "opensInNewTab": false, + "url": "https://wordpress.org/" }, "innerBlocks": [], "originalContent": "" diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.parsed.json b/packages/e2e-tests/fixtures/blocks/core__navigation-link.parsed.json similarity index 77% rename from packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__navigation-link.parsed.json index 8fa4f5340bb83d..a2e566639cc57d 100644 --- a/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__navigation-link.parsed.json @@ -1,9 +1,9 @@ [ { - "blockName": "core/navigation-menu-item", + "blockName": "core/navigation-link", "attrs": { "label": "WordPress", - "destination": "https://wordpress.org/" + "url": "https://wordpress.org/" }, "innerBlocks": [], "innerHTML": "\n", diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation-link.serialized.html b/packages/e2e-tests/fixtures/blocks/core__navigation-link.serialized.html new file mode 100644 index 00000000000000..0d7f09cb6df618 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__navigation-link.serialized.html @@ -0,0 +1 @@ +<!-- wp:navigation-link {"label":"WordPress","url":"https://wordpress.org/"} /--> diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.html b/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.html deleted file mode 100644 index c88644e748ebf8..00000000000000 --- a/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.html +++ /dev/null @@ -1,2 +0,0 @@ -<!-- wp:navigation-menu-item {"label":"WordPress","destination":"https://wordpress.org/"} --> -<!-- /wp:navigation-menu-item --> diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.serialized.html b/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.serialized.html deleted file mode 100644 index b5176129ef2460..00000000000000 --- a/packages/e2e-tests/fixtures/blocks/core__navigation-menu-item.serialized.html +++ /dev/null @@ -1 +0,0 @@ -<!-- wp:navigation-menu-item {"label":"WordPress","destination":"https://wordpress.org/"} /--> diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation-menu.html b/packages/e2e-tests/fixtures/blocks/core__navigation-menu.html deleted file mode 100644 index 7142dc9af25093..00000000000000 --- a/packages/e2e-tests/fixtures/blocks/core__navigation-menu.html +++ /dev/null @@ -1,2 +0,0 @@ -<!-- wp:navigation-menu --> -<!-- /wp:navigation-menu --> diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation-menu.serialized.html b/packages/e2e-tests/fixtures/blocks/core__navigation-menu.serialized.html deleted file mode 100644 index 6925974debe3e6..00000000000000 --- a/packages/e2e-tests/fixtures/blocks/core__navigation-menu.serialized.html +++ /dev/null @@ -1 +0,0 @@ -<!-- wp:navigation-menu /--> diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation.html b/packages/e2e-tests/fixtures/blocks/core__navigation.html new file mode 100644 index 00000000000000..e422110c3ec17e --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__navigation.html @@ -0,0 +1,2 @@ +<!-- wp:navigation --> +<!-- /wp:navigation --> diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation.json b/packages/e2e-tests/fixtures/blocks/core__navigation.json new file mode 100644 index 00000000000000..2ceda519775fed --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__navigation.json @@ -0,0 +1,10 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/navigation", + "isValid": true, + "attributes": {}, + "innerBlocks": [], + "originalContent": "" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation-menu.parsed.json b/packages/e2e-tests/fixtures/blocks/core__navigation.parsed.json similarity index 87% rename from packages/e2e-tests/fixtures/blocks/core__navigation-menu.parsed.json rename to packages/e2e-tests/fixtures/blocks/core__navigation.parsed.json index 06dc47d8b23712..19698d9d1841c5 100644 --- a/packages/e2e-tests/fixtures/blocks/core__navigation-menu.parsed.json +++ b/packages/e2e-tests/fixtures/blocks/core__navigation.parsed.json @@ -1,6 +1,6 @@ [ { - "blockName": "core/navigation-menu", + "blockName": "core/navigation", "attrs": {}, "innerBlocks": [], "innerHTML": "\n", diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation.serialized.html b/packages/e2e-tests/fixtures/blocks/core__navigation.serialized.html new file mode 100644 index 00000000000000..5120a42136ec4e --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__navigation.serialized.html @@ -0,0 +1 @@ +<!-- wp:navigation /--> diff --git a/packages/e2e-tests/fixtures/blocks/core__post-content.html b/packages/e2e-tests/fixtures/blocks/core__post-content.html new file mode 100644 index 00000000000000..888d7460ff8a32 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__post-content.html @@ -0,0 +1 @@ +<!-- wp:post-content /--> diff --git a/packages/e2e-tests/fixtures/blocks/core__post-content.json b/packages/e2e-tests/fixtures/blocks/core__post-content.json new file mode 100644 index 00000000000000..d81012f620289a --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__post-content.json @@ -0,0 +1,10 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/post-content", + "isValid": true, + "attributes": {}, + "innerBlocks": [], + "originalContent": "" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__post-content.parsed.json b/packages/e2e-tests/fixtures/blocks/core__post-content.parsed.json new file mode 100644 index 00000000000000..8517af18fc1a34 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__post-content.parsed.json @@ -0,0 +1,18 @@ +[ + { + "blockName": "core/post-content", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__post-content.serialized.html b/packages/e2e-tests/fixtures/blocks/core__post-content.serialized.html new file mode 100644 index 00000000000000..888d7460ff8a32 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__post-content.serialized.html @@ -0,0 +1 @@ +<!-- wp:post-content /--> diff --git a/packages/e2e-tests/fixtures/blocks/core__post-title.html b/packages/e2e-tests/fixtures/blocks/core__post-title.html new file mode 100644 index 00000000000000..c187f3a18aa9a3 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__post-title.html @@ -0,0 +1 @@ +<!-- wp:post-title /--> diff --git a/packages/e2e-tests/fixtures/blocks/core__post-title.json b/packages/e2e-tests/fixtures/blocks/core__post-title.json new file mode 100644 index 00000000000000..a3c59c8991b37b --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__post-title.json @@ -0,0 +1,10 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/post-title", + "isValid": true, + "attributes": {}, + "innerBlocks": [], + "originalContent": "" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__post-title.parsed.json b/packages/e2e-tests/fixtures/blocks/core__post-title.parsed.json new file mode 100644 index 00000000000000..f13087a1d72b4d --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__post-title.parsed.json @@ -0,0 +1,18 @@ +[ + { + "blockName": "core/post-title", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__post-title.serialized.html b/packages/e2e-tests/fixtures/blocks/core__post-title.serialized.html new file mode 100644 index 00000000000000..c187f3a18aa9a3 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__post-title.serialized.html @@ -0,0 +1 @@ +<!-- wp:post-title /--> diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-3.html b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-3.html new file mode 100644 index 00000000000000..b3fd2c37a633dd --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-3.html @@ -0,0 +1,3 @@ +<!-- wp:pullquote {"mainColor":"accent","textColor":"secondary"} --> +<figure class="wp-block-pullquote" style="border-color:#cd2653"><blockquote class="has-text-color has-secondary-color"><p>pullquote</p></blockquote></figure> +<!-- /wp:pullquote --> diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-3.json b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-3.json new file mode 100644 index 00000000000000..00948d5850752f --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-3.json @@ -0,0 +1,15 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/pullquote", + "isValid": true, + "attributes": { + "value": "<p>pullquote</p>", + "citation": "", + "customMainColor": "#cd2653", + "textColor": "secondary" + }, + "innerBlocks": [], + "originalContent": "<figure class=\"wp-block-pullquote\" style=\"border-color:#cd2653\"><blockquote class=\"has-text-color has-secondary-color\"><p>pullquote</p></blockquote></figure>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-3.parsed.json b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-3.parsed.json new file mode 100644 index 00000000000000..61459172c1eb84 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-3.parsed.json @@ -0,0 +1,23 @@ +[ + { + "blockName": "core/pullquote", + "attrs": { + "mainColor": "accent", + "textColor": "secondary" + }, + "innerBlocks": [], + "innerHTML": "\n<figure class=\"wp-block-pullquote\" style=\"border-color:#cd2653\"><blockquote class=\"has-text-color has-secondary-color\"><p>pullquote</p></blockquote></figure>\n", + "innerContent": [ + "\n<figure class=\"wp-block-pullquote\" style=\"border-color:#cd2653\"><blockquote class=\"has-text-color has-secondary-color\"><p>pullquote</p></blockquote></figure>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-3.serialized.html b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-3.serialized.html new file mode 100644 index 00000000000000..786f15f7b4293e --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__deprecated-3.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:pullquote {"customMainColor":"#cd2653","textColor":"secondary"} --> +<figure class="wp-block-pullquote" style="border-color:#cd2653"><blockquote class="has-text-color has-secondary-color"><p>pullquote</p></blockquote></figure> +<!-- /wp:pullquote --> diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__main-color.html b/packages/e2e-tests/fixtures/blocks/core__pullquote__main-color.html new file mode 100644 index 00000000000000..473e6ecefd480d --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__main-color.html @@ -0,0 +1,3 @@ +<!-- wp:pullquote {"customMainColor":"#2207d0","textColor":"subtle-background","className":"has-background is-style-default"} --> +<figure class="wp-block-pullquote has-background is-style-default" style="border-color:#2207d0"><blockquote class="has-text-color has-subtle-background-color"><p>Pullquote custom color</p><cite>my citation</cite></blockquote></figure> +<!-- /wp:pullquote --> diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__main-color.json b/packages/e2e-tests/fixtures/blocks/core__pullquote__main-color.json new file mode 100644 index 00000000000000..a5e6479043d1a5 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__main-color.json @@ -0,0 +1,16 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/pullquote", + "isValid": true, + "attributes": { + "value": "<p>Pullquote custom color</p>", + "citation": "my citation", + "customMainColor": "#2207d0", + "textColor": "subtle-background", + "className": "has-background is-style-default" + }, + "innerBlocks": [], + "originalContent": "<figure class=\"wp-block-pullquote has-background is-style-default\" style=\"border-color:#2207d0\"><blockquote class=\"has-text-color has-subtle-background-color\"><p>Pullquote custom color</p><cite>my citation</cite></blockquote></figure>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__main-color.parsed.json b/packages/e2e-tests/fixtures/blocks/core__pullquote__main-color.parsed.json new file mode 100644 index 00000000000000..56004374d15637 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__main-color.parsed.json @@ -0,0 +1,24 @@ +[ + { + "blockName": "core/pullquote", + "attrs": { + "customMainColor": "#2207d0", + "textColor": "subtle-background", + "className": "has-background is-style-default" + }, + "innerBlocks": [], + "innerHTML": "\n<figure class=\"wp-block-pullquote has-background is-style-default\" style=\"border-color:#2207d0\"><blockquote class=\"has-text-color has-subtle-background-color\"><p>Pullquote custom color</p><cite>my citation</cite></blockquote></figure>\n", + "innerContent": [ + "\n<figure class=\"wp-block-pullquote has-background is-style-default\" style=\"border-color:#2207d0\"><blockquote class=\"has-text-color has-subtle-background-color\"><p>Pullquote custom color</p><cite>my citation</cite></blockquote></figure>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__pullquote__main-color.serialized.html b/packages/e2e-tests/fixtures/blocks/core__pullquote__main-color.serialized.html new file mode 100644 index 00000000000000..473e6ecefd480d --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__pullquote__main-color.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:pullquote {"customMainColor":"#2207d0","textColor":"subtle-background","className":"has-background is-style-default"} --> +<figure class="wp-block-pullquote has-background is-style-default" style="border-color:#2207d0"><blockquote class="has-text-color has-subtle-background-color"><p>Pullquote custom color</p><cite>my citation</cite></blockquote></figure> +<!-- /wp:pullquote --> diff --git a/packages/e2e-tests/fixtures/blocks/core__site-title.html b/packages/e2e-tests/fixtures/blocks/core__site-title.html new file mode 100644 index 00000000000000..3c42ebc0d2feb7 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__site-title.html @@ -0,0 +1 @@ +<!-- wp:site-title /--> diff --git a/packages/e2e-tests/fixtures/blocks/core__site-title.json b/packages/e2e-tests/fixtures/blocks/core__site-title.json new file mode 100644 index 00000000000000..6070316cf41796 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__site-title.json @@ -0,0 +1,10 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/site-title", + "isValid": true, + "attributes": {}, + "innerBlocks": [], + "originalContent": "" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__site-title.parsed.json b/packages/e2e-tests/fixtures/blocks/core__site-title.parsed.json new file mode 100644 index 00000000000000..bc570f8255a124 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__site-title.parsed.json @@ -0,0 +1,18 @@ +[ + { + "blockName": "core/site-title", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__site-title.serialized.html b/packages/e2e-tests/fixtures/blocks/core__site-title.serialized.html new file mode 100644 index 00000000000000..3c42ebc0d2feb7 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__site-title.serialized.html @@ -0,0 +1 @@ +<!-- wp:site-title /--> diff --git a/packages/e2e-tests/fixtures/blocks/core__table.json b/packages/e2e-tests/fixtures/blocks/core__table.json index 3318420a516b30..e2a7660f63473a 100644 --- a/packages/e2e-tests/fixtures/blocks/core__table.json +++ b/packages/e2e-tests/fixtures/blocks/core__table.json @@ -5,6 +5,7 @@ "isValid": true, "attributes": { "hasFixedLayout": false, + "caption": "", "head": [ { "cells": [ diff --git a/packages/e2e-tests/fixtures/blocks/core__table.serialized.html b/packages/e2e-tests/fixtures/blocks/core__table.serialized.html index 346cd673c17514..95fc72534da7db 100644 --- a/packages/e2e-tests/fixtures/blocks/core__table.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__table.serialized.html @@ -1,3 +1,3 @@ <!-- wp:table --> -<figure class="wp-block-table"><table class=""><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table></figure> +<figure class="wp-block-table"><table><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table></figure> <!-- /wp:table --> diff --git a/packages/e2e-tests/fixtures/blocks/core__table__caption.html b/packages/e2e-tests/fixtures/blocks/core__table__caption.html new file mode 100644 index 00000000000000..a22436c55bdb1f --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__table__caption.html @@ -0,0 +1,3 @@ +<!-- wp:core/table --> +<figure class="wp-block-table"><table><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>&hellip;</td><td>&hellip;</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table><figcaption>A table for testing</figcaption></figure> +<!-- /wp:core/table --> diff --git a/packages/e2e-tests/fixtures/blocks/core__table__caption.json b/packages/e2e-tests/fixtures/blocks/core__table__caption.json new file mode 100644 index 00000000000000..5a956ed2df3614 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__table__caption.json @@ -0,0 +1,146 @@ +[ + { + "clientId": "_clientId_0", + "name": "core/table", + "isValid": true, + "attributes": { + "hasFixedLayout": false, + "caption": "A table for testing", + "head": [ + { + "cells": [ + { + "content": "Version", + "tag": "th" + }, + { + "content": "Musician", + "tag": "th" + }, + { + "content": "Date", + "tag": "th" + } + ] + } + ], + "body": [ + { + "cells": [ + { + "content": "<a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a>", + "tag": "td" + }, + { + "content": "No musician chosen.", + "tag": "td" + }, + { + "content": "May 27, 2003", + "tag": "td" + } + ] + }, + { + "cells": [ + { + "content": "<a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a>", + "tag": "td" + }, + { + "content": "Miles Davis", + "tag": "td" + }, + { + "content": "January 3, 2004", + "tag": "td" + } + ] + }, + { + "cells": [ + { + "content": "Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a>", + "tag": "td" + }, + { + "content": "…", + "tag": "td" + }, + { + "content": "…", + "tag": "td" + } + ] + }, + { + "cells": [ + { + "content": "<a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a>", + "tag": "td" + }, + { + "content": "Clifford Brown", + "tag": "td" + }, + { + "content": "December 8, 2015", + "tag": "td" + } + ] + }, + { + "cells": [ + { + "content": "<a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a>", + "tag": "td" + }, + { + "content": "Coleman Hawkins", + "tag": "td" + }, + { + "content": "April 12, 2016", + "tag": "td" + } + ] + }, + { + "cells": [ + { + "content": "<a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a>", + "tag": "td" + }, + { + "content": "Pepper Adams", + "tag": "td" + }, + { + "content": "August 16, 2016", + "tag": "td" + } + ] + }, + { + "cells": [ + { + "content": "<a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a>", + "tag": "td" + }, + { + "content": "Sarah Vaughan", + "tag": "td" + }, + { + "content": "December 6, 2016", + "tag": "td" + } + ] + } + ], + "foot": [] + }, + "innerBlocks": [], + "originalContent": "<figure class=\"wp-block-table\"><table><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>&hellip;</td><td>&hellip;</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table><figcaption>A table for testing</figcaption></figure>" + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__table__caption.parsed.json b/packages/e2e-tests/fixtures/blocks/core__table__caption.parsed.json new file mode 100644 index 00000000000000..5b73aabf129e48 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__table__caption.parsed.json @@ -0,0 +1,20 @@ +[ + { + "blockName": "core/table", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n<figure class=\"wp-block-table\"><table><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>&hellip;</td><td>&hellip;</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table><figcaption>A table for testing</figcaption></figure>\n", + "innerContent": [ + "\n<figure class=\"wp-block-table\"><table><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href=\"https://wordpress.org/news/2003/05/wordpress-now-available/\">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href=\"https://wordpress.org/news/2004/01/wordpress-10/\">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href=\"https://codex.wordpress.org/WordPress_Versions\">the full list</a></td><td>&hellip;</td><td>&hellip;</td></tr><tr><td><a href=\"https://wordpress.org/news/2015/12/clifford/\">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/04/coleman/\">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/08/pepper/\">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href=\"https://wordpress.org/news/2016/12/vaughan/\">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table><figcaption>A table for testing</figcaption></figure>\n" + ] + }, + { + "blockName": null, + "attrs": {}, + "innerBlocks": [], + "innerHTML": "\n", + "innerContent": [ + "\n" + ] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__table__caption.serialized.html b/packages/e2e-tests/fixtures/blocks/core__table__caption.serialized.html new file mode 100644 index 00000000000000..ff5add7f856b22 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__table__caption.serialized.html @@ -0,0 +1,3 @@ +<!-- wp:table --> +<figure class="wp-block-table"><table><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table><figcaption>A table for testing</figcaption></figure> +<!-- /wp:table --> diff --git a/packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.serialized.html b/packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.serialized.html index 346cd673c17514..95fc72534da7db 100644 --- a/packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__table__deprecated-1.serialized.html @@ -1,3 +1,3 @@ <!-- wp:table --> -<figure class="wp-block-table"><table class=""><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table></figure> +<figure class="wp-block-table"><table><thead><tr><th>Version</th><th>Musician</th><th>Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table></figure> <!-- /wp:table --> diff --git a/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.json b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.json index 23bb732aebff2f..994fd2936de7a5 100644 --- a/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.json +++ b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.json @@ -5,6 +5,7 @@ "isValid": true, "attributes": { "hasFixedLayout": false, + "caption": "", "head": [ { "cells": [ diff --git a/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.serialized.html b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.serialized.html index 88122c62ed9683..dfe5cdd5c26168 100644 --- a/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.serialized.html +++ b/packages/e2e-tests/fixtures/blocks/core__table__scope-attribute.serialized.html @@ -1,3 +1,3 @@ <!-- wp:table --> -<figure class="wp-block-table"><table class=""><thead><tr><th scope="col">Version</th><th scope="col">Musician</th><th scope="col">Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table></figure> +<figure class="wp-block-table"><table><thead><tr><th scope="col">Version</th><th scope="col">Musician</th><th scope="col">Date</th></tr></thead><tbody><tr><td><a href="https://wordpress.org/news/2003/05/wordpress-now-available/">.70</a></td><td>No musician chosen.</td><td>May 27, 2003</td></tr><tr><td><a href="https://wordpress.org/news/2004/01/wordpress-10/">1.0</a></td><td>Miles Davis</td><td>January 3, 2004</td></tr><tr><td>Lots of versions skipped, see <a href="https://codex.wordpress.org/WordPress_Versions">the full list</a></td><td>…</td><td>…</td></tr><tr><td><a href="https://wordpress.org/news/2015/12/clifford/">4.4</a></td><td>Clifford Brown</td><td>December 8, 2015</td></tr><tr><td><a href="https://wordpress.org/news/2016/04/coleman/">4.5</a></td><td>Coleman Hawkins</td><td>April 12, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/08/pepper/">4.6</a></td><td>Pepper Adams</td><td>August 16, 2016</td></tr><tr><td><a href="https://wordpress.org/news/2016/12/vaughan/">4.7</a></td><td>Sarah Vaughan</td><td>December 6, 2016</td></tr></tbody></table></figure> <!-- /wp:table --> diff --git a/packages/e2e-tests/fixtures/blocks/core__template-part.html b/packages/e2e-tests/fixtures/blocks/core__template-part.html new file mode 100644 index 00000000000000..202c4f804c4c35 --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__template-part.html @@ -0,0 +1 @@ +<!-- wp:template-part {"slug":"header","theme":"twentytwenty"} /--> \ No newline at end of file diff --git a/packages/e2e-tests/fixtures/blocks/core__navigation-menu.json b/packages/e2e-tests/fixtures/blocks/core__template-part.json similarity index 61% rename from packages/e2e-tests/fixtures/blocks/core__navigation-menu.json rename to packages/e2e-tests/fixtures/blocks/core__template-part.json index 94d539e2819594..c8a1b7373a6a61 100644 --- a/packages/e2e-tests/fixtures/blocks/core__navigation-menu.json +++ b/packages/e2e-tests/fixtures/blocks/core__template-part.json @@ -1,10 +1,11 @@ [ { "clientId": "_clientId_0", - "name": "core/navigation-menu", + "name": "core/template-part", "isValid": true, "attributes": { - "automaticallyAdd": false + "slug": "header", + "theme": "twentytwenty" }, "innerBlocks": [], "originalContent": "" diff --git a/packages/e2e-tests/fixtures/blocks/core__template-part.parsed.json b/packages/e2e-tests/fixtures/blocks/core__template-part.parsed.json new file mode 100644 index 00000000000000..3391b52a93de2f --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__template-part.parsed.json @@ -0,0 +1,12 @@ +[ + { + "blockName": "core/template-part", + "attrs": { + "slug": "header", + "theme": "twentytwenty" + }, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + } +] diff --git a/packages/e2e-tests/fixtures/blocks/core__template-part.serialized.html b/packages/e2e-tests/fixtures/blocks/core__template-part.serialized.html new file mode 100644 index 00000000000000..d0d1ea355a6b0e --- /dev/null +++ b/packages/e2e-tests/fixtures/blocks/core__template-part.serialized.html @@ -0,0 +1 @@ +<!-- wp:template-part {"slug":"header","theme":"twentytwenty"} /--> diff --git a/packages/e2e-tests/jest.config.js b/packages/e2e-tests/jest.config.js index 2720488c7b36de..22bccf74e3d1a1 100644 --- a/packages/e2e-tests/jest.config.js +++ b/packages/e2e-tests/jest.config.js @@ -12,6 +12,6 @@ module.exports = { testPathIgnorePatterns: [ '/node_modules/', '/wordpress/', - 'e2e-tests/specs/performance.test.js', + 'e2e-tests/specs/performance/', ], }; diff --git a/packages/e2e-tests/jest.performance.config.js b/packages/e2e-tests/jest.performance.config.js index f8fe4cf8ff1ff6..cc011820f08871 100644 --- a/packages/e2e-tests/jest.performance.config.js +++ b/packages/e2e-tests/jest.performance.config.js @@ -1,7 +1,7 @@ module.exports = { ...require( '@wordpress/scripts/config/jest-e2e.config' ), testMatch: [ - '**/performance.test.js', + '**/performance/*.test.js', ], setupFiles: [ '<rootDir>/config/gutenberg-phase.js', diff --git a/packages/e2e-tests/mu-plugins/normalize-theme.php b/packages/e2e-tests/mu-plugins/normalize-theme.php new file mode 100644 index 00000000000000..2adc386a0d6bb7 --- /dev/null +++ b/packages/e2e-tests/mu-plugins/normalize-theme.php @@ -0,0 +1,18 @@ +<?php +/** + * Plugin Name: Gutenberg Test Plugin, Normalize Theme + * Plugin URI: https://github.com/WordPress/gutenberg + * Author: Gutenberg Team + * + * @package gutenberg-test-normalize-theme + */ + +/** + * Fixes colors and font sizes so that tests + * are consistent across different themes. + */ +function normalize_theme_init() { + remove_theme_support( 'editor-color-palette' ); + remove_theme_support( 'editor-font-sizes' ); +} +add_action( 'init', 'normalize_theme_init' ); diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index 1fe2ee6a9464ba..eb481cd6dd53fe 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/e2e-tests", - "version": "1.7.0", + "version": "1.8.0", "description": "End-To-End (E2E) tests for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -26,6 +26,7 @@ "@wordpress/jest-console": "file:../jest-console", "@wordpress/jest-puppeteer-axe": "file:../jest-puppeteer-axe", "@wordpress/scripts": "file:../scripts", + "@wordpress/url": "file:../url", "expect-puppeteer": "^4.3.0", "lodash": "^4.17.15", "uuid": "^3.3.2" diff --git a/packages/e2e-tests/plugins/inner-blocks-locking-all-embed.php b/packages/e2e-tests/plugins/inner-blocks-locking-all-embed.php new file mode 100644 index 00000000000000..c9489d68f357d7 --- /dev/null +++ b/packages/e2e-tests/plugins/inner-blocks-locking-all-embed.php @@ -0,0 +1,28 @@ +<?php +/** + * Plugin Name: Gutenberg Test InnerBlocks Locking All Embed + * Plugin URI: https://github.com/WordPress/gutenberg + * Author: Gutenberg Team + * + * @package gutenberg-test-inner-blocks-locking-all-embed + */ + +/** + * Registers a custom script for the plugin. + */ +function enqueue_container_without_paragraph_plugin_script() { + wp_enqueue_script( + 'gutenberg-test-inner-blocks-locking-all-embed', + plugins_url( 'inner-blocks-locking-all-embed/index.js', __FILE__ ), + array( + 'wp-blocks', + 'wp-element', + 'wp-block-editor', + 'wp-i18n', + ), + filemtime( plugin_dir_path( __FILE__ ) . 'inner-blocks-locking-all-embed/index.js' ), + true + ); +} + +add_action( 'init', 'enqueue_container_without_paragraph_plugin_script' ); diff --git a/packages/e2e-tests/plugins/inner-blocks-locking-all-embed/index.js b/packages/e2e-tests/plugins/inner-blocks-locking-all-embed/index.js new file mode 100644 index 00000000000000..31b7c745d3dcff --- /dev/null +++ b/packages/e2e-tests/plugins/inner-blocks-locking-all-embed/index.js @@ -0,0 +1,35 @@ +( function() { + var registerBlockType = wp.blocks.registerBlockType; + var el = wp.element.createElement; + var InnerBlocks = wp.blockEditor.InnerBlocks; + var __ = wp.i18n.__; + var TEMPLATE = [ + [ 'core/paragraph', { + fontSize: 'large', + content: __( 'Content…' ), + } ], + [ 'core/embed' ], + ]; + + var save = function() { + return el( InnerBlocks.Content ); + }; + + registerBlockType( 'test/test-inner-blocks-locking-all-embed', { + title: 'Test Inner Blocks Locking All Embed', + icon: 'cart', + category: 'common', + + edit: function( props ) { + return el( + InnerBlocks, + { + template: TEMPLATE, + templateLock: 'all', + } + ); + }, + + save, + } ); +} )(); diff --git a/packages/e2e-tests/plugins/meta-attribute-block.php b/packages/e2e-tests/plugins/meta-attribute-block.php index 45fbcc661e1083..58eec7c04244af 100644 --- a/packages/e2e-tests/plugins/meta-attribute-block.php +++ b/packages/e2e-tests/plugins/meta-attribute-block.php @@ -8,20 +8,9 @@ */ /** - * Registers a custom script and a custom meta for the plugin. + * Registers a custom meta for use by the test block. */ function init_test_meta_attribute_block_plugin() { - wp_enqueue_script( - 'gutenberg-test-meta-attribute-block', - plugins_url( 'meta-attribute-block/index.js', __FILE__ ), - array( - 'wp-blocks', - 'wp-element', - ), - filemtime( plugin_dir_path( __FILE__ ) . 'meta-attribute-block/index.js' ), - true - ); - register_meta( 'post', 'my_meta', @@ -34,3 +23,20 @@ function init_test_meta_attribute_block_plugin() { } add_action( 'init', 'init_test_meta_attribute_block_plugin' ); + +/** + * Enqueues block assets for the custom meta test block. + */ +function enqueue_test_meta_attribute_block() { + wp_enqueue_script( + 'gutenberg-test-meta-attribute-block', + plugins_url( 'meta-attribute-block/index.js', __FILE__ ), + array( + 'wp-blocks', + 'wp-element', + ), + filemtime( plugin_dir_path( __FILE__ ) . 'meta-attribute-block/index.js' ), + true + ); +} +add_action( 'enqueue_block_assets', 'enqueue_test_meta_attribute_block' ); diff --git a/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap b/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap deleted file mode 100644 index dbe341ba42e76e..00000000000000 --- a/packages/e2e-tests/specs/__snapshots__/block-transforms.test.js.snap +++ /dev/null @@ -1,531 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Block transforms correctly transform block Audio in fixture core__audio into the File block 1`] = ` -"<!-- wp:file {\\"href\\":\\"data:audio/mpeg;base64,/+MYxAAAAANIAAAAAExBTUUzLjk4LjIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\"} --> -<div class=\\"wp-block-file\\"><a href=\\"data:audio/mpeg;base64,/+MYxAAAAANIAAAAAExBTUUzLjk4LjIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\" class=\\"wp-block-file__button\\" download>Download</a></div> -<!-- /wp:file -->" -`; - -exports[`Block transforms correctly transform block Code in fixture core__code into the Preformatted block 1`] = ` -"<!-- wp:preformatted --> -<pre class=\\"wp-block-preformatted\\">export default function MyButton() { - return <Button>Click Me!</Button>; -}</pre> -<!-- /wp:preformatted -->" -`; - -exports[`Block transforms correctly transform block Cover in fixture core__cover into the Image block 1`] = ` -"<!-- wp:image --> -<figure class=\\"wp-block-image\\"><img src=\\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\\" alt=\\"\\"/></figure> -<!-- /wp:image -->" -`; - -exports[`Block transforms correctly transform block Cover in fixture core__cover__video into the Video block 1`] = ` -"<!-- wp:video --> -<figure class=\\"wp-block-video\\"><video controls src=\\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\\"></video></figure> -<!-- /wp:video -->" -`; - -exports[`Block transforms correctly transform block Cover in fixture core__cover__video-overlay into the Video block 1`] = ` -"<!-- wp:video --> -<figure class=\\"wp-block-video\\"><video controls src=\\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\\"></video></figure> -<!-- /wp:video -->" -`; - -exports[`Block transforms correctly transform block Gallery in fixture core__gallery into the Image block 1`] = ` -"<!-- wp:image --> -<figure class=\\"wp-block-image\\"><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"title\\"/></figure> -<!-- /wp:image --> - -<!-- wp:image --> -<figure class=\\"wp-block-image\\"><img src=\\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\\" alt=\\"title\\"/></figure> -<!-- /wp:image -->" -`; - -exports[`Block transforms correctly transform block Gallery in fixture core__gallery__columns into the Image block 1`] = ` -"<!-- wp:image --> -<figure class=\\"wp-block-image\\"><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"title\\"/></figure> -<!-- /wp:image --> - -<!-- wp:image --> -<figure class=\\"wp-block-image\\"><img src=\\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\\" alt=\\"title\\"/></figure> -<!-- /wp:image -->" -`; - -exports[`Block transforms correctly transform block Gallery in fixture core__gallery-with-caption into the Image block 1`] = ` -"<!-- wp:image --> -<figure class=\\"wp-block-image\\"><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"title\\"/></figure> -<!-- /wp:image --> - -<!-- wp:image --> -<figure class=\\"wp-block-image\\"><img src=\\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\\" alt=\\"title\\"/></figure> -<!-- /wp:image -->" -`; - -exports[`Block transforms correctly transform block Heading in fixture core__heading__h2 into the Paragraph block 1`] = ` -"<!-- wp:paragraph --> -<p>A picture is worth a thousand words, or so the saying goes</p> -<!-- /wp:paragraph -->" -`; - -exports[`Block transforms correctly transform block Heading in fixture core__heading__h2 into the Quote block 1`] = ` -"<!-- wp:quote --> -<blockquote class=\\"wp-block-quote\\"><p>A picture is worth a thousand words, or so the saying goes</p></blockquote> -<!-- /wp:quote -->" -`; - -exports[`Block transforms correctly transform block Heading in fixture core__heading__h2-color into the Paragraph block 1`] = ` -"<!-- wp:paragraph --> -<p>Text</p> -<!-- /wp:paragraph -->" -`; - -exports[`Block transforms correctly transform block Heading in fixture core__heading__h2-color into the Quote block 1`] = ` -"<!-- wp:quote --> -<blockquote class=\\"wp-block-quote\\"><p>Text</p></blockquote> -<!-- /wp:quote -->" -`; - -exports[`Block transforms correctly transform block Heading in fixture core__heading__h4-em into the Paragraph block 1`] = ` -"<!-- wp:paragraph --> -<p>The <em>Inserter</em> Tool</p> -<!-- /wp:paragraph -->" -`; - -exports[`Block transforms correctly transform block Heading in fixture core__heading__h4-em into the Quote block 1`] = ` -"<!-- wp:quote --> -<blockquote class=\\"wp-block-quote\\"><p>The <em>Inserter</em> Tool</p></blockquote> -<!-- /wp:quote -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image into the Cover block 1`] = ` -"<!-- wp:cover {\\"url\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> -<div class=\\"wp-block-cover has-background-dim\\" style=\\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\\"><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\",\\"fontSize\\":\\"large\\"} --> -<p class=\\"has-text-align-center has-large-font-size\\"></p> -<!-- /wp:paragraph --></div></div> -<!-- /wp:cover -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image into the File block 1`] = ` -"<!-- wp:file {\\"href\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> -<div class=\\"wp-block-file\\"><a href=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" class=\\"wp-block-file__button\\" download>Download</a></div> -<!-- /wp:file -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image into the Gallery block 1`] = ` -"<!-- wp:gallery {\\"ids\\":[null]} --> -<figure class=\\"wp-block-gallery columns-1 is-cropped\\"><ul class=\\"blocks-gallery-grid\\"><li class=\\"blocks-gallery-item\\"><figure><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"\\"/></figure></li></ul></figure> -<!-- /wp:gallery -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image into the Group block 1`] = ` -"<!-- wp:group --> -<div class=\\"wp-block-group\\"><div class=\\"wp-block-group__inner-container\\"><!-- wp:image --> -<figure class=\\"wp-block-image\\"><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"\\"/></figure> -<!-- /wp:image --></div></div> -<!-- /wp:group -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image into the Media & Text block 1`] = ` -"<!-- wp:media-text {\\"mediaType\\":\\"image\\"} --> -<div class=\\"wp-block-media-text alignwide is-stacked-on-mobile\\"><figure class=\\"wp-block-media-text__media\\"><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"\\"/></figure><div class=\\"wp-block-media-text__content\\"><!-- wp:paragraph {\\"placeholder\\":\\"Content…\\",\\"fontSize\\":\\"large\\"} --> -<p class=\\"has-large-font-size\\"></p> -<!-- /wp:paragraph --></div></div> -<!-- /wp:media-text -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__attachment-link into the Cover block 1`] = ` -"<!-- wp:cover {\\"url\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> -<div class=\\"wp-block-cover has-background-dim\\" style=\\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\\"><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\",\\"fontSize\\":\\"large\\"} --> -<p class=\\"has-text-align-center has-large-font-size\\"></p> -<!-- /wp:paragraph --></div></div> -<!-- /wp:cover -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__attachment-link into the File block 1`] = ` -"<!-- wp:file {\\"href\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> -<div class=\\"wp-block-file\\"><a href=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" class=\\"wp-block-file__button\\" download>Download</a></div> -<!-- /wp:file -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__attachment-link into the Gallery block 1`] = ` -"<!-- wp:gallery {\\"ids\\":[null]} --> -<figure class=\\"wp-block-gallery columns-1 is-cropped\\"><ul class=\\"blocks-gallery-grid\\"><li class=\\"blocks-gallery-item\\"><figure><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"\\"/></figure></li></ul></figure> -<!-- /wp:gallery -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__attachment-link into the Media & Text block 1`] = ` -"<!-- wp:media-text {\\"mediaType\\":\\"image\\"} --> -<div class=\\"wp-block-media-text alignwide is-stacked-on-mobile\\"><figure class=\\"wp-block-media-text__media\\"><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"\\"/></figure><div class=\\"wp-block-media-text__content\\"><!-- wp:paragraph {\\"placeholder\\":\\"Content…\\",\\"fontSize\\":\\"large\\"} --> -<p class=\\"has-large-font-size\\"></p> -<!-- /wp:paragraph --></div></div> -<!-- /wp:media-text -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__center-caption into the Cover block 1`] = ` -"<!-- wp:cover {\\"url\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\",\\"align\\":\\"center\\"} --> -<div class=\\"wp-block-cover aligncenter has-background-dim\\" style=\\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\\"><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\",\\"fontSize\\":\\"large\\"} --> -<p class=\\"has-text-align-center has-large-font-size\\"></p> -<!-- /wp:paragraph --></div></div> -<!-- /wp:cover -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__center-caption into the File block 1`] = ` -"<!-- wp:file {\\"href\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> -<div class=\\"wp-block-file\\"><a href=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\">Give it a try. Press the \\"really wide\\" button on the image toolbar.</a><a href=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" class=\\"wp-block-file__button\\" download>Download</a></div> -<!-- /wp:file -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__center-caption into the Gallery block 1`] = ` -"<!-- wp:gallery {\\"ids\\":[null],\\"align\\":\\"center\\"} --> -<figure class=\\"wp-block-gallery aligncenter columns-1 is-cropped\\"><ul class=\\"blocks-gallery-grid\\"><li class=\\"blocks-gallery-item\\"><figure><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"\\"/><figcaption class=\\"blocks-gallery-item__caption\\">Give it a try. Press the \\"really wide\\" button on the image toolbar.</figcaption></figure></li></ul></figure> -<!-- /wp:gallery -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__center-caption into the Media & Text block 1`] = ` -"<!-- wp:media-text {\\"mediaType\\":\\"image\\"} --> -<div class=\\"wp-block-media-text alignwide is-stacked-on-mobile\\"><figure class=\\"wp-block-media-text__media\\"><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"\\"/></figure><div class=\\"wp-block-media-text__content\\"><!-- wp:paragraph {\\"placeholder\\":\\"Content…\\",\\"fontSize\\":\\"large\\"} --> -<p class=\\"has-large-font-size\\"></p> -<!-- /wp:paragraph --></div></div> -<!-- /wp:media-text -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__custom-link into the Cover block 1`] = ` -"<!-- wp:cover {\\"url\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> -<div class=\\"wp-block-cover has-background-dim\\" style=\\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\\"><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\",\\"fontSize\\":\\"large\\"} --> -<p class=\\"has-text-align-center has-large-font-size\\"></p> -<!-- /wp:paragraph --></div></div> -<!-- /wp:cover -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__custom-link into the File block 1`] = ` -"<!-- wp:file {\\"href\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> -<div class=\\"wp-block-file\\"><a href=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" class=\\"wp-block-file__button\\" download>Download</a></div> -<!-- /wp:file -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__custom-link into the Gallery block 1`] = ` -"<!-- wp:gallery {\\"ids\\":[null]} --> -<figure class=\\"wp-block-gallery columns-1 is-cropped\\"><ul class=\\"blocks-gallery-grid\\"><li class=\\"blocks-gallery-item\\"><figure><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"\\"/></figure></li></ul></figure> -<!-- /wp:gallery -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__custom-link into the Media & Text block 1`] = ` -"<!-- wp:media-text {\\"mediaType\\":\\"image\\"} --> -<div class=\\"wp-block-media-text alignwide is-stacked-on-mobile\\"><figure class=\\"wp-block-media-text__media\\"><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"\\"/></figure><div class=\\"wp-block-media-text__content\\"><!-- wp:paragraph {\\"placeholder\\":\\"Content…\\",\\"fontSize\\":\\"large\\"} --> -<p class=\\"has-large-font-size\\"></p> -<!-- /wp:paragraph --></div></div> -<!-- /wp:media-text -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__custom-link-class into the Cover block 1`] = ` -"<!-- wp:cover {\\"url\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> -<div class=\\"wp-block-cover has-background-dim\\" style=\\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\\"><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\",\\"fontSize\\":\\"large\\"} --> -<p class=\\"has-text-align-center has-large-font-size\\"></p> -<!-- /wp:paragraph --></div></div> -<!-- /wp:cover -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__custom-link-class into the File block 1`] = ` -"<!-- wp:file {\\"href\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> -<div class=\\"wp-block-file\\"><a href=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" class=\\"wp-block-file__button\\" download>Download</a></div> -<!-- /wp:file -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__custom-link-class into the Gallery block 1`] = ` -"<!-- wp:gallery {\\"ids\\":[null]} --> -<figure class=\\"wp-block-gallery columns-1 is-cropped\\"><ul class=\\"blocks-gallery-grid\\"><li class=\\"blocks-gallery-item\\"><figure><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"\\"/></figure></li></ul></figure> -<!-- /wp:gallery -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__custom-link-class into the Media & Text block 1`] = ` -"<!-- wp:media-text {\\"mediaType\\":\\"image\\"} --> -<div class=\\"wp-block-media-text alignwide is-stacked-on-mobile\\"><figure class=\\"wp-block-media-text__media\\"><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"\\"/></figure><div class=\\"wp-block-media-text__content\\"><!-- wp:paragraph {\\"placeholder\\":\\"Content…\\",\\"fontSize\\":\\"large\\"} --> -<p class=\\"has-large-font-size\\"></p> -<!-- /wp:paragraph --></div></div> -<!-- /wp:media-text -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__custom-link-rel into the Cover block 1`] = ` -"<!-- wp:cover {\\"url\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> -<div class=\\"wp-block-cover has-background-dim\\" style=\\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\\"><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\",\\"fontSize\\":\\"large\\"} --> -<p class=\\"has-text-align-center has-large-font-size\\"></p> -<!-- /wp:paragraph --></div></div> -<!-- /wp:cover -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__custom-link-rel into the File block 1`] = ` -"<!-- wp:file {\\"href\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> -<div class=\\"wp-block-file\\"><a href=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" class=\\"wp-block-file__button\\" download>Download</a></div> -<!-- /wp:file -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__custom-link-rel into the Gallery block 1`] = ` -"<!-- wp:gallery {\\"ids\\":[null]} --> -<figure class=\\"wp-block-gallery columns-1 is-cropped\\"><ul class=\\"blocks-gallery-grid\\"><li class=\\"blocks-gallery-item\\"><figure><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"\\"/></figure></li></ul></figure> -<!-- /wp:gallery -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__custom-link-rel into the Media & Text block 1`] = ` -"<!-- wp:media-text {\\"mediaType\\":\\"image\\"} --> -<div class=\\"wp-block-media-text alignwide is-stacked-on-mobile\\"><figure class=\\"wp-block-media-text__media\\"><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"\\"/></figure><div class=\\"wp-block-media-text__content\\"><!-- wp:paragraph {\\"placeholder\\":\\"Content…\\",\\"fontSize\\":\\"large\\"} --> -<p class=\\"has-large-font-size\\"></p> -<!-- /wp:paragraph --></div></div> -<!-- /wp:media-text -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__media-link into the Cover block 1`] = ` -"<!-- wp:cover {\\"url\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> -<div class=\\"wp-block-cover has-background-dim\\" style=\\"background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==)\\"><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\",\\"fontSize\\":\\"large\\"} --> -<p class=\\"has-text-align-center has-large-font-size\\"></p> -<!-- /wp:paragraph --></div></div> -<!-- /wp:cover -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__media-link into the File block 1`] = ` -"<!-- wp:file {\\"href\\":\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\"} --> -<div class=\\"wp-block-file\\"><a href=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" class=\\"wp-block-file__button\\" download>Download</a></div> -<!-- /wp:file -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__media-link into the Gallery block 1`] = ` -"<!-- wp:gallery {\\"ids\\":[null]} --> -<figure class=\\"wp-block-gallery columns-1 is-cropped\\"><ul class=\\"blocks-gallery-grid\\"><li class=\\"blocks-gallery-item\\"><figure><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"\\"/></figure></li></ul></figure> -<!-- /wp:gallery -->" -`; - -exports[`Block transforms correctly transform block Image in fixture core__image__media-link into the Media & Text block 1`] = ` -"<!-- wp:media-text {\\"mediaType\\":\\"image\\"} --> -<div class=\\"wp-block-media-text alignwide is-stacked-on-mobile\\"><figure class=\\"wp-block-media-text__media\\"><img src=\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==\\" alt=\\"\\"/></figure><div class=\\"wp-block-media-text__content\\"><!-- wp:paragraph {\\"placeholder\\":\\"Content…\\",\\"fontSize\\":\\"large\\"} --> -<p class=\\"has-large-font-size\\"></p> -<!-- /wp:paragraph --></div></div> -<!-- /wp:media-text -->" -`; - -exports[`Block transforms correctly transform block List in fixture core__list__ul into the Paragraph block 1`] = ` -"<!-- wp:paragraph --> -<p>Text &amp; Headings</p> -<!-- /wp:paragraph --> - -<!-- wp:paragraph --> -<p>Images &amp; Videos</p> -<!-- /wp:paragraph --> - -<!-- wp:paragraph --> -<p>Galleries</p> -<!-- /wp:paragraph --> - -<!-- wp:paragraph --> -<p>Embeds, like YouTube, Tweets, or other WordPress posts.</p> -<!-- /wp:paragraph --> - -<!-- wp:paragraph --> -<p>Layout blocks, like Buttons, Hero Images, Separators, etc.</p> -<!-- /wp:paragraph --> - -<!-- wp:paragraph --> -<p>And <em>Lists</em> like this one of course :)</p> -<!-- /wp:paragraph -->" -`; - -exports[`Block transforms correctly transform block List in fixture core__list__ul into the Quote block 1`] = ` -"<!-- wp:quote --> -<blockquote class=\\"wp-block-quote\\"><p>Text &amp; Headings</p><p>Images &amp; Videos</p><p>Galleries</p><p>Embeds, like YouTube, Tweets, or other WordPress posts.</p><p>Layout blocks, like Buttons, Hero Images, Separators, etc.</p><p>And <em>Lists</em> like this one of course :)</p></blockquote> -<!-- /wp:quote -->" -`; - -exports[`Block transforms correctly transform block Media & Text in fixture core__media-text into the Image block 1`] = ` -"<!-- wp:image --> -<figure class=\\"wp-block-image\\"><img src=\\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\\" alt=\\"\\"/></figure> -<!-- /wp:image -->" -`; - -exports[`Block transforms correctly transform block Media & Text in fixture core__media-text__image-alt-no-align into the Image block 1`] = ` -"<!-- wp:image --> -<figure class=\\"wp-block-image\\"><img src=\\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\\" alt=\\"my alt\\"/></figure> -<!-- /wp:image -->" -`; - -exports[`Block transforms correctly transform block Media & Text in fixture core__media-text__image-fill-no-focal-point-selected into the Image block 1`] = ` -"<!-- wp:image --> -<figure class=\\"wp-block-image\\"><img src=\\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\\" alt=\\"My alt text\\"/></figure> -<!-- /wp:image -->" -`; - -exports[`Block transforms correctly transform block Media & Text in fixture core__media-text__image-fill-with-focal-point-selected into the Image block 1`] = ` -"<!-- wp:image --> -<figure class=\\"wp-block-image\\"><img src=\\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\\" alt=\\"My alt text\\"/></figure> -<!-- /wp:image -->" -`; - -exports[`Block transforms correctly transform block Media & Text in fixture core__media-text__is-stacked-on-mobile into the Video block 1`] = ` -"<!-- wp:video --> -<figure class=\\"wp-block-video\\"><video controls src=\\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\\"></video></figure> -<!-- /wp:video -->" -`; - -exports[`Block transforms correctly transform block Media & Text in fixture core__media-text__media-right-custom-width into the Video block 1`] = ` -"<!-- wp:video --> -<figure class=\\"wp-block-video\\"><video controls src=\\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\\"></video></figure> -<!-- /wp:video -->" -`; - -exports[`Block transforms correctly transform block Media & Text in fixture core__media-text__vertical-align-bottom into the Image block 1`] = ` -"<!-- wp:image --> -<figure class=\\"wp-block-image\\"><img src=\\"data:image/jpeg;base64,/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=\\" alt=\\"My alt text\\"/></figure> -<!-- /wp:image -->" -`; - -exports[`Block transforms correctly transform block Media & Text in fixture core__media-text__video into the Video block 1`] = ` -"<!-- wp:video --> -<figure class=\\"wp-block-video\\"><video controls src=\\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\\"></video></figure> -<!-- /wp:video -->" -`; - -exports[`Block transforms correctly transform block Paragraph in fixture core__paragraph__align-right into the Group block 1`] = ` -"<!-- wp:group --> -<div class=\\"wp-block-group\\"><div class=\\"wp-block-group__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"right\\"} --> -<p class=\\"has-text-align-right\\">... like this one, which is separate from the above and right aligned.</p> -<!-- /wp:paragraph --></div></div> -<!-- /wp:group -->" -`; - -exports[`Block transforms correctly transform block Paragraph in fixture core__paragraph__align-right into the Heading block 1`] = ` -"<!-- wp:heading --> -<h2>... like this one, which is separate from the above and right aligned.</h2> -<!-- /wp:heading -->" -`; - -exports[`Block transforms correctly transform block Paragraph in fixture core__paragraph__align-right into the List block 1`] = ` -"<!-- wp:list --> -<ul><li>... like this one, which is separate from the above and right aligned.</li></ul> -<!-- /wp:list -->" -`; - -exports[`Block transforms correctly transform block Paragraph in fixture core__paragraph__align-right into the Preformatted block 1`] = ` -"<!-- wp:preformatted --> -<pre class=\\"wp-block-preformatted\\">... like this one, which is separate from the above and right aligned.</pre> -<!-- /wp:preformatted -->" -`; - -exports[`Block transforms correctly transform block Paragraph in fixture core__paragraph__align-right into the Quote block 1`] = ` -"<!-- wp:quote --> -<blockquote class=\\"wp-block-quote\\"><p>... like this one, which is separate from the above and right aligned.</p></blockquote> -<!-- /wp:quote -->" -`; - -exports[`Block transforms correctly transform block Paragraph in fixture core__paragraph__align-right into the Verse block 1`] = ` -"<!-- wp:verse --> -<pre class=\\"wp-block-verse\\">... like this one, which is separate from the above and right aligned.</pre> -<!-- /wp:verse -->" -`; - -exports[`Block transforms correctly transform block Preformatted in fixture core__preformatted into the Paragraph block 1`] = ` -"<!-- wp:paragraph --> -<p>Some <em>preformatted</em> text...<br>And more!</p> -<!-- /wp:paragraph -->" -`; - -exports[`Block transforms correctly transform block Pullquote in fixture core__pullquote into the Quote block 1`] = ` -"<!-- wp:quote --> -<blockquote class=\\"wp-block-quote\\"><p>Testing pullquote block...</p><cite>...with a caption</cite></blockquote> -<!-- /wp:quote -->" -`; - -exports[`Block transforms correctly transform block Pullquote in fixture core__pullquote__multi-paragraph into the Quote block 1`] = ` -"<!-- wp:quote --> -<blockquote class=\\"wp-block-quote\\"><p>Paragraph <strong>one</strong></p><p>Paragraph two</p><cite>by whomever</cite></blockquote> -<!-- /wp:quote -->" -`; - -exports[`Block transforms correctly transform block Quote in fixture core__quote__style-1 into the Heading block 1`] = ` -"<!-- wp:heading --> -<h2>The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.</h2> -<!-- /wp:heading --> - -<!-- wp:quote --> -<blockquote class=\\"wp-block-quote\\"><p></p><cite>Matt Mullenweg, 2017</cite></blockquote> -<!-- /wp:quote -->" -`; - -exports[`Block transforms correctly transform block Quote in fixture core__quote__style-1 into the List block 1`] = ` -"<!-- wp:list --> -<ul><li>The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.</li></ul> -<!-- /wp:list -->" -`; - -exports[`Block transforms correctly transform block Quote in fixture core__quote__style-1 into the Paragraph block 1`] = ` -"<!-- wp:paragraph --> -<p>The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.</p> -<!-- /wp:paragraph --> - -<!-- wp:paragraph --> -<p>Matt Mullenweg, 2017</p> -<!-- /wp:paragraph -->" -`; - -exports[`Block transforms correctly transform block Quote in fixture core__quote__style-1 into the Pullquote block 1`] = ` -"<!-- wp:pullquote --> -<figure class=\\"wp-block-pullquote\\"><blockquote><p>The editor will endeavour to create a new page and post building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery.</p><cite>Matt Mullenweg, 2017</cite></blockquote></figure> -<!-- /wp:pullquote -->" -`; - -exports[`Block transforms correctly transform block Quote in fixture core__quote__style-2 into the Heading block 1`] = ` -"<!-- wp:heading --> -<h2>There is no greater agony than bearing an untold story inside you.</h2> -<!-- /wp:heading --> - -<!-- wp:quote {\\"className\\":\\"is-style-large\\"} --> -<blockquote class=\\"wp-block-quote is-style-large\\"><p></p><cite>Maya Angelou</cite></blockquote> -<!-- /wp:quote -->" -`; - -exports[`Block transforms correctly transform block Quote in fixture core__quote__style-2 into the List block 1`] = ` -"<!-- wp:list --> -<ul><li>There is no greater agony than bearing an untold story inside you.</li></ul> -<!-- /wp:list -->" -`; - -exports[`Block transforms correctly transform block Quote in fixture core__quote__style-2 into the Paragraph block 1`] = ` -"<!-- wp:paragraph --> -<p>There is no greater agony than bearing an untold story inside you.</p> -<!-- /wp:paragraph --> - -<!-- wp:paragraph --> -<p>Maya Angelou</p> -<!-- /wp:paragraph -->" -`; - -exports[`Block transforms correctly transform block Quote in fixture core__quote__style-2 into the Pullquote block 1`] = ` -"<!-- wp:pullquote --> -<figure class=\\"wp-block-pullquote\\"><blockquote><p>There is no greater agony than bearing an untold story inside you.</p><cite>Maya Angelou</cite></blockquote></figure> -<!-- /wp:pullquote -->" -`; - -exports[`Block transforms correctly transform block Verse in fixture core__verse into the Paragraph block 1`] = ` -"<!-- wp:paragraph --> -<p>A <em>verse</em>…<br>And more!</p> -<!-- /wp:paragraph -->" -`; - -exports[`Block transforms correctly transform block Video in fixture core__video into the Cover block 1`] = ` -"<!-- wp:cover {\\"url\\":\\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\\",\\"backgroundType\\":\\"video\\"} --> -<div class=\\"wp-block-cover has-background-dim\\"><video class=\\"wp-block-cover__video-background\\" autoplay muted loop src=\\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\\"></video><div class=\\"wp-block-cover__inner-container\\"><!-- wp:paragraph {\\"align\\":\\"center\\",\\"placeholder\\":\\"Write title…\\",\\"fontSize\\":\\"large\\"} --> -<p class=\\"has-text-align-center has-large-font-size\\"></p> -<!-- /wp:paragraph --></div></div> -<!-- /wp:cover -->" -`; - -exports[`Block transforms correctly transform block Video in fixture core__video into the File block 1`] = ` -"<!-- wp:file {\\"href\\":\\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\\"} --> -<div class=\\"wp-block-file\\"><a href=\\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\\" class=\\"wp-block-file__button\\" download>Download</a></div> -<!-- /wp:file -->" -`; - -exports[`Block transforms correctly transform block Video in fixture core__video into the Media & Text block 1`] = ` -"<!-- wp:media-text {\\"mediaType\\":\\"video\\"} --> -<div class=\\"wp-block-media-text alignwide is-stacked-on-mobile\\"><figure class=\\"wp-block-media-text__media\\"><video controls src=\\"data:video/mp4;base64,AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAAC721kYXQhEAUgpBv/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3pwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcCEQBSCkG//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADengAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAsJtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAALwABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAB7HRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAIAAAAAAAAALwAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAC8AAAAAAAEAAAAAAWRtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAKxEAAAIAFXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAEPbWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAADTc3RibAAAAGdzdHNkAAAAAAAAAAEAAABXbXA0YQAAAAAAAAABAAAAAAAAAAAAAgAQAAAAAKxEAAAAAAAzZXNkcwAAAAADgICAIgACAASAgIAUQBUAAAAAAfQAAAHz+QWAgIACEhAGgICAAQIAAAAYc3R0cwAAAAAAAAABAAAAAgAABAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAAIAAAABAAAAHHN0c3oAAAAAAAAAAAAAAAIAAAFzAAABdAAAABRzdGNvAAAAAAAAAAEAAAAsAAAAYnVkdGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1Ni40MC4xMDE=\\"></video></figure><div class=\\"wp-block-media-text__content\\"><!-- wp:paragraph {\\"placeholder\\":\\"Content…\\",\\"fontSize\\":\\"large\\"} --> -<p class=\\"has-large-font-size\\"></p> -<!-- /wp:paragraph --></div></div> -<!-- /wp:media-text -->" -`; diff --git a/packages/e2e-tests/specs/block-transforms.test.js b/packages/e2e-tests/specs/block-transforms.test.js deleted file mode 100644 index 381facd4c00673..00000000000000 --- a/packages/e2e-tests/specs/block-transforms.test.js +++ /dev/null @@ -1,173 +0,0 @@ -/** - * External dependencies - */ -import { - flatMap, - map, - mapValues, - pickBy, - some, -} from 'lodash'; - -/** - * WordPress dependencies - */ -import { - getAllBlocks, - getAvailableBlockTransforms, - getBlockSetting, - getEditedPostContent, - hasBlockSwitcher, - createNewPost, - enableExperimentalFeatures, - setPostContent, - selectBlockByClientId, - transformBlockTo, -} from '@wordpress/e2e-test-utils'; - -/** - * Internal dependencies - */ -import { - getAvailableBlockFixturesBasenames, - getBlockFixtureHTML, - getBlockFixtureParsedJSON, -} from '../fixtures/'; -import { EXPECTED_TRANSFORMS } from '../fixtures/block-transforms.js'; - -/* -* Returns true if the fileBase refers to a fixture of a block -* that should not be handled e.g: because of being deprecated, -* or because of being a block that tests an error state. -*/ -const isAnExpectedUnhandledBlock = ( fileBase ) => { - if ( fileBase.includes( 'deprecated' ) ) { - return true; - } - const { file: fixture } = getBlockFixtureParsedJSON( fileBase ); - const parsedBlockObject = JSON.parse( - fixture - )[ 0 ]; - return some( - [ - null, - 'core/block', - 'core/column', - 'core/freeform', - 'core/text-columns', - 'core/subhead', - 'core/text', - 'unregistered/example', - ], - ( blockName ) => parsedBlockObject.blockName === blockName - ); -}; - -const setPostContentAndSelectBlock = async ( content ) => { - await setPostContent( content ); - const blocks = await getAllBlocks(); - const clientId = blocks[ 0 ].clientId; - await page.click( '.editor-post-title .editor-post-title__block' ); - await selectBlockByClientId( clientId ); -}; - -const getTransformStructureFromFile = async ( fileBase ) => { - if ( isAnExpectedUnhandledBlock( fileBase ) ) { - return null; - } - const { file: content } = getBlockFixtureHTML( fileBase ); - await setPostContentAndSelectBlock( content ); - const block = ( await getAllBlocks() )[ 0 ]; - const availableTransforms = await getAvailableBlockTransforms(); - const originalBlock = await getBlockSetting( block.name, 'title' ); - - return { - availableTransforms, - originalBlock, - content, - }; -}; - -const getTransformResult = async ( blockContent, transformName ) => { - await setPostContentAndSelectBlock( blockContent ); - expect( await hasBlockSwitcher() ).toBe( true ); - await transformBlockTo( transformName ); - return getEditedPostContent(); -}; - -describe( 'Block transforms', () => { - const fileBasenames = getAvailableBlockFixturesBasenames(); - - const transformStructure = {}; - beforeAll( async () => { - await enableExperimentalFeatures( [ '#gutenberg-widget-experiments', '#gutenberg-menu-block' ] ); - await createNewPost(); - - for ( const fileBase of fileBasenames ) { - const structure = await getTransformStructureFromFile( - fileBase - ); - if ( ! structure ) { - continue; - } - transformStructure[ fileBase ] = structure; - await setPostContent( '' ); - await page.click( '.editor-post-title .editor-post-title__block' ); - } - } ); - - it( 'should contain the expected transforms', async () => { - const transforms = mapValues( - pickBy( - transformStructure, - ( { availableTransforms } ) => availableTransforms, - ), - ( { availableTransforms, originalBlock } ) => { - return { originalBlock, availableTransforms }; - } - ); - expect( - transforms - ).toEqual( EXPECTED_TRANSFORMS ); - } ); - - describe( 'correctly transform', () => { - beforeAll( async () => { - await createNewPost(); - } ); - - beforeEach( async () => { - await setPostContent( '' ); - await page.click( '.editor-post-title .editor-post-title__block' ); - } ); - - const testTable = flatMap( - EXPECTED_TRANSFORMS, - ( { originalBlock, availableTransforms }, fixture ) => ( - map( - availableTransforms, - ( destinationBlock ) => ( [ - originalBlock, - fixture, - destinationBlock, - ] ) - ) - ) - ); - - // As Group is available as a transform on *all* blocks this would create a lot of - // tests which would impact on the performance of the e2e test suite. - // To avoid this, we remove `core/group` from test table for all but 2 block types. - const testTableWithSomeGroupsFiltered = testTable.filter( ( transform ) => ( transform[ 2 ] !== 'Group' || transform[ 1 ] === 'core__paragraph__align-right' || transform[ 1 ] === 'core__image' ) ); - - it.each( testTableWithSomeGroupsFiltered )( - 'block %s in fixture %s into the %s block', - async ( originalBlock, fixture, destinationBlock ) => { - const { content } = transformStructure[ fixture ]; - expect( - await getTransformResult( content, destinationBlock ) - ).toMatchSnapshot(); - } - ); - } ); -} ); diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap b/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap deleted file mode 100644 index 1a7d08c1ff86fc..00000000000000 --- a/packages/e2e-tests/specs/blocks/__snapshots__/table.test.js.snap +++ /dev/null @@ -1,49 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Table allows adding and deleting columns across the table header, body and footer 1`] = ` -"<!-- wp:table --> -<figure class=\\"wp-block-table\\"><table class=\\"\\"><thead><tr><th></th><th></th><th></th></tr></thead><tbody><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr></tbody><tfoot><tr><td></td><td></td><td></td></tr></tfoot></table></figure> -<!-- /wp:table -->" -`; - -exports[`Table allows adding and deleting columns across the table header, body and footer 2`] = ` -"<!-- wp:table --> -<figure class=\\"wp-block-table\\"><table class=\\"\\"><thead><tr><th></th><th></th></tr></thead><tbody><tr><td></td><td></td></tr><tr><td></td><td></td></tr></tbody><tfoot><tr><td></td><td></td></tr></tfoot></table></figure> -<!-- /wp:table -->" -`; - -exports[`Table allows cells to be selected when the cell area outside of the RichText is clicked 1`] = ` -"<!-- wp:table {\\"hasFixedLayout\\":true} --> -<figure class=\\"wp-block-table\\"><table class=\\"has-fixed-layout\\"><tbody><tr><td><br><br><br><br></td><td>Second cell.</td></tr><tr><td></td><td></td></tr></tbody></table></figure> -<!-- /wp:table -->" -`; - -exports[`Table allows columns to be aligned 1`] = ` -"<!-- wp:table --> -<figure class=\\"wp-block-table\\"><table class=\\"\\"><tbody><tr><td>None</td><td class=\\"has-text-align-left\\" data-align=\\"left\\">To the left</td><td class=\\"has-text-align-center\\" data-align=\\"center\\">Centered</td><td class=\\"has-text-align-right\\" data-align=\\"right\\">Right aligned</td></tr><tr><td></td><td class=\\"has-text-align-left\\" data-align=\\"left\\"></td><td class=\\"has-text-align-center\\" data-align=\\"center\\"></td><td class=\\"has-text-align-right\\" data-align=\\"right\\"></td></tr></tbody></table></figure> -<!-- /wp:table -->" -`; - -exports[`Table allows header and footer rows to be switched on and off 1`] = ` -"<!-- wp:table --> -<figure class=\\"wp-block-table\\"><table class=\\"\\"><thead><tr><th>header</th><th></th></tr></thead><tbody><tr><td>body</td><td></td></tr><tr><td></td><td></td></tr></tbody><tfoot><tr><td>footer</td><td></td></tr></tfoot></table></figure> -<!-- /wp:table -->" -`; - -exports[`Table allows header and footer rows to be switched on and off 2`] = ` -"<!-- wp:table --> -<figure class=\\"wp-block-table\\"><table class=\\"\\"><tbody><tr><td>body</td><td></td></tr><tr><td></td><td></td></tr></tbody></table></figure> -<!-- /wp:table -->" -`; - -exports[`Table allows text to by typed into cells 1`] = ` -"<!-- wp:table --> -<figure class=\\"wp-block-table\\"><table class=\\"\\"><tbody><tr><td>This</td><td>is</td></tr><tr><td>table</td><td>block</td></tr></tbody></table></figure> -<!-- /wp:table -->" -`; - -exports[`Table displays a form for choosing the row and column count of the table 1`] = ` -"<!-- wp:table --> -<figure class=\\"wp-block-table\\"><table class=\\"\\"><tbody><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr></tbody></table></figure> -<!-- /wp:table -->" -`; diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/button.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/button.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/blocks/__snapshots__/button.test.js.snap rename to packages/e2e-tests/specs/editor/blocks/__snapshots__/button.test.js.snap diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/classic.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/classic.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/blocks/__snapshots__/classic.test.js.snap rename to packages/e2e-tests/specs/editor/blocks/__snapshots__/classic.test.js.snap diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/code.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/code.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/blocks/__snapshots__/code.test.js.snap rename to packages/e2e-tests/specs/editor/blocks/__snapshots__/code.test.js.snap diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/group.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/group.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/blocks/__snapshots__/group.test.js.snap rename to packages/e2e-tests/specs/editor/blocks/__snapshots__/group.test.js.snap diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/heading.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/heading.test.js.snap similarity index 89% rename from packages/e2e-tests/specs/blocks/__snapshots__/heading.test.js.snap rename to packages/e2e-tests/specs/editor/blocks/__snapshots__/heading.test.js.snap index d33845c007117c..7e583c75cc68ed 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/heading.test.js.snap +++ b/packages/e2e-tests/specs/editor/blocks/__snapshots__/heading.test.js.snap @@ -19,8 +19,8 @@ exports[`Heading it should correctly apply custom colors 1`] = ` `; exports[`Heading it should correctly apply named colors 1`] = ` -"<!-- wp:heading {\\"textColor\\":\\"accent\\"} --> -<h2 class=\\"has-accent-color has-text-color\\">Heading</h2> +"<!-- wp:heading {\\"textColor\\":\\"very-dark-gray\\"} --> +<h2 class=\\"has-very-dark-gray-color has-text-color\\">Heading</h2> <!-- /wp:heading -->" `; diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/html.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/html.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/blocks/__snapshots__/html.test.js.snap rename to packages/e2e-tests/specs/editor/blocks/__snapshots__/html.test.js.snap diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/list.test.js.snap similarity index 92% rename from packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap rename to packages/e2e-tests/specs/editor/blocks/__snapshots__/list.test.js.snap index 1f42160e3fc922..9a14e06f52d8b0 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/list.test.js.snap +++ b/packages/e2e-tests/specs/editor/blocks/__snapshots__/list.test.js.snap @@ -80,6 +80,12 @@ exports[`List can undo asterisk transform 1`] = ` <!-- /wp:paragraph -->" `; +exports[`List first empty list item is graciously removed 1`] = ` +"<!-- wp:list --> +<ul><li>2</li></ul> +<!-- /wp:list -->" +`; + exports[`List should be immeadiately saved on indentation 1`] = ` "<!-- wp:list --> <ul><li>one<ul><li></li></ul></li></ul> @@ -234,6 +240,18 @@ exports[`List should place the caret in the right place with nested list 1`] = ` <!-- /wp:list -->" `; +exports[`List should preserve indentation after merging backward and forward 1`] = ` +"<!-- wp:list --> +<ul><li>1<ul><li>2</li><li>3</li></ul></li><li></li></ul> +<!-- /wp:list -->" +`; + +exports[`List should preserve indentation after merging backward and forward 2`] = ` +"<!-- wp:list --> +<ul><li>1<ul><li>2</li><li>3</li></ul></li></ul> +<!-- /wp:list -->" +`; + exports[`List should split indented list item 1`] = ` "<!-- wp:list --> <ul><li>one<ul><li>two</li><li>three</li></ul></li></ul> @@ -290,6 +308,12 @@ exports[`List should undo asterisk transform with backspace 1`] = ` <!-- /wp:paragraph -->" `; +exports[`List should undo asterisk transform with backspace after mouse move 1`] = ` +"<!-- wp:paragraph --> +<p>* </p> +<!-- /wp:paragraph -->" +`; + exports[`List should undo asterisk transform with backspace after selection changes 1`] = ` "<!-- wp:paragraph --> <p>* </p> diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/preformatted.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/preformatted.test.js.snap similarity index 65% rename from packages/e2e-tests/specs/blocks/__snapshots__/preformatted.test.js.snap rename to packages/e2e-tests/specs/editor/blocks/__snapshots__/preformatted.test.js.snap index 07a70a19a6b2be..37881193bbacad 100644 --- a/packages/e2e-tests/specs/blocks/__snapshots__/preformatted.test.js.snap +++ b/packages/e2e-tests/specs/editor/blocks/__snapshots__/preformatted.test.js.snap @@ -14,3 +14,11 @@ exports[`Preformatted should preserve character newlines 2`] = ` 2</pre> <!-- /wp:preformatted -->" `; + +exports[`Preformatted should preserve white space when merging 1`] = ` +"<!-- wp:preformatted --> +<pre class=\\"wp-block-preformatted\\">1 +2 +3</pre> +<!-- /wp:preformatted -->" +`; diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/quote.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/quote.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/blocks/__snapshots__/quote.test.js.snap rename to packages/e2e-tests/specs/editor/blocks/__snapshots__/quote.test.js.snap diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/separator.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/separator.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/blocks/__snapshots__/separator.test.js.snap rename to packages/e2e-tests/specs/editor/blocks/__snapshots__/separator.test.js.snap diff --git a/packages/e2e-tests/specs/blocks/__snapshots__/spacer.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/spacer.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/blocks/__snapshots__/spacer.test.js.snap rename to packages/e2e-tests/specs/editor/blocks/__snapshots__/spacer.test.js.snap diff --git a/packages/e2e-tests/specs/editor/blocks/__snapshots__/table.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/table.test.js.snap new file mode 100644 index 00000000000000..c3545a01d8254d --- /dev/null +++ b/packages/e2e-tests/specs/editor/blocks/__snapshots__/table.test.js.snap @@ -0,0 +1,55 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Table allows a caption to be added 1`] = ` +"<!-- wp:table --> +<figure class=\\"wp-block-table\\"><table><tbody><tr><td></td><td></td></tr><tr><td></td><td></td></tr></tbody></table><figcaption>Caption!</figcaption></figure> +<!-- /wp:table -->" +`; + +exports[`Table allows adding and deleting columns across the table header, body and footer 1`] = ` +"<!-- wp:table --> +<figure class=\\"wp-block-table\\"><table><thead><tr><th></th><th></th><th></th></tr></thead><tbody><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr></tbody><tfoot><tr><td></td><td></td><td></td></tr></tfoot></table></figure> +<!-- /wp:table -->" +`; + +exports[`Table allows adding and deleting columns across the table header, body and footer 2`] = ` +"<!-- wp:table --> +<figure class=\\"wp-block-table\\"><table><thead><tr><th></th><th></th></tr></thead><tbody><tr><td></td><td></td></tr><tr><td></td><td></td></tr></tbody><tfoot><tr><td></td><td></td></tr></tfoot></table></figure> +<!-- /wp:table -->" +`; + +exports[`Table allows cells to be selected when the cell area outside of the RichText is clicked 1`] = ` +"<!-- wp:table {\\"hasFixedLayout\\":true} --> +<figure class=\\"wp-block-table\\"><table class=\\"has-fixed-layout\\"><tbody><tr><td><br><br><br><br></td><td>Second cell.</td></tr><tr><td></td><td></td></tr></tbody></table></figure> +<!-- /wp:table -->" +`; + +exports[`Table allows columns to be aligned 1`] = ` +"<!-- wp:table --> +<figure class=\\"wp-block-table\\"><table><tbody><tr><td>None</td><td class=\\"has-text-align-left\\" data-align=\\"left\\">To the left</td><td class=\\"has-text-align-center\\" data-align=\\"center\\">Centered</td><td class=\\"has-text-align-right\\" data-align=\\"right\\">Right aligned</td></tr><tr><td></td><td class=\\"has-text-align-left\\" data-align=\\"left\\"></td><td class=\\"has-text-align-center\\" data-align=\\"center\\"></td><td class=\\"has-text-align-right\\" data-align=\\"right\\"></td></tr></tbody></table></figure> +<!-- /wp:table -->" +`; + +exports[`Table allows header and footer rows to be switched on and off 1`] = ` +"<!-- wp:table --> +<figure class=\\"wp-block-table\\"><table><thead><tr><th>header</th><th></th></tr></thead><tbody><tr><td>body</td><td></td></tr><tr><td></td><td></td></tr></tbody><tfoot><tr><td>footer</td><td></td></tr></tfoot></table></figure> +<!-- /wp:table -->" +`; + +exports[`Table allows header and footer rows to be switched on and off 2`] = ` +"<!-- wp:table --> +<figure class=\\"wp-block-table\\"><table><tbody><tr><td>body</td><td></td></tr><tr><td></td><td></td></tr></tbody></table></figure> +<!-- /wp:table -->" +`; + +exports[`Table allows text to by typed into cells 1`] = ` +"<!-- wp:table --> +<figure class=\\"wp-block-table\\"><table><tbody><tr><td>This</td><td>is</td></tr><tr><td>table</td><td>block</td></tr></tbody></table></figure> +<!-- /wp:table -->" +`; + +exports[`Table displays a form for choosing the row and column count of the table 1`] = ` +"<!-- wp:table --> +<figure class=\\"wp-block-table\\"><table><tbody><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr></tbody></table></figure> +<!-- /wp:table -->" +`; diff --git a/packages/e2e-tests/specs/blocks/button.test.js b/packages/e2e-tests/specs/editor/blocks/button.test.js similarity index 100% rename from packages/e2e-tests/specs/blocks/button.test.js rename to packages/e2e-tests/specs/editor/blocks/button.test.js diff --git a/packages/e2e-tests/specs/blocks/classic.test.js b/packages/e2e-tests/specs/editor/blocks/classic.test.js similarity index 95% rename from packages/e2e-tests/specs/blocks/classic.test.js rename to packages/e2e-tests/specs/editor/blocks/classic.test.js index 29d1605f0874b5..29170335d900f1 100644 --- a/packages/e2e-tests/specs/blocks/classic.test.js +++ b/packages/e2e-tests/specs/editor/blocks/classic.test.js @@ -49,7 +49,7 @@ describe( 'Classic', () => { // Wait for media modal to appear and upload image. await page.waitForSelector( '.media-modal input[type=file]' ); const inputElement = await page.$( '.media-modal input[type=file]' ); - const testImagePath = path.join( __dirname, '..', '..', 'assets', '10x10_e2e_test_image_z9T8jK.png' ); + const testImagePath = path.join( __dirname, '..', '..', '..', 'assets', '10x10_e2e_test_image_z9T8jK.png' ); const filename = uuid(); const tmpFileName = path.join( os.tmpdir(), filename + '.png' ); fs.copyFileSync( testImagePath, tmpFileName ); diff --git a/packages/e2e-tests/specs/blocks/code.test.js b/packages/e2e-tests/specs/editor/blocks/code.test.js similarity index 100% rename from packages/e2e-tests/specs/blocks/code.test.js rename to packages/e2e-tests/specs/editor/blocks/code.test.js diff --git a/packages/e2e-tests/specs/blocks/columns.test.js b/packages/e2e-tests/specs/editor/blocks/columns.test.js similarity index 100% rename from packages/e2e-tests/specs/blocks/columns.test.js rename to packages/e2e-tests/specs/editor/blocks/columns.test.js diff --git a/packages/e2e-tests/specs/blocks/group.test.js b/packages/e2e-tests/specs/editor/blocks/group.test.js similarity index 100% rename from packages/e2e-tests/specs/blocks/group.test.js rename to packages/e2e-tests/specs/editor/blocks/group.test.js diff --git a/packages/e2e-tests/specs/blocks/heading.test.js b/packages/e2e-tests/specs/editor/blocks/heading.test.js similarity index 90% rename from packages/e2e-tests/specs/blocks/heading.test.js rename to packages/e2e-tests/specs/editor/blocks/heading.test.js index c6936dc8238ed3..e58d11e1343d00 100644 --- a/packages/e2e-tests/specs/blocks/heading.test.js +++ b/packages/e2e-tests/specs/editor/blocks/heading.test.js @@ -77,11 +77,11 @@ describe( 'Heading', () => { const [ colorPanelToggle ] = await page.$x( COLOR_PANEL_TOGGLE_X_SELECTOR ); await colorPanelToggle.click(); - const accentColorButtonSelector = `${ TEXT_COLOR_UI_X_SELECTOR }//button[@aria-label='Color: Accent Color']`; - const [ accentColorButton ] = await page.$x( accentColorButtonSelector ); - await accentColorButton.click(); + const colorButtonSelector = `${ TEXT_COLOR_UI_X_SELECTOR }//button[@aria-label='Color: Very dark gray']`; + const [ colorButton ] = await page.$x( colorButtonSelector ); + await colorButton.click(); await page.click( '[data-type="core/heading"] h2' ); - await page.waitForXPath( `${ accentColorButtonSelector }[@aria-pressed='true']` ); + await page.waitForXPath( `${ colorButtonSelector }[@aria-pressed='true']` ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); } ); diff --git a/packages/e2e-tests/specs/blocks/html.test.js b/packages/e2e-tests/specs/editor/blocks/html.test.js similarity index 100% rename from packages/e2e-tests/specs/blocks/html.test.js rename to packages/e2e-tests/specs/editor/blocks/html.test.js diff --git a/packages/e2e-tests/specs/blocks/list.test.js b/packages/e2e-tests/specs/editor/blocks/list.test.js similarity index 89% rename from packages/e2e-tests/specs/blocks/list.test.js rename to packages/e2e-tests/specs/editor/blocks/list.test.js index d456a0c064773a..54cbe86f2841ad 100644 --- a/packages/e2e-tests/specs/blocks/list.test.js +++ b/packages/e2e-tests/specs/editor/blocks/list.test.js @@ -72,6 +72,15 @@ describe( 'List', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + it( 'should undo asterisk transform with backspace after mouse move', async () => { + await clickBlockAppender(); + await page.keyboard.type( '* ' ); + await page.mouse.move( 0, 0, { steps: 10 } ); + await page.keyboard.press( 'Backspace' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + it( 'should undo asterisk transform with backspace after selection changes without requestIdleCallback', async () => { await clickBlockAppender(); await page.evaluate( () => delete window.requestIdleCallback ); @@ -446,4 +455,48 @@ describe( 'List', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should preserve indentation after merging backward and forward', async () => { + await clickBlockAppender(); + + // Tests the shortcut with a non breaking space. + await page.keyboard.type( '* 1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.press( 'Space' ); + await page.keyboard.type( '2' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '3' ); + + // Create a new paragraph. + await page.keyboard.press( 'Enter' ); + await page.keyboard.press( 'Enter' ); + + // Merge the pragraph back. No list items should be joined. + await page.keyboard.press( 'Backspace' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + + // Again create a new paragraph. + await page.keyboard.press( 'Enter' ); + + // Move to the end of the list. + await page.keyboard.press( 'ArrowLeft' ); + + // Merge forward. No list items should be joined. + await page.keyboard.press( 'Delete' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'first empty list item is graciously removed', async () => { + await clickBlockAppender(); + await page.keyboard.type( '* 1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2' ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.press( 'Backspace' ); + await page.keyboard.press( 'Backspace' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/e2e-tests/specs/editor/blocks/navigation.test.js b/packages/e2e-tests/specs/editor/blocks/navigation.test.js new file mode 100644 index 00000000000000..7c3d0f26a33e06 --- /dev/null +++ b/packages/e2e-tests/specs/editor/blocks/navigation.test.js @@ -0,0 +1,29 @@ + +/** + * WordPress dependencies + */ +import { + createNewPost, + insertBlock, +} from '@wordpress/e2e-test-utils'; + +describe( 'Adds Navigation links', () => { + beforeEach( async () => { + await createNewPost(); + } ); + + it( 'Should add a link with one click', async () => { + await insertBlock( 'Navigation' ); + const [ createEmptyButton ] = await page.$x( '//button[text()="Create empty"]' ); + await createEmptyButton.click(); + await page.waitForSelector( 'input[placeholder="Search or type url"]' ); + await page.type( 'input[placeholder="Search or type url"]', 'http://example.com' ); + + await page.keyboard.press( 'Enter' ); + await page.click( '.wp-block-navigation .block-list-appender' ); + + const navigationLinkCount = await page.$$eval( '.wp-block-navigation-link', ( navigationLinks ) => navigationLinks.length ); + + expect( navigationLinkCount ).toBe( 2 ); + } ); +} ); diff --git a/packages/e2e-tests/specs/blocks/preformatted.test.js b/packages/e2e-tests/specs/editor/blocks/preformatted.test.js similarity index 65% rename from packages/e2e-tests/specs/blocks/preformatted.test.js rename to packages/e2e-tests/specs/editor/blocks/preformatted.test.js index ea6575785613cf..11a1b0e5b14efd 100644 --- a/packages/e2e-tests/specs/blocks/preformatted.test.js +++ b/packages/e2e-tests/specs/editor/blocks/preformatted.test.js @@ -32,4 +32,18 @@ describe( 'Preformatted', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should preserve white space when merging', async () => { + await insertBlock( 'Preformatted' ); + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.press( 'Tab' ); + await page.keyboard.type( '3' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.press( 'Backspace' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/e2e-tests/specs/blocks/quote.test.js b/packages/e2e-tests/specs/editor/blocks/quote.test.js similarity index 100% rename from packages/e2e-tests/specs/blocks/quote.test.js rename to packages/e2e-tests/specs/editor/blocks/quote.test.js diff --git a/packages/e2e-tests/specs/blocks/separator.test.js b/packages/e2e-tests/specs/editor/blocks/separator.test.js similarity index 100% rename from packages/e2e-tests/specs/blocks/separator.test.js rename to packages/e2e-tests/specs/editor/blocks/separator.test.js diff --git a/packages/e2e-tests/specs/blocks/spacer.test.js b/packages/e2e-tests/specs/editor/blocks/spacer.test.js similarity index 100% rename from packages/e2e-tests/specs/blocks/spacer.test.js rename to packages/e2e-tests/specs/editor/blocks/spacer.test.js diff --git a/packages/e2e-tests/specs/blocks/table.test.js b/packages/e2e-tests/specs/editor/blocks/table.test.js similarity index 92% rename from packages/e2e-tests/specs/blocks/table.test.js rename to packages/e2e-tests/specs/editor/blocks/table.test.js index 15267da90cb185..99c4a195e458bb 100644 --- a/packages/e2e-tests/specs/blocks/table.test.js +++ b/packages/e2e-tests/specs/editor/blocks/table.test.js @@ -70,7 +70,7 @@ describe( 'Table', () => { await clickButton( createButtonLabel ); // Click the first cell and add some text. - await page.click( '.wp-block-table__cell-content' ); + await page.click( 'td' ); await page.keyboard.type( 'This' ); // Tab to the next cell and add some text. @@ -114,13 +114,13 @@ describe( 'Table', () => { await headerSwitch[ 0 ].click(); await footerSwitch[ 0 ].click(); - await page.click( 'thead .wp-block-table__cell-content' ); + await page.click( 'thead th' ); await page.keyboard.type( 'header' ); - await page.click( 'tbody .wp-block-table__cell-content' ); + await page.click( 'tbody td' ); await page.keyboard.type( 'body' ); - await page.click( 'tfoot .wp-block-table__cell-content' ); + await page.click( 'tfoot td' ); await page.keyboard.type( 'footer' ); // Expect the table to have a header, body and footer with written content. @@ -146,7 +146,7 @@ describe( 'Table', () => { await headerSwitch[ 0 ].click(); await footerSwitch[ 0 ].click(); - await page.click( '.wp-block-table__cell-content' ); + await page.click( 'td' ); // Add a column. await clickBlockToolbarButton( 'Edit table' ); @@ -155,7 +155,7 @@ describe( 'Table', () => { // Expect the table to have 3 columns across the header, body and footer. expect( await getEditedPostContent() ).toMatchSnapshot(); - await page.click( '.wp-block-table__cell-content' ); + await page.click( 'td' ); // Delete a column. await clickBlockToolbarButton( 'Edit table' ); @@ -177,7 +177,7 @@ describe( 'Table', () => { await clickButton( createButtonLabel ); // Click the first cell and add some text. Don't align. - const cells = await page.$$( '.wp-block-table__cell-content' ); + const cells = await page.$$( 'td,th' ); await cells[ 0 ].click(); await page.keyboard.type( 'None' ); @@ -212,7 +212,7 @@ describe( 'Table', () => { await fixedWidthSwitch.click(); // Add multiple new lines to the first cell to make it taller. - await page.click( '.wp-block-table__cell-content' ); + await page.click( 'td' ); await page.keyboard.type( '\n\n\n\n' ); // Get the bounding client rect for the second cell. @@ -232,4 +232,17 @@ describe( 'Table', () => { // Expect that the snapshot shows the text in the second cell. expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'allows a caption to be added', async () => { + await insertBlock( 'Table' ); + + // Create the table. + await clickButton( createButtonLabel ); + + // Click the first cell and add some text. + await page.click( '.wp-block-table figcaption' ); + await page.keyboard.type( 'Caption!' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/align-hook.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/align-hook.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/plugins/__snapshots__/align-hook.test.js.snap rename to packages/e2e-tests/specs/editor/plugins/__snapshots__/align-hook.test.js.snap diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/container-blocks.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/container-blocks.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/plugins/__snapshots__/container-blocks.test.js.snap rename to packages/e2e-tests/specs/editor/plugins/__snapshots__/container-blocks.test.js.snap diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/cpt-locking.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/cpt-locking.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/plugins/__snapshots__/cpt-locking.test.js.snap rename to packages/e2e-tests/specs/editor/plugins/__snapshots__/cpt-locking.test.js.snap diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/deprecated-node-matcher.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/deprecated-node-matcher.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/plugins/__snapshots__/deprecated-node-matcher.test.js.snap rename to packages/e2e-tests/specs/editor/plugins/__snapshots__/deprecated-node-matcher.test.js.snap diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/format-api.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/format-api.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/plugins/__snapshots__/format-api.test.js.snap rename to packages/e2e-tests/specs/editor/plugins/__snapshots__/format-api.test.js.snap diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/hooks-api.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/hooks-api.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/plugins/__snapshots__/hooks-api.test.js.snap rename to packages/e2e-tests/specs/editor/plugins/__snapshots__/hooks-api.test.js.snap diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/meta-attribute-block.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/meta-attribute-block.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/plugins/__snapshots__/meta-attribute-block.test.js.snap rename to packages/e2e-tests/specs/editor/plugins/__snapshots__/meta-attribute-block.test.js.snap diff --git a/packages/e2e-tests/specs/editor/plugins/__snapshots__/plugins-api.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/plugins-api.test.js.snap new file mode 100644 index 00000000000000..beb4e9b8a206e5 --- /dev/null +++ b/packages/e2e-tests/specs/editor/plugins/__snapshots__/plugins-api.test.js.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Using Plugins API Document Setting Custom Panel Should render a custom panel inside Document Setting sidebar 1`] = `"My Custom Panel"`; + +exports[`Using Plugins API Sidebar Medium screen Should open plugins sidebar using More Menu item and render content 1`] = `"<div class=\\"components-panel__header edit-post-sidebar-header__small\\"><span class=\\"edit-post-sidebar-header__title\\">(no title)</span><button type=\\"button\\" aria-label=\\"Close plugin\\" class=\\"components-button components-icon-button\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\" class=\\"dashicon dashicons-no-alt\\"><path d=\\"M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z\\"></path></svg></button></div><div class=\\"components-panel__header edit-post-sidebar-header\\"><strong>Sidebar title plugin</strong><button type=\\"button\\" aria-pressed=\\"true\\" aria-label=\\"Unpin from toolbar\\" aria-expanded=\\"true\\" class=\\"components-button components-icon-button is-pressed\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\" class=\\"dashicon dashicons-star-filled\\"><path d=\\"M10 1l3 6 6 .75-4.12 4.62L16 19l-6-3-6 3 1.13-6.63L1 7.75 7 7z\\"></path></svg></button><button type=\\"button\\" aria-label=\\"Close plugin\\" class=\\"components-button components-icon-button\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\" class=\\"dashicon dashicons-no-alt\\"><path d=\\"M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z\\"></path></svg></button></div><div class=\\"components-panel\\"><div class=\\"components-panel__body is-opened\\"><div class=\\"components-panel__row\\"><label for=\\"title-plain-text\\">Title:</label><textarea class=\\"editor-plain-text block-editor-plain-text\\" id=\\"title-plain-text\\" placeholder=\\"(no title)\\" rows=\\"1\\" style=\\"overflow: hidden; overflow-wrap: break-word; resize: none; height: 18px;\\"></textarea></div><div class=\\"components-panel__row\\"><button type=\\"button\\" class=\\"components-button is-button is-primary\\">Reset</button></div></div></div>"`; + +exports[`Using Plugins API Sidebar Should open plugins sidebar using More Menu item and render content 1`] = `"<div class=\\"components-panel__header edit-post-sidebar-header__small\\"><span class=\\"edit-post-sidebar-header__title\\">(no title)</span><button type=\\"button\\" aria-label=\\"Close plugin\\" class=\\"components-button components-icon-button\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\" class=\\"dashicon dashicons-no-alt\\"><path d=\\"M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z\\"></path></svg></button></div><div class=\\"components-panel__header edit-post-sidebar-header\\"><strong>Sidebar title plugin</strong><button type=\\"button\\" aria-pressed=\\"true\\" aria-label=\\"Unpin from toolbar\\" aria-expanded=\\"true\\" class=\\"components-button components-icon-button is-pressed\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\" class=\\"dashicon dashicons-star-filled\\"><path d=\\"M10 1l3 6 6 .75-4.12 4.62L16 19l-6-3-6 3 1.13-6.63L1 7.75 7 7z\\"></path></svg></button><button type=\\"button\\" aria-label=\\"Close plugin\\" class=\\"components-button components-icon-button\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\" class=\\"dashicon dashicons-no-alt\\"><path d=\\"M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z\\"></path></svg></button></div><div class=\\"components-panel\\"><div class=\\"components-panel__body is-opened\\"><div class=\\"components-panel__row\\"><label for=\\"title-plain-text\\">Title:</label><textarea class=\\"editor-plain-text block-editor-plain-text\\" id=\\"title-plain-text\\" placeholder=\\"(no title)\\" rows=\\"1\\" style=\\"overflow: hidden; overflow-wrap: break-word; resize: none; height: 18px;\\"></textarea></div><div class=\\"components-panel__row\\"><button type=\\"button\\" class=\\"components-button is-button is-primary\\">Reset</button></div></div></div>"`; diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/templates.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/templates.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/plugins/__snapshots__/templates.test.js.snap rename to packages/e2e-tests/specs/editor/plugins/__snapshots__/templates.test.js.snap diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/wp-editor-meta-box.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/wp-editor-meta-box.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/plugins/__snapshots__/wp-editor-meta-box.test.js.snap rename to packages/e2e-tests/specs/editor/plugins/__snapshots__/wp-editor-meta-box.test.js.snap diff --git a/packages/e2e-tests/specs/plugins/align-hook.test.js b/packages/e2e-tests/specs/editor/plugins/align-hook.test.js similarity index 100% rename from packages/e2e-tests/specs/plugins/align-hook.test.js rename to packages/e2e-tests/specs/editor/plugins/align-hook.test.js diff --git a/packages/e2e-tests/specs/plugins/allowed-blocks.test.js b/packages/e2e-tests/specs/editor/plugins/allowed-blocks.test.js similarity index 100% rename from packages/e2e-tests/specs/plugins/allowed-blocks.test.js rename to packages/e2e-tests/specs/editor/plugins/allowed-blocks.test.js diff --git a/packages/e2e-tests/specs/plugins/annotations.test.js b/packages/e2e-tests/specs/editor/plugins/annotations.test.js similarity index 100% rename from packages/e2e-tests/specs/plugins/annotations.test.js rename to packages/e2e-tests/specs/editor/plugins/annotations.test.js diff --git a/packages/e2e-tests/specs/plugins/block-icons.test.js b/packages/e2e-tests/specs/editor/plugins/block-icons.test.js similarity index 100% rename from packages/e2e-tests/specs/plugins/block-icons.test.js rename to packages/e2e-tests/specs/editor/plugins/block-icons.test.js diff --git a/packages/e2e-tests/specs/plugins/container-blocks.test.js b/packages/e2e-tests/specs/editor/plugins/container-blocks.test.js similarity index 100% rename from packages/e2e-tests/specs/plugins/container-blocks.test.js rename to packages/e2e-tests/specs/editor/plugins/container-blocks.test.js diff --git a/packages/e2e-tests/specs/plugins/cpt-locking.test.js b/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js similarity index 100% rename from packages/e2e-tests/specs/plugins/cpt-locking.test.js rename to packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js diff --git a/packages/e2e-tests/specs/plugins/custom-taxonomies.test.js b/packages/e2e-tests/specs/editor/plugins/custom-taxonomies.test.js similarity index 100% rename from packages/e2e-tests/specs/plugins/custom-taxonomies.test.js rename to packages/e2e-tests/specs/editor/plugins/custom-taxonomies.test.js diff --git a/packages/e2e-tests/specs/plugins/deprecated-node-matcher.test.js b/packages/e2e-tests/specs/editor/plugins/deprecated-node-matcher.test.js similarity index 100% rename from packages/e2e-tests/specs/plugins/deprecated-node-matcher.test.js rename to packages/e2e-tests/specs/editor/plugins/deprecated-node-matcher.test.js diff --git a/packages/e2e-tests/specs/plugins/format-api.test.js b/packages/e2e-tests/specs/editor/plugins/format-api.test.js similarity index 100% rename from packages/e2e-tests/specs/plugins/format-api.test.js rename to packages/e2e-tests/specs/editor/plugins/format-api.test.js diff --git a/packages/e2e-tests/specs/plugins/hooks-api.test.js b/packages/e2e-tests/specs/editor/plugins/hooks-api.test.js similarity index 100% rename from packages/e2e-tests/specs/plugins/hooks-api.test.js rename to packages/e2e-tests/specs/editor/plugins/hooks-api.test.js diff --git a/packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js b/packages/e2e-tests/specs/editor/plugins/inner-blocks-allowed-blocks.test.js similarity index 100% rename from packages/e2e-tests/specs/plugins/inner-blocks-allowed-blocks.test.js rename to packages/e2e-tests/specs/editor/plugins/inner-blocks-allowed-blocks.test.js diff --git a/packages/e2e-tests/specs/editor/plugins/innerblocks-locking-all-embed.js b/packages/e2e-tests/specs/editor/plugins/innerblocks-locking-all-embed.js new file mode 100644 index 00000000000000..3cba39c9f129d2 --- /dev/null +++ b/packages/e2e-tests/specs/editor/plugins/innerblocks-locking-all-embed.js @@ -0,0 +1,35 @@ +/** + * WordPress dependencies + */ +import { + activatePlugin, + createNewPost, + deactivatePlugin, + insertBlock, +} from '@wordpress/e2e-test-utils'; + +describe( 'Embed block inside a locked all parent', () => { + beforeAll( async () => { + await activatePlugin( 'gutenberg-test-innerblocks-locking-all-embed' ); + } ); + + beforeEach( async () => { + await createNewPost(); + } ); + + afterAll( async () => { + await deactivatePlugin( 'gutenberg-test-innerblocks-locking-all-embed' ); + } ); + + it( 'embed block should be able to embed external content', async () => { + await insertBlock( 'Test Inner Blocks Locking All Embed' ); + const embedInputSelector = '.components-placeholder__input[aria-label="Embed URL"]'; + await page.waitForSelector( embedInputSelector ); + await page.click( embedInputSelector ); + // This URL should not have a trailing slash. + await page.keyboard.type( 'https://twitter.com/WordPress' ); + await page.keyboard.press( 'Enter' ); + // The twitter block should appear correctly. + await page.waitForSelector( 'figure.wp-block-embed' ); + } ); +} ); diff --git a/packages/e2e-tests/specs/plugins/meta-attribute-block.test.js b/packages/e2e-tests/specs/editor/plugins/meta-attribute-block.test.js similarity index 100% rename from packages/e2e-tests/specs/plugins/meta-attribute-block.test.js rename to packages/e2e-tests/specs/editor/plugins/meta-attribute-block.test.js diff --git a/packages/e2e-tests/specs/plugins/meta-boxes.test.js b/packages/e2e-tests/specs/editor/plugins/meta-boxes.test.js similarity index 100% rename from packages/e2e-tests/specs/plugins/meta-boxes.test.js rename to packages/e2e-tests/specs/editor/plugins/meta-boxes.test.js diff --git a/packages/e2e-tests/specs/plugins/nonce.test.js b/packages/e2e-tests/specs/editor/plugins/nonce.test.js similarity index 100% rename from packages/e2e-tests/specs/plugins/nonce.test.js rename to packages/e2e-tests/specs/editor/plugins/nonce.test.js diff --git a/packages/e2e-tests/specs/plugins/plugins-api.test.js b/packages/e2e-tests/specs/editor/plugins/plugins-api.test.js similarity index 100% rename from packages/e2e-tests/specs/plugins/plugins-api.test.js rename to packages/e2e-tests/specs/editor/plugins/plugins-api.test.js diff --git a/packages/e2e-tests/specs/plugins/templates.test.js b/packages/e2e-tests/specs/editor/plugins/templates.test.js similarity index 100% rename from packages/e2e-tests/specs/plugins/templates.test.js rename to packages/e2e-tests/specs/editor/plugins/templates.test.js diff --git a/packages/e2e-tests/specs/plugins/wp-editor-meta-box.test.js b/packages/e2e-tests/specs/editor/plugins/wp-editor-meta-box.test.js similarity index 76% rename from packages/e2e-tests/specs/plugins/wp-editor-meta-box.test.js rename to packages/e2e-tests/specs/editor/plugins/wp-editor-meta-box.test.js index 74dd45374e946d..32286764092f17 100644 --- a/packages/e2e-tests/specs/plugins/wp-editor-meta-box.test.js +++ b/packages/e2e-tests/specs/editor/plugins/wp-editor-meta-box.test.js @@ -8,9 +8,7 @@ import { publishPost, } from '@wordpress/e2e-test-utils'; -// This test isn't reliable on Travis and fails from time to time. -// See: https://github.com/WordPress/gutenberg/pull/15211. -describe.skip( 'WP Editor Meta Boxes', () => { +describe( 'WP Editor Meta Boxes', () => { beforeAll( async () => { await activatePlugin( 'gutenberg-test-plugin-wp-editor-meta-box' ); await createNewPost(); @@ -25,7 +23,7 @@ describe.skip( 'WP Editor Meta Boxes', () => { await page.type( '.editor-post-title__input', 'Hello Meta' ); // Type something - await page.click( '#test_tinymce_id-html' ); + await expect( page ).toClick( '#test_tinymce_id-html' ); await page.type( '#test_tinymce_id', 'Typing in a metabox' ); await page.click( '#test_tinymce_id-tmce' ); @@ -33,7 +31,8 @@ describe.skip( 'WP Editor Meta Boxes', () => { await page.reload(); - await page.click( '#test_tinymce_id-html' ); + await expect( page ).toClick( '#test_tinymce_id-html' ); + await page.waitForSelector( '#test_tinymce_id' ); const content = await page.$eval( '#test_tinymce_id', ( textarea ) => textarea.value diff --git a/packages/e2e-tests/specs/__snapshots__/adding-blocks.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/adding-blocks.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/__snapshots__/adding-blocks.test.js.snap rename to packages/e2e-tests/specs/editor/various/__snapshots__/adding-blocks.test.js.snap diff --git a/packages/e2e-tests/specs/__snapshots__/block-deletion.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/block-deletion.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/__snapshots__/block-deletion.test.js.snap rename to packages/e2e-tests/specs/editor/various/__snapshots__/block-deletion.test.js.snap diff --git a/packages/e2e-tests/specs/__snapshots__/block-grouping.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/block-grouping.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/__snapshots__/block-grouping.test.js.snap rename to packages/e2e-tests/specs/editor/various/__snapshots__/block-grouping.test.js.snap diff --git a/packages/e2e-tests/specs/__snapshots__/block-hierarchy-navigation.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/block-hierarchy-navigation.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/__snapshots__/block-hierarchy-navigation.test.js.snap rename to packages/e2e-tests/specs/editor/various/__snapshots__/block-hierarchy-navigation.test.js.snap diff --git a/packages/e2e-tests/specs/__snapshots__/compatibility-classic-editor.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/compatibility-classic-editor.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/__snapshots__/compatibility-classic-editor.test.js.snap rename to packages/e2e-tests/specs/editor/various/__snapshots__/compatibility-classic-editor.test.js.snap diff --git a/packages/e2e-tests/specs/__snapshots__/convert-block-type.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/convert-block-type.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/__snapshots__/convert-block-type.test.js.snap rename to packages/e2e-tests/specs/editor/various/__snapshots__/convert-block-type.test.js.snap diff --git a/packages/e2e-tests/specs/__snapshots__/embedding.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/embedding.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/__snapshots__/embedding.test.js.snap rename to packages/e2e-tests/specs/editor/various/__snapshots__/embedding.test.js.snap diff --git a/packages/e2e-tests/specs/__snapshots__/font-size-picker.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/font-size-picker.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/__snapshots__/font-size-picker.test.js.snap rename to packages/e2e-tests/specs/editor/various/__snapshots__/font-size-picker.test.js.snap diff --git a/packages/e2e-tests/specs/__snapshots__/links.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/links.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/__snapshots__/links.test.js.snap rename to packages/e2e-tests/specs/editor/various/__snapshots__/links.test.js.snap diff --git a/packages/e2e-tests/specs/__snapshots__/mentions.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/mentions.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/__snapshots__/mentions.test.js.snap rename to packages/e2e-tests/specs/editor/various/__snapshots__/mentions.test.js.snap diff --git a/packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap similarity index 50% rename from packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap rename to packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap index f68e40f606dc4c..6e241261ff78e7 100644 --- a/packages/e2e-tests/specs/__snapshots__/multi-block-selection.test.js.snap +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap @@ -8,6 +8,40 @@ exports[`Multi-block selection should allow selecting outer edge if there is no exports[`Multi-block selection should always expand single line selection 1`] = `""`; +exports[`Multi-block selection should copy and paste 1`] = ` +"<!-- wp:paragraph --> +<p>1</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>2</p> +<!-- /wp:paragraph -->" +`; + +exports[`Multi-block selection should copy and paste 2`] = `""`; + +exports[`Multi-block selection should copy and paste 3`] = ` +"<!-- wp:paragraph --> +<p>1</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>2</p> +<!-- /wp:paragraph -->" +`; + +exports[`Multi-block selection should cut and paste 1`] = `""`; + +exports[`Multi-block selection should cut and paste 2`] = ` +"<!-- wp:paragraph --> +<p>1</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>2</p> +<!-- /wp:paragraph -->" +`; + exports[`Multi-block selection should only trigger multi-selection when at the end 1`] = ` "<!-- wp:paragraph --> <p>1.</p> diff --git a/packages/e2e-tests/specs/__snapshots__/reusable-blocks.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/reusable-blocks.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/__snapshots__/reusable-blocks.test.js.snap rename to packages/e2e-tests/specs/editor/various/__snapshots__/reusable-blocks.test.js.snap diff --git a/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/rich-text.test.js.snap similarity index 76% rename from packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap rename to packages/e2e-tests/specs/editor/various/__snapshots__/rich-text.test.js.snap index 78b78101a59a5d..16fefd206e09d7 100644 --- a/packages/e2e-tests/specs/__snapshots__/rich-text.test.js.snap +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/rich-text.test.js.snap @@ -36,6 +36,12 @@ exports[`RichText should keep internal selection after blur 1`] = ` <!-- /wp:paragraph -->" `; +exports[`RichText should make bold after split and merge 1`] = ` +"<!-- wp:paragraph --> +<p>1<strong>2</strong></p> +<!-- /wp:paragraph -->" +`; + exports[`RichText should not format text after code backtick 1`] = ` "<!-- wp:paragraph --> <p>A <code>backtick</code> and more.</p> @@ -48,6 +54,18 @@ exports[`RichText should not lose selection direction 1`] = ` <!-- /wp:paragraph -->" `; +exports[`RichText should not split rich text on inline paste 1`] = ` +"<!-- wp:paragraph --> +<p>123</p> +<!-- /wp:paragraph -->" +`; + +exports[`RichText should not split rich text on inline paste with formatting 1`] = ` +"<!-- wp:paragraph --> +<p>a1<strong>2</strong>3b</p> +<!-- /wp:paragraph -->" +`; + exports[`RichText should not undo backtick transform with backspace after selection change 1`] = `""`; exports[`RichText should not undo backtick transform with backspace after typing 1`] = `""`; @@ -64,6 +82,24 @@ exports[`RichText should return focus when pressing formatting button 1`] = ` <!-- /wp:paragraph -->" `; +exports[`RichText should split rich text on paste 1`] = ` +"<!-- wp:paragraph --> +<p>a</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>1</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>2</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>b</p> +<!-- /wp:paragraph -->" +`; + exports[`RichText should transform backtick to code 1`] = ` "<!-- wp:paragraph --> <p>A <code>backtick</code></p> @@ -76,14 +112,14 @@ exports[`RichText should transform backtick to code 2`] = ` <!-- /wp:paragraph -->" `; -exports[`RichText should update internal selection after fresh focus 1`] = ` +exports[`RichText should undo backtick transform with backspace 1`] = ` "<!-- wp:paragraph --> -<p>1<strong>2</strong></p> +<p>\`a\`</p> <!-- /wp:paragraph -->" `; -exports[`RichText should undo backtick transform with backspace 1`] = ` +exports[`RichText should update internal selection after fresh focus 1`] = ` "<!-- wp:paragraph --> -<p>\`a\`</p> +<p>1<strong>2</strong></p> <!-- /wp:paragraph -->" `; diff --git a/packages/e2e-tests/specs/__snapshots__/rtl.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/rtl.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/__snapshots__/rtl.test.js.snap rename to packages/e2e-tests/specs/editor/various/__snapshots__/rtl.test.js.snap diff --git a/packages/e2e-tests/specs/__snapshots__/splitting-merging.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/splitting-merging.test.js.snap similarity index 91% rename from packages/e2e-tests/specs/__snapshots__/splitting-merging.test.js.snap rename to packages/e2e-tests/specs/editor/various/__snapshots__/splitting-merging.test.js.snap index 6046a90ae894f4..1448fdaa151607 100644 --- a/packages/e2e-tests/specs/__snapshots__/splitting-merging.test.js.snap +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/splitting-merging.test.js.snap @@ -42,6 +42,16 @@ exports[`splitting and merging blocks should not merge paragraphs if the selecti <!-- /wp:paragraph -->" `; +exports[`splitting and merging blocks should not split with line break in front 1`] = ` +"<!-- wp:paragraph --> +<p>1</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>2</p> +<!-- /wp:paragraph -->" +`; + exports[`splitting and merging blocks should remove at most one paragraph in forward direction 1`] = ` "<!-- wp:paragraph --> <p>First</p> diff --git a/packages/e2e-tests/specs/__snapshots__/style-variation.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/style-variation.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/__snapshots__/style-variation.test.js.snap rename to packages/e2e-tests/specs/editor/various/__snapshots__/style-variation.test.js.snap diff --git a/packages/e2e-tests/specs/__snapshots__/undo.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/undo.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/__snapshots__/undo.test.js.snap rename to packages/e2e-tests/specs/editor/various/__snapshots__/undo.test.js.snap diff --git a/packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/writing-flow.test.js.snap similarity index 100% rename from packages/e2e-tests/specs/__snapshots__/writing-flow.test.js.snap rename to packages/e2e-tests/specs/editor/various/__snapshots__/writing-flow.test.js.snap diff --git a/packages/e2e-tests/specs/a11y.test.js b/packages/e2e-tests/specs/editor/various/a11y.test.js similarity index 100% rename from packages/e2e-tests/specs/a11y.test.js rename to packages/e2e-tests/specs/editor/various/a11y.test.js diff --git a/packages/e2e-tests/specs/adding-blocks.test.js b/packages/e2e-tests/specs/editor/various/adding-blocks.test.js similarity index 89% rename from packages/e2e-tests/specs/adding-blocks.test.js rename to packages/e2e-tests/specs/editor/various/adding-blocks.test.js index ed44aa7817e2ad..c0e72c9dc63682 100644 --- a/packages/e2e-tests/specs/adding-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/adding-blocks.test.js @@ -7,8 +7,11 @@ import { getEditedPostContent, pressKeyTimes, switchEditorModeTo, + setBrowserViewport, } from '@wordpress/e2e-test-utils'; +/** @typedef {import('puppeteer').ElementHandle} ElementHandle */ + describe( 'adding blocks', () => { beforeEach( async () => { await createNewPost(); @@ -17,20 +20,28 @@ describe( 'adding blocks', () => { /** * Given a Puppeteer ElementHandle, clicks below its bounding box. * - * @param {Puppeteer.ElementHandle} elementHandle Element handle. + * @param {ElementHandle} elementHandle Element handle. * * @return {Promise} Promise resolving when click occurs. */ - async function clickBelow( elementHandle ) { + async function clickAtBottom( elementHandle ) { const box = await elementHandle.boundingBox(); const x = box.x + ( box.width / 2 ); - const y = box.y + box.height + 100; + const y = box.y + box.height - 50; return page.mouse.click( x, y ); } it( 'Should insert content using the placeholder and the regular inserter', async () => { + // This ensures the editor is loaded in navigation mode. + await page.reload(); + + // Set a tall viewport. The typewriter's intrinsic height can be enough + // to scroll the page on a shorter viewport, thus obscuring the presence + // of any potential buggy behavior with the "stretched" click redirect. + await setBrowserViewport( { width: 960, height: 1400 } ); + // Click below editor to focus last field (block appender) - await clickBelow( await page.$( '.block-editor-default-block-appender' ) ); + await clickAtBottom( await page.$( '.edit-post-editor-regions__content' ) ); expect( await page.$( '[data-type="core/paragraph"]' ) ).not.toBeNull(); await page.keyboard.type( 'Paragraph block' ); diff --git a/packages/e2e-tests/specs/adding-inline-tokens.test.js b/packages/e2e-tests/specs/editor/various/adding-inline-tokens.test.js similarity index 93% rename from packages/e2e-tests/specs/adding-inline-tokens.test.js rename to packages/e2e-tests/specs/editor/various/adding-inline-tokens.test.js index f028a73c3df931..b11077e70fb3d3 100644 --- a/packages/e2e-tests/specs/adding-inline-tokens.test.js +++ b/packages/e2e-tests/specs/editor/various/adding-inline-tokens.test.js @@ -33,7 +33,7 @@ describe( 'adding inline tokens', () => { // Wait for media modal to appear and upload image. await page.waitForSelector( '.media-modal input[type=file]' ); const inputElement = await page.$( '.media-modal input[type=file]' ); - const testImagePath = path.join( __dirname, '..', 'assets', '10x10_e2e_test_image_z9T8jK.png' ); + const testImagePath = path.join( __dirname, '..', '..', '..', 'assets', '10x10_e2e_test_image_z9T8jK.png' ); const filename = uuid(); const tmpFileName = path.join( os.tmpdir(), filename + '.png' ); fs.copyFileSync( testImagePath, tmpFileName ); diff --git a/packages/e2e-tests/specs/autosave.test.js b/packages/e2e-tests/specs/editor/various/autosave.test.js similarity index 77% rename from packages/e2e-tests/specs/autosave.test.js rename to packages/e2e-tests/specs/editor/various/autosave.test.js index 9f97f0f1c3f291..3c966f15a0748d 100644 --- a/packages/e2e-tests/specs/autosave.test.js +++ b/packages/e2e-tests/specs/editor/various/autosave.test.js @@ -17,13 +17,22 @@ const AUTOSAVE_INTERVAL_SECONDS = 5; const AUTOSAVE_NOTICE_REMOTE = 'There is an autosave of this post that is more recent than the version below.'; const AUTOSAVE_NOTICE_LOCAL = 'The backup of this post in your browser is different from the version below.'; +// Save and wait for "Saved" to confirm save complete. Preserves focus in the +// editing area. async function saveDraftWithKeyboard() { - return pressKeyWithModifier( 'primary', 's' ); + await page.waitForSelector( '.editor-post-save-draft' ); + await Promise.all( [ + page.waitForSelector( '.editor-post-saved-state.is-saved' ), + pressKeyWithModifier( 'primary', 'S' ), + ] ); } async function sleep( durationInSeconds ) { - return new Promise( ( resolve ) => - setTimeout( resolve, durationInSeconds * 1000 ) ); + // Rule `no-restricted-syntax` recommends `waitForSelector` against + // `waitFor`, which isn't apt for the use case, when provided an integer, + // of waiting for a given amount of time. + // eslint-disable-next-line no-restricted-syntax + await page.waitFor( durationInSeconds * 1000 ); } async function clearSessionStorage() { @@ -153,11 +162,11 @@ describe( 'autosave', () => { } ); it( 'should clear local autosave after successful remote autosave', async () => { + // Edit, save draft, edit again await clickBlockAppender(); await page.keyboard.type( 'before save' ); - await saveDraft(); - - await page.keyboard.type( 'after save' ); + await saveDraftWithKeyboard(); + await page.keyboard.type( ' after save' ); // Trigger local autosave await page.evaluate( () => window.wp.data.dispatch( 'core/editor' ).__experimentalLocalAutosave() ); @@ -169,21 +178,52 @@ describe( 'autosave', () => { } ); it( 'shouldn\'t clear local autosave if remote autosave fails', async () => { + // Edit, save draft, edit again await clickBlockAppender(); await page.keyboard.type( 'before save' ); - await saveDraft(); + await saveDraftWithKeyboard(); + await page.keyboard.type( ' after save' ); - await page.keyboard.type( 'after save' ); + // Trigger local autosave await page.evaluate( () => window.wp.data.dispatch( 'core/editor' ).__experimentalLocalAutosave() ); expect( await page.evaluate( () => window.sessionStorage.length ) ).toBe( 1 ); + // Bring network down and attempt to autosave remotely toggleOfflineMode( true ); - - // Trigger remote autosave await page.evaluate( () => window.wp.data.dispatch( 'core/editor' ).autosave() ); expect( await page.evaluate( () => window.sessionStorage.length ) ).toBe( 1 ); + } ); - toggleOfflineMode( false ); + it( 'should clear local autosave after successful save', async () => { + // Edit, save draft, edit again + await clickBlockAppender(); + await page.keyboard.type( 'before save' ); + await saveDraftWithKeyboard(); + await page.keyboard.type( ' after save' ); + + // Trigger local autosave + await page.evaluate( () => window.wp.data.dispatch( 'core/editor' ).__experimentalLocalAutosave() ); + expect( await page.evaluate( () => window.sessionStorage.length ) ).toBe( 1 ); + + await saveDraftWithKeyboard(); + expect( await page.evaluate( () => window.sessionStorage.length ) ).toBe( 0 ); + } ); + + it( 'shouldn\'t clear local autosave if save fails', async () => { + // Edit, save draft, edit again + await clickBlockAppender(); + await page.keyboard.type( 'before save' ); + await saveDraftWithKeyboard(); + await page.keyboard.type( ' after save' ); + + // Trigger local autosave + await page.evaluate( () => window.wp.data.dispatch( 'core/editor' ).__experimentalLocalAutosave() ); + expect( await page.evaluate( () => window.sessionStorage.length ) ).toBe( 1 ); + + // Bring network down and attempt to save + toggleOfflineMode( true ); + saveDraftWithKeyboard(); + expect( await page.evaluate( () => window.sessionStorage.length ) ).toBe( 1 ); } ); it( 'shouldn\'t conflict with server-side autosave', async () => { @@ -221,7 +261,8 @@ describe( 'autosave', () => { expect( notice ).toContain( AUTOSAVE_NOTICE_REMOTE ); } ); - afterAll( async () => { + afterEach( async () => { + toggleOfflineMode( false ); await clearSessionStorage(); } ); } ); diff --git a/packages/e2e-tests/specs/block-deletion.test.js b/packages/e2e-tests/specs/editor/various/block-deletion.test.js similarity index 100% rename from packages/e2e-tests/specs/block-deletion.test.js rename to packages/e2e-tests/specs/editor/various/block-deletion.test.js diff --git a/packages/e2e-tests/specs/block-grouping.test.js b/packages/e2e-tests/specs/editor/various/block-grouping.test.js similarity index 100% rename from packages/e2e-tests/specs/block-grouping.test.js rename to packages/e2e-tests/specs/editor/various/block-grouping.test.js diff --git a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js b/packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js similarity index 97% rename from packages/e2e-tests/specs/block-hierarchy-navigation.test.js rename to packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js index 2afbaa994abed9..2dc5dc1f6cc544 100644 --- a/packages/e2e-tests/specs/block-hierarchy-navigation.test.js +++ b/packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js @@ -37,7 +37,7 @@ describe( 'Navigating the block hierarchy', () => { await columnsBlockMenuItem.click(); // Tweak the columns count. - await page.focus( '.edit-post-settings-sidebar__panel-block .components-range-control__number[aria-label="Columns"]' ); + await page.focus( '.block-editor-block-inspector .components-range-control__number[aria-label="Columns"]' ); await page.keyboard.down( 'Shift' ); await page.keyboard.press( 'ArrowLeft' ); await page.keyboard.up( 'Shift' ); diff --git a/packages/e2e-tests/specs/block-mover.test.js b/packages/e2e-tests/specs/editor/various/block-mover.test.js similarity index 100% rename from packages/e2e-tests/specs/block-mover.test.js rename to packages/e2e-tests/specs/editor/various/block-mover.test.js diff --git a/packages/e2e-tests/specs/block-switcher.test.js b/packages/e2e-tests/specs/editor/various/block-switcher.test.js similarity index 100% rename from packages/e2e-tests/specs/block-switcher.test.js rename to packages/e2e-tests/specs/editor/various/block-switcher.test.js diff --git a/packages/e2e-tests/specs/change-detection.test.js b/packages/e2e-tests/specs/editor/various/change-detection.test.js similarity index 82% rename from packages/e2e-tests/specs/change-detection.test.js rename to packages/e2e-tests/specs/editor/various/change-detection.test.js index 0f384d7c3efd5a..7add035fd79256 100644 --- a/packages/e2e-tests/specs/change-detection.test.js +++ b/packages/e2e-tests/specs/editor/various/change-detection.test.js @@ -7,6 +7,9 @@ import { pressKeyWithModifier, ensureSidebarOpened, publishPost, + saveDraft, + openDocumentSettingsSidebar, + isCurrentURL, } from '@wordpress/e2e-test-utils'; describe( 'Change detection', () => { @@ -159,13 +162,7 @@ describe( 'Change detection', () => { it( 'Should not prompt if changes saved', async () => { await page.type( '.editor-post-title__input', 'Hello World' ); - await Promise.all( [ - // Wait for "Saved" to confirm save complete. - page.waitForSelector( '.editor-post-saved-state.is-saved' ), - - // Keyboard shortcut Ctrl+S save. - pressKeyWithModifier( 'primary', 'S' ), - ] ); + await saveDraft(); await assertIsDirty( false ); } ); @@ -174,13 +171,7 @@ describe( 'Change detection', () => { await clickBlockAppender(); await page.keyboard.type( 'Hello World' ); - await Promise.all( [ - // Wait for "Saved" to confirm save complete. - page.waitForSelector( '.editor-post-saved-state.is-saved' ), - - // Keyboard shortcut Ctrl+S save. - pressKeyWithModifier( 'primary', 'S' ), - ] ); + await saveDraft(); await assertIsDirty( false ); } ); @@ -188,13 +179,7 @@ describe( 'Change detection', () => { it( 'Should not save if all changes saved', async () => { await page.type( '.editor-post-title__input', 'Hello World' ); - await Promise.all( [ - // Wait for "Saved" to confirm save complete. - page.waitForSelector( '.editor-post-saved-state.is-saved' ), - - // Keyboard shortcut Ctrl+S save. - pressKeyWithModifier( 'primary', 'S' ), - ] ); + await saveDraft(); await interceptSave(); @@ -215,7 +200,7 @@ describe( 'Change detection', () => { // Ensure save update fails and presents button. page.waitForXPath( - '//*[contains(@class, "components-notice") and contains(@class, "is-error")]/*[text()="Updating failed. Error message: The response is not a valid JSON response."]' + '//*[contains(@class, "components-notice") and contains(@class, "is-error")]/*[text()="Updating failed. You are probably offline."]' ), page.waitForSelector( '.editor-post-save-draft' ), ] ); @@ -330,13 +315,7 @@ describe( 'Change detection', () => { await page.keyboard.type( 'Paragraph' ); // Save - await Promise.all( [ - // Wait for "Saved" to confirm save complete. - page.waitForSelector( '.editor-post-saved-state.is-saved' ), - - // Keyboard shortcut Ctrl+S save. - pressKeyWithModifier( 'primary', 'S' ), - ] ); + await saveDraft(); // Verify that the title is empty. const title = await page.$eval( @@ -348,4 +327,69 @@ describe( 'Change detection', () => { // Verify that the post is not dirty. await assertIsDirty( false ); } ); + + it( 'should not prompt to confirm unsaved changes when trashing an existing post', async () => { + // Enter title. + await page.type( '.editor-post-title__input', 'Hello World' ); + + // Save + await saveDraft(); + const postId = await page.evaluate( + () => window.wp.data.select( 'core/editor' ).getCurrentPostId() + ); + + // Trash post. + await openDocumentSettingsSidebar(); + await page.click( '.editor-post-trash.components-button' ); + + await Promise.all( [ + // Wait for "Saved" to confirm save complete. + await page.waitForSelector( '.editor-post-saved-state.is-saved' ), + + // Make sure redirection happens. + await page.waitForNavigation(), + ] ); + + expect( isCurrentURL( '/wp-admin/edit.php', `post_type=post&ids=${ postId }` ) ).toBe( true ); + } ); + + it( 'consecutive edits to the same attribute should mark the post as dirty after a save', async () => { + // Open the sidebar block settings. + await openDocumentSettingsSidebar(); + await page.click( '.edit-post-sidebar__panel-tab[data-label="Block"]' ); + + // Insert a paragraph. + await clickBlockAppender(); + await page.keyboard.type( 'Hello, World!' ); + + // Save and wait till the post is clean. + await Promise.all( [ + page.waitForSelector( '.editor-post-saved-state.is-saved' ), + pressKeyWithModifier( 'primary', 'S' ), + ] ); + + // Increase the paragraph's font size. + await page.click( '[data-type="core/paragraph"]' ); + await page.click( '.components-font-size-picker__select' ); + await page.click( '.components-custom-select-control__item:nth-child(3)' ); + await page.click( '[data-type="core/paragraph"]' ); + + // Check that the post is dirty. + await page.waitForSelector( '.editor-post-save-draft' ); + + // Save and wait till the post is clean. + await Promise.all( [ + page.waitForSelector( '.editor-post-saved-state.is-saved' ), + pressKeyWithModifier( 'primary', 'S' ), + ] ); + + // Increase the paragraph's font size again. + await page.click( '[data-type="core/paragraph"]' ); + await page.click( '.components-font-size-picker__select' ); + await page.click( '.components-custom-select-control__item:nth-child(4)' ); + await page.click( '[data-type="core/paragraph"]' ); + + // Check that the post is dirty. + await page.waitForSelector( '.editor-post-save-draft' ); + } ); } ); diff --git a/packages/e2e-tests/specs/compatibility-classic-editor.test.js b/packages/e2e-tests/specs/editor/various/compatibility-classic-editor.test.js similarity index 100% rename from packages/e2e-tests/specs/compatibility-classic-editor.test.js rename to packages/e2e-tests/specs/editor/various/compatibility-classic-editor.test.js diff --git a/packages/e2e-tests/specs/convert-block-type.test.js b/packages/e2e-tests/specs/editor/various/convert-block-type.test.js similarity index 100% rename from packages/e2e-tests/specs/convert-block-type.test.js rename to packages/e2e-tests/specs/editor/various/convert-block-type.test.js diff --git a/packages/e2e-tests/specs/datepicker.test.js b/packages/e2e-tests/specs/editor/various/datepicker.test.js similarity index 100% rename from packages/e2e-tests/specs/datepicker.test.js rename to packages/e2e-tests/specs/editor/various/datepicker.test.js diff --git a/packages/e2e-tests/specs/editor-modes.test.js b/packages/e2e-tests/specs/editor/various/editor-modes.test.js similarity index 97% rename from packages/e2e-tests/specs/editor-modes.test.js rename to packages/e2e-tests/specs/editor/various/editor-modes.test.js index 3969ac43f80fc7..ac5c7180d39067 100644 --- a/packages/e2e-tests/specs/editor-modes.test.js +++ b/packages/e2e-tests/specs/editor/various/editor-modes.test.js @@ -78,7 +78,8 @@ describe( 'Editing modes (visual/HTML)', () => { expect( htmlBlockContent ).toEqual( '<p>Hello world!</p>' ); // Change the font size using the sidebar. - await page.select( '.components-font-size-picker__select .components-select-control__input', 'large' ); + await page.click( '.components-font-size-picker__select' ); + await page.click( '.components-custom-select-control__item:nth-child(4)' ); // Make sure the HTML content updated. htmlBlockContent = await page.$eval( '.block-editor-block-list__layout .block-editor-block-list__block .block-editor-block-list__block-html-textarea', ( node ) => node.textContent ); diff --git a/packages/e2e-tests/specs/embedding.test.js b/packages/e2e-tests/specs/editor/various/embedding.test.js similarity index 71% rename from packages/e2e-tests/specs/embedding.test.js rename to packages/e2e-tests/specs/editor/various/embedding.test.js index df7fdb30a7a4c9..53dec261440586 100644 --- a/packages/e2e-tests/specs/embedding.test.js +++ b/packages/e2e-tests/specs/editor/various/embedding.test.js @@ -3,15 +3,15 @@ */ import { clickBlockAppender, - createNewPost, + clickButton, createEmbeddingMatcher, - createURLMatcher, - setUpResponseMocking, createJSONResponse, + createNewPost, + createURLMatcher, getEditedPostContent, - clickButton, insertBlock, publishPost, + setUpResponseMocking, } from '@wordpress/e2e-test-utils'; const MOCK_EMBED_WORDPRESS_SUCCESS_RESPONSE = { @@ -133,57 +133,6 @@ const MOCK_RESPONSES = [ }, ]; -const addAllEmbeds = async () => { - // Valid embed. - await clickBlockAppender(); - await page.keyboard.type( '/embed' ); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'https://twitter.com/notnownikki' ); - await page.keyboard.press( 'Enter' ); - - // Valid provider; invalid content. - await clickBlockAppender(); - await page.keyboard.type( '/embed' ); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'https://twitter.com/wooyaygutenberg123454312' ); - await page.keyboard.press( 'Enter' ); - - // WordPress invalid content. - await clickBlockAppender(); - await page.keyboard.type( '/embed' ); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'https://wordpress.org/gutenberg/handbook/' ); - await page.keyboard.press( 'Enter' ); - - // Provider whose oembed API has gone wrong. - await clickBlockAppender(); - await page.keyboard.type( '/embed' ); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'https://twitter.com/thatbunty' ); - await page.keyboard.press( 'Enter' ); - - // WordPress content that can be embedded. - await clickBlockAppender(); - await page.keyboard.type( '/embed' ); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'https://wordpress.org/gutenberg/handbook/block-api/attributes/' ); - await page.keyboard.press( 'Enter' ); - - // Video content. - await clickBlockAppender(); - await page.keyboard.type( '/embed' ); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'https://www.youtube.com/watch?v=lXMskKTw3Bc' ); - await page.keyboard.press( 'Enter' ); - - // Photo content. - await clickBlockAppender(); - await page.keyboard.type( '/embed' ); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'https://cloudup.com/cQFlxqtY4ob' ); - await page.keyboard.press( 'Enter' ); -}; - describe( 'Embedding content', () => { beforeEach( async () => { await setUpResponseMocking( MOCK_RESPONSES ); @@ -191,19 +140,63 @@ describe( 'Embedding content', () => { } ); it( 'should render embeds in the correct state', async () => { - await addAllEmbeds(); - // The successful embeds should be in a correctly classed figure element. - // This tests that they have switched to the correct block. + // Valid embed. Should render valid figure element. + await clickBlockAppender(); + await page.keyboard.type( '/embed' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'https://twitter.com/notnownikki' ); + await page.keyboard.press( 'Enter' ); await page.waitForSelector( 'figure.wp-block-embed-twitter' ); - await page.waitForSelector( 'figure.wp-block-embed-cloudup' ); - await page.waitForSelector( 'figure.wp-block-embed-wordpress' ); - // Video embed should also have the aspect ratio class. - await page.waitForSelector( 'figure.wp-block-embed-youtube.wp-embed-aspect-16-9' ); - // Each failed embed should be in the edit state. + // Valid provider; invalid content. Should render failed, edit state. + await clickBlockAppender(); + await page.keyboard.type( '/embed' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'https://twitter.com/wooyaygutenberg123454312' ); + await page.keyboard.press( 'Enter' ); await page.waitForSelector( 'input[value="https://twitter.com/wooyaygutenberg123454312"]' ); - await page.waitForSelector( 'input[value="https://twitter.com/thatbunty"]' ); + + // WordPress invalid content. Should render failed, edit state. + await clickBlockAppender(); + await page.keyboard.type( '/embed' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'https://wordpress.org/gutenberg/handbook/' ); + await page.keyboard.press( 'Enter' ); await page.waitForSelector( 'input[value="https://wordpress.org/gutenberg/handbook/"]' ); + + // Provider whose oembed API has gone wrong. Should render failed, edit + // state. + await clickBlockAppender(); + await page.keyboard.type( '/embed' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'https://twitter.com/thatbunty' ); + await page.keyboard.press( 'Enter' ); + await page.waitForSelector( 'input[value="https://twitter.com/thatbunty"]' ); + + // WordPress content that can be embedded. Should render valid figure + // element. + await clickBlockAppender(); + await page.keyboard.type( '/embed' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'https://wordpress.org/gutenberg/handbook/block-api/attributes/' ); + await page.keyboard.press( 'Enter' ); + await page.waitForSelector( 'figure.wp-block-embed-wordpress' ); + + // Video content. Should render valid figure element, and include the + // aspect ratio class. + await clickBlockAppender(); + await page.keyboard.type( '/embed' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'https://www.youtube.com/watch?v=lXMskKTw3Bc' ); + await page.keyboard.press( 'Enter' ); + await page.waitForSelector( 'figure.wp-block-embed-youtube.wp-embed-aspect-16-9' ); + + // Photo content. Should render valid figure element. + await clickBlockAppender(); + await page.keyboard.type( '/embed' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'https://cloudup.com/cQFlxqtY4ob' ); + await page.keyboard.press( 'Enter' ); } ); it( 'should allow the user to convert unembeddable URLs to a paragraph with a link in it', async () => { @@ -271,33 +264,4 @@ describe( 'Embedding content', () => { // Check the block has become a WordPress block. await page.waitForSelector( '.wp-block-embed-wordpress' ); } ); - - it.skip( 'should transform from video to embed block when YouTube URL is pasted', async () => { - await clickBlockAppender(); - await insertBlock( 'Video' ); - await page.click( '.editor-media-placeholder__url-input-container button' ); - await page.keyboard.type( 'https://www.youtube.com/watch?v=lXMskKTw3Bc' ); - await page.keyboard.press( 'Enter' ); - await page.waitForSelector( '.wp-block-embed-youtube' ); - } ); - - it.skip( 'should transform from image to embed block when Instagram URL is pasted', async () => { - await clickBlockAppender(); - await page.keyboard.type( '/image' ); - await page.keyboard.press( 'Enter' ); - await page.click( '.editor-media-placeholder__url-input-container button' ); - await page.keyboard.type( 'https://www.instagram.com/p/Bvl97o2AK6x/' ); - await page.keyboard.press( 'Enter' ); - await page.waitForSelector( '.wp-block-embed-instagram' ); - } ); - - it.skip( 'should transform from audio to embed block when Soundcloud URL is pasted', async () => { - await clickBlockAppender(); - await page.keyboard.type( '/audio' ); - await page.keyboard.press( 'Enter' ); - await page.click( '.editor-media-placeholder__url-input-container button' ); - await page.keyboard.type( 'https://soundcloud.com/a-boogie-wit-da-hoodie/swervin' ); - await page.keyboard.press( 'Enter' ); - await page.waitForSelector( '.wp-block-embed-soundcloud' ); - } ); } ); diff --git a/packages/e2e-tests/specs/font-size-picker.test.js b/packages/e2e-tests/specs/editor/various/font-size-picker.test.js similarity index 84% rename from packages/e2e-tests/specs/font-size-picker.test.js rename to packages/e2e-tests/specs/editor/various/font-size-picker.test.js index 362751e8522cc8..04639b26814861 100644 --- a/packages/e2e-tests/specs/font-size-picker.test.js +++ b/packages/e2e-tests/specs/editor/various/font-size-picker.test.js @@ -17,7 +17,8 @@ describe( 'Font Size Picker', () => { // Create a paragraph block with some content. await clickBlockAppender(); await page.keyboard.type( 'Paragraph to be made "large"' ); - await page.select( '.components-font-size-picker__select .components-select-control__input', 'large' ); + await page.click( '.components-font-size-picker__select' ); + await page.click( '.components-custom-select-control__item:nth-child(4)' ); // Ensure content matches snapshot. const content = await getEditedPostContent(); @@ -30,8 +31,8 @@ describe( 'Font Size Picker', () => { await page.keyboard.type( 'Paragraph to be made "small"' ); await page.click( '.blocks-font-size .components-range-control__number' ); - // This should be the "small" font-size of the current theme. - await page.keyboard.type( '16' ); + // This should be the "small" font-size of the editor defaults. + await page.keyboard.type( '13' ); // Ensure content matches snapshot. const content = await getEditedPostContent(); @@ -56,7 +57,8 @@ describe( 'Font Size Picker', () => { await clickBlockAppender(); await page.keyboard.type( 'Paragraph with font size reset using button' ); - await page.select( '.components-font-size-picker__select .components-select-control__input', 'normal' ); + await page.click( '.components-font-size-picker__select' ); + await page.click( '.components-custom-select-control__item:nth-child(2)' ); const resetButton = ( await page.$x( '//*[contains(concat(" ", @class, " "), " components-font-size-picker__controls ")]//*[text()=\'Reset\']' ) )[ 0 ]; await resetButton.click(); @@ -71,12 +73,13 @@ describe( 'Font Size Picker', () => { await clickBlockAppender(); await page.keyboard.type( 'Paragraph with font size reset using input field' ); - await page.select( '.components-font-size-picker__select .components-select-control__input', 'large' ); + await page.click( '.components-font-size-picker__select' ); + await page.click( '.components-custom-select-control__item:nth-child(3)' ); // Clear the custom font size input. await page.click( '.blocks-font-size .components-range-control__number' ); - await pressKeyTimes( 'ArrowRight', 4 ); - await pressKeyTimes( 'Backspace', 4 ); + await pressKeyTimes( 'ArrowRight', 5 ); + await pressKeyTimes( 'Backspace', 5 ); // Ensure content matches snapshot. const content = await getEditedPostContent(); diff --git a/packages/e2e-tests/specs/fullscreen-mode.test.js b/packages/e2e-tests/specs/editor/various/fullscreen-mode.test.js similarity index 100% rename from packages/e2e-tests/specs/fullscreen-mode.test.js rename to packages/e2e-tests/specs/editor/various/fullscreen-mode.test.js diff --git a/packages/e2e-tests/specs/invalid-block.test.js b/packages/e2e-tests/specs/editor/various/invalid-block.test.js similarity index 100% rename from packages/e2e-tests/specs/invalid-block.test.js rename to packages/e2e-tests/specs/editor/various/invalid-block.test.js diff --git a/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js b/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js similarity index 98% rename from packages/e2e-tests/specs/keyboard-navigable-blocks.test.js rename to packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js index a8ebe96c34fcd2..085ddea5128c20 100644 --- a/packages/e2e-tests/specs/keyboard-navigable-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js @@ -58,6 +58,9 @@ const tabThroughBlockMoverControl = async () => { ); await expect( isFocusedMoveUpControl ).toBe( true ); + // Tab to focus the drag handle + await page.keyboard.press( 'Tab' ); + // Tab to focus on the 'move down' control await page.keyboard.press( 'Tab' ); const isFocusedMoveDownControl = await page.evaluate( () => diff --git a/packages/e2e-tests/specs/links.test.js b/packages/e2e-tests/specs/editor/various/links.test.js similarity index 80% rename from packages/e2e-tests/specs/links.test.js rename to packages/e2e-tests/specs/editor/various/links.test.js index f7e78a7b68a1e2..7589f449d1d00b 100644 --- a/packages/e2e-tests/specs/links.test.js +++ b/packages/e2e-tests/specs/editor/various/links.test.js @@ -266,87 +266,6 @@ describe( 'Links', () => { return page.evaluate( () => document.querySelector( '.post-publish-panel__postpublish-post-address input' ).value ); }; - // Test for regressions of https://github.com/WordPress/gutenberg/issues/10496. - it.skip( 'allows autocomplete suggestions to be selected with the mouse', async () => { - // First create a post that we can search for using the link autocompletion. - const titleText = 'Test post mouse'; - const postURL = await createPostWithTitle( titleText ); - - // Now create a new post and try to select the post created previously - // from the autocomplete suggestions. - await createNewPost(); - await clickBlockAppender(); - await page.keyboard.type( 'This is Gutenberg' ); - await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); - await page.click( 'button[aria-label="Link"]' ); - - // Wait for the URL field to auto-focus - await waitForAutoFocus(); - - await page.keyboard.type( titleText ); - const suggestionXPath = `//*[contains(@class, "block-editor-url-input__suggestion")]//button[contains(text(), '${ titleText }')]`; - await page.waitForXPath( suggestionXPath ); - const autocompleteSuggestions = await page.$x( suggestionXPath ); - - // Expect there to be some autocomplete suggestions. - expect( autocompleteSuggestions ).toHaveLength( 1 ); - - const firstSuggestion = autocompleteSuggestions[ 0 ]; - - // Expect that clicking on the autocomplete suggestion doesn't dismiss the link popover. - await firstSuggestion.click(); - expect( await page.$( '.block-editor-url-popover' ) ).not.toBeNull(); - - // Expect the url input value to have been updated with the post url. - const inputValue = await page.evaluate( () => document.querySelector( '.block-editor-url-input input[aria-label="URL"]' ).value ); - expect( inputValue ).toEqual( postURL ); - - // Expect the link to apply correctly. - // Note - have avoided using snapshots here since the link url can't be determined ahead of time. - await page.click( 'button[aria-label="Apply"]' ); - const linkHref = await page.evaluate( () => document.querySelector( '.block-editor-format-toolbar__link-container-value' ).href ); - expect( linkHref ).toEqual( postURL ); - } ); - - // Test for regressions of https://github.com/WordPress/gutenberg/issues/10496. - // This test isn't reliable on Travis and fails from time to time. - // See: https://github.com/WordPress/gutenberg/pull/15211. - it.skip( 'allows autocomplete suggestions to be navigated with the keyboard', async () => { - const titleText = 'Test post keyboard'; - const postURL = await createPostWithTitle( titleText ); - - await createNewPost(); - await clickBlockAppender(); - - // Now in a new post and try to create a link from an autocomplete suggestion using the keyboard. - await page.keyboard.type( 'This is Gutenberg' ); - await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); - - // Press Cmd+K to insert a link - await pressKeyWithModifier( 'primary', 'K' ); - - // Wait for the URL field to auto-focus - await waitForAutoFocus(); - - await page.keyboard.type( titleText ); - await page.waitForSelector( '.block-editor-url-input__suggestion' ); - const autocompleteSuggestions = await page.$x( `//*[contains(@class, "block-editor-url-input__suggestion")]//button[contains(text(), '${ titleText }')]` ); - - // Expect there to be some autocomplete suggestions. - expect( autocompleteSuggestions ).toHaveLength( 1 ); - - // Expect the the first suggestion to be selected when pressing the down arrow. - await page.keyboard.press( 'ArrowDown' ); - const isSelected = await page.evaluate( () => document.querySelector( '.block-editor-url-input__suggestion' ).getAttribute( 'aria-selected' ) ); - expect( isSelected ).toBe( 'true' ); - - // Expect the link to apply correctly when pressing Enter. - // Note - have avoided using snapshots here since the link url can't be determined ahead of time. - await page.keyboard.press( 'Enter' ); - const linkHref = await page.evaluate( () => document.querySelector( '.block-editor-format-toolbar__link-container-value' ).href ); - expect( linkHref ).toEqual( postURL ); - } ); - it( 'allows use of escape key to dismiss the url popover', async () => { const titleText = 'Test post escape'; await createPostWithTitle( titleText ); @@ -427,7 +346,7 @@ describe( 'Links', () => { // focused with the value previously inserted. await pressKeyWithModifier( 'primary', 'K' ); await waitForAutoFocus(); - const activeElementParentClasses = await page.evaluate( () => Object.values( document.activeElement.parentElement.classList ) ); + const activeElementParentClasses = await page.evaluate( () => Object.values( document.activeElement.parentElement.parentElement.classList ) ); expect( activeElementParentClasses ).toContain( 'block-editor-url-input' ); const activeElementValue = await page.evaluate( () => document.activeElement.value ); expect( activeElementValue ).toBe( URL ); diff --git a/packages/e2e-tests/specs/manage-reusable-blocks.test.js b/packages/e2e-tests/specs/editor/various/manage-reusable-blocks.test.js similarity index 93% rename from packages/e2e-tests/specs/manage-reusable-blocks.test.js rename to packages/e2e-tests/specs/editor/various/manage-reusable-blocks.test.js index 37340d669f5a74..9d17922f519f39 100644 --- a/packages/e2e-tests/specs/manage-reusable-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/manage-reusable-blocks.test.js @@ -32,7 +32,7 @@ describe( 'Managing reusable blocks', () => { await importButton.click(); // Select the file to upload - const testReusableBlockFile = path.join( __dirname, '..', 'assets', 'greeting-reusable-block.json' ); + const testReusableBlockFile = path.join( __dirname, '..', '..', '..', 'assets', 'greeting-reusable-block.json' ); const input = await page.$( '.list-reusable-blocks-import-form input' ); await input.uploadFile( testReusableBlockFile ); diff --git a/packages/e2e-tests/specs/mentions.test.js b/packages/e2e-tests/specs/editor/various/mentions.test.js similarity index 100% rename from packages/e2e-tests/specs/mentions.test.js rename to packages/e2e-tests/specs/editor/various/mentions.test.js diff --git a/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js b/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js new file mode 100644 index 00000000000000..c685dd4bca4f6d --- /dev/null +++ b/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js @@ -0,0 +1,367 @@ +/** + * WordPress dependencies + */ +import { + clickBlockAppender, + createNewPost, + pressKeyWithModifier, + pressKeyTimes, + getEditedPostContent, +} from '@wordpress/e2e-test-utils'; + +async function getSelectedFlatIndices() { + return await page.evaluate( () => { + const indices = []; + let single; + + Array.from( + document.querySelectorAll( '.wp-block' ) + ).forEach( ( node, index ) => { + if ( node.classList.contains( 'is-selected' ) ) { + single = index; + } + + if ( node.classList.contains( 'is-multi-selected' ) ) { + indices.push( index ); + } + } ); + + return single !== undefined ? single : indices; + } ); +} + +/** + * Tests if the native selection matches the block selection. + */ +async function testNativeSelection() { + // Wait for the selection to update. + await page.evaluate( () => new Promise( window.requestAnimationFrame ) ); + await page.evaluate( () => { + const selection = window.getSelection(); + const elements = Array.from( + document.querySelectorAll( '.is-multi-selected' ) + ); + + if ( ! elements.length ) { + const element = document.querySelector( '.is-selected' ); + + if ( ! element || ! selection.rangeCount ) { + return; + } + + const { startContainer, endContainer } = selection.getRangeAt( 0 ); + + if ( ! element.contains( startContainer ) ) { + throw 'expected selection to start in the selected block'; + } + + if ( ! element.contains( endContainer ) ) { + throw 'expected selection to start in the selected block'; + } + + return; + } + + if ( ! selection.rangeCount === 1 ) { + throw 'expected one range'; + } + + if ( selection.isCollapsed ) { + throw 'expected an uncollapsed selection'; + } + + const firstElement = elements[ 0 ]; + const lastElement = elements[ elements.length - 1 ]; + const { startContainer, endContainer } = selection.getRangeAt( 0 ); + + if ( ! firstElement.contains( startContainer ) ) { + throw 'expected selection to start in the first selected block'; + } + + if ( ! lastElement.contains( endContainer ) ) { + throw 'expected selection to end in the last selected block'; + } + } ); +} + +describe( 'Multi-block selection', () => { + beforeEach( async () => { + await createNewPost(); + } ); + + it( 'should select with double ctrl+a and speak', async () => { + await clickBlockAppender(); + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '3' ); + + // Multiselect via keyboard. + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); + + await testNativeSelection(); + expect( await getSelectedFlatIndices() ).toEqual( [ 1, 2, 3 ] ); + + // TODO: It would be great to do this test by spying on `wp.a11y.speak`, + // but it's very difficult to do that because `wp.a11y` has + // DOM-dependant side-effect setup code and doesn't seem straightforward + // to mock. Instead, we check for the DOM node that `wp.a11y.speak()` + // inserts text into. + const speakTextContent = await page.$eval( '#a11y-speak-assertive', ( element ) => element.textContent ); + expect( speakTextContent.trim() ).toEqual( '3 blocks selected.' ); + } ); + + // See #14448: an incorrect buffer may trigger multi-selection too soon. + it( 'should only trigger multi-selection when at the end', async () => { + // Create a paragraph with four lines. + await clickBlockAppender(); + await page.keyboard.type( '1.' ); + await pressKeyWithModifier( 'shift', 'Enter' ); + await page.keyboard.type( '2.' ); + await pressKeyWithModifier( 'shift', 'Enter' ); + await page.keyboard.type( '3.' ); + await pressKeyWithModifier( 'shift', 'Enter' ); + await page.keyboard.type( '4.' ); + // Create a second block. + await page.keyboard.press( 'Enter' ); + // Move to the middle of the first line. + await pressKeyTimes( 'ArrowUp', 4 ); + await page.keyboard.press( 'ArrowRight' ); + // Select mid line one to mid line four. + await pressKeyWithModifier( 'shift', 'ArrowDown' ); + await pressKeyWithModifier( 'shift', 'ArrowDown' ); + await pressKeyWithModifier( 'shift', 'ArrowDown' ); + // Delete the text to see if the selection was correct. + await page.keyboard.press( 'Backspace' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should use selection direction to determine vertical edge', async () => { + await clickBlockAppender(); + await page.keyboard.type( '1' ); + await pressKeyWithModifier( 'shift', 'Enter' ); + await page.keyboard.type( '2' ); + + await pressKeyWithModifier( 'shift', 'ArrowUp' ); + await pressKeyWithModifier( 'shift', 'ArrowDown' ); + + // Should type at the end of the paragraph. + await page.keyboard.type( '.' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should always expand single line selection', async () => { + await clickBlockAppender(); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '12' ); + await page.keyboard.press( 'ArrowLeft' ); + await pressKeyWithModifier( 'shift', 'ArrowRight' ); + await pressKeyWithModifier( 'shift', 'ArrowUp' ); + await testNativeSelection(); + // This delete all blocks. + await page.keyboard.press( 'Backspace' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should allow selecting outer edge if there is no sibling block', async () => { + await clickBlockAppender(); + await page.keyboard.type( '1' ); + await pressKeyWithModifier( 'shift', 'ArrowUp' ); + // This should replace the content. + await page.keyboard.type( '2' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should select and deselect with shift and arrow keys', async () => { + await clickBlockAppender(); + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '3' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '4' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '5' ); + await page.keyboard.press( 'ArrowUp' ); + await page.keyboard.press( 'ArrowUp' ); + await pressKeyWithModifier( 'shift', 'ArrowDown' ); + + await testNativeSelection(); + expect( await getSelectedFlatIndices() ).toEqual( [ 3, 4 ] ); + + await pressKeyWithModifier( 'shift', 'ArrowDown' ); + + await testNativeSelection(); + expect( await getSelectedFlatIndices() ).toEqual( [ 3, 4, 5 ] ); + + await pressKeyWithModifier( 'shift', 'ArrowUp' ); + + await testNativeSelection(); + expect( await getSelectedFlatIndices() ).toEqual( [ 3, 4 ] ); + + await pressKeyWithModifier( 'shift', 'ArrowUp' ); + + await testNativeSelection(); + expect( await getSelectedFlatIndices() ).toBe( 3 ); + + await pressKeyWithModifier( 'shift', 'ArrowUp' ); + + await testNativeSelection(); + expect( await getSelectedFlatIndices() ).toEqual( [ 2, 3 ] ); + + await pressKeyWithModifier( 'shift', 'ArrowUp' ); + + await testNativeSelection(); + expect( await getSelectedFlatIndices() ).toEqual( [ 1, 2, 3 ] ); + + await pressKeyWithModifier( 'shift', 'ArrowDown' ); + + await testNativeSelection(); + expect( await getSelectedFlatIndices() ).toEqual( [ 2, 3 ] ); + + await pressKeyWithModifier( 'shift', 'ArrowDown' ); + + await testNativeSelection(); + expect( await getSelectedFlatIndices() ).toBe( 3 ); + } ); + + it( 'should deselect with Escape', async () => { + await clickBlockAppender(); + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2' ); + + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); + + await testNativeSelection(); + expect( await getSelectedFlatIndices() ).toEqual( [ 1, 2 ] ); + + await page.keyboard.press( 'Escape' ); + + expect( await getSelectedFlatIndices() ).toEqual( [] ); + } ); + + it( 'should select with shift + click', async () => { + await clickBlockAppender(); + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2' ); + await page.keyboard.down( 'Shift' ); + await page.click( '.wp-block-paragraph' ); + await page.keyboard.up( 'Shift' ); + + await testNativeSelection(); + expect( await getSelectedFlatIndices() ).toEqual( [ 1, 2 ] ); + } ); + + it( 'should select by dragging', async () => { + await clickBlockAppender(); + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2' ); + await page.keyboard.press( 'ArrowUp' ); + + const [ coord1, coord2 ] = await page.evaluate( () => { + const elements = Array.from( document.querySelectorAll( '.wp-block-paragraph' ) ); + const rect1 = elements[ 0 ].getBoundingClientRect(); + const rect2 = elements[ 1 ].getBoundingClientRect(); + return [ + { + x: rect1.x + ( rect1.width / 2 ), + y: rect1.y + ( rect1.height / 2 ), + }, + { + x: rect2.x + ( rect2.width / 2 ), + y: rect2.y + ( rect2.height / 2 ), + }, + ]; + } ); + + await page.mouse.move( coord1.x, coord1.y ); + await page.mouse.down(); + await page.mouse.move( coord2.x, coord2.y, { steps: 10 } ); + await page.mouse.up(); + + await testNativeSelection(); + expect( await getSelectedFlatIndices() ).toEqual( [ 1, 2 ] ); + } ); + + it( 'should select by dragging into nested block', async () => { + await clickBlockAppender(); + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '/cover' ); + await page.keyboard.press( 'Enter' ); + await page.click( '.components-circular-option-picker__option' ); + await page.keyboard.type( '2' ); + await page.keyboard.press( 'ArrowUp' ); + + const [ coord1, coord2 ] = await page.evaluate( () => { + const elements = Array.from( document.querySelectorAll( '.wp-block-paragraph' ) ); + const rect1 = elements[ 0 ].getBoundingClientRect(); + const rect2 = elements[ 1 ].getBoundingClientRect(); + return [ + { + x: rect1.x + ( rect1.width / 2 ), + y: rect1.y + ( rect1.height / 2 ), + }, + { + x: rect2.x + ( rect2.width / 2 ), + y: rect2.y + ( rect2.height / 2 ), + }, + ]; + } ); + + await page.mouse.move( coord1.x, coord1.y ); + await page.mouse.down(); + await page.mouse.move( coord2.x, coord2.y, { steps: 10 } ); + await page.mouse.up(); + + await testNativeSelection(); + expect( await getSelectedFlatIndices() ).toEqual( [ 1, 2 ] ); + } ); + + it( 'should cut and paste', async () => { + await clickBlockAppender(); + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2' ); + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'x' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await pressKeyWithModifier( 'primary', 'v' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should copy and paste', async () => { + await clickBlockAppender(); + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2' ); + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'c' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await page.keyboard.press( 'Backspace' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await pressKeyWithModifier( 'primary', 'v' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); +} ); diff --git a/packages/e2e-tests/specs/navigable-toolbar.test.js b/packages/e2e-tests/specs/editor/various/navigable-toolbar.test.js similarity index 100% rename from packages/e2e-tests/specs/navigable-toolbar.test.js rename to packages/e2e-tests/specs/editor/various/navigable-toolbar.test.js diff --git a/packages/e2e-tests/specs/new-post-default-content.test.js b/packages/e2e-tests/specs/editor/various/new-post-default-content.test.js similarity index 100% rename from packages/e2e-tests/specs/new-post-default-content.test.js rename to packages/e2e-tests/specs/editor/various/new-post-default-content.test.js diff --git a/packages/e2e-tests/specs/new-post.test.js b/packages/e2e-tests/specs/editor/various/new-post.test.js similarity index 100% rename from packages/e2e-tests/specs/new-post.test.js rename to packages/e2e-tests/specs/editor/various/new-post.test.js diff --git a/packages/e2e-tests/specs/editor/various/nux.test.js b/packages/e2e-tests/specs/editor/various/nux.test.js new file mode 100644 index 00000000000000..d172f138cb50f2 --- /dev/null +++ b/packages/e2e-tests/specs/editor/various/nux.test.js @@ -0,0 +1,105 @@ +/** + * WordPress dependencies + */ +import { createNewPost, clickOnMoreMenuItem } from '@wordpress/e2e-test-utils'; + +describe( 'New User Experience (NUX)', () => { + it( 'should show the guide to first-time users', async () => { + let welcomeGuideText, welcomeGuide; + + // Create a new post as a first-time user + await createNewPost( { enableTips: true } ); + + // Guide should be on page 1 of 3 + welcomeGuideText = await page.$eval( '.edit-post-welcome-guide', ( element ) => element.innerText ); + expect( welcomeGuideText ).toContain( 'Welcome to the Block Editor' ); + + // Click on the 'Next' button + const [ nextButton ] = await page.$x( '//button[contains(text(), "Next")]' ); + await nextButton.click(); + + // Guide should be on page 2 of 3 + welcomeGuideText = await page.$eval( '.edit-post-welcome-guide', ( element ) => element.innerText ); + expect( welcomeGuideText ).toContain( 'Make each block your own' ); + + // Click on the 'Previous' button + const [ previousButton ] = await page.$x( '//button[contains(text(), "Previous")]' ); + await previousButton.click(); + + // Guide should be on page 1 of 3 + welcomeGuideText = await page.$eval( '.edit-post-welcome-guide', ( element ) => element.innerText ); + expect( welcomeGuideText ).toContain( 'Welcome to the Block Editor' ); + + // Press the button for Page 2 + await page.click( 'button[aria-label="Page 2 of 3"]' ); + + // Press the right arrow key + await page.keyboard.press( 'ArrowRight' ); + + // Guide should be on page 3 of 3 + welcomeGuideText = await page.$eval( '.edit-post-welcome-guide', ( element ) => element.innerText ); + expect( welcomeGuideText ).toContain( 'Get to know the Block Library' ); + + // Click on the *visible* 'Get started' button. There are two in the DOM + // but only one is shown depending on viewport size + let getStartedButton; + for ( const buttonHandle of await page.$x( '//button[contains(text(), "Get started")]' ) ) { + if ( + await page.evaluate( ( button ) => button.style.display !== 'none', buttonHandle ) + ) { + getStartedButton = buttonHandle; + } + } + await getStartedButton.click(); + + // Guide should be closed + welcomeGuide = await page.$( '.edit-post-welcome-guide' ); + expect( welcomeGuide ).toBeNull(); + + // Reload the editor + await page.reload(); + + // Guide should be closed + welcomeGuide = await page.$( '.edit-post-welcome-guide' ); + expect( welcomeGuide ).toBeNull(); + } ); + + it( 'should not show the welcome guide again if it is dismissed', async () => { + let welcomeGuide; + + // Create a new post as a first-time user + await createNewPost( { enableTips: true } ); + + // Guide should be open + welcomeGuide = await page.$( '.edit-post-welcome-guide' ); + expect( welcomeGuide ).not.toBeNull(); + + // Close the guide + await page.click( 'button[aria-label="Close dialog"]' ); + + // Reload the editor + await page.reload(); + + // Guide should be closed + welcomeGuide = await page.$( '.edit-post-welcome-guide' ); + expect( welcomeGuide ).toBeNull(); + } ); + + it( 'should show the welcome guide if it is manually opened', async () => { + let welcomeGuide; + + // Create a new post as a returning user + await createNewPost(); + + // Guide should be closed + welcomeGuide = await page.$( '.edit-post-welcome-guide' ); + expect( welcomeGuide ).toBeNull(); + + // Manually open the guide + await clickOnMoreMenuItem( 'Welcome Guide' ); + + // Guide should be open + welcomeGuide = await page.$( '.edit-post-welcome-guide' ); + expect( welcomeGuide ).not.toBeNull(); + } ); +} ); diff --git a/packages/e2e-tests/specs/popovers.test.js b/packages/e2e-tests/specs/editor/various/popovers.test.js similarity index 100% rename from packages/e2e-tests/specs/popovers.test.js rename to packages/e2e-tests/specs/editor/various/popovers.test.js diff --git a/packages/e2e-tests/specs/editor/various/post-visibility.test.js b/packages/e2e-tests/specs/editor/various/post-visibility.test.js new file mode 100644 index 00000000000000..2aa49ef22c96df --- /dev/null +++ b/packages/e2e-tests/specs/editor/various/post-visibility.test.js @@ -0,0 +1,66 @@ +/** + * WordPress dependencies + */ +import { + setBrowserViewport, + createNewPost, + openDocumentSettingsSidebar, +} from '@wordpress/e2e-test-utils'; + +describe( 'Post visibility', () => { + afterEach( async () => { + await setBrowserViewport( 'large' ); + } ); + [ 'large', 'small' ].forEach( ( viewport ) => { + it( `can be changed when the viewport is ${ viewport }`, async () => { + await setBrowserViewport( viewport ); + + await createNewPost(); + + await openDocumentSettingsSidebar(); + + await page.click( '.edit-post-post-visibility__toggle' ); + + const [ privateLabel ] = await page.$x( '//label[text()="Private"]' ); + await privateLabel.click(); + + const currentStatus = await page.evaluate( () => { + return wp.data.select( 'core/editor' ).getEditedPostAttribute( 'status' ); + } ); + + expect( currentStatus ).toBe( 'private' ); + } ); + } ); + + it( 'visibility remains private even if the publish date is in the future', async () => { + await createNewPost(); + + // Enter a title for this post. + await page.type( '.editor-post-title__input', 'Title' ); + + await openDocumentSettingsSidebar(); + + // Set a publish date for the next month. + await page.click( '.edit-post-post-schedule__toggle' ); + await page.click( 'div[aria-label="Move forward to switch to the next month."]' ); + await ( + await page.$x( '//td[contains(concat(" ", @class, " "), " CalendarDay ")][text() = "15"]' ) + )[ 0 ].click(); + + await page.click( '.edit-post-post-visibility__toggle' ); + + const [ privateLabel ] = await page.$x( '//label[text()="Private"]' ); + await privateLabel.click(); + + // Enter a title for this post. + await page.type( '.editor-post-title__input', ' Changed' ); + + await page.click( '.editor-post-publish-button' ); + + const currentStatus = await page.evaluate( () => { + return wp.data.select( 'core/editor' ).getEditedPostAttribute( 'status' ); + } ); + + expect( currentStatus ).toBe( 'private' ); + } ); +} ); diff --git a/packages/e2e-tests/specs/preferences.test.js b/packages/e2e-tests/specs/editor/various/preferences.test.js similarity index 100% rename from packages/e2e-tests/specs/preferences.test.js rename to packages/e2e-tests/specs/editor/various/preferences.test.js diff --git a/packages/e2e-tests/specs/preview.test.js b/packages/e2e-tests/specs/editor/various/preview.test.js similarity index 98% rename from packages/e2e-tests/specs/preview.test.js rename to packages/e2e-tests/specs/editor/various/preview.test.js index 505f8cca3ba3a4..db92a0ff26e8c2 100644 --- a/packages/e2e-tests/specs/preview.test.js +++ b/packages/e2e-tests/specs/editor/various/preview.test.js @@ -15,9 +15,10 @@ import { saveDraft, clickOnMoreMenuItem, pressKeyWithModifier, - disableNavigationMode, } from '@wordpress/e2e-test-utils'; +/** @typedef {import('puppeteer').Page} Page */ + async function openPreviewPage( editorPage ) { let openTabs = await browser.pages(); const expectedTabsCount = openTabs.length + 1; @@ -41,7 +42,7 @@ async function openPreviewPage( editorPage ) { * Given a Puppeteer Page instance for a preview window, clicks Preview, and * awaits the window navigation. * - * @param {puppeteer.Page} previewPage Page on which to await navigation. + * @param {Page} previewPage Page on which to await navigation. * * @return {Promise} Promise resolving once navigation completes. */ @@ -206,7 +207,6 @@ describe( 'Preview with Custom Fields enabled', () => { beforeEach( async () => { await createNewPost(); await toggleCustomFieldsOption( true ); - await disableNavigationMode(); } ); afterEach( async () => { diff --git a/packages/e2e-tests/specs/publish-button.test.js b/packages/e2e-tests/specs/editor/various/publish-button.test.js similarity index 100% rename from packages/e2e-tests/specs/publish-button.test.js rename to packages/e2e-tests/specs/editor/various/publish-button.test.js diff --git a/packages/e2e-tests/specs/publish-panel.test.js b/packages/e2e-tests/specs/editor/various/publish-panel.test.js similarity index 100% rename from packages/e2e-tests/specs/publish-panel.test.js rename to packages/e2e-tests/specs/editor/various/publish-panel.test.js diff --git a/packages/e2e-tests/specs/publishing.test.js b/packages/e2e-tests/specs/editor/various/publishing.test.js similarity index 100% rename from packages/e2e-tests/specs/publishing.test.js rename to packages/e2e-tests/specs/editor/various/publishing.test.js diff --git a/packages/e2e-tests/specs/reusable-blocks.test.js b/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js similarity index 98% rename from packages/e2e-tests/specs/reusable-blocks.test.js rename to packages/e2e-tests/specs/editor/various/reusable-blocks.test.js index cfcd565254f2e3..a160da7a4e7898 100644 --- a/packages/e2e-tests/specs/reusable-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js @@ -118,6 +118,9 @@ describe( 'Reusable Blocks', () => { // Tab three times to navigate to the block's content await page.keyboard.press( 'Tab' ); await page.keyboard.press( 'Tab' ); + + // Quickly focus the paragraph block + await page.keyboard.press( 'Escape' ); // Enter navigation mode await page.keyboard.press( 'Enter' ); // Enter edit mode // Change the block's content diff --git a/packages/e2e-tests/specs/rich-text.test.js b/packages/e2e-tests/specs/editor/various/rich-text.test.js similarity index 82% rename from packages/e2e-tests/specs/rich-text.test.js rename to packages/e2e-tests/specs/editor/various/rich-text.test.js index 13acc998e39466..84d95a210449f2 100644 --- a/packages/e2e-tests/specs/rich-text.test.js +++ b/packages/e2e-tests/specs/editor/various/rich-text.test.js @@ -282,4 +282,58 @@ describe( 'RichText', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should split rich text on paste', async () => { + await clickBlockAppender(); + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2' ); + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'x' ); + await page.keyboard.type( 'ab' ); + await page.keyboard.press( 'ArrowLeft' ); + await pressKeyWithModifier( 'primary', 'v' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should not split rich text on inline paste', async () => { + await clickBlockAppender(); + await page.keyboard.type( '2' ); + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'x' ); + await page.keyboard.type( '13' ); + await page.keyboard.press( 'ArrowLeft' ); + await pressKeyWithModifier( 'primary', 'v' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should not split rich text on inline paste with formatting', async () => { + await clickBlockAppender(); + await page.keyboard.type( '1' ); + await pressKeyWithModifier( 'primary', 'b' ); + await page.keyboard.type( '2' ); + await pressKeyWithModifier( 'primary', 'b' ); + await page.keyboard.type( '3' ); + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'x' ); + await page.keyboard.type( 'ab' ); + await page.keyboard.press( 'ArrowLeft' ); + await pressKeyWithModifier( 'primary', 'v' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should make bold after split and merge', async () => { + await clickBlockAppender(); + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.press( 'Backspace' ); + await pressKeyWithModifier( 'primary', 'b' ); + await page.keyboard.type( '2' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/e2e-tests/specs/rtl.test.js b/packages/e2e-tests/specs/editor/various/rtl.test.js similarity index 100% rename from packages/e2e-tests/specs/rtl.test.js rename to packages/e2e-tests/specs/editor/various/rtl.test.js diff --git a/packages/e2e-tests/specs/scheduling.test.js b/packages/e2e-tests/specs/editor/various/scheduling.test.js similarity index 100% rename from packages/e2e-tests/specs/scheduling.test.js rename to packages/e2e-tests/specs/editor/various/scheduling.test.js diff --git a/packages/e2e-tests/specs/shortcut-help.test.js b/packages/e2e-tests/specs/editor/various/shortcut-help.test.js similarity index 100% rename from packages/e2e-tests/specs/shortcut-help.test.js rename to packages/e2e-tests/specs/editor/various/shortcut-help.test.js diff --git a/packages/e2e-tests/specs/sidebar-permalink-panel.test.js b/packages/e2e-tests/specs/editor/various/sidebar-permalink-panel.test.js similarity index 100% rename from packages/e2e-tests/specs/sidebar-permalink-panel.test.js rename to packages/e2e-tests/specs/editor/various/sidebar-permalink-panel.test.js diff --git a/packages/e2e-tests/specs/sidebar.test.js b/packages/e2e-tests/specs/editor/various/sidebar.test.js similarity index 91% rename from packages/e2e-tests/specs/sidebar.test.js rename to packages/e2e-tests/specs/editor/various/sidebar.test.js index ad621736ec4ca6..90dc69789de644 100644 --- a/packages/e2e-tests/specs/sidebar.test.js +++ b/packages/e2e-tests/specs/editor/various/sidebar.test.js @@ -2,9 +2,11 @@ * WordPress dependencies */ import { - findSidebarPanelWithTitle, + clearLocalStorage, createNewPost, - observeFocusLoss, + findSidebarPanelWithTitle, + enableFocusLossObservation, + disableFocusLossObservation, openDocumentSettingsSidebar, pressKeyWithModifier, setBrowserViewport, @@ -15,13 +17,15 @@ const ACTIVE_SIDEBAR_TAB_SELECTOR = '.edit-post-sidebar__panel-tab.is-active'; const ACTIVE_SIDEBAR_BUTTON_TEXT = 'Document'; describe( 'Sidebar', () => { - beforeAll( () => { - observeFocusLoss(); + afterEach( () => { + disableFocusLossObservation(); } ); it( 'should have sidebar visible at the start with document sidebar active on desktop', async () => { await setBrowserViewport( 'large' ); + await clearLocalStorage(); await createNewPost(); + await enableFocusLossObservation(); const { nodesCount, content, height, width } = await page.$$eval( ACTIVE_SIDEBAR_TAB_SELECTOR, ( nodes ) => { const firstNode = nodes[ 0 ]; return { @@ -45,14 +49,18 @@ describe( 'Sidebar', () => { it( 'should have the sidebar closed by default on mobile', async () => { await setBrowserViewport( 'small' ); + await clearLocalStorage(); await createNewPost(); + await enableFocusLossObservation(); const sidebar = await page.$( SIDEBAR_SELECTOR ); expect( sidebar ).toBeNull(); } ); it( 'should close the sidebar when resizing from desktop to mobile', async () => { await setBrowserViewport( 'large' ); + await clearLocalStorage(); await createNewPost(); + await enableFocusLossObservation(); const sidebars = await page.$$( SIDEBAR_SELECTOR ); expect( sidebars ).toHaveLength( 1 ); @@ -66,7 +74,9 @@ describe( 'Sidebar', () => { it( 'should reopen sidebar the sidebar when resizing from mobile to desktop if the sidebar was closed automatically', async () => { await setBrowserViewport( 'large' ); + await clearLocalStorage(); await createNewPost(); + await enableFocusLossObservation(); await setBrowserViewport( 'small' ); const sidebarsMobile = await page.$$( SIDEBAR_SELECTOR ); @@ -80,6 +90,7 @@ describe( 'Sidebar', () => { it( 'should preserve tab order while changing active tab', async () => { await createNewPost(); + await enableFocusLossObservation(); // Region navigate to Sidebar. await pressKeyWithModifier( 'ctrl', '`' ); @@ -107,6 +118,7 @@ describe( 'Sidebar', () => { it( 'should be possible to programmatically remove Document Settings panels', async () => { await createNewPost(); + await enableFocusLossObservation(); await openDocumentSettingsSidebar(); diff --git a/packages/e2e-tests/specs/splitting-merging.test.js b/packages/e2e-tests/specs/editor/various/splitting-merging.test.js similarity index 94% rename from packages/e2e-tests/specs/splitting-merging.test.js rename to packages/e2e-tests/specs/editor/various/splitting-merging.test.js index 8c3559e3f3645c..a394317c5dd752 100644 --- a/packages/e2e-tests/specs/splitting-merging.test.js +++ b/packages/e2e-tests/specs/editor/various/splitting-merging.test.js @@ -212,4 +212,16 @@ describe( 'splitting and merging blocks', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should not split with line break in front', async () => { + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + await pressKeyWithModifier( 'shift', 'Enter' ); + await page.keyboard.type( '2' ); + await page.keyboard.press( 'ArrowLeft' ); + await page.keyboard.press( 'Backspace' ); + + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); diff --git a/packages/e2e-tests/specs/style-variation.test.js b/packages/e2e-tests/specs/editor/various/style-variation.test.js similarity index 100% rename from packages/e2e-tests/specs/style-variation.test.js rename to packages/e2e-tests/specs/editor/various/style-variation.test.js diff --git a/packages/e2e-tests/specs/taxonomies.test.js b/packages/e2e-tests/specs/editor/various/taxonomies.test.js similarity index 82% rename from packages/e2e-tests/specs/taxonomies.test.js rename to packages/e2e-tests/specs/editor/various/taxonomies.test.js index 791fd79b12f8d4..f68343dcb2866f 100644 --- a/packages/e2e-tests/specs/taxonomies.test.js +++ b/packages/e2e-tests/specs/editor/various/taxonomies.test.js @@ -1,9 +1,14 @@ +/** + * External dependencies + */ +import { random } from 'lodash'; + /** * WordPress dependencies */ import { - findSidebarPanelWithTitle, createNewPost, + findSidebarPanelWithTitle, openDocumentSettingsSidebar, publishPost, } from '@wordpress/e2e-test-utils'; @@ -11,7 +16,7 @@ import { /** * Module constants */ -const TAG_TOKEN_SELECTOR = '.components-form-token-field__token-text span:not(.screen-reader-text)'; +const TAG_TOKEN_SELECTOR = '.components-form-token-field__token-text span:not(.components-visually-hidden)'; describe( 'Taxonomies', () => { const canCreatTermInTaxonomy = ( taxonomy ) => { @@ -50,22 +55,25 @@ describe( 'Taxonomies', () => { }, tagsPanel, TAG_TOKEN_SELECTOR ); }; + const openSidebarPanelWithTitle = async ( title ) => { + const panel = await page.waitForXPath( + `//div[contains(@class,"edit-post-sidebar")]//button[@class="components-button components-panel__body-toggle"][contains(text(),"${ title }")]` + ); + await panel.click(); + }; + it( 'should be able to open the categories panel and create a new main category if the user has the right capabilities', async () => { await createNewPost(); await openDocumentSettingsSidebar(); - const categoriesPanel = await findSidebarPanelWithTitle( 'Categories' ); - expect( categoriesPanel ).toBeDefined(); + await openSidebarPanelWithTitle( 'Categories' ); // If the user has no permission to add a new category finish the test. if ( ! ( await canCreatTermInTaxonomy( 'categories' ) ) ) { return; } - // Open the categories panel. - await categoriesPanel.click( 'button' ); - await page.waitForSelector( 'button.editor-post-taxonomies__hierarchical-terms-add' ); // Click add new category button. @@ -108,27 +116,18 @@ describe( 'Taxonomies', () => { expect( selectedCategories[ 0 ] ).toEqual( 'z rand category 1' ); } ); - // This test isn't reliable locally because repeated execution of the test triggers 400 network - // because of the tag's duplication. Also, it randomly doesn't add a new tag after pressing enter. - // See: https://github.com/WordPress/gutenberg/pull/15211. - it.skip( 'should be able to open the tags panel and create a new tag if the user has the right capabilities', async () => { + it( 'should be able to open the tags panel and create a new tag if the user has the right capabilities', async () => { await createNewPost(); await openDocumentSettingsSidebar(); - const tagsPanel = await findSidebarPanelWithTitle( 'Tags' ); - - //expect( await page.evaluate( ( el ) => el.outerHTML, tagsPanel ) ).toEqual( 'tag1 ok' ); - expect( tagsPanel ).toBeDefined(); + await openSidebarPanelWithTitle( 'Tags' ); // If the user has no permission to add a new tag finish the test. if ( ! ( await canCreatTermInTaxonomy( 'tags' ) ) ) { return; } - // Open the tags panel. - await tagsPanel.click( 'button' ); - // At the start there are no tag tokens expect( await page.$$( @@ -136,13 +135,16 @@ describe( 'Taxonomies', () => { ) ).toHaveLength( 0 ); + const tagsPanel = await findSidebarPanelWithTitle( 'Tags' ); const tagInput = await tagsPanel.$( '.components-form-token-field__input' ); // Click the tag input field. await tagInput.click(); + const tagName = 'tag-' + random( 1, Number.MAX_SAFE_INTEGER ); + // Type the category name in the field. - await tagInput.type( 'tag1' ); + await tagInput.type( tagName ); // Press enter to create a new tag. await tagInput.press( 'Enter' ); @@ -154,7 +156,7 @@ describe( 'Taxonomies', () => { // The post should only contain the tag we added. expect( tags ).toHaveLength( 1 ); - expect( tags[ 0 ] ).toEqual( 'tag1' ); + expect( tags[ 0 ] ).toEqual( tagName ); // Type something in the title so we can publish the post. await page.type( '.editor-post-title__input', 'Hello World' ); @@ -172,6 +174,6 @@ describe( 'Taxonomies', () => { // The tag selection was persisted after the publish process. expect( tags ).toHaveLength( 1 ); - expect( tags[ 0 ] ).toEqual( 'tag1' ); + expect( tags[ 0 ] ).toEqual( tagName ); } ); } ); diff --git a/packages/e2e-tests/specs/typewriter.test.js b/packages/e2e-tests/specs/editor/various/typewriter.test.js similarity index 100% rename from packages/e2e-tests/specs/typewriter.test.js rename to packages/e2e-tests/specs/editor/various/typewriter.test.js diff --git a/packages/e2e-tests/specs/undo.test.js b/packages/e2e-tests/specs/editor/various/undo.test.js similarity index 55% rename from packages/e2e-tests/specs/undo.test.js rename to packages/e2e-tests/specs/editor/various/undo.test.js index b5da166dfaedda..7c1a2602d7bfe3 100644 --- a/packages/e2e-tests/specs/undo.test.js +++ b/packages/e2e-tests/specs/editor/various/undo.test.js @@ -10,9 +10,42 @@ import { getAllBlocks, saveDraft, publishPost, - disableNavigationMode, } from '@wordpress/e2e-test-utils'; +const getSelection = async () => { + return await page.evaluate( () => { + const selectedBlock = document.activeElement.closest( '.wp-block' ); + const blocks = Array.from( document.querySelectorAll( '.wp-block' ) ); + const blockIndex = blocks.indexOf( selectedBlock ); + + if ( blockIndex === -1 ) { + return {}; + } + + const editables = Array.from( selectedBlock.querySelectorAll( '[contenteditable]' ) ); + const editableIndex = editables.indexOf( document.activeElement ); + const selection = window.getSelection(); + + if ( editableIndex === -1 || ! selection.rangeCount ) { + return { blockIndex }; + } + + const range = selection.getRangeAt( 0 ); + const cloneStart = range.cloneRange(); + const cloneEnd = range.cloneRange(); + + cloneStart.setStart( document.activeElement, 0 ); + cloneEnd.setStart( document.activeElement, 0 ); + + return { + blockIndex, + editableIndex, + startOffset: cloneStart.toString().length, + endOffset: cloneEnd.toString().length, + }; + } ); +}; + describe( 'undo', () => { beforeEach( async () => { await createNewPost(); @@ -25,15 +58,51 @@ describe( 'undo', () => { await new Promise( ( resolve ) => setTimeout( resolve, 1000 ) ); await page.keyboard.type( ' after pause' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); + const after = await getEditedPostContent(); + + expect( after ).toMatchSnapshot(); await pressKeyWithModifier( 'primary', 'z' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); + const before = await getEditedPostContent(); + + expect( before ).toMatchSnapshot(); + expect( await getSelection() ).toEqual( { + blockIndex: 1, + editableIndex: 0, + startOffset: 'before pause'.length, + endOffset: 'before pause'.length, + } ); await pressKeyWithModifier( 'primary', 'z' ); expect( await getEditedPostContent() ).toBe( '' ); + expect( await getSelection() ).toEqual( { + blockIndex: 1, + editableIndex: 0, + startOffset: 0, + endOffset: 0, + } ); + + await pressKeyWithModifier( 'primaryShift', 'z' ); + + expect( await getEditedPostContent() ).toBe( before ); + expect( await getSelection() ).toEqual( { + blockIndex: 1, + editableIndex: 0, + startOffset: 'before pause'.length, + endOffset: 'before pause'.length, + } ); + + await pressKeyWithModifier( 'primaryShift', 'z' ); + + expect( await getEditedPostContent() ).toBe( after ); + expect( await getSelection() ).toEqual( { + blockIndex: 1, + editableIndex: 0, + startOffset: 'before pause after pause'.length, + endOffset: 'before pause after pause'.length, + } ); } ); it( 'should undo typing after non input change', async () => { @@ -43,15 +112,65 @@ describe( 'undo', () => { await pressKeyWithModifier( 'primary', 'b' ); await page.keyboard.type( 'after keyboard' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); + const after = await getEditedPostContent(); + + expect( after ).toMatchSnapshot(); await pressKeyWithModifier( 'primary', 'z' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); + const before = await getEditedPostContent(); + + expect( before ).toMatchSnapshot(); + expect( await getSelection() ).toEqual( { + blockIndex: 1, + editableIndex: 0, + startOffset: 'before keyboard '.length, + endOffset: 'before keyboard '.length, + } ); await pressKeyWithModifier( 'primary', 'z' ); expect( await getEditedPostContent() ).toBe( '' ); + expect( await getSelection() ).toEqual( { + blockIndex: 1, + editableIndex: 0, + startOffset: 0, + endOffset: 0, + } ); + + await pressKeyWithModifier( 'primaryShift', 'z' ); + + expect( await getEditedPostContent() ).toBe( before ); + expect( await getSelection() ).toEqual( { + blockIndex: 1, + editableIndex: 0, + startOffset: 'before keyboard '.length, + endOffset: 'before keyboard '.length, + } ); + + await pressKeyWithModifier( 'primaryShift', 'z' ); + + expect( await getEditedPostContent() ).toBe( after ); + expect( await getSelection() ).toEqual( { + blockIndex: 1, + editableIndex: 0, + startOffset: 'before keyboard after keyboard'.length, + endOffset: 'before keyboard after keyboard'.length, + } ); + } ); + + it( 'should undo bold', async () => { + await clickBlockAppender(); + await page.keyboard.type( 'test' ); + await saveDraft(); + await page.reload(); + await page.click( '.wp-block-paragraph' ); + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'b' ); + await pressKeyWithModifier( 'primary', 'z' ); + + const visibleResult = await page.evaluate( () => document.activeElement.innerHTML ); + expect( visibleResult ).toBe( 'test' ); } ); it( 'Should undo/redo to expected level intervals', async () => { @@ -79,59 +198,124 @@ describe( 'undo', () => { const thirdText = await getEditedPostContent(); - await new Promise( ( resolve ) => setTimeout( resolve, 1000 ) ); - await pressKeyWithModifier( 'primary', 'z' ); // Undo 3rd paragraph text. expect( await getEditedPostContent() ).toBe( thirdBlock ); + expect( await getSelection() ).toEqual( { + blockIndex: 3, + editableIndex: 0, + startOffset: 0, + endOffset: 0, + } ); await pressKeyWithModifier( 'primary', 'z' ); // Undo 3rd block. expect( await getEditedPostContent() ).toBe( secondText ); + expect( await getSelection() ).toEqual( { + blockIndex: 2, + editableIndex: 0, + startOffset: 0, + endOffset: 0, + } ); await pressKeyWithModifier( 'primary', 'z' ); // Undo 2nd paragraph text. expect( await getEditedPostContent() ).toBe( secondBlock ); + expect( await getSelection() ).toEqual( { + blockIndex: 2, + editableIndex: 0, + startOffset: 0, + endOffset: 0, + } ); await pressKeyWithModifier( 'primary', 'z' ); // Undo 2nd block. expect( await getEditedPostContent() ).toBe( firstText ); + expect( await getSelection() ).toEqual( { + blockIndex: 1, + editableIndex: 0, + startOffset: 0, + endOffset: 0, + } ); await pressKeyWithModifier( 'primary', 'z' ); // Undo 1st paragraph text. expect( await getEditedPostContent() ).toBe( firstBlock ); + expect( await getSelection() ).toEqual( { + blockIndex: 1, + editableIndex: 0, + startOffset: 0, + endOffset: 0, + } ); await pressKeyWithModifier( 'primary', 'z' ); // Undo 1st block. expect( await getEditedPostContent() ).toBe( '' ); + expect( await getSelection() ).toEqual( {} ); // After undoing every action, there should be no more undo history. expect( await page.$( '.editor-history__undo[aria-disabled="true"]' ) ).not.toBeNull(); await pressKeyWithModifier( 'primaryShift', 'z' ); // Redo 1st block. expect( await getEditedPostContent() ).toBe( firstBlock ); + expect( await getSelection() ).toEqual( { + blockIndex: 1, + editableIndex: 0, + startOffset: 0, + endOffset: 0, + } ); // After redoing one change, the undo button should be enabled again. expect( await page.$( '.editor-history__undo[aria-disabled="true"]' ) ).toBeNull(); await pressKeyWithModifier( 'primaryShift', 'z' ); // Redo 1st paragraph text. expect( await getEditedPostContent() ).toBe( firstText ); + expect( await getSelection() ).toEqual( { + blockIndex: 1, + editableIndex: 0, + startOffset: 'This'.length, + endOffset: 'This'.length, + } ); await pressKeyWithModifier( 'primaryShift', 'z' ); // Redo 2nd block. expect( await getEditedPostContent() ).toBe( secondBlock ); + expect( await getSelection() ).toEqual( { + blockIndex: 2, + editableIndex: 0, + startOffset: 0, + endOffset: 0, + } ); await pressKeyWithModifier( 'primaryShift', 'z' ); // Redo 2nd paragraph text. expect( await getEditedPostContent() ).toBe( secondText ); + expect( await getSelection() ).toEqual( { + blockIndex: 2, + editableIndex: 0, + startOffset: 'is'.length, + endOffset: 'is'.length, + } ); await pressKeyWithModifier( 'primaryShift', 'z' ); // Redo 3rd block. expect( await getEditedPostContent() ).toBe( thirdBlock ); + expect( await getSelection() ).toEqual( { + blockIndex: 3, + editableIndex: 0, + startOffset: 0, + endOffset: 0, + } ); await pressKeyWithModifier( 'primaryShift', 'z' ); // Redo 3rd paragraph text. expect( await getEditedPostContent() ).toBe( thirdText ); + expect( await getSelection() ).toEqual( { + blockIndex: 3, + editableIndex: 0, + startOffset: 'test'.length, + endOffset: 'test'.length, + } ); } ); it( 'should undo for explicit persistence editing post', async () => { @@ -146,7 +330,6 @@ describe( 'undo', () => { await page.keyboard.type( 'original' ); await saveDraft(); await page.reload(); - await disableNavigationMode(); // Issue is demonstrated by forcing state merges (multiple inputs) on // an existing text after a fresh reload. diff --git a/packages/e2e-tests/specs/writing-flow.test.js b/packages/e2e-tests/specs/editor/various/writing-flow.test.js similarity index 100% rename from packages/e2e-tests/specs/writing-flow.test.js rename to packages/e2e-tests/specs/editor/various/writing-flow.test.js diff --git a/packages/e2e-tests/specs/demo.test.js b/packages/e2e-tests/specs/local/demo.test.js similarity index 100% rename from packages/e2e-tests/specs/demo.test.js rename to packages/e2e-tests/specs/local/demo.test.js diff --git a/packages/e2e-tests/specs/multi-block-selection.test.js b/packages/e2e-tests/specs/multi-block-selection.test.js deleted file mode 100644 index 6bf7a62bf7789e..00000000000000 --- a/packages/e2e-tests/specs/multi-block-selection.test.js +++ /dev/null @@ -1,206 +0,0 @@ -/** - * WordPress dependencies - */ -import { - clickBlockAppender, - insertBlock, - createNewPost, - pressKeyWithModifier, - pressKeyTimes, - getEditedPostContent, -} from '@wordpress/e2e-test-utils'; - -describe( 'Multi-block selection', () => { - beforeEach( async () => { - await createNewPost(); - } ); - - it( 'Should select/unselect multiple blocks', async () => { - const firstBlockSelector = '[data-type="core/paragraph"]'; - const secondBlockSelector = '[data-type="core/image"]'; - const thirdBlockSelector = '[data-type="core/quote"]'; - const multiSelectedCssClass = 'is-multi-selected'; - - // Creating test blocks - await clickBlockAppender(); - await page.keyboard.type( 'First Paragraph' ); - await insertBlock( 'Image' ); - await insertBlock( 'Quote' ); - await page.keyboard.type( 'Quote Block' ); - - const blocks = [ firstBlockSelector, secondBlockSelector, thirdBlockSelector ]; - const expectMultiSelected = async ( selectors, areMultiSelected ) => { - for ( const selector of selectors ) { - const className = await page.$eval( selector, ( element ) => element.className ); - if ( areMultiSelected ) { - expect( className ).toEqual( expect.stringContaining( multiSelectedCssClass ) ); - } else { - expect( className ).not.toEqual( expect.stringContaining( multiSelectedCssClass ) ); - } - } - }; - - // Default: No selection - await expectMultiSelected( blocks, false ); - - // Multiselect via Shift + click - await page.mouse.move( 200, 300 ); - await page.click( firstBlockSelector ); - await page.keyboard.down( 'Shift' ); - await page.click( thirdBlockSelector ); - await page.keyboard.up( 'Shift' ); - - // Verify selection - await expectMultiSelected( blocks, true ); - - // Unselect - await page.click( secondBlockSelector ); - - // No selection - await expectMultiSelected( blocks, false ); - - // Multiselect via keyboard - await page.click( 'body' ); - await pressKeyWithModifier( 'primary', 'a' ); - - // Verify selection - await expectMultiSelected( blocks, true ); - - // Unselect - await page.keyboard.press( 'Escape' ); - - // No selection - await expectMultiSelected( blocks, false ); - - // Select all via double shortcut. - await page.click( firstBlockSelector ); - await pressKeyWithModifier( 'primary', 'a' ); - await pressKeyWithModifier( 'primary', 'a' ); - await expectMultiSelected( blocks, true ); - } ); - - it( 'Should select/unselect multiple blocks using Shift + Arrows', async () => { - const firstBlockSelector = '[data-type="core/paragraph"]'; - const secondBlockSelector = '[data-type="core/image"]'; - const thirdBlockSelector = '[data-type="core/quote"]'; - const multiSelectedCssClass = 'is-multi-selected'; - - // Creating test blocks - await clickBlockAppender(); - await page.keyboard.type( 'First Paragraph' ); - await insertBlock( 'Image' ); - await insertBlock( 'Quote' ); - await page.keyboard.type( 'Quote Block' ); - - const blocks = [ firstBlockSelector, secondBlockSelector, thirdBlockSelector ]; - const expectMultiSelected = async ( selectors, areMultiSelected ) => { - for ( const selector of selectors ) { - const className = await page.$eval( selector, ( element ) => element.className ); - if ( areMultiSelected ) { - expect( className ).toEqual( expect.stringContaining( multiSelectedCssClass ) ); - } else { - expect( className ).not.toEqual( expect.stringContaining( multiSelectedCssClass ) ); - } - } - }; - - // Default: No selection - await expectMultiSelected( blocks, false ); - - // Multiselect via Shift + click - await page.mouse.move( 200, 300 ); - await page.click( firstBlockSelector ); - await page.keyboard.down( 'Shift' ); - await page.keyboard.press( 'ArrowDown' ); // Two blocks selected - await page.keyboard.press( 'ArrowDown' ); // Three blocks selected - await page.keyboard.up( 'Shift' ); - - // Verify selection - await expectMultiSelected( blocks, true ); - } ); - - it( 'should speak() number of blocks selected with multi-block selection', async () => { - await clickBlockAppender(); - await page.keyboard.type( 'First Paragraph' ); - await insertBlock( 'Paragraph' ); - await page.keyboard.type( 'Second Paragraph' ); - await insertBlock( 'Paragraph' ); - await page.keyboard.type( 'Third Paragraph' ); - - // Multiselect via keyboard. - await pressKeyWithModifier( 'primary', 'a' ); - await pressKeyWithModifier( 'primary', 'a' ); - - // TODO: It would be great to do this test by spying on `wp.a11y.speak`, - // but it's very difficult to do that because `wp.a11y` has - // DOM-dependant side-effect setup code and doesn't seem straightforward - // to mock. Instead, we check for the DOM node that `wp.a11y.speak()` - // inserts text into. - const speakTextContent = await page.$eval( '#a11y-speak-assertive', ( element ) => element.textContent ); - expect( speakTextContent.trim() ).toEqual( '3 blocks selected.' ); - } ); - - // See #14448: an incorrect buffer may trigger multi-selection too soon. - it( 'should only trigger multi-selection when at the end', async () => { - // Create a paragraph with four lines. - await clickBlockAppender(); - await page.keyboard.type( '1.' ); - await pressKeyWithModifier( 'shift', 'Enter' ); - await page.keyboard.type( '2.' ); - await pressKeyWithModifier( 'shift', 'Enter' ); - await page.keyboard.type( '3.' ); - await pressKeyWithModifier( 'shift', 'Enter' ); - await page.keyboard.type( '4.' ); - // Create a second block. - await page.keyboard.press( 'Enter' ); - // Move to the middle of the first line. - await pressKeyTimes( 'ArrowUp', 4 ); - await page.keyboard.press( 'ArrowRight' ); - // Select mid line one to mid line four. - await pressKeyWithModifier( 'shift', 'ArrowDown' ); - await pressKeyWithModifier( 'shift', 'ArrowDown' ); - await pressKeyWithModifier( 'shift', 'ArrowDown' ); - // Delete the text to see if the selection was correct. - await page.keyboard.press( 'Backspace' ); - - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'should use selection direction to determine vertical edge', async () => { - await clickBlockAppender(); - await page.keyboard.type( '1' ); - await pressKeyWithModifier( 'shift', 'Enter' ); - await page.keyboard.type( '2' ); - - await pressKeyWithModifier( 'shift', 'ArrowUp' ); - await pressKeyWithModifier( 'shift', 'ArrowDown' ); - - // Should type at the end of the paragraph. - await page.keyboard.type( '.' ); - - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'should always expand single line selection', async () => { - await clickBlockAppender(); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( '12' ); - await page.keyboard.press( 'ArrowLeft' ); - await pressKeyWithModifier( 'shift', 'ArrowRight' ); - await pressKeyWithModifier( 'shift', 'ArrowUp' ); - // This delete all blocks. - await page.keyboard.press( 'Backspace' ); - - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'should allow selecting outer edge if there is no sibling block', async () => { - await clickBlockAppender(); - await page.keyboard.type( '1' ); - await pressKeyWithModifier( 'shift', 'ArrowUp' ); - // This should replace the content. - await page.keyboard.type( '2' ); - - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); -} ); diff --git a/packages/e2e-tests/specs/nux.test.js b/packages/e2e-tests/specs/nux.test.js deleted file mode 100644 index 659263cea35335..00000000000000 --- a/packages/e2e-tests/specs/nux.test.js +++ /dev/null @@ -1,180 +0,0 @@ -/** - * WordPress dependencies - */ -import { - clickBlockAppender, - clickOnMoreMenuItem, - createNewPost, - saveDraft, - toggleScreenOption, -} from '@wordpress/e2e-test-utils'; - -describe( 'New User Experience (NUX)', () => { - async function clickAllTips( page ) { - // Click through all available tips. - const tips = await getTips( page ); - const numberOfTips = tips.tipIds.length; - - for ( let i = 1; i < numberOfTips; i++ ) { - await page.click( '.nux-dot-tip .components-button.is-link' ); - } - - return { numberOfTips, tips }; - } - - async function getTips( page ) { - return await page.evaluate( () => { - return wp.data.select( 'core/nux' ).getAssociatedGuide( 'core/editor.inserter' ); - } ); - } - - async function getTipsEnabled( page ) { - return await page.evaluate( () => { - return wp.data.select( 'core/nux' ).areTipsEnabled(); - } ); - } - - beforeEach( async () => { - await createNewPost( { enableTips: true } ); - } ); - - it( 'should show tips to a first-time user', async () => { - const firstTipText = await page.$eval( '.nux-dot-tip', ( element ) => element.innerText ); - expect( firstTipText ).toContain( 'Welcome to the wonderful world of blocks!' ); - - const [ nextTipButton ] = await page.$x( "//button[contains(text(), 'See next tip')]" ); - await nextTipButton.click(); - - const secondTipText = await page.$eval( '.nux-dot-tip', ( element ) => element.innerText ); - expect( secondTipText ).toContain( 'You’ll find more settings for your page and blocks in the sidebar.' ); - } ); - - it( 'should show "Got it" once all tips have been displayed', async () => { - await clickAllTips( page ); - - // Make sure "Got it" button appears on the last tip. - const gotItButton = await page.$x( "//button[contains(text(), 'Got it')]" ); - expect( gotItButton ).toHaveLength( 1 ); - - // Click the "Got it button". - await page.click( '.nux-dot-tip .components-button.is-link' ); - - // Verify no more tips are visible on the page. - const nuxTipElements = await page.$$( '.nux-dot-tip' ); - expect( nuxTipElements ).toHaveLength( 0 ); - - // Tips should not be marked as disabled, but when the user has seen all - // of the available tips, they will not appear. - const areTipsEnabled = await getTipsEnabled( page ); - expect( areTipsEnabled ).toEqual( true ); - } ); - - it( 'should hide and disable tips if "disable tips" button is clicked', async () => { - await page.click( '.nux-dot-tip__disable' ); - - // Verify no more tips are visible on the page. - let nuxTipElements = await page.$$( '.nux-dot-tip' ); - expect( nuxTipElements ).toHaveLength( 0 ); - - // We should be disabling the tips so they don't appear again. - const areTipsEnabled = await getTipsEnabled( page ); - expect( areTipsEnabled ).toEqual( false ); - - // Refresh the page; tips should not show because they were disabled. - await page.reload(); - - nuxTipElements = await page.$$( '.nux-dot-tip' ); - expect( nuxTipElements ).toHaveLength( 0 ); - } ); - - it( 'should enable tips when the "Tips" option is toggled on', async () => { - // Start by disabling tips. - await page.click( '.nux-dot-tip__disable' ); - - // Verify no more tips are visible on the page. - let nuxTipElements = await page.$$( '.nux-dot-tip' ); - expect( nuxTipElements ).toHaveLength( 0 ); - - // Tips should be disabled in localStorage as well. - let areTipsEnabled = await getTipsEnabled( page ); - expect( areTipsEnabled ).toEqual( false ); - - // Toggle the 'Tips' option to enable. - await toggleScreenOption( 'Tips' ); - - // Tips should once again appear. - nuxTipElements = await page.$$( '.nux-dot-tip' ); - expect( nuxTipElements ).toHaveLength( 1 ); - - // Tips should be enabled in localStorage as well. - areTipsEnabled = await getTipsEnabled( page ); - expect( areTipsEnabled ).toEqual( true ); - } ); - - // TODO: This test should be enabled once - // https://github.com/WordPress/gutenberg/issues/7458 is fixed. - it.skip( 'should show tips as disabled if all tips have been shown', async () => { - await clickAllTips( page ); - - // Open the "More" menu to check the "Show Tips" element. - await page.click( '.edit-post-more-menu [aria-label="More tools & options"]' ); - const showTipsButton = await page.$x( '//button[contains(text(), "Show Tips")][@aria-pressed="false"]' ); - - expect( showTipsButton ).toHaveLength( 1 ); - } ); - - // TODO: This test should be enabled once - // https://github.com/WordPress/gutenberg/issues/7458 is fixed. - it.skip( 'should reset tips if all tips have been shown and show tips was unchecked', async () => { - const { numberOfTips } = await clickAllTips( page ); - - // Click again to re-enable tips; they should appear. - await clickOnMoreMenuItem( 'Show Tips' ); - - // Open the "More" menu to check the "Show Tips" element. - await page.click( '.edit-post-more-menu [aria-label="More tools & options"]' ); - const showTipsButton = await page.$x( '//button[contains(text(), "Show Tips")][@aria-pressed="true"]' ); - - expect( showTipsButton ).toHaveLength( 1 ); - - // Tips should re-appear on the page. - const nuxTipElements = await page.$$( '.nux-dot-tip' ); - expect( nuxTipElements ).toHaveLength( 1 ); - - // Tips should be enabled again. - const areTipsEnabled = await getTipsEnabled( page ); - expect( areTipsEnabled ).toEqual( true ); - - // Dismissed tips should be reset and ready to be shown again. - const resetTips = await getTips( page ); - const newNumberOfTips = resetTips.tipIds.length; - expect( newNumberOfTips ).toHaveLength( numberOfTips ); - } ); - - // TODO: This test should be enabled once - // https://github.com/WordPress/gutenberg/issues/7753 is fixed. - // See: https://github.com/WordPress/gutenberg/issues/7753#issuecomment-403952816 - it.skip( 'should show tips if "Show tips" was disabled on a draft and then enabled', async () => { - // Click the "Show tips" button (enabled by default) to disable tips. - await clickOnMoreMenuItem( 'Show Tips' ); - - // Let's type something so there's content in this post. - await page.click( '.editor-post-title__input' ); - await page.keyboard.type( 'Post title' ); - await clickBlockAppender(); - await page.keyboard.type( 'Post content goes here.' ); - await saveDraft(); - - // Refresh the page; tips should be disabled. - await page.reload(); - let nuxTipElements = await page.$$( '.nux-dot-tip' ); - expect( nuxTipElements ).toHaveLength( 0 ); - - // Clicking should re-enable tips. - await clickOnMoreMenuItem( 'Show Tips' ); - - // Tips should re-appear on the page. - nuxTipElements = await page.$$( '.nux-dot-tip' ); - expect( nuxTipElements ).toHaveLength( 1 ); - } ); -} ); diff --git a/packages/e2e-tests/specs/.gitignore b/packages/e2e-tests/specs/performance/.gitignore similarity index 100% rename from packages/e2e-tests/specs/.gitignore rename to packages/e2e-tests/specs/performance/.gitignore diff --git a/packages/e2e-tests/specs/performance.test.js b/packages/e2e-tests/specs/performance/performance.test.js similarity index 92% rename from packages/e2e-tests/specs/performance.test.js rename to packages/e2e-tests/specs/performance/performance.test.js index 174bf2c6ab1301..7f402dad605ca6 100644 --- a/packages/e2e-tests/specs/performance.test.js +++ b/packages/e2e-tests/specs/performance/performance.test.js @@ -11,7 +11,6 @@ import { createNewPost, saveDraft, insertBlock, - disableNavigationMode, } from '@wordpress/e2e-test-utils'; function readFile( filePath ) { @@ -20,7 +19,7 @@ function readFile( filePath ) { describe( 'Performance', () => { it( '1000 paragraphs', async () => { - const html = readFile( join( __dirname, '../assets/large-post.html' ) ); + const html = readFile( join( __dirname, '../../assets/large-post.html' ) ); await createNewPost(); await page.evaluate( ( _html ) => { @@ -54,7 +53,6 @@ describe( 'Performance', () => { while ( i-- ) { startTime = new Date(); await page.reload( { waitUntil: [ 'domcontentloaded', 'load' ] } ); - await disableNavigationMode(); } await insertBlock( 'Paragraph' ); diff --git a/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap b/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap deleted file mode 100644 index c2baa4270d086f..00000000000000 --- a/packages/e2e-tests/specs/plugins/__snapshots__/plugins-api.test.js.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Using Plugins API Document Setting Custom Panel Should render a custom panel inside Document Setting sidebar 1`] = `"My Custom Panel"`; - -exports[`Using Plugins API Sidebar Medium screen Should open plugins sidebar using More Menu item and render content 1`] = `"<div class=\\"components-panel__header edit-post-sidebar-header__small\\"><span class=\\"edit-post-sidebar-header__title\\">(no title)</span><button type=\\"button\\" aria-label=\\"Close plugin\\" class=\\"components-button components-icon-button\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" class=\\"dashicon dashicons-no-alt\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\"><path d=\\"M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z\\"></path></svg></button></div><div class=\\"components-panel__header edit-post-sidebar-header\\"><strong>Sidebar title plugin</strong><button type=\\"button\\" aria-label=\\"Unpin from toolbar\\" aria-expanded=\\"true\\" class=\\"components-button components-icon-button is-toggled\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" class=\\"dashicon dashicons-star-filled\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\"><path d=\\"M10 1l3 6 6 .75-4.12 4.62L16 19l-6-3-6 3 1.13-6.63L1 7.75 7 7z\\"></path></svg></button><button type=\\"button\\" aria-label=\\"Close plugin\\" class=\\"components-button components-icon-button\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" class=\\"dashicon dashicons-no-alt\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\"><path d=\\"M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z\\"></path></svg></button></div><div class=\\"components-panel\\"><div class=\\"components-panel__body is-opened\\"><div class=\\"components-panel__row\\"><label for=\\"title-plain-text\\">Title:</label><textarea class=\\"editor-plain-text block-editor-plain-text\\" id=\\"title-plain-text\\" placeholder=\\"(no title)\\" rows=\\"1\\" style=\\"overflow: hidden; overflow-wrap: break-word; resize: none; height: 18px;\\"></textarea></div><div class=\\"components-panel__row\\"><button type=\\"button\\" class=\\"components-button is-button is-primary\\">Reset</button></div></div></div>"`; - -exports[`Using Plugins API Sidebar Should open plugins sidebar using More Menu item and render content 1`] = `"<div class=\\"components-panel__header edit-post-sidebar-header__small\\"><span class=\\"edit-post-sidebar-header__title\\">(no title)</span><button type=\\"button\\" aria-label=\\"Close plugin\\" class=\\"components-button components-icon-button\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" class=\\"dashicon dashicons-no-alt\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\"><path d=\\"M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z\\"></path></svg></button></div><div class=\\"components-panel__header edit-post-sidebar-header\\"><strong>Sidebar title plugin</strong><button type=\\"button\\" aria-label=\\"Unpin from toolbar\\" aria-expanded=\\"true\\" class=\\"components-button components-icon-button is-toggled\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" class=\\"dashicon dashicons-star-filled\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\"><path d=\\"M10 1l3 6 6 .75-4.12 4.62L16 19l-6-3-6 3 1.13-6.63L1 7.75 7 7z\\"></path></svg></button><button type=\\"button\\" aria-label=\\"Close plugin\\" class=\\"components-button components-icon-button\\"><svg aria-hidden=\\"true\\" role=\\"img\\" focusable=\\"false\\" class=\\"dashicon dashicons-no-alt\\" xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"20\\" height=\\"20\\" viewBox=\\"0 0 20 20\\"><path d=\\"M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z\\"></path></svg></button></div><div class=\\"components-panel\\"><div class=\\"components-panel__body is-opened\\"><div class=\\"components-panel__row\\"><label for=\\"title-plain-text\\">Title:</label><textarea class=\\"editor-plain-text block-editor-plain-text\\" id=\\"title-plain-text\\" placeholder=\\"(no title)\\" rows=\\"1\\" style=\\"overflow: hidden; overflow-wrap: break-word; resize: none; height: 18px;\\"></textarea></div><div class=\\"components-panel__row\\"><button type=\\"button\\" class=\\"components-button is-button is-primary\\">Reset</button></div></div></div>"`; diff --git a/packages/e2e-tests/specs/post-visibility.test.js b/packages/e2e-tests/specs/post-visibility.test.js deleted file mode 100644 index 0c93dcdef0f896..00000000000000 --- a/packages/e2e-tests/specs/post-visibility.test.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * WordPress dependencies - */ -import { - setBrowserViewport, - createNewPost, - openDocumentSettingsSidebar, -} from '@wordpress/e2e-test-utils'; - -describe( 'Post visibility', () => { - [ 'large', 'small' ].forEach( ( viewport ) => { - it( `can be changed when the viewport is ${ viewport }`, async () => { - await setBrowserViewport( viewport ); - - await createNewPost(); - - await openDocumentSettingsSidebar(); - - await page.click( '.edit-post-post-visibility__toggle' ); - - const [ privateLabel ] = await page.$x( '//label[text()="Private"]' ); - await privateLabel.click(); - - const currentStatus = await page.evaluate( () => { - return wp.data.select( 'core/editor' ).getEditedPostAttribute( 'status' ); - } ); - - expect( currentStatus ).toBe( 'private' ); - } ); - } ); -} ); diff --git a/packages/edit-post/CHANGELOG.md b/packages/edit-post/CHANGELOG.md index 69dd699b23426e..c1d1576e310bc5 100644 --- a/packages/edit-post/CHANGELOG.md +++ b/packages/edit-post/CHANGELOG.md @@ -1,4 +1,4 @@ -## Master +## 3.8.2 ### Bug Fixes diff --git a/packages/edit-post/README.md b/packages/edit-post/README.md index 3b7b01a53e6dd3..0a9441a55b101f 100644 --- a/packages/edit-post/README.md +++ b/packages/edit-post/README.md @@ -92,13 +92,13 @@ _Parameters_ - _props_ `Object`: Component props. - _props.allowedBlocks_ `[Array]`: An array containing a list of block names for which the item should be shown. If not present, it'll be rendered for any block. If multiple blocks are selected, it'll be shown if and only if all of them are in the whitelist. -- _props.icon_ `[(string|Element)]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element. +- _props.icon_ `[WPBlockTypeIconRender]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element. - _props.label_ `string`: The menu item text. - _props.onClick_ `Function`: Callback function to be executed when the user click the menu item. _Returns_ -- `WPElement`: The WPElement to be rendered. +- `WPComponent`: The component to be rendered. <a name="PluginDocumentSettingPanel" href="#PluginDocumentSettingPanel">#</a> **PluginDocumentSettingPanel** @@ -149,11 +149,11 @@ _Parameters_ - _props.name_ `[string]`: The machine-friendly name for the panel. - _props.className_ `[string]`: An optional class name added to the row. - _props.title_ `[string]`: The title of the panel -- _props.icon_ `[(string|Element)]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. +- _props.icon_ `[WPBlockTypeIconRender]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. _Returns_ -- `WPElement`: The WPElement to be rendered. +- `WPComponent`: The component to be rendered. <a name="PluginMoreMenuItem" href="#PluginMoreMenuItem">#</a> **PluginMoreMenuItem** @@ -206,13 +206,13 @@ _Parameters_ - _props_ `Object`: Component properties. - _props.href_ `[string]`: When `href` is provided then the menu item is represented as an anchor rather than button. It corresponds to the `href` attribute of the anchor. -- _props.icon_ `[(string|Element)]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. +- _props.icon_ `[WPBlockTypeIconRender]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. - _props.onClick_ `[Function]`: The callback function to be executed when the user clicks the menu item. - _props.other_ `[...*]`: Any additional props are passed through to the underlying [MenuItem](/packages/components/src/menu-item/README.md) component. _Returns_ -- `WPElement`: The element to be rendered. +- `WPComponent`: The component to be rendered. <a name="PluginPostPublishPanel" href="#PluginPostPublishPanel">#</a> **PluginPostPublishPanel** @@ -261,11 +261,11 @@ _Parameters_ - _props.className_ `[string]`: An optional class name added to the panel. - _props.title_ `[string]`: Title displayed at the top of the panel. - _props.initialOpen_ `[boolean]`: Whether to have the panel initially opened. When no title is provided it is always opened. -- _props.icon_ `[(string|Element)]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. +- _props.icon_ `[WPBlockTypeIconRender]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. _Returns_ -- `WPElement`: The WPElement to be rendered. +- `WPComponent`: The component to be rendered. <a name="PluginPostStatusInfo" href="#PluginPostStatusInfo">#</a> **PluginPostStatusInfo** @@ -312,7 +312,7 @@ _Parameters_ _Returns_ -- `WPElement`: The WPElement to be rendered. +- `WPComponent`: The component to be rendered. <a name="PluginPrePublishPanel" href="#PluginPrePublishPanel">#</a> **PluginPrePublishPanel** @@ -361,11 +361,11 @@ _Parameters_ - _props.className_ `[string]`: An optional class name added to the panel. - _props.title_ `[string]`: Title displayed at the top of the panel. - _props.initialOpen_ `[boolean]`: Whether to have the panel initially opened. When no title is provided it is always opened. -- _props.icon_ `[(string|Element)]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. +- _props.icon_ `[WPBlockTypeIconRender]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. _Returns_ -- `WPElement`: The WPElement to be rendered. +- `WPComponent`: The component to be rendered. <a name="PluginSidebar" href="#PluginSidebar">#</a> **PluginSidebar** @@ -432,11 +432,11 @@ _Parameters_ - _props.className_ `[string]`: An optional class name added to the sidebar body. - _props.title_ `string`: Title displayed at the top of the sidebar. - _props.isPinnable_ `[boolean]`: Whether to allow to pin sidebar to toolbar. -- _props.icon_ `[(string|Element)]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. +- _props.icon_ `[WPBlockTypeIconRender]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. _Returns_ -- `WPElement`: Plugin sidebar component. +- `WPComponent`: Plugin sidebar component. <a name="PluginSidebarMoreMenuItem" href="#PluginSidebarMoreMenuItem">#</a> **PluginSidebarMoreMenuItem** @@ -482,11 +482,11 @@ _Parameters_ - _props_ `Object`: Component props. - _props.target_ `string`: A string identifying the target sidebar you wish to be activated by this menu item. Must be the same as the `name` prop you have given to that sidebar. -- _props.icon_ `[(string|Element)]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. +- _props.icon_ `[WPBlockTypeIconRender]`: The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. _Returns_ -- `WPElement`: The element to be rendered. +- `WPComponent`: The component to be rendered. <a name="reinitializeEditor" href="#reinitializeEditor">#</a> **reinitializeEditor** diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 98fd08247be8f0..7f00de877c36c2 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-post", - "version": "3.8.0", + "version": "3.9.0", "description": "Edit Post module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js b/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js index 8503692c289b07..938805e057406b 100644 --- a/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js +++ b/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js @@ -35,7 +35,7 @@ const shouldRenderItem = ( selectedBlocks, allowedBlocks ) => ! Array.isArray( a * * @param {Object} props Component props. * @param {Array} [props.allowedBlocks] An array containing a list of block names for which the item should be shown. If not present, it'll be rendered for any block. If multiple blocks are selected, it'll be shown if and only if all of them are in the whitelist. - * @param {string|Element} [props.icon] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element. + * @param {WPBlockTypeIconRender} [props.icon] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element. * @param {string} props.label The menu item text. * @param {Function} props.onClick Callback function to be executed when the user click the menu item. * @@ -81,7 +81,7 @@ const shouldRenderItem = ( selectedBlocks, allowedBlocks ) => ! Array.isArray( a * ); * ``` * - * @return {WPElement} The WPElement to be rendered. + * @return {WPComponent} The component to be rendered. */ const PluginBlockSettingsMenuItem = ( { allowedBlocks, icon, label, onClick, small, role } ) => ( <PluginBlockSettingsMenuGroup> diff --git a/packages/edit-post/src/components/browser-url/index.js b/packages/edit-post/src/components/browser-url/index.js index ccaa0bcaa98f7a..34a8e889cefd42 100644 --- a/packages/edit-post/src/components/browser-url/index.js +++ b/packages/edit-post/src/components/browser-url/index.js @@ -42,10 +42,12 @@ export class BrowserURL extends Component { } componentDidUpdate( prevProps ) { - const { postId, postStatus, postType } = this.props; + const { postId, postStatus, postType, isSavingPost } = this.props; const { historyId } = this.state; - if ( postStatus === 'trash' ) { + // Posts are still dirty while saving so wait for saving to finish + // to avoid the unsaved changes warning when trashing posts. + if ( postStatus === 'trash' && ! isSavingPost ) { this.setTrashURL( postId, postType ); return; } @@ -92,12 +94,13 @@ export class BrowserURL extends Component { } export default withSelect( ( select ) => { - const { getCurrentPost } = select( 'core/editor' ); + const { getCurrentPost, isSavingPost } = select( 'core/editor' ); const { id, status, type } = getCurrentPost(); return { postId: id, postStatus: status, postType: type, + isSavingPost: isSavingPost(), }; } )( BrowserURL ); diff --git a/packages/edit-post/src/components/editor-initialization/index.js b/packages/edit-post/src/components/editor-initialization/index.js index 51cb90681f8ae8..a4695fa01aa3b4 100644 --- a/packages/edit-post/src/components/editor-initialization/index.js +++ b/packages/edit-post/src/components/editor-initialization/index.js @@ -1,9 +1,3 @@ -/** - * WordPress dependencies - */ -import { useEffect } from '@wordpress/element'; -import { useDispatch } from '@wordpress/data'; - /** * Internal dependencies */ @@ -21,17 +15,8 @@ import { * @return {null} This is a data component so does not render any ui. */ export default function( { postId } ) { - useAdjustSidebarListener( postId ); useBlockSelectionListener( postId ); + useAdjustSidebarListener( postId ); useUpdatePostLinkListener( postId ); - const { triggerGuide } = useDispatch( 'core/nux' ); - useEffect( () => { - triggerGuide( [ - 'core/editor.inserter', - 'core/editor.settings', - 'core/editor.preview', - 'core/editor.publish', - ] ); - }, [ triggerGuide ] ); return null; } diff --git a/packages/edit-post/src/components/editor-initialization/listener-hooks.js b/packages/edit-post/src/components/editor-initialization/listener-hooks.js index 51b1b2920e67b9..edefe8972fddcd 100644 --- a/packages/edit-post/src/components/editor-initialization/listener-hooks.js +++ b/packages/edit-post/src/components/editor-initialization/listener-hooks.js @@ -64,7 +64,7 @@ export const useAdjustSidebarListener = ( postId ) => { const { openGeneralSidebar, closeGeneralSidebar } = useDispatch( STORE_KEY ); - const previousIsSmall = useRef( isSmall ); + const previousIsSmall = useRef( null ); const sidebarToReOpenOnExpand = useRef( null ); useEffect( () => { diff --git a/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js b/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js index 87c2bc616e0bf5..0c8742bbe02fcc 100644 --- a/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js +++ b/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js @@ -137,11 +137,10 @@ describe( 'listener hook tests', () => { getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) ).not.toHaveBeenCalled(); } ); - it( 'does not close sidebar if viewport is small and there is an active ' + + it( 'closes sidebar if viewport is small and there is an active ' + 'sidebar name available on initial render', () => { setMockReturnValue( 'core/viewport', 'isViewportMatch', true ); setMockReturnValue( STORE_KEY, 'getActiveGeneralSidebarName', 'foo' ); - // initial render does nothing (and sidebar will be closed already) act( () => { renderComponent( useAdjustSidebarListener, 10 ); } ); @@ -150,7 +149,7 @@ describe( 'listener hook tests', () => { ).not.toHaveBeenCalled(); expect( getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) - ).not.toHaveBeenCalled(); + ).toHaveBeenCalled(); } ); it( 'closes sidebar if viewport is small and there is an active ' + 'sidebar name available when viewport size changes', () => { diff --git a/packages/edit-post/src/components/editor-regions/index.js b/packages/edit-post/src/components/editor-regions/index.js new file mode 100644 index 00000000000000..f208fed2cef5e0 --- /dev/null +++ b/packages/edit-post/src/components/editor-regions/index.js @@ -0,0 +1,72 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { navigateRegions } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +function EditorRegions( { footer, header, sidebar, content, publish, className } ) { + return ( + <div className={ classnames( className, 'edit-post-editor-regions' ) }> + { !! header && ( + <div + className="edit-post-editor-regions__header" + role="region" + /* translators: accessibility text for the top bar landmark region. */ + aria-label={ __( 'Editor top bar' ) } + tabIndex="-1" + > + { header } + </div> + ) } + <div className="edit-post-editor-regions__body"> + <div + className="edit-post-editor-regions__content" + role="region" + /* translators: accessibility text for the content landmark region. */ + aria-label={ __( 'Editor content' ) } + tabIndex="-1" + > + { content } + </div> + { !! publish && ( + <div + className="edit-post-editor-regions__publish" + role="region" + /* translators: accessibility text for the publish landmark region. */ + aria-label={ __( 'Editor publish' ) } + tabIndex="-1" + > + { publish } + </div> + ) } + { !! sidebar && ( + <div + className="edit-post-editor-regions__sidebar" + role="region" + aria-label={ 'Editor settings' } + tabIndex="-1" + > + { sidebar } + </div> + ) } + </div> + { !! footer && ( + <div + className="edit-post-editor-regions__footer" + role="region" + aria-label={ 'Editor footer' } + tabIndex="-1" + > + { footer } + </div> + ) } + </div> + ); +} + +export default navigateRegions( EditorRegions ); diff --git a/packages/edit-post/src/components/editor-regions/style.scss b/packages/edit-post/src/components/editor-regions/style.scss new file mode 100644 index 00000000000000..65eceb6e17f040 --- /dev/null +++ b/packages/edit-post/src/components/editor-regions/style.scss @@ -0,0 +1,143 @@ +// On Mobile devices, swiping the HTML element should not scroll. +// By making it fixed, we prevent that. +html { + position: fixed; + width: 100%; + + @include break-medium() { + position: initial; + width: initial; + } +} + +.edit-post-editor-regions { + display: flex; + flex-direction: column; + height: auto; + max-height: 100%; + + // Fill the available space on Mobile. + position: fixed; + top: $admin-bar-height-big; + left: 0; + right: 0; + bottom: 0; + + // Adjust to admin-bar going small. + @include break-medium() { + top: $admin-bar-height; + + .is-fullscreen-mode & { + top: 0; + } + } +} + +@include editor-left(".edit-post-editor-regions"); + +.edit-post-editor-regions__body { + flex-grow: 1; + display: flex; + + // Even on Mobile, we choose to scroll this element on its own. + // This helps enable a fixed-to-top toolbar that makes the editing experience + // on Mobile Safari usable. + // Unfortunately an issue still exists where if you swipe the top toolbar + // or beyond the bottom of the page when the soft keyboard is showing, you scroll + // the body element and can scroll the toolbar out of view. + // This is still preferable, though, as it allows the editor to function at all. + overflow: auto; + + // In future versions of Mobile Safari, hopefully overscroll-behavior will be supported. + // This allows us to disallow the scroll-chaining and rubber-banding that is currently + // is the cause of the issue outlined above. + // In other words, the following behavior doesn't yet work in Safari, but if/when + // it is added, it should take care of the issue. + // See also: https://drafts.csswg.org/css-overscroll/ + overscroll-behavior-y: none; +} + +.edit-post-editor-regions__content { + flex-grow: 1; + + // Treat as flex container to allow children to grow to occupy full + // available height of the content area. + display: flex; + flex-direction: column; + + // On Mobile the header is fixed to keep HTML as scrollable. + // Beyond the medium breakpoint, we allow the sidebar. + // The sidebar should scroll independently, so enable scroll here also. + @include break-medium() { + overflow: auto; + } +} + +.edit-post-editor-regions__sidebar { + width: auto; // Keep the sidebar width flexible. + flex-shrink: 0; + position: absolute; + z-index: z-index(".edit-post-editor-regions__sidebar"); + top: 0; + right: 0; + bottom: 0; + left: 0; + background: $white; + + &:empty { + display: none; + } + + // On Mobile the header is fixed to keep HTML as scrollable. + @include break-medium() { + overflow: auto; + border-left: $border-width solid $light-gray-500; + position: relative !important; + z-index: z-index(".edit-post-editor-regions__sidebar {greater than small}"); + } +} + +.edit-post-editor-regions__header { + flex-shrink: 0; + height: auto; // Keep the height flexible. + border-bottom: $border-width solid $light-gray-500; + z-index: z-index(".edit-post-editor-regions__header"); + + // On Mobile the header is sticky. + position: sticky; + top: 0; + + // Cancel the fixed positioning used on Mobile. + @include break-small() { + position: initial; + top: 0; + } +} + +.edit-post-editor-regions__footer { + height: auto; // Keep the height flexible. + flex-shrink: 0; + overflow: auto; + border-top: $border-width solid $light-gray-500; + + // On Mobile the footer is hidden + display: none; + @include break-medium() { + display: block; + } +} + +.edit-post-editor-regions__publish { + z-index: z-index(".edit-post-editor-regions__publish"); + position: fixed !important; // Need to override the default relative positionning + top: -9999em; + bottom: auto; + left: auto; + right: 0; + width: $sidebar-width; + + &:focus { + top: auto; + bottom: 0; + } +} diff --git a/packages/edit-post/src/components/header/header-toolbar/index.js b/packages/edit-post/src/components/header/header-toolbar/index.js index d02a82156fcfe0..2fe10fbcf7b948 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.js @@ -1,16 +1,15 @@ /** * WordPress dependencies */ -import { compose } from '@wordpress/compose'; -import { withSelect } from '@wordpress/data'; -import { withViewportMatch } from '@wordpress/viewport'; -import { DotTip } from '@wordpress/nux'; +import { useViewportMatch } from '@wordpress/compose'; +import { useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { Inserter, BlockToolbar, NavigableToolbar, BlockNavigationDropdown, + ToolSelector, } from '@wordpress/block-editor'; import { TableOfContents, @@ -18,7 +17,15 @@ import { EditorHistoryUndo, } from '@wordpress/editor'; -function HeaderToolbar( { hasFixedToolbar, isLargeViewport, showInserter, isTextModeEnabled } ) { +function HeaderToolbar() { + const { hasFixedToolbar, showInserter, isTextModeEnabled } = useSelect( ( select ) => ( { + hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ), + // This setting (richEditingEnabled) should not live in the block editor's setting. + showInserter: select( 'core/edit-post' ).getEditorMode() === 'visual' && select( 'core/editor' ).getEditorSettings().richEditingEnabled, + isTextModeEnabled: select( 'core/edit-post' ).getEditorMode() === 'text', + } ), [] ); + const isLargeViewport = useViewportMatch( 'medium' ); + const toolbarAriaLabel = hasFixedToolbar ? /* translators: accessibility text for the editor toolbar when Top Toolbar is on */ __( 'Document and block tools' ) : @@ -30,17 +37,13 @@ function HeaderToolbar( { hasFixedToolbar, isLargeViewport, showInserter, isText className="edit-post-header-toolbar" aria-label={ toolbarAriaLabel } > - <div> - <Inserter disabled={ ! showInserter } position="bottom right" showInserterHelpPanel /> - <DotTip tipId="core/editor.inserter"> - { __( 'Welcome to the wonderful world of blocks! Click the “+” (“Add block”) button to add a new block. There are blocks available for all kinds of content: you can insert text, headings, images, lists, and lots more!' ) } - </DotTip> - </div> + <Inserter disabled={ ! showInserter } position="bottom right" showInserterHelpPanel /> <EditorHistoryUndo /> <EditorHistoryRedo /> <TableOfContents hasOutlineItemsDisabled={ isTextModeEnabled } /> <BlockNavigationDropdown isDisabled={ isTextModeEnabled } /> - { hasFixedToolbar && isLargeViewport && ( + <ToolSelector /> + { ( hasFixedToolbar || ! isLargeViewport ) && ( <div className="edit-post-header-toolbar__block-toolbar"> <BlockToolbar /> </div> @@ -49,12 +52,4 @@ function HeaderToolbar( { hasFixedToolbar, isLargeViewport, showInserter, isText ); } -export default compose( [ - withSelect( ( select ) => ( { - hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ), - // This setting (richEditingEnabled) should not live in the block editor's setting. - showInserter: select( 'core/edit-post' ).getEditorMode() === 'visual' && select( 'core/editor' ).getEditorSettings().richEditingEnabled, - isTextModeEnabled: select( 'core/edit-post' ).getEditorMode() === 'text', - } ) ), - withViewportMatch( { isLargeViewport: 'medium' } ), -] )( HeaderToolbar ); +export default HeaderToolbar; diff --git a/packages/edit-post/src/components/header/header-toolbar/style.scss b/packages/edit-post/src/components/header/header-toolbar/style.scss index ceed4a0364ffdb..0f8c0ac306bfc7 100644 --- a/packages/edit-post/src/components/header/header-toolbar/style.scss +++ b/packages/edit-post/src/components/header/header-toolbar/style.scss @@ -26,13 +26,17 @@ .edit-post-header-toolbar__block-toolbar { // Stack toolbar below Editor Bar. position: absolute; - top: $header-height; + top: $header-height + 1px; left: 0; right: 0; background: $white; min-height: $block-toolbar-height; border-bottom: $border-width solid $light-gray-500; + &:empty { + display: none; + } + .block-editor-block-toolbar .components-toolbar { border-top: none; border-bottom: none; diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js index dff5622593603a..26dd1812d6b5ea 100644 --- a/packages/edit-post/src/components/header/index.js +++ b/packages/edit-post/src/components/header/index.js @@ -9,7 +9,6 @@ import { } from '@wordpress/editor'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; -import { DotTip } from '@wordpress/nux'; /** * Internal dependencies @@ -32,13 +31,7 @@ function Header( { const toggleGeneralSidebar = isEditorSidebarOpened ? closeGeneralSidebar : openGeneralSidebar; return ( - <div - role="region" - /* translators: accessibility text for the top bar landmark region. */ - aria-label={ __( 'Editor top bar' ) } - className="edit-post-header" - tabIndex="-1" - > + <div className="edit-post-header"> <div className="edit-post-header__toolbar"> <FullscreenModeClose /> <HeaderToolbar /> @@ -63,19 +56,14 @@ function Header( { forceIsDirty={ hasActiveMetaboxes } forceIsSaving={ isSaving } /> - <div> - <IconButton - icon="admin-generic" - label={ __( 'Settings' ) } - onClick={ toggleGeneralSidebar } - isToggled={ isEditorSidebarOpened } - aria-expanded={ isEditorSidebarOpened } - shortcut={ shortcuts.toggleSidebar } - /> - <DotTip tipId="core/editor.settings"> - { __( 'You’ll find more settings for your page and blocks in the sidebar. Click the cog icon to toggle the sidebar open and closed.' ) } - </DotTip> - </div> + <IconButton + icon="admin-generic" + label={ __( 'Settings' ) } + onClick={ toggleGeneralSidebar } + isPressed={ isEditorSidebarOpened } + aria-expanded={ isEditorSidebarOpened } + shortcut={ shortcuts.toggleSidebar } + /> <PinnedPlugins.Slot /> <MoreMenu /> </div> diff --git a/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap index 5096eaa7c803b5..e0337df629bd3a 100644 --- a/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/header/more-menu/test/__snapshots__/index.js.snap @@ -30,7 +30,7 @@ exports[`MoreMenu should match snapshot 1`] = ` renderToggle={[Function]} > <div - className="components-dropdown-menu edit-post-more-menu" + className="components-dropdown components-dropdown-menu edit-post-more-menu" > <ForwardRef(IconButton) aria-expanded={false} diff --git a/packages/edit-post/src/components/header/pinned-plugins/style.scss b/packages/edit-post/src/components/header/pinned-plugins/style.scss index 030d6305ae9033..bd5268c0ea77c2 100644 --- a/packages/edit-post/src/components/header/pinned-plugins/style.scss +++ b/packages/edit-post/src/components/header/pinned-plugins/style.scss @@ -8,24 +8,29 @@ .components-icon-button { margin-left: 4px; - &.is-toggled { + &.is-pressed { margin-left: 5px; } + + svg { + max-width: 24px; + max-height: 24px; + } } // Colorize plugin icons to ensure contrast and cohesion, but allow plugin developers to override. - .components-icon-button:not(.is-toggled) svg, - .components-icon-button:not(.is-toggled) svg * { + .components-icon-button:not(.is-pressed) svg, + .components-icon-button:not(.is-pressed) svg * { stroke: $dark-gray-500; fill: $dark-gray-500; stroke-width: 0; // !important is omitted here, so stroke-only icons can override easily. } // Forcefully colorize hover and toggled plugin icon states to ensure legibility and consistency. - .components-icon-button.is-toggled svg, - .components-icon-button.is-toggled svg *, - .components-icon-button.is-toggled:hover svg, - .components-icon-button.is-toggled:hover svg * { + .components-icon-button.is-pressed svg, + .components-icon-button.is-pressed svg *, + .components-icon-button.is-pressed:hover svg, + .components-icon-button.is-pressed:hover svg * { stroke: $white !important; fill: $white !important; stroke-width: 0; // !important is omitted here, so stroke-only icons can override easily. diff --git a/packages/edit-post/src/components/header/plugin-more-menu-item/index.js b/packages/edit-post/src/components/header/plugin-more-menu-item/index.js index e29ec22e90a208..08a5bef83b35eb 100644 --- a/packages/edit-post/src/components/header/plugin-more-menu-item/index.js +++ b/packages/edit-post/src/components/header/plugin-more-menu-item/index.js @@ -32,7 +32,7 @@ const PluginMoreMenuItem = ( { onClick = noop, ...props } ) => ( * * @param {Object} props Component properties. * @param {string} [props.href] When `href` is provided then the menu item is represented as an anchor rather than button. It corresponds to the `href` attribute of the anchor. - * @param {string|Element} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. + * @param {WPBlockTypeIconRender} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. * @param {Function} [props.onClick=noop] The callback function to be executed when the user clicks the menu item. * @param {...*} [props.other] Any additional props are passed through to the underlying [MenuItem](/packages/components/src/menu-item/README.md) component. * @@ -78,7 +78,7 @@ const PluginMoreMenuItem = ( { onClick = noop, ...props } ) => ( * ); * ``` * - * @return {WPElement} The element to be rendered. + * @return {WPComponent} The component to be rendered. */ export default compose( withPluginContext( ( context, ownProps ) => { diff --git a/packages/edit-post/src/components/header/plugin-sidebar-more-menu-item/index.js b/packages/edit-post/src/components/header/plugin-sidebar-more-menu-item/index.js index e1849f2e7890ed..c1f4ff42ae98aa 100644 --- a/packages/edit-post/src/components/header/plugin-sidebar-more-menu-item/index.js +++ b/packages/edit-post/src/components/header/plugin-sidebar-more-menu-item/index.js @@ -28,7 +28,7 @@ const PluginSidebarMoreMenuItem = ( { children, icon, isSelected, onClick } ) => * * @param {Object} props Component props. * @param {string} props.target A string identifying the target sidebar you wish to be activated by this menu item. Must be the same as the `name` prop you have given to that sidebar. - * @param {string|Element} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. + * @param {WPBlockTypeIconRender} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. * * @example <caption>ES5</caption> * ```js @@ -64,7 +64,7 @@ const PluginSidebarMoreMenuItem = ( { children, icon, isSelected, onClick } ) => * ); * ``` * - * @return {WPElement} The element to be rendered. + * @return {WPComponent} The component to be rendered. */ export default compose( withPluginContext( ( context, ownProps ) => { diff --git a/packages/edit-post/src/components/header/post-publish-button-or-toggle.js b/packages/edit-post/src/components/header/post-publish-button-or-toggle.js index 967b17f43d04a4..e483e3ee270bbd 100644 --- a/packages/edit-post/src/components/header/post-publish-button-or-toggle.js +++ b/packages/edit-post/src/components/header/post-publish-button-or-toggle.js @@ -6,17 +6,15 @@ import { get } from 'lodash'; /** * WordPress dependencies */ -import { compose } from '@wordpress/compose'; +import { useViewportMatch, compose } from '@wordpress/compose'; import { withDispatch, withSelect } from '@wordpress/data'; import { PostPublishButton } from '@wordpress/editor'; -import { withViewportMatch } from '@wordpress/viewport'; export function PostPublishButtonOrToggle( { forceIsDirty, forceIsSaving, hasPublishAction, isBeingScheduled, - isLessThanMediumViewport, isPending, isPublished, isPublishSidebarEnabled, @@ -26,6 +24,7 @@ export function PostPublishButtonOrToggle( { } ) { const IS_TOGGLE = 'toggle'; const IS_BUTTON = 'button'; + const isSmallerThanMediumViewport = useViewportMatch( 'medium', '<' ); let component; /** @@ -53,10 +52,10 @@ export function PostPublishButtonOrToggle( { if ( isPublished || ( isScheduled && isBeingScheduled ) || - ( isPending && ! hasPublishAction && ! isLessThanMediumViewport ) + ( isPending && ! hasPublishAction && ! isSmallerThanMediumViewport ) ) { component = IS_BUTTON; - } else if ( isLessThanMediumViewport ) { + } else if ( isSmallerThanMediumViewport ) { component = IS_TOGGLE; } else if ( isPublishSidebarEnabled ) { component = IS_TOGGLE; @@ -95,5 +94,4 @@ export default compose( togglePublishSidebar, }; } ), - withViewportMatch( { isLessThanMediumViewport: '< medium' } ), )( PostPublishButtonOrToggle ); diff --git a/packages/edit-post/src/components/header/style.scss b/packages/edit-post/src/components/header/style.scss index 64699eeab73a2d..45e5eb1893b5b3 100644 --- a/packages/edit-post/src/components/header/style.scss +++ b/packages/edit-post/src/components/header/style.scss @@ -1,7 +1,6 @@ .edit-post-header { height: $header-height; padding: $grid-size-small 2px; - border-bottom: $border-width solid $light-gray-500; background: $white; display: flex; flex-wrap: wrap; @@ -9,31 +8,14 @@ align-items: center; // The header should never be wider than the viewport, or buttons might be hidden. Especially relevant at high zoom levels. Related to https://core.trac.wordpress.org/ticket/47603#ticket. max-width: 100vw; - z-index: z-index(".edit-post-header"); - left: 0; - right: 0; // Make toolbar sticky on larger breakpoints @include break-zoomed-in { - height: $header-height; - top: 0; - position: sticky; flex-wrap: nowrap; } - // On mobile the main content area has to scroll, otherwise you can invoke the overscroll bounce on the non-scrolling container. @include break-small { - position: fixed; padding: $grid-size; - top: $admin-bar-height-big; - } - - @include break-medium() { - top: $admin-bar-height; - - body.is-fullscreen-mode & { - top: 0; - } } // Some browsers, most notably IE11, honor an older version of the flexbox spec @@ -55,8 +37,6 @@ } } -@include editor-left(".edit-post-header"); - .edit-post-header__toolbar { display: flex; } @@ -69,16 +49,14 @@ .edit-post-header .components-button { // Header toggle buttons. - &.is-toggled { + &.is-pressed { color: $white; background: $dark-gray-500; - margin: 1px; - padding: 7px; } // The !important in this ruleset need to override the pile of :not() selectors used in the icon-button. - &.is-toggled:hover, - &.is-toggled:focus { + &.is-pressed:hover, + &.is-pressed:focus { box-shadow: 0 0 0 $border-width $dark-gray-500, inset 0 0 0 $border-width $white !important; color: $white !important; background: $dark-gray-500 !important; @@ -91,7 +69,7 @@ &.editor-post-publish-button, &.editor-post-publish-panel__toggle { margin: 2px; - height: 33px; + height: 34px; line-height: 32px; font-size: $default-font-size; } @@ -105,13 +83,17 @@ } } + // These paddings actually duplicate existing rules from button component. + // But they are duplicated so as to provide smaller paddings to fit the buttons on mobile. &.editor-post-preview, &.editor-post-publish-button, &.editor-post-publish-panel__toggle { - padding: 0 5px 2px; + padding-left: 5px; + padding-right: 5px; @include break-small() { - padding: 0 12px 2px; + padding-left: 12px; + padding-right: 12px; } } diff --git a/packages/edit-post/src/components/header/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/header/test/__snapshots__/index.js.snap index 95ed8d68d7ded8..28ee11b68f6105 100644 --- a/packages/edit-post/src/components/header/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/header/test/__snapshots__/index.js.snap @@ -29,9 +29,3 @@ exports[`PostPublishButtonOrToggle should render a toggle when post is not (1), isToggle={true} /> `; - -exports[`PostPublishButtonOrToggle should render a toggle when post is not published or scheduled and the viewport is < medium 1`] = ` -<WithSelect(WithDispatch(PostPublishButton)) - isToggle={true} -/> -`; diff --git a/packages/edit-post/src/components/header/test/index.js b/packages/edit-post/src/components/header/test/index.js index e5e0516a94951e..95ce0b5fc61361 100644 --- a/packages/edit-post/src/components/header/test/index.js +++ b/packages/edit-post/src/components/header/test/index.js @@ -26,13 +26,8 @@ describe( 'PostPublishButtonOrToggle should render a', () => { const wrapper = shallow( <PostPublishButtonOrToggle isPending={ true } hasPublishAction={ false } - isLessThanMediumViewport={ false } /> ); - expect( wrapper ).toMatchSnapshot(); - } ); - it( 'toggle when post is not published or scheduled and the viewport is < medium', () => { - const wrapper = shallow( <PostPublishButtonOrToggle isLessThanMediumViewport={ true } /> ); expect( wrapper ).toMatchSnapshot(); } ); diff --git a/packages/edit-post/src/components/header/writing-menu/index.js b/packages/edit-post/src/components/header/writing-menu/index.js index 0484e32c28cd6e..0a528fa0c30b66 100644 --- a/packages/edit-post/src/components/header/writing-menu/index.js +++ b/packages/edit-post/src/components/header/writing-menu/index.js @@ -3,7 +3,7 @@ */ import { MenuGroup } from '@wordpress/components'; import { __, _x } from '@wordpress/i18n'; -import { ifViewportMatches } from '@wordpress/viewport'; +import { useViewportMatch } from '@wordpress/compose'; /** * Internal dependencies @@ -11,6 +11,11 @@ import { ifViewportMatches } from '@wordpress/viewport'; import FeatureToggle from '../feature-toggle'; function WritingMenu() { + const isLargeViewport = useViewportMatch( 'medium' ); + if ( ! isLargeViewport ) { + return null; + } + return ( <MenuGroup label={ _x( 'View', 'noun' ) } @@ -40,4 +45,4 @@ function WritingMenu() { ); } -export default ifViewportMatches( 'medium' )( WritingMenu ); +export default WritingMenu; diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/style.scss b/packages/edit-post/src/components/keyboard-shortcut-help-modal/style.scss index cef4621547840d..627b8c56037654 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/style.scss +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/style.scss @@ -5,7 +5,7 @@ &__main-shortcuts .edit-post-keyboard-shortcut-help__shortcut-list { // Push the shortcut to be flush with top modal header. - margin-top: -$panel-padding -$border-width; + margin-top: -$grid-size-xlarge -$border-width; } &__section-title { diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index e29011c8cec529..69d69662b0615c 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -6,14 +6,6 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { - Button, - Popover, - ScrollLock, - FocusReturnProvider, - navigateRegions, -} from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; import { AutosaveMonitor, LocalAutosaveMonitor, @@ -21,138 +13,141 @@ import { EditorNotices, PostPublishPanel, } from '@wordpress/editor'; -import { withDispatch, withSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { + BlockBreadcrumb, + __experimentalPageTemplatePicker, + __experimentalUsePageTemplatePickerVisible, +} from '@wordpress/block-editor'; +import { + Button, + ScrollLock, + Popover, + FocusReturnProvider, +} from '@wordpress/components'; +import { useViewportMatch } from '@wordpress/compose'; import { PluginArea } from '@wordpress/plugins'; -import { withViewportMatch } from '@wordpress/viewport'; -import { compose } from '@wordpress/compose'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import BrowserURL from '../browser-url'; -import Header from '../header'; import TextEditor from '../text-editor'; import VisualEditor from '../visual-editor'; import EditorModeKeyboardShortcuts from '../keyboard-shortcuts'; import KeyboardShortcutHelpModal from '../keyboard-shortcut-help-modal'; import ManageBlocksModal from '../manage-blocks-modal'; import OptionsModal from '../options-modal'; -import MetaBoxes from '../meta-boxes'; +import EditorRegions from '../editor-regions'; +import FullscreenMode from '../fullscreen-mode'; +import BrowserURL from '../browser-url'; +import Header from '../header'; import SettingsSidebar from '../sidebar/settings-sidebar'; import Sidebar from '../sidebar'; +import MetaBoxes from '../meta-boxes'; import PluginPostPublishPanel from '../sidebar/plugin-post-publish-panel'; import PluginPrePublishPanel from '../sidebar/plugin-pre-publish-panel'; -import FullscreenMode from '../fullscreen-mode'; +import WelcomeGuide from '../welcome-guide'; -function Layout( { - mode, - editorSidebarOpened, - pluginSidebarOpened, - publishSidebarOpened, - hasFixedToolbar, - closePublishSidebar, - togglePublishSidebar, - hasActiveMetaboxes, - isSaving, - isMobileViewport, - isRichEditingEnabled, -} ) { +function Layout() { + const isMobileViewport = useViewportMatch( 'small', '<' ); + const { closePublishSidebar, togglePublishSidebar } = useDispatch( 'core/edit-post' ); + const { + mode, + isRichEditingEnabled, + editorSidebarOpened, + pluginSidebarOpened, + publishSidebarOpened, + hasActiveMetaboxes, + isSaving, + hasFixedToolbar, + } = useSelect( ( select ) => { + return ( { + hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ), + editorSidebarOpened: select( 'core/edit-post' ).isEditorSidebarOpened(), + pluginSidebarOpened: select( 'core/edit-post' ).isPluginSidebarOpened(), + publishSidebarOpened: select( 'core/edit-post' ).isPublishSidebarOpened(), + mode: select( 'core/edit-post' ).getEditorMode(), + isRichEditingEnabled: select( 'core/editor' ).getEditorSettings().richEditingEnabled, + hasActiveMetaboxes: select( 'core/edit-post' ).hasMetaBoxes(), + isSaving: select( 'core/edit-post' ).isSavingMetaBoxes(), + } ); + }, [] ); + const showPageTemplatePicker = __experimentalUsePageTemplatePickerVisible(); const sidebarIsOpened = editorSidebarOpened || pluginSidebarOpened || publishSidebarOpened; - - const className = classnames( 'edit-post-layout', { + const className = classnames( 'edit-post-layout', 'is-mode-' + mode, { 'is-sidebar-opened': sidebarIsOpened, 'has-fixed-toolbar': hasFixedToolbar, 'has-metaboxes': hasActiveMetaboxes, } ); - const publishLandmarkProps = { - role: 'region', - /* translators: accessibility text for the publish landmark region. */ - 'aria-label': __( 'Editor publish' ), - tabIndex: -1, - }; return ( - <FocusReturnProvider className={ className }> + <> <FullscreenMode /> <BrowserURL /> <UnsavedChangesWarning /> <AutosaveMonitor /> <LocalAutosaveMonitor /> - <Header /> - <div - className="edit-post-layout__content" - role="region" - /* translators: accessibility text for the content landmark region. */ - aria-label={ __( 'Editor content' ) } - tabIndex="-1" - > - <EditorNotices /> - <EditorModeKeyboardShortcuts /> - <KeyboardShortcutHelpModal /> + <EditorModeKeyboardShortcuts /> + <FocusReturnProvider> + <EditorRegions + className={ className } + header={ <Header /> } + sidebar={ ! publishSidebarOpened && ( + <> + <SettingsSidebar /> + <Sidebar.Slot /> + </> + ) } + content={ + <> + <EditorNotices /> + { ( mode === 'text' || ! isRichEditingEnabled ) && <TextEditor /> } + { isRichEditingEnabled && mode === 'visual' && <VisualEditor /> } + <div className="edit-post-layout__metaboxes"> + <MetaBoxes location="normal" /> + <MetaBoxes location="advanced" /> + </div> + { isMobileViewport && sidebarIsOpened && <ScrollLock /> } + </> + } + footer={ isRichEditingEnabled && mode === 'visual' && ( + <div className="edit-post-layout__footer"> + <BlockBreadcrumb /> + </div> + ) } + publish={ publishSidebarOpened ? ( + <PostPublishPanel + onClose={ closePublishSidebar } + forceIsDirty={ hasActiveMetaboxes } + forceIsSaving={ isSaving } + PrePublishExtension={ PluginPrePublishPanel.Slot } + PostPublishExtension={ PluginPostPublishPanel.Slot } + /> + ) : ( + <div className="edit-post-toggle-publish-panel"> + <Button + isDefault + className="edit-post-toggle-publish-panel__button" + onClick={ togglePublishSidebar } + aria-expanded={ false } + > + { __( 'Open publish panel' ) } + </Button> + </div> + ) } + /> <ManageBlocksModal /> <OptionsModal /> - { ( mode === 'text' || ! isRichEditingEnabled ) && <TextEditor /> } - { isRichEditingEnabled && mode === 'visual' && <VisualEditor /> } - <div className="edit-post-layout__metaboxes"> - <MetaBoxes location="normal" /> - </div> - <div className="edit-post-layout__metaboxes"> - <MetaBoxes location="advanced" /> - </div> - </div> - { publishSidebarOpened ? ( - <PostPublishPanel - { ...publishLandmarkProps } - onClose={ closePublishSidebar } - forceIsDirty={ hasActiveMetaboxes } - forceIsSaving={ isSaving } - PrePublishExtension={ PluginPrePublishPanel.Slot } - PostPublishExtension={ PluginPostPublishPanel.Slot } - /> - ) : ( - <> - <div className="edit-post-toggle-publish-panel" { ...publishLandmarkProps }> - <Button - isDefault - type="button" - className="edit-post-toggle-publish-panel__button" - onClick={ togglePublishSidebar } - aria-expanded={ false } - > - { __( 'Open publish panel' ) } - </Button> - </div> - <SettingsSidebar /> - <Sidebar.Slot /> - { - isMobileViewport && sidebarIsOpened && <ScrollLock /> - } - </> - ) } - <Popover.Slot /> - <PluginArea /> - </FocusReturnProvider> + <KeyboardShortcutHelpModal /> + <WelcomeGuide /> + <Popover.Slot /> + <PluginArea /> + { showPageTemplatePicker && <__experimentalPageTemplatePicker /> } + </FocusReturnProvider> + + </> ); } -export default compose( - withSelect( ( select ) => ( { - mode: select( 'core/edit-post' ).getEditorMode(), - editorSidebarOpened: select( 'core/edit-post' ).isEditorSidebarOpened(), - pluginSidebarOpened: select( 'core/edit-post' ).isPluginSidebarOpened(), - publishSidebarOpened: select( 'core/edit-post' ).isPublishSidebarOpened(), - hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ), - hasActiveMetaboxes: select( 'core/edit-post' ).hasMetaBoxes(), - isSaving: select( 'core/edit-post' ).isSavingMetaBoxes(), - isRichEditingEnabled: select( 'core/editor' ).getEditorSettings().richEditingEnabled, - } ) ), - withDispatch( ( dispatch ) => { - const { closePublishSidebar, togglePublishSidebar } = dispatch( 'core/edit-post' ); - return { - closePublishSidebar, - togglePublishSidebar, - }; - } ), - navigateRegions, - withViewportMatch( { isMobileViewport: '< small' } ), -)( Layout ); +export default Layout; diff --git a/packages/edit-post/src/components/layout/index.native.js b/packages/edit-post/src/components/layout/index.native.js index cc5a089a464ff7..2423dd1dabfd13 100644 --- a/packages/edit-post/src/components/layout/index.native.js +++ b/packages/edit-post/src/components/layout/index.native.js @@ -10,7 +10,7 @@ import { sendNativeEditorDidLayout } from 'react-native-gutenberg-bridge'; */ import { Component } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; -import { BottomSheetSettings } from '@wordpress/block-editor'; +import { BottomSheetSettings, __experimentalPageTemplatePicker, __experimentalWithPageTemplatePickerVisible } from '@wordpress/block-editor'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { HTMLTextInput, KeyboardAvoidingView, ReadableContentView } from '@wordpress/components'; import { AutosaveMonitor } from '@wordpress/editor'; @@ -102,6 +102,7 @@ class Layout extends Component { const { mode, getStylesFromColorScheme, + showPageTemplatePicker, } = this.props; const isHtmlView = mode === 'text'; @@ -131,6 +132,7 @@ class Layout extends Component { <Header /> <BottomSheetSettings /> </KeyboardAvoidingView> ) } + { showPageTemplatePicker && <__experimentalPageTemplatePicker /> } </SafeAreaView> ); } @@ -151,4 +153,5 @@ export default compose( [ }; } ), withPreferredColorScheme, + __experimentalWithPageTemplatePickerVisible, ] )( Layout ); diff --git a/packages/edit-post/src/components/layout/style.scss b/packages/edit-post/src/components/layout/style.scss index 43996d98f8c8f0..9f759a50950569 100644 --- a/packages/edit-post/src/components/layout/style.scss +++ b/packages/edit-post/src/components/layout/style.scss @@ -1,24 +1,8 @@ -.edit-post-layout, -.edit-post-layout__content { - height: 100%; +.edit-post-layout__metaboxes { + flex-shrink: 0; } - -.edit-post-layout { - position: relative; - box-sizing: border-box; - - // Beyond the mobile breakpoint, the editor bar is fixed, so make room for it eabove the layout content. - @include break-small { - padding-top: $header-height; - } - - // Beyond the medium breakpoint, the main scrolling area itself becomes fixed so the padding then becomes - // unnecessary, but until then it's still needed. -} - .edit-post-layout__metaboxes:not(:empty) { border-top: $border-width solid $light-gray-500; - margin-top: 10px; padding: 10px 0 10px; clear: both; @@ -28,122 +12,14 @@ } // Adjust the position of the notices -.edit-post-layout__content .components-editor-notices__snackbar { +.edit-post-layout .components-editor-notices__snackbar { position: fixed; right: 0; bottom: 20px; padding-left: 16px; padding-right: 16px; } -@include editor-left(".edit-post-layout__content .components-editor-notices__snackbar"); - -.edit-post-layout__content { - display: flex; - flex-direction: column; - - // These rules are specific to mobile and small breakpoints. - min-height: 100%; - position: relative; - - // We scroll the main editing canvas, the sidebar, and the block library separately to prevent scroll bleed. - // Because the navigation sidebar menu has "flyout" menus, we can't yet, scroll that independently, but as soon - // as we can, we should simplify these rules. - // In the mean time, if a user has a small screen and lots of plugin-added menu items in the navigation menu, - // they have to be able to scroll. To accommodate the flyout menus, we scroll the `body` element for this. - @include break-medium() { - // Because the body element scrolls the navigation sidebar, we have to use position fixed here. - // Otherwise you would scroll the editing canvas out of view when you scroll the sidebar. - position: fixed; - bottom: 0; - left: 0; - right: 0; - - // Because this is scoped to break-medium and larger, the admin-bar is always this height. - top: $header-height + $admin-bar-height; - - // Sadly, `position: fixed;` do not inherit boundaries from a relative parent. Due to that we have to compensate using `calc`. - min-height: calc(100% - #{ $header-height + $admin-bar-height }); - height: auto; // This overrides the 100% height the element inherits from line 3. - - // In this matrix, we compensate for any configurations for the presence and width of the navigation sidebar. - // This is similar to the code in the @editor-left mixin, but uses margins instead. - // Because we are beyond the medium breakpoint, we only have to worry about folded, auto-folded, and default. - margin-left: $admin-sidebar-width; - - // Auto fold is when on smaller breakpoints, nav menu auto colllapses. - body.auto-fold & { - margin-left: $admin-sidebar-width-collapsed; - - @include break-large() { - margin-left: $admin-sidebar-width; - } - } - - // Sidebar manually collapsed. - body.folded & { - margin-left: $admin-sidebar-width-collapsed; - } - - // Provide special rules for fullscreen mode. - body.is-fullscreen-mode & { - margin-left: 0 !important; - top: $header-height; - } - } - - // For users with the Top Toolbar option enabled, special rules apply to the height of the content area. - .has-fixed-toolbar & { - // From the medium breakpoint it sits below the editor bar. - @include break-medium() { - top: $header-height + $admin-bar-height + $block-controls-height; - } - - // From the xlarge breakpoint it sits in the editor bar. - @include break-xlarge() { - top: $header-height + $admin-bar-height; - } - } - - // Pad the scroll box so content on the bottom can be scrolled up. - padding-bottom: 50vh; - @include break-small { - padding-bottom: 0; - } - - // On mobile the main content (html or body) area has to scroll. - // If, like we do on the desktop, scroll an element (.edit-post-layout__content) you can invoke - // the overscroll bounce on the non-scrolling container, causing for a frustrating scrolling experience. - // The following rule enables this scrolling beyond the mobile breakpoint, because on the desktop - // breakpoints scrolling an isolated element helps avoid scroll bleed. - @include break-small() { - overflow-y: auto; - } - -webkit-overflow-scrolling: touch; - - // This rule ensures that if you've scrolled to the end of a container, - // then pause, then keep scrolling downwards, the browser doesn't try to scroll - // the parent element, usually invoking a "bounce" effect and then preventing you - // from scrolling upwards until you pause again. - // This is only necessary beyond the small breakpoint because that's when the scroll container changes. - @include break-small() { - overscroll-behavior-y: none; - } - - .edit-post-visual-editor { - flex: 1 1 auto; - - // In IE11 flex-basis: 100% cause a bug where the metaboxes area overlap with the content area. - // But it works as expected without it. - // The flex-basis is needed for the other browsers to make sure the content area is full-height. - @supports (position: sticky) { - flex-basis: 100%; - } - } - - .edit-post-layout__metaboxes { - flex-shrink: 0; - } -} +@include editor-left(".edit-post-layout .components-editor-notices__snackbar"); .edit-post-layout .editor-post-publish-panel { position: fixed; @@ -182,34 +58,18 @@ } .edit-post-layout .editor-post-publish-panel__header-publish-button { + justify-content: center; + // Match the size of the Publish... button. - .components-button.is-large { + .components-button { height: 33px; line-height: 32px; } - - // Size the spacer flexibly to allow for different button lengths. - .editor-post-publish-panel__spacer { - display: inline-flex; - flex: 0 1 52px; // This number is approximative to keep the publish button at the same position when opening the panel - } } .edit-post-toggle-publish-panel { - position: fixed; - top: -9999em; - bottom: auto; - left: auto; - right: 0; - z-index: z-index(".edit-post-toggle-publish-panel"); - padding: 10px 10px 10px 0; - width: $sidebar-width; background-color: $white; - - &:focus { - top: auto; - bottom: 0; - } + padding: 10px 10px 10px 0; .edit-post-toggle-publish-panel__button { width: auto; @@ -234,3 +94,18 @@ } } } + +.edit-post-layout__footer { + display: none; + z-index: z-index(".edit-post-layout__footer"); + + // Stretch to mimic outline padding on desktop. + @include break-medium() { + display: flex; + background: $white; + height: $footer-height; + padding: 0 $grid-size; + align-items: center; + font-size: $default-font-size; + } +} diff --git a/packages/edit-post/src/components/meta-boxes/meta-boxes-area/style.scss b/packages/edit-post/src/components/meta-boxes/meta-boxes-area/style.scss index b86d15952683b0..3f050647f9af61 100644 --- a/packages/edit-post/src/components/meta-boxes/meta-boxes-area/style.scss +++ b/packages/edit-post/src/components/meta-boxes/meta-boxes-area/style.scss @@ -81,6 +81,24 @@ .is-hidden { display: none; } + + + // Until checkboxes WordPress-wide are updated to match the new style, + // checkboxes used in metaboxes have to be slightly unstyled here. + // @todo: remove this entire rule once checkboxes are the same everywhere. + // See: https://github.com/WordPress/gutenberg/issues/18053 + .metabox-location-side .postbox input[type="checkbox"] { + border: $border-width solid $dark-gray-300; + + &:checked { + background: $white; + border-color: $dark-gray-300; + } + + &::before { + margin: -3px -4px; + } + } } .edit-post-meta-boxes-area__clear { diff --git a/packages/edit-post/src/components/options-modal/index.js b/packages/edit-post/src/components/options-modal/index.js index 792cc348ae1110..58600900bc1d0a 100644 --- a/packages/edit-post/src/components/options-modal/index.js +++ b/packages/edit-post/src/components/options-modal/index.js @@ -25,7 +25,6 @@ import Section from './section'; import { EnablePluginDocumentSettingPanelOption, EnablePublishSidebarOption, - EnableTipsOption, EnablePanelOption, EnableFeature, } from './options'; @@ -47,7 +46,6 @@ export function OptionsModal( { isModalActive, isViewable, closeModal } ) { > <Section title={ __( 'General' ) }> <EnablePublishSidebarOption label={ __( 'Pre-publish Checks' ) } /> - <EnableTipsOption label={ __( 'Tips' ) } /> <EnableFeature feature="showInserterHelpPanel" label={ __( 'Inserter Help Panel' ) } /> </Section> <Section title={ __( 'Document Panels' ) }> diff --git a/packages/edit-post/src/components/options-modal/options/deferred.js b/packages/edit-post/src/components/options-modal/options/deferred.js deleted file mode 100644 index 2a0f84348d23bb..00000000000000 --- a/packages/edit-post/src/components/options-modal/options/deferred.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * WordPress dependencies - */ -import { Component } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import BaseOption from './base'; - -class DeferredOption extends Component { - constructor( { isChecked } ) { - super( ...arguments ); - this.state = { - isChecked, - }; - } - - componentWillUnmount() { - if ( this.state.isChecked !== this.props.isChecked ) { - this.props.onChange( this.state.isChecked ); - } - } - - render() { - return ( - <BaseOption - label={ this.props.label } - isChecked={ this.state.isChecked } - onChange={ ( isChecked ) => this.setState( { isChecked } ) } - /> - ); - } -} - -export default DeferredOption; diff --git a/packages/edit-post/src/components/options-modal/options/enable-tips.js b/packages/edit-post/src/components/options-modal/options/enable-tips.js deleted file mode 100644 index 8771f8437ba53a..00000000000000 --- a/packages/edit-post/src/components/options-modal/options/enable-tips.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * WordPress dependencies - */ -import { compose } from '@wordpress/compose'; -import { withSelect, withDispatch } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import DeferredOption from './deferred'; - -export default compose( - withSelect( ( select ) => ( { - isChecked: select( 'core/nux' ).areTipsEnabled(), - } ) ), - withDispatch( ( dispatch ) => { - const { enableTips, disableTips } = dispatch( 'core/nux' ); - return { - onChange: ( isEnabled ) => ( isEnabled ? enableTips() : disableTips() ), - }; - } ) -)( - // Using DeferredOption here means enableTips() is called when the Options - // modal is dismissed. This stops the NUX guide from appearing above the - // Options modal, which looks totally weird. - DeferredOption -); diff --git a/packages/edit-post/src/components/options-modal/options/index.js b/packages/edit-post/src/components/options-modal/options/index.js index 8684b680377054..b5cb1c6c4fe1b7 100644 --- a/packages/edit-post/src/components/options-modal/options/index.js +++ b/packages/edit-post/src/components/options-modal/options/index.js @@ -2,5 +2,4 @@ export { default as EnableCustomFieldsOption } from './enable-custom-fields'; export { default as EnablePanelOption } from './enable-panel'; export { default as EnablePluginDocumentSettingPanelOption } from './enable-plugin-document-setting-panel'; export { default as EnablePublishSidebarOption } from './enable-publish-sidebar'; -export { default as EnableTipsOption } from './enable-tips'; export { default as EnableFeature } from './enable-feature'; diff --git a/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap index 401d2d952dbab3..f418cd5a335268 100644 --- a/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/options-modal/test/__snapshots__/index.js.snap @@ -12,9 +12,6 @@ exports[`OptionsModal should match snapshot when the modal is active 1`] = ` <WithSelect(WithDispatch(IfViewportMatches(BaseOption))) label="Pre-publish Checks" /> - <WithSelect(WithDispatch(DeferredOption)) - label="Tips" - /> <WithSelect(WithDispatch(BaseOption)) feature="showInserterHelpPanel" label="Inserter Help Panel" diff --git a/packages/edit-post/src/components/sidebar/index.js b/packages/edit-post/src/components/sidebar/index.js index 21559c2eea2553..790ce933fa9722 100644 --- a/packages/edit-post/src/components/sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/index.js @@ -17,14 +17,9 @@ const { Fill, Slot } = createSlotFill( 'Sidebar' ); * * @return {Object} The rendered sidebar. */ -function Sidebar( { children, label, className } ) { +function Sidebar( { children, className } ) { return ( - <div - className={ classnames( 'edit-post-sidebar', className ) } - role="region" - aria-label={ label } - tabIndex="-1" - > + <div className={ classnames( 'edit-post-sidebar', className ) }> { children } </div> ); diff --git a/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js b/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js index ef10b4ed77ef30..62a651c7f0c24e 100644 --- a/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js @@ -48,7 +48,7 @@ const PluginDocumentSettingFill = ( { isEnabled, panelName, opened, onToggle, cl * @param {string} [props.name] The machine-friendly name for the panel. * @param {string} [props.className] An optional class name added to the row. * @param {string} [props.title] The title of the panel - * @param {string|Element} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. + * @param {WPBlockTypeIconRender} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. * * @example <caption>ES5</caption> * ```js @@ -89,7 +89,7 @@ const PluginDocumentSettingFill = ( { isEnabled, panelName, opened, onToggle, cl * registerPlugin( 'document-setting-test', { render: MyDocumentSettingTest } ); * ``` * - * @return {WPElement} The WPElement to be rendered. + * @return {WPComponent} The component to be rendered. */ const PluginDocumentSettingPanel = compose( withPluginContext( ( context, ownProps ) => { diff --git a/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/index.js b/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/index.js index a84124905d7d46..b1c53372857023 100644 --- a/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/index.js @@ -27,7 +27,7 @@ const PluginPostPublishPanelFill = ( { children, className, title, initialOpen = * @param {string} [props.className] An optional class name added to the panel. * @param {string} [props.title] Title displayed at the top of the panel. * @param {boolean} [props.initialOpen=false] Whether to have the panel initially opened. When no title is provided it is always opened. - * @param {string|Element} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. + * @param {WPBlockTypeIconRender} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. * * @example <caption>ES5</caption> * ```js @@ -65,7 +65,7 @@ const PluginPostPublishPanelFill = ( { children, className, title, initialOpen = * ); * ``` * - * @return {WPElement} The WPElement to be rendered. + * @return {WPComponent} The component to be rendered. */ const PluginPostPublishPanel = compose( diff --git a/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/test/__snapshots__/index.js.snap index 79459ca118d2f6..c4c84425a8ba53 100644 --- a/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/test/__snapshots__/index.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PluginPostPublishPanel renders fill properly 1`] = `"<div class=\\"components-panel__body my-plugin-post-publish-panel is-opened\\"><h2 class=\\"components-panel__body-title\\"><button type=\\"button\\" aria-expanded=\\"true\\" class=\\"components-button components-panel__body-toggle\\"><span aria-hidden=\\"true\\"><svg class=\\"components-panel__arrow\\" width=\\"24px\\" height=\\"24px\\" viewBox=\\"0 0 24 24\\" xmlns=\\"http://www.w3.org/2000/svg\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><g><path fill=\\"none\\" d=\\"M0,0h24v24H0V0z\\"></path></g><g><path d=\\"M12,8l-6,6l1.41,1.41L12,10.83l4.59,4.58L18,14L12,8z\\"></path></g></svg></span>My panel title</button></h2>My panel content</div>"`; +exports[`PluginPostPublishPanel renders fill properly 1`] = `"<div class=\\"components-panel__body my-plugin-post-publish-panel is-opened\\"><h2 class=\\"components-panel__body-title\\"><button type=\\"button\\" aria-expanded=\\"true\\" class=\\"components-button components-panel__body-toggle\\"><span aria-hidden=\\"true\\"><svg width=\\"24px\\" height=\\"24px\\" viewBox=\\"0 0 24 24\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"components-panel__arrow\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><g><path fill=\\"none\\" d=\\"M0,0h24v24H0V0z\\"></path></g><g><path d=\\"M12,8l-6,6l1.41,1.41L12,10.83l4.59,4.58L18,14L12,8z\\"></path></g></svg></span>My panel title</button></h2>My panel content</div>"`; diff --git a/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js b/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js index e099fa232e84f6..2d149639130eda 100644 --- a/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js @@ -49,7 +49,7 @@ export const { Fill, Slot } = createSlotFill( 'PluginPostStatusInfo' ); * ); * ``` * - * @return {WPElement} The WPElement to be rendered. + * @return {WPComponent} The component to be rendered. */ const PluginPostStatusInfo = ( { children, className } ) => ( <Fill> diff --git a/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/index.js b/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/index.js index 4a3b02fba2803b..13863b24f16d50 100644 --- a/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/index.js @@ -18,16 +18,19 @@ const PluginPrePublishPanelFill = ( { children, className, title, initialOpen = </PanelBody> </Fill> ); + /** * Renders provided content to the pre-publish side panel in the publish flow * (side panel that opens when a user first pushes "Publish" from the main editor). * - * @param {Object} props Component props. - * @param {string} [props.className] An optional class name added to the panel. - * @param {string} [props.title] Title displayed at the top of the panel. - * @param {boolean} [props.initialOpen=false] Whether to have the panel initially opened. When no title is provided it is always opened. - * @param {string|Element} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. - + * @param {Object} props Component props. + * @param {string} [props.className] An optional class name added to the panel. + * @param {string} [props.title] Title displayed at the top of the panel. + * @param {boolean} [props.initialOpen=false] Whether to have the panel initially opened. + * When no title is provided it is always opened. + * @param {WPBlockTypeIconRender} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) + * icon slug string, or an SVG WP element, to be rendered when + * the sidebar is pinned to toolbar. * * @example <caption>ES5</caption> * ```js @@ -65,7 +68,7 @@ const PluginPrePublishPanelFill = ( { children, className, title, initialOpen = * ); * ``` * - * @return {WPElement} The WPElement to be rendered. + * @return {WPComponent} The component to be rendered. */ const PluginPrePublishPanel = compose( withPluginContext( ( context, ownProps ) => { diff --git a/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/test/__snapshots__/index.js.snap b/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/test/__snapshots__/index.js.snap index df197d90cc84c8..9b509415dd4cd8 100644 --- a/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/test/__snapshots__/index.js.snap +++ b/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/test/__snapshots__/index.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PluginPrePublishPanel renders fill properly 1`] = `"<div class=\\"components-panel__body my-plugin-pre-publish-panel is-opened\\"><h2 class=\\"components-panel__body-title\\"><button type=\\"button\\" aria-expanded=\\"true\\" class=\\"components-button components-panel__body-toggle\\"><span aria-hidden=\\"true\\"><svg class=\\"components-panel__arrow\\" width=\\"24px\\" height=\\"24px\\" viewBox=\\"0 0 24 24\\" xmlns=\\"http://www.w3.org/2000/svg\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><g><path fill=\\"none\\" d=\\"M0,0h24v24H0V0z\\"></path></g><g><path d=\\"M12,8l-6,6l1.41,1.41L12,10.83l4.59,4.58L18,14L12,8z\\"></path></g></svg></span>My panel title</button></h2>My panel content</div>"`; +exports[`PluginPrePublishPanel renders fill properly 1`] = `"<div class=\\"components-panel__body my-plugin-pre-publish-panel is-opened\\"><h2 class=\\"components-panel__body-title\\"><button type=\\"button\\" aria-expanded=\\"true\\" class=\\"components-button components-panel__body-toggle\\"><span aria-hidden=\\"true\\"><svg width=\\"24px\\" height=\\"24px\\" viewBox=\\"0 0 24 24\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"components-panel__arrow\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><g><path fill=\\"none\\" d=\\"M0,0h24v24H0V0z\\"></path></g><g><path d=\\"M12,8l-6,6l1.41,1.41L12,10.83l4.59,4.58L18,14L12,8z\\"></path></g></svg></span>My panel title</button></h2>My panel content</div>"`; diff --git a/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js b/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js index 5303de703b30b9..94754a4dc05405 100644 --- a/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js @@ -36,15 +36,12 @@ function PluginSidebar( props ) { icon={ icon } label={ title } onClick={ toggleSidebar } - isToggled={ isActive } + isPressed={ isActive } aria-expanded={ isActive } /> } </PinnedPlugins> ) } - <Sidebar - name={ sidebarName } - label={ __( 'Editor plugins' ) } - > + <Sidebar name={ sidebarName }> <SidebarHeader closeLabel={ __( 'Close plugin' ) } > @@ -54,7 +51,7 @@ function PluginSidebar( props ) { icon={ isPinned ? 'star-filled' : 'star-empty' } label={ isPinned ? __( 'Unpin from toolbar' ) : __( 'Pin to toolbar' ) } onClick={ togglePin } - isToggled={ isPinned } + isPressed={ isPinned } aria-expanded={ isPinned } /> ) } @@ -82,7 +79,7 @@ function PluginSidebar( props ) { * @param {string} [props.className] An optional class name added to the sidebar body. * @param {string} props.title Title displayed at the top of the sidebar. * @param {boolean} [props.isPinnable=true] Whether to allow to pin sidebar to toolbar. - * @param {string|Element} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. + * @param {WPBlockTypeIconRender} [props.icon=inherits from the plugin] The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered when the sidebar is pinned to toolbar. * * @example <caption>ES5</caption> * ```js @@ -129,7 +126,7 @@ function PluginSidebar( props ) { * ); * ``` * - * @return {WPElement} Plugin sidebar component. + * @return {WPComponent} Plugin sidebar component. */ export default compose( withPluginContext( ( context, ownProps ) => { diff --git a/packages/edit-post/src/components/sidebar/post-link/index.js b/packages/edit-post/src/components/sidebar/post-link/index.js index 22fca71d98497d..18863fc518375e 100644 --- a/packages/edit-post/src/components/sidebar/post-link/index.js +++ b/packages/edit-post/src/components/sidebar/post-link/index.js @@ -88,7 +88,8 @@ function PostLink( { } } /> <p> - { __( 'The last part of the URL. ' ) } + { __( 'The last part of the URL.' ) } + { ' ' } <ExternalLink href="https://wordpress.org/support/article/writing-posts/#post-field-descriptions"> { __( 'Read about permalinks' ) } </ExternalLink> diff --git a/packages/edit-post/src/components/sidebar/post-schedule/index.js b/packages/edit-post/src/components/sidebar/post-schedule/index.js index eca127989f2e64..e483e741490d8a 100644 --- a/packages/edit-post/src/components/sidebar/post-schedule/index.js +++ b/packages/edit-post/src/components/sidebar/post-schedule/index.js @@ -18,7 +18,6 @@ export function PostSchedule() { renderToggle={ ( { onToggle, isOpen } ) => ( <> <Button - type="button" className="edit-post-post-schedule__toggle" onClick={ onToggle } aria-expanded={ isOpen } diff --git a/packages/edit-post/src/components/sidebar/post-schedule/style.scss b/packages/edit-post/src/components/sidebar/post-schedule/style.scss index 800fe7c6e6d24c..cea429f48f22b3 100644 --- a/packages/edit-post/src/components/sidebar/post-schedule/style.scss +++ b/packages/edit-post/src/components/sidebar/post-schedule/style.scss @@ -6,11 +6,3 @@ .components-button.edit-post-post-schedule__toggle { text-align: right; } - -.edit-post-post-schedule__dialog .components-popover__content { - padding: 10px; - - @include break-medium { - width: 270px; - } -} diff --git a/packages/edit-post/src/components/sidebar/post-slug/index.js b/packages/edit-post/src/components/sidebar/post-slug/index.js new file mode 100644 index 00000000000000..fe539a38decce6 --- /dev/null +++ b/packages/edit-post/src/components/sidebar/post-slug/index.js @@ -0,0 +1,17 @@ +/** + * WordPress dependencies + */ +import { PanelRow } from '@wordpress/components'; +import { PostSlug as PostSlugForm, PostSlugCheck } from '@wordpress/editor'; + +export function PostSlug() { + return ( + <PostSlugCheck> + <PanelRow> + <PostSlugForm /> + </PanelRow> + </PostSlugCheck> + ); +} + +export default PostSlug; diff --git a/packages/edit-post/src/components/sidebar/post-slug/style.scss b/packages/edit-post/src/components/sidebar/post-slug/style.scss new file mode 100644 index 00000000000000..b031424164dcec --- /dev/null +++ b/packages/edit-post/src/components/sidebar/post-slug/style.scss @@ -0,0 +1,4 @@ +.editor-post-slug__input { + margin: -5px 0; + padding: 2px; +} diff --git a/packages/edit-post/src/components/sidebar/post-status/index.js b/packages/edit-post/src/components/sidebar/post-status/index.js index a3403a8209c9e5..b9d50676f7141a 100644 --- a/packages/edit-post/src/components/sidebar/post-status/index.js +++ b/packages/edit-post/src/components/sidebar/post-status/index.js @@ -14,6 +14,7 @@ import PostTrash from '../post-trash'; import PostSchedule from '../post-schedule'; import PostSticky from '../post-sticky'; import PostAuthor from '../post-author'; +import PostSlug from '../post-slug'; import PostFormat from '../post-format'; import PostPendingStatus from '../post-pending-status'; import PluginPostStatusInfo from '../plugin-post-status-info'; @@ -34,6 +35,7 @@ function PostStatus( { isOpened, onTogglePanel } ) { <PostFormat /> <PostSticky /> <PostPendingStatus /> + <PostSlug /> <PostAuthor /> { fills } <PostTrash /> diff --git a/packages/edit-post/src/components/sidebar/post-visibility/index.js b/packages/edit-post/src/components/sidebar/post-visibility/index.js index 4bb0b4786a64f6..5bc1e4c563e662 100644 --- a/packages/edit-post/src/components/sidebar/post-visibility/index.js +++ b/packages/edit-post/src/components/sidebar/post-visibility/index.js @@ -17,7 +17,6 @@ export function PostVisibility() { contentClassName="edit-post-post-visibility__dialog" renderToggle={ ( { isOpen, onToggle } ) => ( <Button - type="button" aria-expanded={ isOpen } className="edit-post-post-visibility__toggle" onClick={ onToggle } diff --git a/packages/edit-post/src/components/sidebar/settings-header/index.js b/packages/edit-post/src/components/sidebar/settings-header/index.js index 04132d6f2664ea..4bb0ddc0c2235f 100644 --- a/packages/edit-post/src/components/sidebar/settings-header/index.js +++ b/packages/edit-post/src/components/sidebar/settings-header/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { withDispatch } from '@wordpress/data'; @@ -31,24 +32,24 @@ const SettingsHeader = ( { openDocumentSettings, openBlockSettings, sidebarName { /* Use a list so screen readers will announce how many tabs there are. */ } <ul> <li> - <button + <Button onClick={ openDocumentSettings } className={ `edit-post-sidebar__panel-tab ${ documentActiveClass }` } aria-label={ documentAriaLabel } data-label={ __( 'Document' ) } > { __( 'Document' ) } - </button> + </Button> </li> <li> - <button + <Button onClick={ openBlockSettings } className={ `edit-post-sidebar__panel-tab ${ blockActiveClass }` } aria-label={ blockAriaLabel } data-label={ blockLabel } > { blockLabel } - </button> + </Button> </li> </ul> </SidebarHeader> @@ -57,11 +58,9 @@ const SettingsHeader = ( { openDocumentSettings, openBlockSettings, sidebarName export default withDispatch( ( dispatch ) => { const { openGeneralSidebar } = dispatch( 'core/edit-post' ); - const { clearSelectedBlock } = dispatch( 'core/block-editor' ); return { openDocumentSettings() { openGeneralSidebar( 'edit-post/document' ); - clearSelectedBlock(); }, openBlockSettings() { openGeneralSidebar( 'edit-post/block' ); diff --git a/packages/edit-post/src/components/sidebar/settings-header/style.scss b/packages/edit-post/src/components/sidebar/settings-header/style.scss index 73d9431661c267..93111a873cec12 100644 --- a/packages/edit-post/src/components/sidebar/settings-header/style.scss +++ b/packages/edit-post/src/components/sidebar/settings-header/style.scss @@ -3,9 +3,6 @@ padding-left: 0; padding-right: $grid-size-small; border-top: 0; - position: sticky; - z-index: z-index(".components-panel__header.edit-post-sidebar__panel-tabs"); - top: 0; ul { display: flex; @@ -15,11 +12,12 @@ } } -.edit-post-sidebar__panel-tab { +.components-button.edit-post-sidebar__panel-tab { background: transparent; border: none; box-shadow: none; cursor: pointer; + display: inline-block; padding: 3px 15px; // Use padding to offset the is-active border, this benefits Windows High Contrast mode margin-left: 0; font-weight: 400; @@ -59,6 +57,7 @@ } &:focus { + background-color: transparent; @include square-style__focus; } } diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js index 49bb45eec0ce38..b1d3b8d417ac41 100644 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js @@ -1,11 +1,11 @@ /** * WordPress dependencies */ -import { Panel, PanelBody } from '@wordpress/components'; +import { Panel } from '@wordpress/components'; import { compose, ifCondition } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; import { BlockInspector } from '@wordpress/block-editor'; -import { __ } from '@wordpress/i18n'; + /** * Internal dependencies */ @@ -23,10 +23,7 @@ import MetaBoxes from '../../meta-boxes'; import PluginDocumentSettingPanel from '../plugin-document-setting-panel'; const SettingsSidebar = ( { sidebarName } ) => ( - <Sidebar - name={ sidebarName } - label={ __( 'Editor settings' ) } - > + <Sidebar name={ sidebarName }> <SettingsHeader sidebarName={ sidebarName } /> <Panel> { sidebarName === 'edit-post/document' && ( @@ -44,9 +41,7 @@ const SettingsSidebar = ( { sidebarName } ) => ( </> ) } { sidebarName === 'edit-post/block' && ( - <PanelBody className="edit-post-settings-sidebar__panel-block"> - <BlockInspector /> - </PanelBody> + <BlockInspector /> ) } </Panel> </Sidebar> diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/style.scss b/packages/edit-post/src/components/sidebar/settings-sidebar/style.scss deleted file mode 100644 index 83749d10a4d61b..00000000000000 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/style.scss +++ /dev/null @@ -1,25 +0,0 @@ -.edit-post-settings-sidebar__panel-block .components-panel__body { - border: none; - border-top: $border-width solid $light-gray-500; - margin: 0 #{ -$grid-size-large }; - - .components-base-control { - margin-bottom: #{ $grid-size * 3 }; - - &:last-child { - margin-bottom: $grid-size; - } - } - - .components-panel__body-toggle { - color: $dark-gray-900; - } - - &:first-child { - margin-top: $grid-size-large; - } - - &:last-child { - margin-bottom: -$grid-size-large; - } -} diff --git a/packages/edit-post/src/components/sidebar/style.scss b/packages/edit-post/src/components/sidebar/style.scss index 740355cf636f75..420e423d362907 100644 --- a/packages/edit-post/src/components/sidebar/style.scss +++ b/packages/edit-post/src/components/sidebar/style.scss @@ -1,30 +1,17 @@ .edit-post-sidebar { - position: fixed; - z-index: z-index(".edit-post-sidebar"); - top: 0; - right: 0; - bottom: 0; - width: $sidebar-width; - border-left: $border-width solid $light-gray-500; background: $white; color: $dark-gray-500; - height: 100vh; - overflow: hidden; + overflow: visible; @include break-small() { - top: $admin-bar-height-big + $header-height; - z-index: z-index(".edit-post-sidebar {greater than small}"); - height: auto; - overflow: auto; + z-index: auto; + height: 100%; + overflow: visible; -webkit-overflow-scrolling: touch; } @include break-medium() { - top: $admin-bar-height + $header-height; - - body.is-fullscreen-mode & { - top: $header-height; - } + width: $sidebar-width; } > .components-panel { @@ -37,10 +24,9 @@ margin-top: -1px; margin-bottom: -1px; position: relative; - z-index: z-index(".edit-post-sidebar .components-panel"); @include break-small() { - overflow: hidden; + overflow: visible; height: auto; max-height: none; } @@ -99,26 +85,6 @@ } } -/* Visual and Text editor both */ -.edit-post-layout.is-sidebar-opened .edit-post-layout__content { - @include break-medium() { - margin-right: $sidebar-width; - } -} - -.edit-post-layout.is-sidebar-opened { - .edit-post-sidebar, - .edit-post-plugin-sidebar__sidebar-layout { - /* Sidebar covers screen on mobile */ - width: 100%; - - /* Sidebar sits on the side on larger breakpoints */ - @include break-medium() { - width: $sidebar-width; - } - } -} - /* Text Editor specific */ .components-panel__header.edit-post-sidebar__header { background: $white; @@ -183,7 +149,8 @@ } } - &:focus { + &:focus:not(:disabled) { @include square-style__focus; + box-shadow: none; } } diff --git a/packages/edit-post/src/components/text-editor/style.scss b/packages/edit-post/src/components/text-editor/style.scss index 234eb46917b247..f6135be67b9c3f 100644 --- a/packages/edit-post/src/components/text-editor/style.scss +++ b/packages/edit-post/src/components/text-editor/style.scss @@ -1,30 +1,6 @@ -.edit-post-text-editor__body { - padding-top: $grid-size * 5; - - @include break-small() { - padding-top: ($grid-size * 5) + $admin-bar-height-big; - } - - @include break-medium() { - padding-top: $grid-size * 5; - - body.is-fullscreen-mode & { - padding-top: $grid-size * 5; - } - } -} - .edit-post-text-editor { + position: relative; width: 100%; - max-width: calc(100% - #{$grid-size-large * 2}); - margin-left: $grid-size-large; - margin-right: $grid-size-large; - - @include break-small() { - max-width: $content-width; - margin-left: auto; - margin-right: auto; - } // Always show outlines in code editor .editor-post-title__block { @@ -67,34 +43,41 @@ } } - .editor-post-text-editor { - padding: $block-padding; - min-height: 200px; - line-height: 1.8; - } - // Make room for toolbar. padding-top: $block-controls-height + $grid-size; +} - // Exit Code Editor toolbar. - .edit-post-text-editor__toolbar { - position: absolute; - top: $grid-size; - left: 0; - right: 0; - height: $block-controls-height; - line-height: $block-controls-height; - padding: 0 $grid-size 0 $grid-size-large; - display: flex; +// Exit Code Editor toolbar. +.edit-post-text-editor__toolbar { + position: absolute; + top: $grid-size; + left: 0; + right: 0; + height: $block-controls-height; + line-height: $block-controls-height; + padding: 0 $grid-size 0 $grid-size-large; + display: flex; + + h2 { + margin: 0 auto 0 0; + font-size: $default-font-size; + color: $dark-gray-500; + } - h2 { - margin: 0 auto 0 0; - font-size: $default-font-size; - color: $dark-gray-500; - } + .components-icon-button svg { + order: 1; + } +} - .components-icon-button svg { - order: 1; - } +.edit-post-text-editor__body { + max-width: calc(100% - #{$grid-size-large * 2}); + margin-left: $grid-size-large; + margin-right: $grid-size-large; + padding-top: $grid-size * 5; + + @include break-small() { + max-width: $content-width; + margin-left: auto; + margin-right: auto; } } diff --git a/packages/edit-post/src/components/visual-editor/style.native.scss b/packages/edit-post/src/components/visual-editor/style.native.scss index 4ade220b5dd9e9..59587f891a5e07 100644 --- a/packages/edit-post/src/components/visual-editor/style.native.scss +++ b/packages/edit-post/src/components/visual-editor/style.native.scss @@ -13,9 +13,9 @@ } .blockHolderFocused { - border-color: $gray-lighten-30; + border-color: $blue-wordpress; } .blockHolderFocusedDark { - border-color: $gray-70; + border-color: $blue-30; } diff --git a/packages/edit-post/src/components/visual-editor/style.scss b/packages/edit-post/src/components/visual-editor/style.scss index cfa17d6d3e5175..20a56f91d988e9 100644 --- a/packages/edit-post/src/components/visual-editor/style.scss +++ b/packages/edit-post/src/components/visual-editor/style.scss @@ -5,11 +5,26 @@ & .components-button { font-family: $default-font; } + + flex: 1 1 auto; + + // In IE11 flex-basis: 100% cause a bug where the metaboxes area overlap with the content area. + // But it works as expected without it. + // The flex-basis is needed for the other browsers to make sure the content area is full-height. + @supports (position: sticky) { + flex-basis: 100%; + } +} + +.edit-post-visual-editor > .block-editor__typewriter, +.edit-post-visual-editor > .block-editor__typewriter > .block-editor-writing-flow, +.edit-post-visual-editor > .block-editor__typewriter > .block-editor-writing-flow > .block-editor-writing-flow__click-redirect { + height: 100%; } .edit-post-visual-editor .block-editor-writing-flow__click-redirect { // Allow the page to be scrolled with the last block in the middle. - height: 50vh; + min-height: 50vh; width: 100%; } @@ -24,12 +39,6 @@ margin-right: auto; @include break-small() { - // Compensate for side UI width. - .block-editor-block-list__block-edit { - margin-left: -$block-side-ui-width; - margin-right: -$block-side-ui-width; - } - // Center the block toolbar on wide and full-wide blocks. // Use specific selector to not affect nested block toolbars. &[data-align="wide"] > .block-editor-block-list__block-edit > .block-editor-block-contextual-toolbar, @@ -60,14 +69,20 @@ } } -// The base width of the title should match that of blocks even if it isn't a block + +// The base width of the title should match that of blocks even if it isn't a block. +// @todo: This duplicates CSS from line 49 in block-list/style.scss, and should be +// removed when the Title field becomes an actual block. .editor-post-title { + // Beyond the mobile breakpoint, compensate for side UI. @include break-small() { - padding-left: $block-container-side-padding; - padding-right: $block-container-side-padding; + padding-left: $block-padding + $block-side-ui-width + $block-padding + $border-width * 2; + padding-right: $block-padding + $block-side-ui-width + $block-padding + $border-width * 2; } } + .edit-post-visual-editor .editor-post-title__block { + // Center. margin-left: auto; margin-right: auto; @@ -83,10 +98,11 @@ } // Stretch to mimic outline padding on desktop. + // Note that we can't target the textarea as it can't be stretched. @include break-small() { > div { - margin-left: -$block-side-ui-clearance; - margin-right: -$block-side-ui-clearance; + margin-left: -$block-padding - $block-side-ui-clearance; + margin-right: -$block-padding - $block-side-ui-clearance; } } } diff --git a/packages/edit-post/src/components/welcome-guide/images.js b/packages/edit-post/src/components/welcome-guide/images.js new file mode 100644 index 00000000000000..9b44eb9709f7d2 --- /dev/null +++ b/packages/edit-post/src/components/welcome-guide/images.js @@ -0,0 +1,36 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +export const CanvasImage = ( props ) => ( + <img + alt="" + src="data:image/svg+xml,%3Csvg width='306' height='286' viewBox='0 0 306 286' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='306' height='286' rx='4' fill='%2366C6E4'/%3E%3Crect x='36' y='30' width='234' height='256' fill='white'/%3E%3Crect x='36' y='80' width='234' height='94' fill='%23E2E4E7'/%3E%3Cpath d='M140.237 121.47L142.109 125H157.255V133H140.237V121.47ZM159.382 119H155.128L157.255 123H154.064L151.937 119H149.809L151.937 123H148.746L146.618 119H144.491L146.618 123H143.428L141.3 119H140.237C139.067 119 138.12 119.9 138.12 121L138.109 133C138.109 134.1 139.067 135 140.237 135H157.255C158.425 135 159.382 134.1 159.382 133V119Z' fill='%23444444'/%3E%3Crect x='57' y='182' width='91.4727' height='59' fill='%23E2E4E7'/%3E%3Crect x='156.982' y='182' width='91.4727' height='59' fill='%23E2E4E7'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M112.309 203H93.1634C92.0998 203 91.0361 204 91.0361 205V219C91.0361 220.1 91.9934 221 93.1634 221H112.309C113.372 221 114.436 220 114.436 219V205C114.436 204 113.372 203 112.309 203ZM112.309 218.92C112.294 218.941 112.269 218.962 112.248 218.979L112.248 218.979C112.239 218.987 112.23 218.994 112.224 219H93.1634V205.08L93.2485 205H112.213C112.235 205.014 112.258 205.038 112.276 205.057C112.284 205.066 112.292 205.074 112.298 205.08V218.92H112.309ZM99.0134 212.5L101.672 215.51L105.395 211L110.182 217H95.2907L99.0134 212.5Z' fill='%2340464D'/%3E%3Cmask id='mask0' mask-type='alpha' maskUnits='userSpaceOnUse' x='91' y='203' width='24' height='18'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M112.309 203H93.1634C92.0998 203 91.0361 204 91.0361 205V219C91.0361 220.1 91.9934 221 93.1634 221H112.309C113.372 221 114.436 220 114.436 219V205C114.436 204 113.372 203 112.309 203ZM112.309 218.92C112.294 218.941 112.269 218.962 112.248 218.979L112.248 218.979C112.239 218.987 112.23 218.994 112.224 219H93.1634V205.08L93.2485 205H112.213C112.235 205.014 112.258 205.038 112.276 205.057C112.284 205.066 112.292 205.074 112.298 205.08V218.92H112.309ZM99.0134 212.5L101.672 215.51L105.395 211L110.182 217H95.2907L99.0134 212.5Z' fill='white'/%3E%3C/mask%3E%3Cg mask='url(%23mask0)'%3E%3Crect x='89.9727' y='200' width='25.5273' height='24' fill='%2340464D'/%3E%3C/g%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M212.291 203H193.145C192.082 203 191.018 204 191.018 205V219C191.018 220.1 191.975 221 193.145 221H212.291C213.354 221 214.418 220 214.418 219V205C214.418 204 213.354 203 212.291 203ZM212.291 218.92C212.276 218.941 212.251 218.962 212.23 218.979L212.23 218.979C212.221 218.987 212.212 218.994 212.206 219H193.145V205.08L193.23 205H212.195C212.217 205.014 212.24 205.038 212.258 205.057C212.266 205.066 212.274 205.074 212.28 205.08V218.92H212.291ZM198.995 212.5L201.654 215.51L205.377 211L210.164 217H195.273L198.995 212.5Z' fill='%2340464D'/%3E%3Cmask id='mask1' mask-type='alpha' maskUnits='userSpaceOnUse' x='191' y='203' width='24' height='18'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M212.291 203H193.145C192.082 203 191.018 204 191.018 205V219C191.018 220.1 191.975 221 193.145 221H212.291C213.354 221 214.418 220 214.418 219V205C214.418 204 213.354 203 212.291 203ZM212.291 218.92C212.276 218.941 212.251 218.962 212.23 218.979L212.23 218.979C212.221 218.987 212.212 218.994 212.206 219H193.145V205.08L193.23 205H212.195C212.217 205.014 212.24 205.038 212.258 205.057C212.266 205.066 212.274 205.074 212.28 205.08V218.92H212.291ZM198.995 212.5L201.654 215.51L205.377 211L210.164 217H195.273L198.995 212.5Z' fill='white'/%3E%3C/mask%3E%3Cg mask='url(%23mask1)'%3E%3Crect x='189.955' y='200' width='25.5273' height='24' fill='%2340464D'/%3E%3C/g%3E%3Crect x='57' y='38' width='191.455' height='34' fill='%23E2E4E7'/%3E%3Cpath d='M155.918 47.8V54.04H149.537V47.8H146.346V63.4H149.537V57.16H155.918V63.4H159.109V47.8' fill='%2340464D'/%3E%3Crect x='58' y='249' width='191' height='37' fill='%23E2E4E7'/%3E%3Cpath d='M160.127 261.4H150.606C149.546 261.4 148.576 261.64 147.696 262.12C146.802 262.612 146.1 263.272 145.59 264.1C145.066 264.928 144.811 265.84 144.811 266.824C144.811 267.808 145.066 268.72 145.59 269.548C146.1 270.376 146.802 271.036 147.696 271.516C148.576 272.008 149.546 272.248 150.606 272.248H151.155V279.4C151.155 279.724 151.282 280.012 151.525 280.252C151.78 280.48 152.086 280.6 152.431 280.6C152.788 280.6 153.082 280.48 153.337 280.252C153.592 280.012 153.72 279.724 153.72 279.4V265C153.72 264.676 153.835 264.388 154.09 264.148C154.345 263.92 154.652 263.8 154.996 263.8C155.341 263.8 155.647 263.92 155.903 264.148C156.145 264.388 156.273 264.676 156.273 265V279.4C156.273 279.724 156.4 280.012 156.656 280.252C156.911 280.48 157.205 280.6 157.562 280.6C157.907 280.6 158.213 280.48 158.468 280.252C158.711 280.012 158.838 279.724 158.838 279.4V263.8H160.127C160.472 263.8 160.766 263.68 161.021 263.44C161.276 263.212 161.404 262.924 161.404 262.6C161.404 262.276 161.276 261.988 161.021 261.748C160.766 261.52 160.472 261.4 160.127 261.4Z' fill='%2340464D'/%3E%3C/svg%3E%0A" + { ...props } + /> +); + +export const EditorImage = ( props ) => ( + <img + alt="" + src="data:image/svg+xml,%3Csvg width='306' height='286' viewBox='0 0 306 286' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='306' height='286' rx='4' fill='%2366C6E4'/%3E%3Crect x='34.5' y='89.9424' width='237' height='113.423' fill='white' stroke='%238D96A0'/%3E%3Crect x='42.2383' y='98.5962' width='219.692' height='95.6618' fill='%23E2E4E7'/%3E%3Crect x='34.5' y='71.6346' width='27.0718' height='18.1324' fill='white' stroke='%238D96A0'/%3E%3Crect x='152.89' y='71.6346' width='18.5282' height='18.1324' fill='white' stroke='%238D96A0'/%3E%3Crect x='61.3516' y='71.6346' width='51.482' height='18.1324' fill='white' stroke='%238D96A0'/%3E%3Crect x='112.613' y='71.6346' width='40.4974' height='18.1324' fill='white' stroke='%238D96A0'/%3E%3Cpath d='M157.577 137.408H149.383C148.471 137.408 147.636 137.628 146.878 138.068C146.109 138.518 145.505 139.122 145.066 139.88C144.615 140.638 144.396 141.473 144.396 142.373C144.396 143.274 144.615 144.109 145.066 144.867C145.505 145.625 146.109 146.229 146.878 146.668C147.636 147.119 148.471 147.339 149.383 147.339H149.855V153.885C149.855 154.182 149.965 154.446 150.173 154.665C150.393 154.874 150.657 154.984 150.953 154.984C151.261 154.984 151.514 154.874 151.733 154.665C151.953 154.446 152.063 154.182 152.063 153.885V140.704C152.063 140.407 152.162 140.144 152.381 139.924C152.601 139.715 152.865 139.605 153.161 139.605C153.458 139.605 153.721 139.715 153.941 139.924C154.15 140.144 154.26 140.407 154.26 140.704V153.885C154.26 154.182 154.37 154.446 154.589 154.665C154.809 154.874 155.062 154.984 155.369 154.984C155.666 154.984 155.929 154.874 156.149 154.665C156.358 154.446 156.468 154.182 156.468 153.885V139.605H157.577C157.874 139.605 158.126 139.496 158.346 139.276C158.566 139.067 158.676 138.803 158.676 138.507C158.676 138.21 158.566 137.947 158.346 137.727C158.126 137.518 157.874 137.408 157.577 137.408Z' fill='%2340464D'/%3E%3Crect x='41.3232' y='77.1135' width='15.8667' height='7.17464' fill='%23E2E4E7'/%3E%3Crect x='66.9536' y='77.1135' width='7.32308' height='7.17464' fill='%23E2E4E7'/%3E%3Crect x='77.9385' y='77.1135' width='7.32308' height='7.17464' fill='%23E2E4E7'/%3E%3Crect x='88.9229' y='77.1135' width='7.32308' height='7.17464' fill='%23E2E4E7'/%3E%3Crect x='99.9077' y='77.1135' width='7.32308' height='7.17464' fill='%23E2E4E7'/%3E%3Crect x='118.215' y='77.1135' width='7.32308' height='7.17464' fill='%23E2E4E7'/%3E%3Crect x='129.2' y='77.1135' width='7.32308' height='7.17464' fill='%23E2E4E7'/%3E%3Crect x='140.185' y='77.1135' width='7.32308' height='7.17464' fill='%23E2E4E7'/%3E%3Crect x='158.492' y='77.1135' width='7.32308' height='7.17464' fill='%23E2E4E7'/%3E%3C/svg%3E%0A" + { ...props } + /> +); + +export const BlockLibraryImage = ( props ) => ( + <img + alt="" + src="data:image/svg+xml,%3Csvg width='306' height='286' viewBox='0 0 306 286' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='306' height='286' rx='4' fill='%2366C6E4'/%3E%3Cmask id='mask0' mask-type='alpha' maskUnits='userSpaceOnUse' x='141' y='25' width='24' height='24'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M152.765 25C146.294 25 141 30.2943 141 36.7651C141 43.2359 146.294 48.5302 152.765 48.5302C159.236 48.5302 164.53 43.2359 164.53 36.7651C164.53 30.2943 159.236 25 152.765 25ZM151.589 32.0591V35.5886H148.059V37.9416H151.589V41.4711H153.942V37.9416H157.471V35.5886H153.942V32.0591H151.589ZM143.353 36.7651C143.353 41.9417 147.588 46.1772 152.765 46.1772C157.942 46.1772 162.177 41.9417 162.177 36.7651C162.177 31.5885 157.942 27.353 152.765 27.353C147.588 27.353 143.353 31.5885 143.353 36.7651Z' fill='white'/%3E%3C/mask%3E%3Cg mask='url(%23mask0)'%3E%3Crect x='141' y='25' width='23.5253' height='23.5253' fill='white'/%3E%3C/g%3E%3Cg filter='url(%23filter0_d)'%3E%3Crect x='48' y='63' width='210' height='190' fill='white'/%3E%3C/g%3E%3Cmask id='mask1' mask-type='alpha' maskUnits='userSpaceOnUse' x='143' y='139' width='20' height='16'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M143.75 141C143.75 140.17 144.42 139.5 145.25 139.5C146.08 139.5 146.75 140.17 146.75 141C146.75 141.83 146.08 142.5 145.25 142.5C144.42 142.5 143.75 141.83 143.75 141ZM143.75 147C143.75 146.17 144.42 145.5 145.25 145.5C146.08 145.5 146.75 146.17 146.75 147C146.75 147.83 146.08 148.5 145.25 148.5C144.42 148.5 143.75 147.83 143.75 147ZM145.25 151.5C144.42 151.5 143.75 152.18 143.75 153C143.75 153.82 144.43 154.5 145.25 154.5C146.07 154.5 146.75 153.82 146.75 153C146.75 152.18 146.08 151.5 145.25 151.5ZM162.25 154H148.25V152H162.25V154ZM148.25 148H162.25V146H148.25V148ZM148.25 142V140H162.25V142H148.25Z' fill='white'/%3E%3C/mask%3E%3Cg mask='url(%23mask1)'%3E%3Crect x='141' y='135' width='24' height='24' fill='%23444444'/%3E%3C/g%3E%3Cmask id='mask2' mask-type='alpha' maskUnits='userSpaceOnUse' x='139' y='54' width='28' height='11'%3E%3Crect x='139' y='54' width='28' height='11' fill='%23C4C4C4'/%3E%3C/mask%3E%3Cg mask='url(%23mask2)'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M139 67L153 54L167 67H139Z' fill='white'/%3E%3C/g%3E%3Crect x='59' y='74' width='188' height='28' rx='3' stroke='%231486B8' stroke-width='2'/%3E%3Cpath d='M211 207.47L212.76 211H227V219H211V207.47ZM229 205H225L227 209H224L222 205H220L222 209H219L217 205H215L217 209H214L212 205H211C209.9 205 209.01 205.9 209.01 207L209 219C209 220.1 209.9 221 211 221H227C228.1 221 229 220.1 229 219V205Z' fill='%23444444'/%3E%3Cpath d='M94.0001 136.4H85.0481C84.0521 136.4 83.1401 136.64 82.3121 137.12C81.4721 137.612 80.8121 138.272 80.3321 139.1C79.8401 139.928 79.6001 140.84 79.6001 141.824C79.6001 142.808 79.8401 143.72 80.3321 144.548C80.8121 145.376 81.4721 146.036 82.3121 146.516C83.1401 147.008 84.0521 147.248 85.0481 147.248H85.5641V154.4C85.5641 154.724 85.6841 155.012 85.9121 155.252C86.1521 155.48 86.4401 155.6 86.7641 155.6C87.1001 155.6 87.3761 155.48 87.6161 155.252C87.8561 155.012 87.9761 154.724 87.9761 154.4V140C87.9761 139.676 88.0841 139.388 88.3241 139.148C88.5641 138.92 88.8521 138.8 89.1761 138.8C89.5001 138.8 89.7881 138.92 90.0281 139.148C90.2561 139.388 90.3761 139.676 90.3761 140V154.4C90.3761 154.724 90.4961 155.012 90.7361 155.252C90.9761 155.48 91.2521 155.6 91.5881 155.6C91.9121 155.6 92.2001 155.48 92.4401 155.252C92.6681 155.012 92.7881 154.724 92.7881 154.4V138.8H94.0001C94.3241 138.8 94.6001 138.68 94.8401 138.44C95.0801 138.212 95.2001 137.924 95.2001 137.6C95.2001 137.276 95.0801 136.988 94.8401 136.748C94.6001 136.52 94.3241 136.4 94.0001 136.4Z' fill='%23444444'/%3E%3Cmask id='mask3' mask-type='alpha' maskUnits='userSpaceOnUse' x='76' y='204' width='22' height='18'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M96 204H78C77 204 76 205 76 206V220C76 221.1 76.9 222 78 222H96C97 222 98 221 98 220V206C98 205 97 204 96 204ZM96 219.92C95.9861 219.941 95.9624 219.962 95.9426 219.979C95.9339 219.987 95.9261 219.994 95.92 220H78V206.08L78.08 206H95.91C95.9309 206.014 95.9518 206.038 95.9694 206.057C95.977 206.066 95.9839 206.074 95.99 206.08V219.92H96ZM83.5 213.5L86 216.51L89.5 212L94 218H80L83.5 213.5Z' fill='white'/%3E%3C/mask%3E%3Cg mask='url(%23mask3)'%3E%3Crect x='75' y='201' width='24' height='24' fill='%23444444'/%3E%3C/g%3E%3Cpath d='M161 205V217H149V205H161ZM161 203H149C147.9 203 147 203.9 147 205V217C147 218.1 147.9 219 149 219H161C162.1 219 163 218.1 163 217V205C163 203.9 162.1 203 161 203ZM152.5 212.67L154.19 214.93L156.67 211.83L160 216H150L152.5 212.67ZM143 207V221C143 222.1 143.9 223 145 223H159V221H145V207H143Z' fill='%23444444'/%3E%3Cmask id='mask4' mask-type='alpha' maskUnits='userSpaceOnUse' x='210' y='140' width='18' height='12'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M215.62 152H210.38L212.38 148H210V140H218V147.24L215.62 152ZM220.38 152H225.62L228 147.24V140H220V148H222.38L220.38 152ZM224.38 150H223.62L225.62 146H222V142H226V146.76L224.38 150ZM214.38 150H213.62L215.62 146H212V142H216V146.76L214.38 150Z' fill='white'/%3E%3C/mask%3E%3Cg mask='url(%23mask4)'%3E%3Crect x='207' y='134' width='24' height='24' fill='%23444444'/%3E%3C/g%3E%3Cdefs%3E%3Cfilter id='filter0_d' x='18' y='36' width='270' height='250' filterUnits='userSpaceOnUse' color-interpolation-filters='sRGB'%3E%3CfeFlood flood-opacity='0' result='BackgroundImageFix'/%3E%3CfeColorMatrix in='SourceAlpha' type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'/%3E%3CfeOffset dy='3'/%3E%3CfeGaussianBlur stdDeviation='15'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0.0980392 0 0 0 0 0.117647 0 0 0 0 0.137255 0 0 0 0.1 0'/%3E%3CfeBlend mode='normal' in2='BackgroundImageFix' result='effect1_dropShadow'/%3E%3CfeBlend mode='normal' in='SourceGraphic' in2='effect1_dropShadow' result='shape'/%3E%3C/filter%3E%3C/defs%3E%3C/svg%3E%0A" + { ...props } + /> +); + +export const InserterIconImage = ( props ) => ( + <img + alt={ __( 'inserter' ) } + src="data:image/svg+xml;charset=utf8,%3Csvg width='18' height='18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.824 0C3.97 0 0 3.97 0 8.824c0 4.853 3.97 8.824 8.824 8.824 4.853 0 8.824-3.971 8.824-8.824S13.677 0 8.824 0zM7.94 5.294v2.647H5.294v1.765h2.647v2.647h1.765V9.706h2.647V7.941H9.706V5.294H7.941zm-6.176 3.53c0 3.882 3.176 7.059 7.059 7.059 3.882 0 7.059-3.177 7.059-7.06 0-3.882-3.177-7.058-7.06-7.058-3.882 0-7.058 3.176-7.058 7.059z' fill='%234A4A4A'/%3E%3Cmask id='a' maskUnits='userSpaceOnUse' x='0' y='0' width='18' height='18'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.824 0C3.97 0 0 3.97 0 8.824c0 4.853 3.97 8.824 8.824 8.824 4.853 0 8.824-3.971 8.824-8.824S13.677 0 8.824 0zM7.94 5.294v2.647H5.294v1.765h2.647v2.647h1.765V9.706h2.647V7.941H9.706V5.294H7.941zm-6.176 3.53c0 3.882 3.176 7.059 7.059 7.059 3.882 0 7.059-3.177 7.059-7.06 0-3.882-3.177-7.058-7.06-7.058-3.882 0-7.058 3.176-7.058 7.059z' fill='%23fff'/%3E%3C/mask%3E%3Cg mask='url(%23a)'%3E%3Cpath fill='%23444' d='M0 0h17.644v17.644H0z'/%3E%3C/g%3E%3C/svg%3E" + { ...props } + /> +); diff --git a/packages/edit-post/src/components/welcome-guide/index.js b/packages/edit-post/src/components/welcome-guide/index.js new file mode 100644 index 00000000000000..dcdb1b64548d9f --- /dev/null +++ b/packages/edit-post/src/components/welcome-guide/index.js @@ -0,0 +1,71 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { Guide, GuidePage } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { __experimentalCreateInterpolateElement } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { CanvasImage, EditorImage, BlockLibraryImage, InserterIconImage } from './images'; + +export default function WelcomeGuide() { + const areTipsEnabled = useSelect( ( select ) => select( 'core/nux' ).areTipsEnabled(), [] ); + + const { disableTips } = useDispatch( 'core/nux' ); + + if ( ! areTipsEnabled ) { + return null; + } + + return ( + <Guide + className="edit-post-welcome-guide" + finishButtonText={ __( 'Get started' ) } + onFinish={ disableTips } + > + + <GuidePage className="edit-post-welcome-guide__page"> + <h1 className="edit-post-welcome-guide__heading"> + { __( 'Welcome to the Block Editor' ) } + </h1> + <CanvasImage className="edit-post-welcome-guide__image" /> + <p className="edit-post-welcome-guide__text"> + { __( 'In the WordPress editor, each paragraph, image, or video is presented as a distinct “block” of content.' ) } + </p> + </GuidePage> + + <GuidePage className="edit-post-welcome-guide__page"> + <h1 className="edit-post-welcome-guide__heading"> + { __( 'Make each block your own' ) } + </h1> + <EditorImage className="edit-post-welcome-guide__image" /> + <p className="edit-post-welcome-guide__text"> + { __( 'Each block comes with its own set of controls for changing things like color, width, and alignment. These will show and hide automatically when you have a block selected.' ) } + </p> + </GuidePage> + + <GuidePage className="edit-post-welcome-guide__page"> + <h1 className="edit-post-welcome-guide__heading"> + { __( 'Get to know the Block Library' ) } + </h1> + <BlockLibraryImage className="edit-post-welcome-guide__image" /> + <p className="edit-post-welcome-guide__text"> + { __experimentalCreateInterpolateElement( + __( 'All of the blocks available to you live in the Block Library. You’ll find it wherever you see the <InserterIconImage /> icon.' ), + { + InserterIconImage: ( + <InserterIconImage + className="edit-post-welcome-guide__inserter-icon" + /> + ), + } + ) } + </p> + </GuidePage> + + </Guide> + ); +} diff --git a/packages/edit-post/src/components/welcome-guide/style.scss b/packages/edit-post/src/components/welcome-guide/style.scss new file mode 100644 index 00000000000000..e73c1dce01ea8b --- /dev/null +++ b/packages/edit-post/src/components/welcome-guide/style.scss @@ -0,0 +1,49 @@ +.edit-post-welcome-guide { + $image-height: 300px; + $image-width: 320px; + + &__page { + display: flex; + flex-direction: column; + justify-content: center; + position: relative; + + @include break-small() { + min-height: $image-height; + padding-left: $image-width + $grid-size-xlarge; + } + } + + &__heading { + font-family: $editor-font; + font-size: 32px; + line-height: 44px; + margin: $grid-size 0; + } + + &__image { + background: #66c6e4; + border-radius: $radius-round-rectangle; + height: 200px; + margin: $grid-size 0; + + @include break-small() { + height: $image-height; + left: 0; + position: absolute; + width: $image-width; + } + } + + &__text { + font-size: $editor-font-size; + line-height: 1.5; + margin: $grid-size 0; + } + + &__inserter-icon { + margin: 0 4px; + position: relative; + top: 4px; + } +} diff --git a/packages/edit-post/src/editor.native.js b/packages/edit-post/src/editor.native.js index 74c2cec2e4b97d..097bf4b8c00410 100644 --- a/packages/edit-post/src/editor.native.js +++ b/packages/edit-post/src/editor.native.js @@ -96,6 +96,7 @@ class Editor extends Component { hiddenBlockTypes, blockTypes, post, + postType, ...props } = this.props; @@ -118,7 +119,7 @@ class Editor extends Component { // For now, let's assume: serialize( parse( html ) ) !== html raw: serialize( parse( props.initialHtml || '' ) ), }, - type: 'post', + type: postType, status: 'draft', meta: [], }; diff --git a/packages/edit-post/src/hooks/validate-multiple-use/index.js b/packages/edit-post/src/hooks/validate-multiple-use/index.js index ea2958949f7e2d..54a502603c2064 100644 --- a/packages/edit-post/src/hooks/validate-multiple-use/index.js +++ b/packages/edit-post/src/hooks/validate-multiple-use/index.js @@ -28,9 +28,9 @@ const enhance = compose( * "original" block is not the current one. Thus, an inexisting * `originalBlockClientId` prop signals that the block is valid. * - * @param {Component} WrappedBlockEdit A filtered BlockEdit instance. + * @param {WPComponent} WrappedBlockEdit A filtered BlockEdit instance. * - * @return {Component} Enhanced component with merged state data props. + * @return {WPComponent} Enhanced component with merged state data props. */ withSelect( ( select, block ) => { const multiple = hasBlockSupport( block.name, 'multiple', true ); diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index 531f8e1c0267ee..277adb42105344 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -77,6 +77,29 @@ export function initializeEditor( id, postType, postId, settings, initialEdits ) console.warn( "Your browser is using Quirks Mode. \nThis can cause rendering issues such as blocks overlaying meta boxes in the editor. Quirks Mode can be triggered by PHP errors or HTML code appearing before the opening <!DOCTYPE html>. Try checking the raw page source or your site's PHP error log and resolving errors there, removing any HTML before the doctype, or disabling plugins." ); } + // This is a temporary fix for a couple of issues specific to Webkit on iOS. + // Without this hack the browser scrolls the mobile toolbar off-screen. + // Once supported in Safari we can replace this in favor of preventScroll. + // For details see issue #18632 and PR #18686 + // Specifically, we scroll `edit-post-editor-regions__body` to enable a fixed top toolbar. + // But Mobile Safari forces the `html` element to scroll upwards, hiding the toolbar. + + const isIphone = window.navigator.userAgent.indexOf( 'iPhone' ) !== -1; + if ( isIphone ) { + window.addEventListener( 'scroll', function( event ) { + const editorScrollContainer = document.getElementsByClassName( 'edit-post-editor-regions__body' )[ 0 ]; + if ( event.target === document ) { + // Scroll element into view by scrolling the editor container by the same amount + // that Mobile Safari tried to scroll the html element upwards. + if ( window.scrollY > 100 ) { + editorScrollContainer.scrollTop = editorScrollContainer.scrollTop + window.scrollY; + } + //Undo unwanted scroll on html element + window.scrollTo( 0, 0 ); + } + } ); + } + render( <Editor settings={ settings } diff --git a/packages/edit-post/src/plugins/copy-content-menu-item/index.js b/packages/edit-post/src/plugins/copy-content-menu-item/index.js index a46355cde7f969..8b1fe930608fc9 100644 --- a/packages/edit-post/src/plugins/copy-content-menu-item/index.js +++ b/packages/edit-post/src/plugins/copy-content-menu-item/index.js @@ -17,7 +17,7 @@ function CopyContentMenuItem( { createNotice, editedPostContent, hasCopied, setS setState( { hasCopied: true } ); createNotice( 'info', - 'All content copied.', + __( 'All content copied.' ), { isDismissible: true, type: 'snackbar', diff --git a/packages/edit-post/src/plugins/index.js b/packages/edit-post/src/plugins/index.js index bae6f081189ee6..669a870a8e53c6 100644 --- a/packages/edit-post/src/plugins/index.js +++ b/packages/edit-post/src/plugins/index.js @@ -13,6 +13,7 @@ import CopyContentMenuItem from './copy-content-menu-item'; import ManageBlocksMenuItem from './manage-blocks-menu-item'; import KeyboardShortcutsHelpMenuItem from './keyboard-shortcuts-help-menu-item'; import ToolsMoreMenuGroup from '../components/header/tools-more-menu-group'; +import WelcomeGuideMenuItem from './welcome-guide-menu-item'; registerPlugin( 'edit-post', { render() { @@ -29,6 +30,7 @@ registerPlugin( 'edit-post', { { __( 'Manage All Reusable Blocks' ) } </MenuItem> <KeyboardShortcutsHelpMenuItem onSelect={ onClose } /> + <WelcomeGuideMenuItem /> <CopyContentMenuItem /> </> ) } diff --git a/packages/edit-post/src/plugins/welcome-guide-menu-item/index.js b/packages/edit-post/src/plugins/welcome-guide-menu-item/index.js new file mode 100644 index 00000000000000..5c4c877c796905 --- /dev/null +++ b/packages/edit-post/src/plugins/welcome-guide-menu-item/index.js @@ -0,0 +1,16 @@ +/** + * WordPress dependencies + */ +import { useDispatch } from '@wordpress/data'; +import { MenuItem } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +export default function WelcomeGuideMenuItem() { + const { enableTips } = useDispatch( 'core/nux' ); + + return ( + <MenuItem onClick={ enableTips }> + { __( 'Welcome Guide' ) } + </MenuItem> + ); +} diff --git a/packages/edit-post/src/store/selectors.js b/packages/edit-post/src/store/selectors.js index 2430ee41087232..fff04ac721fe37 100644 --- a/packages/edit-post/src/store/selectors.js +++ b/packages/edit-post/src/store/selectors.js @@ -78,9 +78,9 @@ export function getPreferences( state ) { * * @param {Object} state Global application state. * @param {string} preferenceKey Preference Key. - * @param {Mixed} defaultValue Default Value. + * @param {*} defaultValue Default Value. * - * @return {Mixed} Preference Value. + * @return {*} Preference Value. */ export function getPreference( state, preferenceKey, defaultValue ) { const preferences = getPreferences( state ); diff --git a/packages/edit-post/src/style.scss b/packages/edit-post/src/style.scss index 0ffb8871dece8b..43b69c59da22cc 100644 --- a/packages/edit-post/src/style.scss +++ b/packages/edit-post/src/style.scss @@ -1,3 +1,6 @@ +$footer-height: $icon-button-size-small; + +@import "./components/editor-regions/style.scss"; @import "./components/fullscreen-mode/style.scss"; @import "./components/header/style.scss"; @import "./components/header/fullscreen-mode-close/style.scss"; @@ -13,14 +16,15 @@ @import "./components/sidebar/post-author/style.scss"; @import "./components/sidebar/post-link/style.scss"; @import "./components/sidebar/post-schedule/style.scss"; +@import "./components/sidebar/post-slug/style.scss"; @import "./components/sidebar/post-status/style.scss"; @import "./components/sidebar/post-visibility/style.scss"; @import "./components/sidebar/settings-header/style.scss"; -@import "./components/sidebar/settings-sidebar/style.scss"; @import "./components/sidebar/sidebar-header/style.scss"; @import "./components/text-editor/style.scss"; @import "./components/visual-editor/style.scss"; @import "./components/options-modal/style.scss"; +@import "./components/welcome-guide/style.scss"; /** @@ -81,6 +85,15 @@ body.block-editor-page { } } + img { + max-width: 100%; + height: auto; + } + + iframe { + width: 100%; + } + .components-navigate-regions { height: 100%; } diff --git a/packages/edit-post/src/test/editor.native.js b/packages/edit-post/src/test/editor.native.js index a48884c52e9ea0..89cfc4a9bcc96c 100644 --- a/packages/edit-post/src/test/editor.native.js +++ b/packages/edit-post/src/test/editor.native.js @@ -52,6 +52,7 @@ const renderEditorWith = ( content ) => { initialHtml={ content } initialHtmlModeEnabled={ false } initialTitle={ '' } + postType="post" /> ); }; diff --git a/packages/edit-widgets/package.json b/packages/edit-widgets/package.json index e5ba0beff046e2..b3ce47dff40638 100644 --- a/packages/edit-widgets/package.json +++ b/packages/edit-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/edit-widgets", - "version": "0.7.0", + "version": "0.8.0", "private": true, "description": "Widgets Page module for WordPress..", "author": "The WordPress Contributors", diff --git a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js index db31dd0a48d6e3..b4abe1296faaa1 100644 --- a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js +++ b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js @@ -7,6 +7,7 @@ import { navigateRegions, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { useState } from '@wordpress/element'; /** * Internal dependencies @@ -16,6 +17,7 @@ import WidgetAreas from '../widget-areas'; import './sync-customizer'; function CustomizerEditWidgetsInitializer( { settings } ) { + const [ selectedArea, setSelectedArea ] = useState( null ); return ( <SlotFillProvider> <div @@ -24,7 +26,11 @@ function CustomizerEditWidgetsInitializer( { settings } ) { aria-label={ __( 'Widgets screen content' ) } tabIndex="-1" > - <WidgetAreas blockEditorSettings={ settings } /> + <WidgetAreas + selectedArea={ selectedArea } + setSelectedArea={ setSelectedArea } + blockEditorSettings={ settings } + /> </div> <Popover.Slot /> </SlotFillProvider> diff --git a/packages/edit-widgets/src/components/layout/index.js b/packages/edit-widgets/src/components/layout/index.js index e3a264980d5d88..9011f8fff87f7c 100644 --- a/packages/edit-widgets/src/components/layout/index.js +++ b/packages/edit-widgets/src/components/layout/index.js @@ -4,9 +4,11 @@ import { __ } from '@wordpress/i18n'; import { navigateRegions, + DropZoneProvider, Popover, SlotFillProvider, } from '@wordpress/components'; +import { useState } from '@wordpress/element'; /** * Internal dependencies @@ -17,22 +19,30 @@ import WidgetAreas from '../widget-areas'; import Notices from '../notices'; function Layout( { blockEditorSettings } ) { + const [ selectedArea, setSelectedArea ] = useState( null ); return ( <SlotFillProvider> - <Header /> - <Sidebar /> - <Notices /> - <div - className="edit-widgets-layout__content" - role="region" - aria-label={ __( 'Widgets screen content' ) } - tabIndex="-1" - > - <WidgetAreas - blockEditorSettings={ blockEditorSettings } - /> - </div> - <Popover.Slot /> + <DropZoneProvider> + <Header /> + <Sidebar /> + <Notices /> + <div + className="edit-widgets-layout__content" + role="region" + aria-label={ __( 'Widgets screen content' ) } + tabIndex="-1" + onFocus={ () => { + setSelectedArea( null ); + } } + > + <WidgetAreas + selectedArea={ selectedArea } + setSelectedArea={ setSelectedArea } + blockEditorSettings={ blockEditorSettings } + /> + </div> + <Popover.Slot /> + </DropZoneProvider> </SlotFillProvider> ); } diff --git a/packages/edit-widgets/src/components/notices/index.js b/packages/edit-widgets/src/components/notices/index.js index fb962df3a6e8a4..6e45a3e8005715 100644 --- a/packages/edit-widgets/src/components/notices/index.js +++ b/packages/edit-widgets/src/components/notices/index.js @@ -14,7 +14,7 @@ function Notices() { return { notices: select( 'core/notices' ).getNotices(), }; - } ); + }, [] ); const snackbarNotices = filter( notices, { type: 'snackbar', } ); diff --git a/packages/edit-widgets/src/components/sidebar/style.scss b/packages/edit-widgets/src/components/sidebar/style.scss index 53bab2c7ffac8d..5c744d535f45b7 100644 --- a/packages/edit-widgets/src/components/sidebar/style.scss +++ b/packages/edit-widgets/src/components/sidebar/style.scss @@ -13,7 +13,7 @@ @include break-small() { top: $admin-bar-height-big + $header-height; - z-index: z-index(".edit-post-sidebar {greater than small}"); + z-index: z-index(".edit-widgets-sidebar {greater than small}"); height: auto; overflow: auto; -webkit-overflow-scrolling: touch; diff --git a/packages/edit-widgets/src/components/widget-area/index.js b/packages/edit-widgets/src/components/widget-area/index.js index e22c880f50cf22..c30271526926b4 100644 --- a/packages/edit-widgets/src/components/widget-area/index.js +++ b/packages/edit-widgets/src/components/widget-area/index.js @@ -42,7 +42,7 @@ function getBlockEditorSettings( blockEditorSettings, hasUploadPermissions ) { }; return { ...blockEditorSettings, - __experimentalMediaUpload: mediaUploadBlockEditor, + mediaUpload: mediaUploadBlockEditor, }; } @@ -105,38 +105,47 @@ function WidgetArea( { title={ widgetAreaName } initialOpen={ initialOpen } > - <BlockEditorProvider - value={ blocks } - onInput={ onInput } - onChange={ onChange } - settings={ settings } + <div + onFocus={ ( event ) => { + // Stop propagation of the focus event to avoid the parent + // widget layout component catching the event and removing the selected area. + event.stopPropagation(); + event.preventDefault(); + } } > - { isSelectedArea && ( - <> - <Inserter> - <BlockInserter /> - </Inserter> - <BlockEditorKeyboardShortcuts /> - </> - ) } - <SelectionObserver - isSelectedArea={ isSelectedArea } - onBlockSelected={ onBlockSelected } - /> - <Sidebar.Inspector> - <BlockInspector showNoBlockSelectedMessage={ false } /> - </Sidebar.Inspector> - <div className="editor-styles-wrapper"> - <WritingFlow> - <ObserveTyping> - <BlockList - className="edit-widgets-main-block-list" - renderAppender={ ButtonBlockerAppender } - /> - </ObserveTyping> - </WritingFlow> - </div> - </BlockEditorProvider> + <BlockEditorProvider + value={ blocks } + onInput={ onInput } + onChange={ onChange } + settings={ settings } + > + { isSelectedArea && ( + <> + <Inserter> + <BlockInserter /> + </Inserter> + <BlockEditorKeyboardShortcuts /> + </> + ) } + <SelectionObserver + isSelectedArea={ isSelectedArea } + onBlockSelected={ onBlockSelected } + /> + <Sidebar.Inspector> + <BlockInspector showNoBlockSelectedMessage={ false } /> + </Sidebar.Inspector> + <div className="editor-styles-wrapper"> + <WritingFlow> + <ObserveTyping> + <BlockList + className="edit-widgets-main-block-list" + renderAppender={ ButtonBlockerAppender } + /> + </ObserveTyping> + </WritingFlow> + </div> + </BlockEditorProvider> + </div> </PanelBody> </Panel> ); diff --git a/packages/edit-widgets/src/components/widget-area/style.scss b/packages/edit-widgets/src/components/widget-area/style.scss index 392d1ae8ff2128..1b5a3b4f9a3161 100644 --- a/packages/edit-widgets/src/components/widget-area/style.scss +++ b/packages/edit-widgets/src/components/widget-area/style.scss @@ -4,8 +4,8 @@ // Reduce padding inside widget areas .block-editor-block-list__layout { - padding-left: 0; - padding-right: 0; + padding-left: $block-side-ui-width + $block-padding; + padding-right: $block-side-ui-width + $block-padding; } // By default the default block appender inserter has a negative position, // but given that on the widget screen we have 0 padding we need to remove the negative position. @@ -15,12 +15,10 @@ } } .edit-widgets-main-block-list { - padding-top: $block-toolbar-height + 2 * $grid-size; + padding-top: $grid-size-xlarge; } .edit-widgets-main-block-list > .block-list-appender { padding-top: $panel-padding; - // Add a margin equivalent to block side UI so the appender is aligned with the blocks. - margin-left: $block-side-ui-width; - margin-right: $block-side-ui-width; + position: relative; } diff --git a/packages/edit-widgets/src/components/widget-areas/index.js b/packages/edit-widgets/src/components/widget-areas/index.js index 5919c8aa6ca1f3..89d19501618b9b 100644 --- a/packages/edit-widgets/src/components/widget-areas/index.js +++ b/packages/edit-widgets/src/components/widget-areas/index.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { useMemo, useState } from '@wordpress/element'; import { compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; @@ -12,19 +11,11 @@ import WidgetArea from '../widget-area'; const EMPTY_ARRAY = []; -function WidgetAreas( { areas, blockEditorSettings } ) { - const [ selectedArea, setSelectedArea ] = useState( 0 ); - const onBlockSelectedInArea = useMemo( - () => areas.map( ( value, index ) => ( () => { - setSelectedArea( index ); - } ) ), - [ areas, setSelectedArea ] - ); - +function WidgetAreas( { areas, blockEditorSettings, selectedArea, setSelectedArea } ) { return areas.map( ( { id }, index ) => ( <WidgetArea isSelectedArea={ index === selectedArea } - onBlockSelected={ onBlockSelectedInArea[ index ] } + onBlockSelected={ () => setSelectedArea( index ) } blockEditorSettings={ blockEditorSettings } key={ id } id={ id } diff --git a/packages/editor/package.json b/packages/editor/package.json index 22e78a023ca3f6..b2055f150bb3e4 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/editor", - "version": "9.7.0", + "version": "9.8.0", "description": "Building blocks for WordPress editors.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/editor/src/components/autocompleters/block.js b/packages/editor/src/components/autocompleters/block.js index 70860643ae096b..73ff5583873fae 100644 --- a/packages/editor/src/components/autocompleters/block.js +++ b/packages/editor/src/components/autocompleters/block.js @@ -10,6 +10,10 @@ import { select, dispatch } from '@wordpress/data'; import { createBlock } from '@wordpress/blocks'; import { BlockIcon } from '@wordpress/block-editor'; +/** @typedef {import('@wordpress/block-editor').WPEditorInserterItem} WPEditorInserterItem */ + +/** @typedef {import('@wordpress/components').WPCompleter} WPCompleter */ + /** * Returns the client ID of the parent where a newly inserted block would be * placed. @@ -27,7 +31,7 @@ function defaultGetBlockInsertionParentClientId() { * @param {string} rootClientId Client ID of the block for which to retrieve * inserter items. * - * @return {Array<Editor.InserterItem>} The inserter items for the specified + * @return {Array<WPEditorInserterItem>} The inserter items for the specified * parent. */ function defaultGetInserterItems( rootClientId ) { @@ -68,7 +72,7 @@ const fetchReusableBlocks = once( () => { /** * Creates a blocks repeater for replacing the current block with a selected block type. * - * @return {Completer} A blocks completer. + * @return {WPCompleter} A blocks completer. */ export function createBlockCompleter( { // Allow store-based selectors to be overridden for unit test. @@ -119,6 +123,6 @@ export function createBlockCompleter( { /** * Creates a blocks repeater for replacing the current block with a selected block type. * - * @return {Completer} A blocks completer. + * @return {WPCompleter} A blocks completer. */ export default createBlockCompleter(); diff --git a/packages/editor/src/components/autocompleters/user.js b/packages/editor/src/components/autocompleters/user.js index e3965a33f28d6f..45eecce3b8e4de 100644 --- a/packages/editor/src/components/autocompleters/user.js +++ b/packages/editor/src/components/autocompleters/user.js @@ -3,10 +3,12 @@ */ import apiFetch from '@wordpress/api-fetch'; +/** @typedef {import('@wordpress/components').WPCompleter} WPCompleter */ + /** * A user mentions completer. * - * @type {Completer} + * @type {WPCompleter} */ export default { name: 'users', diff --git a/packages/editor/src/components/editor-notices/style.scss b/packages/editor/src/components/editor-notices/style.scss index 330976df2dfe7b..ffdc425228e34c 100644 --- a/packages/editor/src/components/editor-notices/style.scss +++ b/packages/editor/src/components/editor-notices/style.scss @@ -23,7 +23,8 @@ .components-editor-notices__pinned { .components-notice { box-sizing: border-box; - margin: 0 0 5px; + margin: 0; + border-bottom: 1px solid rgba(0, 0, 0, 0.2); padding: 6px 12px; // Min-height matches the height of a single-line notice with an action button. diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js index fed6f633ffbfd0..d5af7f4ff443bf 100644 --- a/packages/editor/src/components/index.js +++ b/packages/editor/src/components/index.js @@ -42,6 +42,8 @@ export { default as PostSavedState } from './post-saved-state'; export { default as PostSchedule } from './post-schedule'; export { default as PostScheduleCheck } from './post-schedule/check'; export { default as PostScheduleLabel } from './post-schedule/label'; +export { default as PostSlug } from './post-slug'; +export { default as PostSlugCheck } from './post-slug/check'; export { default as PostSticky } from './post-sticky'; export { default as PostStickyCheck } from './post-sticky/check'; export { default as PostSwitchToDraftButton } from './post-switch-to-draft-button'; diff --git a/packages/editor/src/components/local-autosave-monitor/index.js b/packages/editor/src/components/local-autosave-monitor/index.js index 59be30218f20cd..3f48bc57de5e37 100644 --- a/packages/editor/src/components/local-autosave-monitor/index.js +++ b/packages/editor/src/components/local-autosave-monitor/index.js @@ -54,7 +54,7 @@ function useAutosaveNotice() { postId: select( 'core/editor' ).getCurrentPostId(), getEditedPostAttribute: select( 'core/editor' ).getEditedPostAttribute, hasRemoteAutosave: !! select( 'core/editor' ).getEditorSettings().autosave, - } ) ); + } ), [] ); const { createWarningNotice, removeNotice } = useDispatch( 'core/notices' ); const { editPost, resetEditorBlocks } = useDispatch( 'core/editor' ); @@ -124,13 +124,18 @@ function useAutosavePurge() { isDirty: select( 'core/editor' ).isEditedPostDirty(), isAutosaving: select( 'core/editor' ).isAutosavingPost(), didError: select( 'core/editor' ).didPostSaveRequestFail(), - } ) ); + } ), [] ); const lastIsDirty = useRef( isDirty ); const lastIsAutosaving = useRef( isAutosaving ); useEffect( () => { - if ( lastIsAutosaving.current && ! isAutosaving && ! didError ) { + if ( + ! didError && ( + ( lastIsAutosaving.current && ! isAutosaving ) || + ( lastIsDirty.current && ! isDirty ) + ) + ) { localAutosaveClear( postId ); } @@ -150,7 +155,7 @@ function LocalAutosaveMonitor() { const { localAutosaveInterval } = useSelect( ( select ) => ( { localAutosaveInterval: select( 'core/editor' ) .getEditorSettings().__experimentalLocalAutosaveInterval, - } ) ); + } ), [] ); return ( <AutosaveMonitor diff --git a/packages/editor/src/components/post-featured-image/index.js b/packages/editor/src/components/post-featured-image/index.js index 0141bf8f40c0e5..4400f03f0f5982 100644 --- a/packages/editor/src/components/post-featured-image/index.js +++ b/packages/editor/src/components/post-featured-image/index.js @@ -8,7 +8,14 @@ import { has, get } from 'lodash'; */ import { __ } from '@wordpress/i18n'; import { applyFilters } from '@wordpress/hooks'; -import { Button, Spinner, ResponsiveWrapper, withFilters } from '@wordpress/components'; +import { + DropZone, + Button, + Spinner, + ResponsiveWrapper, + withNotices, + withFilters, +} from '@wordpress/components'; import { compose } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor'; @@ -25,7 +32,16 @@ const DEFAULT_FEATURE_IMAGE_LABEL = __( 'Featured Image' ); const DEFAULT_SET_FEATURE_IMAGE_LABEL = __( 'Set Featured Image' ); const DEFAULT_REMOVE_FEATURE_IMAGE_LABEL = __( 'Remove Image' ); -function PostFeaturedImage( { currentPostId, featuredImageId, onUpdateImage, onRemoveImage, media, postType } ) { +function PostFeaturedImage( { + currentPostId, + featuredImageId, + onUpdateImage, + onDropImage, + onRemoveImage, + media, + postType, + noticeUI, +} ) { const postLabel = get( postType, [ 'labels' ], {} ); const instructions = <p>{ __( 'To edit the featured image, you need permission to upload media.' ) }</p>; @@ -33,18 +49,30 @@ function PostFeaturedImage( { currentPostId, featuredImageId, onUpdateImage, onR if ( media ) { const mediaSize = applyFilters( 'editor.PostFeaturedImage.imageSize', 'post-thumbnail', media.id, currentPostId ); if ( has( media, [ 'media_details', 'sizes', mediaSize ] ) ) { + // use mediaSize when available mediaWidth = media.media_details.sizes[ mediaSize ].width; mediaHeight = media.media_details.sizes[ mediaSize ].height; mediaSourceUrl = media.media_details.sizes[ mediaSize ].source_url; } else { - mediaWidth = media.media_details.width; - mediaHeight = media.media_details.height; - mediaSourceUrl = media.source_url; + // get fallbackMediaSize if mediaSize is not available + const fallbackMediaSize = applyFilters( 'editor.PostFeaturedImage.imageSize', 'thumbnail', media.id, currentPostId ); + if ( has( media, [ 'media_details', 'sizes', fallbackMediaSize ] ) ) { + // use fallbackMediaSize when mediaSize is not available + mediaWidth = media.media_details.sizes[ fallbackMediaSize ].width; + mediaHeight = media.media_details.sizes[ fallbackMediaSize ].height; + mediaSourceUrl = media.media_details.sizes[ fallbackMediaSize ].source_url; + } else { + // use full image size when mediaFallbackSize and mediaSize are not available + mediaWidth = media.media_details.width; + mediaHeight = media.media_details.height; + mediaSourceUrl = media.source_url; + } } } return ( <PostFeaturedImageCheck> + { noticeUI } <div className="editor-post-featured-image"> <MediaUploadCheck fallback={ instructions }> <MediaUpload @@ -54,21 +82,25 @@ function PostFeaturedImage( { currentPostId, featuredImageId, onUpdateImage, onR allowedTypes={ ALLOWED_MEDIA_TYPES } modalClass={ ! featuredImageId ? 'editor-post-featured-image__media-modal' : 'editor-post-featured-image__media-modal' } render={ ( { open } ) => ( - <Button - className={ ! featuredImageId ? 'editor-post-featured-image__toggle' : 'editor-post-featured-image__preview' } - onClick={ open } - aria-label={ ! featuredImageId ? null : __( 'Edit or update the image' ) }> - { !! featuredImageId && media && - <ResponsiveWrapper - naturalWidth={ mediaWidth } - naturalHeight={ mediaHeight } - > - <img src={ mediaSourceUrl } alt="" /> - </ResponsiveWrapper> - } - { !! featuredImageId && ! media && <Spinner /> } - { ! featuredImageId && ( postLabel.set_featured_image || DEFAULT_SET_FEATURE_IMAGE_LABEL ) } - </Button> + <div className="editor-post-featured-image__container"> + <Button + className={ ! featuredImageId ? 'editor-post-featured-image__toggle' : 'editor-post-featured-image__preview' } + onClick={ open } + aria-label={ ! featuredImageId ? null : __( 'Edit or update the image' ) }> + { !! featuredImageId && media && + <ResponsiveWrapper + naturalWidth={ mediaWidth } + naturalHeight={ mediaHeight } + isInline + > + <img src={ mediaSourceUrl } alt="" /> + </ResponsiveWrapper> + } + { !! featuredImageId && ! media && <Spinner /> } + { ! featuredImageId && ( postLabel.set_featured_image || DEFAULT_SET_FEATURE_IMAGE_LABEL ) } + </Button> + <DropZone onFilesDrop={ onDropImage } /> + </div> ) } value={ featuredImageId } /> @@ -114,12 +146,27 @@ const applyWithSelect = withSelect( ( select ) => { }; } ); -const applyWithDispatch = withDispatch( ( dispatch ) => { +const applyWithDispatch = withDispatch( ( dispatch, { noticeOperations }, { select } ) => { const { editPost } = dispatch( 'core/editor' ); return { onUpdateImage( image ) { editPost( { featured_media: image.id } ); }, + onDropImage( filesList ) { + select( 'core/block-editor' ) + .getSettings() + .mediaUpload( { + allowedTypes: [ 'image' ], + filesList, + onFileChange( [ image ] ) { + editPost( { featured_media: image.id } ); + }, + onError( message ) { + noticeOperations.removeAllNotices(); + noticeOperations.createErrorNotice( message ); + }, + } ); + }, onRemoveImage() { editPost( { featured_media: 0 } ); }, @@ -127,6 +174,7 @@ const applyWithDispatch = withDispatch( ( dispatch ) => { } ); export default compose( + withNotices, applyWithSelect, applyWithDispatch, withFilters( 'editor.PostFeaturedImage' ), diff --git a/packages/editor/src/components/post-featured-image/style.scss b/packages/editor/src/components/post-featured-image/style.scss index 71af23d863de3a..e9693a26af9ef4 100644 --- a/packages/editor/src/components/post-featured-image/style.scss +++ b/packages/editor/src/components/post-featured-image/style.scss @@ -1,8 +1,17 @@ .editor-post-featured-image { padding: 0; + &__container { + margin-bottom: 1em; + position: relative; + } + .components-spinner { - margin: 0; + position: absolute; + top: 50%; + left: 50%; + margin-top: -9px; + margin-left: -9px; } // Stack consecutive buttons. @@ -36,6 +45,7 @@ .editor-post-featured-image__toggle { border: $border-width dashed $light-gray-900; background-color: $light-gray-300; + min-height: 90px; line-height: 20px; padding: $grid-size 0; text-align: center; diff --git a/packages/editor/src/components/post-preview-button/index.js b/packages/editor/src/components/post-preview-button/index.js index a715c0fd3da5aa..415d9da9ba070d 100644 --- a/packages/editor/src/components/post-preview-button/index.js +++ b/packages/editor/src/components/post-preview-button/index.js @@ -10,7 +10,6 @@ import { Component, renderToString } from '@wordpress/element'; import { Button, Path, SVG } from '@wordpress/components'; import { __, _x } from '@wordpress/i18n'; import { withSelect, withDispatch } from '@wordpress/data'; -import { DotTip } from '@wordpress/nux'; import { ifCondition, compose } from '@wordpress/compose'; import { applyFilters } from '@wordpress/hooks'; @@ -191,9 +190,6 @@ export class PostPreviewButton extends Component { __( '(opens in a new tab)' ) } </span> - <DotTip tipId="core/editor.preview"> - { __( 'Click “Preview” to load a preview of this page, so you can make sure you’re happy with your blocks.' ) } - </DotTip> </Button> ); } diff --git a/packages/editor/src/components/post-preview-button/test/__snapshots__/index.js.snap b/packages/editor/src/components/post-preview-button/test/__snapshots__/index.js.snap index fc928b5e12cb9e..587f30bf8a91bb 100644 --- a/packages/editor/src/components/post-preview-button/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/post-preview-button/test/__snapshots__/index.js.snap @@ -15,11 +15,6 @@ exports[`PostPreviewButton render() should render currentPostLink otherwise 1`] > (opens in a new tab) </span> - <WithSelect(WithDispatch(DotTip)) - tipId="core/editor.preview" - > - Click “Preview” to load a preview of this page, so you can make sure you’re happy with your blocks. - </WithSelect(WithDispatch(DotTip))> </ForwardRef(Button)> `; @@ -38,10 +33,5 @@ exports[`PostPreviewButton render() should render previewLink if provided 1`] = > (opens in a new tab) </span> - <WithSelect(WithDispatch(DotTip)) - tipId="core/editor.preview" - > - Click “Preview” to load a preview of this page, so you can make sure you’re happy with your blocks. - </WithSelect(WithDispatch(DotTip))> </ForwardRef(Button)> `; diff --git a/packages/editor/src/components/post-publish-button/index.js b/packages/editor/src/components/post-publish-button/index.js index 1513dfbfbb414f..964d207ee680ed 100644 --- a/packages/editor/src/components/post-publish-button/index.js +++ b/packages/editor/src/components/post-publish-button/index.js @@ -11,7 +11,6 @@ import { Component, createRef } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; -import { DotTip } from '@wordpress/nux'; /** * Internal dependencies @@ -64,10 +63,10 @@ export class PostPublishButton extends Component { let publishStatus; if ( ! hasPublishAction ) { publishStatus = 'pending'; - } else if ( isBeingScheduled ) { - publishStatus = 'future'; } else if ( visibility === 'private' ) { publishStatus = 'private'; + } else if ( isBeingScheduled ) { + publishStatus = 'future'; } else { publishStatus = 'publish'; } @@ -111,18 +110,13 @@ export class PostPublishButton extends Component { const componentProps = isToggle ? toggleProps : buttonProps; const componentChildren = isToggle ? toggleChildren : buttonChildren; return ( - <div> - <Button - ref={ this.buttonNode } - { ...componentProps } - > - { componentChildren } - </Button> - { /* Todo: Remove the wrapping div when DotTips are removed. */ } - <DotTip tipId="core/editor.publish"> - { __( 'Finished writing? That’s great, let’s get this published right now. Just click “Publish” and you’re good to go.' ) } - </DotTip> - </div> + <Button + isLarge + ref={ this.buttonNode } + { ...componentProps } + > + { componentChildren } + </Button> ); } } diff --git a/packages/editor/src/components/post-publish-panel/index.js b/packages/editor/src/components/post-publish-panel/index.js index 9f5e0e6814e178..b6c5f5612c787c 100644 --- a/packages/editor/src/components/post-publish-panel/index.js +++ b/packages/editor/src/components/post-publish-panel/index.js @@ -75,7 +75,6 @@ export class PostPublishPanel extends Component { ) : ( <div className="editor-post-publish-panel__header-publish-button"> <PostPublishButton focusOnMount={ true } onSubmit={ this.onSubmit } forceIsDirty={ forceIsDirty } forceIsSaving={ forceIsSaving } /> - <span className="editor-post-publish-panel__spacer"></span> </div> ) } <IconButton diff --git a/packages/editor/src/components/post-publish-panel/style.scss b/packages/editor/src/components/post-publish-panel/style.scss index 31654dcc64b001..887f25ffe9fc3c 100644 --- a/packages/editor/src/components/post-publish-panel/style.scss +++ b/packages/editor/src/components/post-publish-panel/style.scss @@ -1,6 +1,5 @@ .editor-post-publish-panel { background: $white; - color: $dark-gray-500; } .editor-post-publish-panel__content { @@ -16,12 +15,18 @@ .editor-post-publish-panel__header { background: $white; - padding-left: 16px; + padding-left: 8px; + padding-right: 8px; height: $header-height; border-bottom: $border-width solid $light-gray-500; display: flex; align-items: center; align-content: space-between; + + .components-icon-button { + position: absolute; + right: 8px; + } } .editor-post-publish-panel__header-publish-button { @@ -74,6 +79,12 @@ .editor-post-visibility__dialog-legend { display: none; } + + // The DateTime component has an intrinsic padding in order for the horizontal scrolling to function inside a popover. + // We unset that here when used inline. + .components-datetime { + padding: 0; + } } .post-publish-panel__postpublish .components-panel__body { diff --git a/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap b/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap index 752b03174a55f1..7cb1c2b47f7b23 100644 --- a/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/post-publish-panel/test/__snapshots__/index.js.snap @@ -84,9 +84,6 @@ exports[`PostPublishPanel should render the pre-publish panel if post status is focusOnMount={true} onSubmit={[Function]} /> - <span - className="editor-post-publish-panel__spacer" - /> </div> <ForwardRef(IconButton) aria-expanded={true} @@ -123,9 +120,6 @@ exports[`PostPublishPanel should render the pre-publish panel if the post is not focusOnMount={true} onSubmit={[Function]} /> - <span - className="editor-post-publish-panel__spacer" - /> </div> <ForwardRef(IconButton) aria-expanded={true} @@ -162,9 +156,6 @@ exports[`PostPublishPanel should render the spinner if the post is being saved 1 focusOnMount={true} onSubmit={[Function]} /> - <span - className="editor-post-publish-panel__spacer" - /> </div> <ForwardRef(IconButton) aria-expanded={true} diff --git a/packages/editor/src/components/post-saved-state/test/__snapshots__/index.js.snap b/packages/editor/src/components/post-saved-state/test/__snapshots__/index.js.snap index bd41d7a5fbbadc..c51298cfe756a4 100644 --- a/packages/editor/src/components/post-saved-state/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/post-saved-state/test/__snapshots__/index.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PostSavedState returns a switch to draft link if the post is published 1`] = `<WithSelect(WithDispatch(WithViewportMatch(PostSwitchToDraftButton))) />`; +exports[`PostSavedState returns a switch to draft link if the post is published 1`] = `<WithSelect(WithDispatch(PostSwitchToDraftButton)) />`; exports[`PostSavedState should return Save button if edits to be saved 1`] = ` <ForwardRef(Button) diff --git a/packages/editor/src/components/post-slug/check.js b/packages/editor/src/components/post-slug/check.js new file mode 100644 index 00000000000000..6b243bd6b9f641 --- /dev/null +++ b/packages/editor/src/components/post-slug/check.js @@ -0,0 +1,10 @@ +/** + * Internal dependencies + */ +import PostTypeSupportCheck from '../post-type-support-check'; + +export default function PostSlugCheck( { children } ) { + return ( + <PostTypeSupportCheck supportKeys="slug">{ children }</PostTypeSupportCheck> + ); +} diff --git a/packages/editor/src/components/post-slug/index.js b/packages/editor/src/components/post-slug/index.js new file mode 100644 index 00000000000000..9a9b2200e72be0 --- /dev/null +++ b/packages/editor/src/components/post-slug/index.js @@ -0,0 +1,85 @@ +/** + * WordPress dependencies + */ +import { withDispatch, withSelect } from '@wordpress/data'; +import { Component } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { withInstanceId, compose } from '@wordpress/compose'; +import { safeDecodeURIComponent } from '@wordpress/url'; + +/** + * Internal dependencies + */ +import PostSlugCheck from './check'; +import { cleanForSlug } from '../../utils/url'; + +export class PostSlug extends Component { + constructor( { postSlug, postTitle, postID } ) { + super( ...arguments ); + + this.state = { + editedSlug: safeDecodeURIComponent( postSlug ) || cleanForSlug( postTitle ) || postID, + }; + + this.setSlug = this.setSlug.bind( this ); + } + + setSlug( event ) { + const { postSlug, onUpdateSlug } = this.props; + const { value } = event.target; + + const editedSlug = cleanForSlug( value ); + + if ( editedSlug === postSlug ) { + return; + } + + onUpdateSlug( editedSlug ); + } + + render() { + const { instanceId } = this.props; + const { editedSlug } = this.state; + + const inputId = 'editor-post-slug-' + instanceId; + + return ( + <PostSlugCheck> + <label htmlFor={ inputId }>{ __( 'Slug' ) }</label> + <input + type="text" + id={ inputId } + value={ editedSlug } + onChange={ ( event ) => this.setState( { editedSlug: event.target.value } ) } + onBlur={ this.setSlug } + className="editor-post-slug__input" + /> + </PostSlugCheck> + ); + } +} + +export default compose( [ + withSelect( ( select ) => { + const { + getCurrentPost, + getEditedPostAttribute, + } = select( 'core/editor' ); + + const { id } = getCurrentPost(); + return { + postSlug: getEditedPostAttribute( 'slug' ), + postTitle: getEditedPostAttribute( 'title' ), + postID: id, + }; + } ), + withDispatch( ( dispatch ) => { + const { editPost } = dispatch( 'core/editor' ); + return { + onUpdateSlug( slug ) { + editPost( { slug } ); + }, + }; + } ), + withInstanceId, +] )( PostSlug ); diff --git a/packages/editor/src/components/post-slug/test/check.js b/packages/editor/src/components/post-slug/test/check.js new file mode 100644 index 00000000000000..90fe356236adbc --- /dev/null +++ b/packages/editor/src/components/post-slug/test/check.js @@ -0,0 +1,21 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import PostSlugCheck from '../check'; + +describe( 'PostSlugCheck', () => { + it( 'should render control', () => { + const wrapper = shallow( + <PostSlugCheck> + slug + </PostSlugCheck> + ); + + expect( wrapper.type() ).not.toBe( null ); + } ); +} ); diff --git a/packages/editor/src/components/post-slug/test/index.js b/packages/editor/src/components/post-slug/test/index.js new file mode 100644 index 00000000000000..cf25f25e0fe377 --- /dev/null +++ b/packages/editor/src/components/post-slug/test/index.js @@ -0,0 +1,45 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; + +/** + * Internal dependencies + */ +import { PostSlug } from '../'; + +describe( 'PostSlug', () => { + describe( '#render()', () => { + it( 'should update internal slug', () => { + const wrapper = shallow( + <PostSlug + postSlug="index" /> + ); + + wrapper.find( 'input' ).simulate( 'change', { + target: { + value: 'single-post', + }, + } ); + + expect( wrapper.state().editedSlug ).toEqual( 'single-post' ); + } ); + + it( 'should update slug', () => { + const onUpdateSlug = jest.fn(); + const wrapper = shallow( + <PostSlug + postSlug="index" + onUpdateSlug={ onUpdateSlug } /> + ); + + wrapper.find( 'input' ).simulate( 'blur', { + target: { + value: 'single-post', + }, + } ); + + expect( onUpdateSlug ).toHaveBeenCalledWith( 'single-post' ); + } ); + } ); +} ); diff --git a/packages/editor/src/components/post-switch-to-draft-button/index.js b/packages/editor/src/components/post-switch-to-draft-button/index.js index 07270c4ed33a26..39256c5b6b32b6 100644 --- a/packages/editor/src/components/post-switch-to-draft-button/index.js +++ b/packages/editor/src/components/post-switch-to-draft-button/index.js @@ -4,16 +4,16 @@ import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { withSelect, withDispatch } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; -import { withViewportMatch } from '@wordpress/viewport'; +import { compose, useViewportMatch } from '@wordpress/compose'; function PostSwitchToDraftButton( { isSaving, isPublished, isScheduled, onClick, - isMobileViewport, } ) { + const isMobileViewport = useViewportMatch( 'small', '<' ); + if ( ! isPublished && ! isScheduled ) { return null; } @@ -61,6 +61,5 @@ export default compose( [ }, }; } ), - withViewportMatch( { isMobileViewport: '< small' } ), ] )( PostSwitchToDraftButton ); diff --git a/packages/editor/src/components/post-taxonomies/flat-term-selector.js b/packages/editor/src/components/post-taxonomies/flat-term-selector.js index 3b4c1c8a0c2926..06db56eef101f5 100644 --- a/packages/editor/src/components/post-taxonomies/flat-term-selector.js +++ b/packages/editor/src/components/post-taxonomies/flat-term-selector.js @@ -128,13 +128,13 @@ class FlatTermSelector extends Component { } updateSelectedTerms( terms = [] ) { - const selectedTerms = terms.reduce( ( result, termId ) => { + const selectedTerms = terms.reduce( ( accumulator, termId ) => { const termObject = find( this.state.availableTerms, ( term ) => term.id === termId ); if ( termObject ) { - result.push( termObject.name ); + accumulator.push( termObject.name ); } - return result; + return accumulator; }, [] ); this.setState( { selectedTerms, diff --git a/packages/editor/src/components/post-title/index.js b/packages/editor/src/components/post-title/index.js index d5f81b5d143c8f..734f863c85637d 100644 --- a/packages/editor/src/components/post-title/index.js +++ b/packages/editor/src/components/post-title/index.js @@ -3,7 +3,7 @@ */ import Textarea from 'react-autosize-textarea'; import classnames from 'classnames'; -import { get } from 'lodash'; +import { get, escape, unescape } from 'lodash'; /** * WordPress dependencies @@ -123,7 +123,7 @@ class PostTitle extends Component { <Textarea id={ `post-title-${ instanceId }` } className="editor-post-title__input" - value={ title } + value={ unescape( title ) } onChange={ this.onChange } placeholder={ decodedPlaceholder || __( 'Add title' ) } onFocus={ this.onSelect } @@ -181,7 +181,7 @@ const applyWithDispatch = withDispatch( ( dispatch ) => { insertDefaultBlock( undefined, undefined, 0 ); }, onUpdate( title ) { - editPost( { title } ); + editPost( { title: escape( title ) } ); }, onUndo: undo, onRedo: redo, diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index a47de19cfaa1c3..b2355b52e15b8f 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -8,7 +8,7 @@ import { isEmpty } from 'lodash'; * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { __experimentalRichText as RichText } from '@wordpress/rich-text'; +import { __experimentalRichText as RichText, create, insert } from '@wordpress/rich-text'; import { decodeEntities } from '@wordpress/html-entities'; import { withDispatch, withSelect } from '@wordpress/data'; import { withFocusOutside } from '@wordpress/components'; @@ -43,6 +43,19 @@ class PostTitle extends Component { this.props.onSelect(); } + onPaste( { value, onChange, plainText } ) { + const content = pasteHandler( { + plainText, + mode: 'INLINE', + tagName: 'p', + } ); + + if ( typeof content === 'string' ) { + const valueToInsert = create( { html: content } ); + onChange( insert( value, valueToInsert ) ); + } + } + render() { const { placeholder, @@ -50,6 +63,7 @@ class PostTitle extends Component { title, focusedBorderColor, borderStyle, + isDimmed, } = this.props; const decodedPlaceholder = decodeEntities( placeholder ); @@ -57,7 +71,7 @@ class PostTitle extends Component { return ( <View - style={ [ styles.titleContainer, borderStyle, { borderColor } ] } + style={ [ styles.titleContainer, borderStyle, { borderColor }, isDimmed && styles.dimmed ] } accessible={ ! this.props.isSelected } accessibilityLabel={ isEmpty( title ) ? @@ -84,13 +98,14 @@ class PostTitle extends Component { onChange={ ( value ) => { this.props.onUpdate( value ); } } + onPaste={ this.onPaste } placeholder={ decodedPlaceholder } value={ title } onSelectionChange={ () => { } } onEnter={ this.props.onEnterPress } disableEditingMenu={ true } - __unstablePasteHandler={ pasteHandler } __unstableIsSelected={ this.props.isSelected } + __unstableOnCreateUndoLevel={ () => { } } > </RichText> </View> @@ -104,11 +119,15 @@ export default compose( isPostTitleSelected, } = select( 'core/editor' ); - const { getSelectedBlockClientId } = select( 'core/block-editor' ); + const { getSelectedBlockClientId, getBlockRootClientId } = select( 'core/block-editor' ); + + const selectedId = getSelectedBlockClientId(); + const selectionIsNested = !! getBlockRootClientId( selectedId ); return { - isAnyBlockSelected: !! getSelectedBlockClientId(), + isAnyBlockSelected: !! selectedId, isSelected: isPostTitleSelected(), + isDimmed: selectionIsNested, }; } ), withDispatch( ( dispatch ) => { diff --git a/packages/editor/src/components/post-title/style.native.scss b/packages/editor/src/components/post-title/style.native.scss index a583018c37e377..416cd4c0ec33b6 100644 --- a/packages/editor/src/components/post-title/style.native.scss +++ b/packages/editor/src/components/post-title/style.native.scss @@ -7,3 +7,7 @@ margin-top: 24; } +.dimmed { + opacity: 0.2; +} + diff --git a/packages/editor/src/components/post-title/style.scss b/packages/editor/src/components/post-title/style.scss index 3bf8c5ad9f321d..b3f62b8b591c63 100644 --- a/packages/editor/src/components/post-title/style.scss +++ b/packages/editor/src/components/post-title/style.scss @@ -82,16 +82,6 @@ } } - &:not(.is-focus-mode):not(.has-fixed-toolbar):not(.is-selected) { - .editor-post-title__input:hover { - box-shadow: -$block-left-border-width 0 0 0 $dark-opacity-light-500; - - .is-dark-theme & { - box-shadow: -$block-left-border-width 0 0 0 $light-opacity-light-400; - } - } - } - &.is-focus-mode .editor-post-title__input { opacity: 0.5; transition: opacity 0.1s linear; diff --git a/packages/editor/src/components/post-type-support-check/index.js b/packages/editor/src/components/post-type-support-check/index.js index 8096e4ccf7f8d9..7aae5e6abb0362 100644 --- a/packages/editor/src/components/post-type-support-check/index.js +++ b/packages/editor/src/components/post-type-support-check/index.js @@ -12,14 +12,14 @@ import { withSelect } from '@wordpress/data'; * A component which renders its own children only if the current editor post * type supports one of the given `supportKeys` prop. * - * @param {Object} props - * @param {string} [props.postType] Current post type. - * @param {WPElement} props.children Children to be rendered if post - * type supports. - * @param {(string|string[])} props.supportKeys String or string array of keys - * to test. + * @param {Object} props Props. + * @param {string} [props.postType] Current post type. + * @param {WPElement} props.children Children to be rendered if post + * type supports. + * @param {(string|string[])} props.supportKeys String or string array of keys + * to test. * - * @return {WPElement} Rendered element. + * @return {WPComponent} The component to be rendered. */ export function PostTypeSupportCheck( { postType, children, supportKeys } ) { let isSupported = true; diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index c386f02097167b..1d3714e5c09885 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { map, pick, defaultTo, differenceBy, isEqual, noop } from 'lodash'; +import { map, pick, defaultTo } from 'lodash'; import memize from 'memize'; /** @@ -16,7 +16,6 @@ import { BlockEditorProvider, transformStyles } from '@wordpress/block-editor'; import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; import { decodeEntities } from '@wordpress/html-entities'; -import { unregisterBlockType } from '@wordpress/blocks'; /** * Internal dependencies @@ -25,7 +24,6 @@ import withRegistryProvider from './with-registry-provider'; import { mediaUpload } from '../../utils'; import ReusableBlocksButtons from '../reusable-blocks-buttons'; import ConvertToGroupButtons from '../convert-to-group-buttons'; -import InserterMenuDownloadableBlocksPanel from '../inserter-menu-downloadable-blocks-panel'; const fetchLinkSuggestions = async ( search ) => { const posts = await apiFetch( { @@ -40,11 +38,10 @@ const fetchLinkSuggestions = async ( search ) => { id: post.id, url: post.url, title: decodeEntities( post.title ) || __( '(no title)' ), + type: post.subtype || post.type, } ) ); }; -const UNINSTALL_ERROR_NOTICE_ID = 'block-uninstall-error'; - class EditorProvider extends Component { constructor( props ) { super( ...arguments ); @@ -80,6 +77,7 @@ class EditorProvider extends Component { getBlockEditorSettings( settings, reusableBlocks, + __experimentalFetchReusableBlocks, hasUploadPermissions, canUserUseUnfilteredHTML ) { @@ -94,6 +92,7 @@ class EditorProvider extends Component { 'colors', 'disableCustomColors', 'disableCustomFontSizes', + 'disableCustomGradients', 'focusMode', 'fontSizes', 'hasFixedToolbar', @@ -107,12 +106,16 @@ class EditorProvider extends Component { 'titlePlaceholder', 'onUpdateDefaultBlockStyles', '__experimentalEnableLegacyWidgetBlock', - '__experimentalEnableMenuBlock', '__experimentalBlockDirectory', + '__experimentalEnableFullSiteEditing', + '__experimentalEnableFullSiteEditingDemo', + '__experimentalEnablePageTemplates', 'showInserterHelpPanel', + 'gradients', ] ), + mediaUpload: hasUploadPermissions ? mediaUpload : undefined, __experimentalReusableBlocks: reusableBlocks, - __experimentalMediaUpload: hasUploadPermissions ? mediaUpload : undefined, + __experimentalFetchReusableBlocks, __experimentalFetchLinkSuggestions: fetchLinkSuggestions, __experimentalCanUserUseUnfilteredHTML: canUserUseUnfilteredHTML, }; @@ -140,21 +143,6 @@ class EditorProvider extends Component { if ( this.props.settings !== prevProps.settings ) { this.props.updateEditorSettings( this.props.settings ); } - - // When a block is installed from the inserter and is unused, - // it is removed when saving the post. - // Todo: move this to the edit-post package into a separate component. - if ( ! isEqual( this.props.downloadableBlocksToUninstall, prevProps.downloadableBlocksToUninstall ) ) { - this.props.downloadableBlocksToUninstall.forEach( ( blockType ) => { - this.props.uninstallBlock( blockType, noop, () => { - this.props.createWarningNotice( - __( 'Block previews can\'t uninstall.' ), { - id: UNINSTALL_ERROR_NOTICE_ID, - } ); - } ); - unregisterBlockType( blockType.name ); - } ); - } } componentWillUnmount() { @@ -168,11 +156,14 @@ class EditorProvider extends Component { post, blocks, resetEditorBlocks, + selectionStart, + selectionEnd, isReady, settings, reusableBlocks, resetEditorBlocksWithoutUndoLevel, hasUploadPermissions, + __experimentalFetchReusableBlocks, } = this.props; if ( ! isReady ) { @@ -182,24 +173,28 @@ class EditorProvider extends Component { const editorSettings = this.getBlockEditorSettings( settings, reusableBlocks, + __experimentalFetchReusableBlocks, hasUploadPermissions, canUserUseUnfilteredHTML, ); return ( - <EntityProvider kind="postType" type={ post.type } id={ post.id }> - <BlockEditorProvider - value={ blocks } - onInput={ resetEditorBlocksWithoutUndoLevel } - onChange={ resetEditorBlocks } - settings={ editorSettings } - useSubRegistry={ false } - > - { children } - <ReusableBlocksButtons /> - <ConvertToGroupButtons /> - { editorSettings.__experimentalBlockDirectory && <InserterMenuDownloadableBlocksPanel /> } - </BlockEditorProvider> + <EntityProvider kind="root" type="site"> + <EntityProvider kind="postType" type={ post.type } id={ post.id }> + <BlockEditorProvider + value={ blocks } + onInput={ resetEditorBlocksWithoutUndoLevel } + onChange={ resetEditorBlocks } + selectionStart={ selectionStart } + selectionEnd={ selectionEnd } + settings={ editorSettings } + useSubRegistry={ false } + > + { children } + <ReusableBlocksButtons /> + <ConvertToGroupButtons /> + </BlockEditorProvider> + </EntityProvider> </EntityProvider> ); } @@ -212,21 +207,20 @@ export default compose( [ canUserUseUnfilteredHTML, __unstableIsEditorReady: isEditorReady, getEditorBlocks, + getEditorSelectionStart, + getEditorSelectionEnd, __experimentalGetReusableBlocks, } = select( 'core/editor' ); const { canUser } = select( 'core' ); - const { getInstalledBlockTypes } = select( 'core/block-directory' ); - const { getBlocks } = select( 'core/block-editor' ); - - const downloadableBlocksToUninstall = differenceBy( getInstalledBlockTypes(), getBlocks(), 'name' ); return { canUserUseUnfilteredHTML: canUserUseUnfilteredHTML(), isReady: isEditorReady(), blocks: getEditorBlocks(), + selectionStart: getEditorSelectionStart(), + selectionEnd: getEditorSelectionEnd(), reusableBlocks: __experimentalGetReusableBlocks(), hasUploadPermissions: defaultTo( canUser( 'create', 'media' ), true ), - downloadableBlocksToUninstall, }; } ), withDispatch( ( dispatch ) => { @@ -235,10 +229,10 @@ export default compose( [ updatePostLock, resetEditorBlocks, updateEditorSettings, + __experimentalFetchReusableBlocks, __experimentalTearDownEditor, } = dispatch( 'core/editor' ); const { createWarningNotice } = dispatch( 'core/notices' ); - const { uninstallBlock } = dispatch( 'core/block-directory' ); return { setupEditor, @@ -246,13 +240,14 @@ export default compose( [ createWarningNotice, resetEditorBlocks, updateEditorSettings, - resetEditorBlocksWithoutUndoLevel( blocks ) { + resetEditorBlocksWithoutUndoLevel( blocks, options ) { resetEditorBlocks( blocks, { + ...options, __unstableShouldCreateUndoLevel: false, } ); }, tearDownEditor: __experimentalTearDownEditor, - uninstallBlock, + __experimentalFetchReusableBlocks, }; } ), ] )( EditorProvider ); diff --git a/packages/editor/src/components/provider/index.native.js b/packages/editor/src/components/provider/index.native.js index 092f485ff5cc50..70b6f3e744f5bc 100644 --- a/packages/editor/src/components/provider/index.native.js +++ b/packages/editor/src/components/provider/index.native.js @@ -16,6 +16,7 @@ import { Component } from '@wordpress/element'; import { parse, serialize, getUnregisteredTypeHandlerName, createBlock } from '@wordpress/blocks'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; +import { doAction } from '@wordpress/hooks'; const postTypeEntities = [ { name: 'post', baseURL: '/wp/v2/posts' }, @@ -112,7 +113,9 @@ class NativeEditorProvider extends Component { serializeToNativeAction() { if ( this.props.mode === 'text' ) { - this.updateHtmlAction( this.props.getEditedPostContent() ); + // The HTMLTextInput component does not update the store when user is doing changes + // Let's request a store update when parent is asking for it + doAction( 'native-editor.persist-html', 'core/editor' ); } const html = serialize( this.props.blocks ); diff --git a/packages/editor/src/components/table-of-contents/style.scss b/packages/editor/src/components/table-of-contents/style.scss index 3678621237fa46..0454e513dfdd23 100644 --- a/packages/editor/src/components/table-of-contents/style.scss +++ b/packages/editor/src/components/table-of-contents/style.scss @@ -1,4 +1,4 @@ -.table-of-contents__popover.components-popover:not(.is-mobile) .components-popover__content { +.table-of-contents__popover.components-popover .components-popover__content { min-width: 380px; } diff --git a/packages/editor/src/components/unsaved-changes-warning/index.js b/packages/editor/src/components/unsaved-changes-warning/index.js index 24599cc107104e..66cd294bd61fbe 100644 --- a/packages/editor/src/components/unsaved-changes-warning/index.js +++ b/packages/editor/src/components/unsaved-changes-warning/index.js @@ -27,9 +27,9 @@ class UnsavedChangesWarning extends Component { * @return {?string} Warning prompt message, if unsaved changes exist. */ warnIfUnsavedChanges( event ) { - const { isDirty } = this.props; + const { isEditedPostDirty } = this.props; - if ( isDirty ) { + if ( isEditedPostDirty() ) { event.returnValue = __( 'You have unsaved changes. If you proceed, they will be lost.' ); return event.returnValue; } @@ -41,5 +41,9 @@ class UnsavedChangesWarning extends Component { } export default withSelect( ( select ) => ( { - isDirty: select( 'core/editor' ).isEditedPostDirty(), + // We need to call the selector directly in the listener to avoid race + // conditions with `BrowserURL` where `componentDidUpdate` gets the + // new value of `isEditedPostDirty` before this component does, + // causing this component to incorrectly think a trashed post is still dirty. + isEditedPostDirty: select( 'core/editor' ).isEditedPostDirty, } ) )( UnsavedChangesWarning ); diff --git a/packages/editor/src/editor-styles.scss b/packages/editor/src/editor-styles.scss index c44e3d87a32299..978b34a61deaf3 100644 --- a/packages/editor/src/editor-styles.scss +++ b/packages/editor/src/editor-styles.scss @@ -104,6 +104,8 @@ ul, ol { margin-bottom: $default-block-margin; padding: inherit; + padding-left: 1.3em; + margin-left: 1.3em; // Remove bottom margin from nested lists. ul, diff --git a/packages/editor/src/hooks/custom-sources-backwards-compatibility.js b/packages/editor/src/hooks/custom-sources-backwards-compatibility.js index 6b56281feade72..ff52b478f105f0 100644 --- a/packages/editor/src/hooks/custom-sources-backwards-compatibility.js +++ b/packages/editor/src/hooks/custom-sources-backwards-compatibility.js @@ -1,67 +1,71 @@ +/** + * External dependencies + */ +import { pickBy, mapValues, isEmpty, mapKeys } from 'lodash'; + /** * WordPress dependencies */ -import { getBlockType } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; import { useEntityProp } from '@wordpress/core-data'; -import { useMemo, useCallback } from '@wordpress/element'; +import { useMemo } from '@wordpress/element'; import { createHigherOrderComponent } from '@wordpress/compose'; import { addFilter } from '@wordpress/hooks'; -const EMPTY_OBJECT = {}; -function useMetaAttributeSource( name, _attributes, _setAttributes ) { - const { attributes: attributeTypes = EMPTY_OBJECT } = - getBlockType( name ) || EMPTY_OBJECT; - let [ attributes, setAttributes ] = [ _attributes, _setAttributes ]; +/** @typedef {import('@wordpress/compose').WPHigherOrderComponent} WPHigherOrderComponent */ +/** @typedef {import('@wordpress/blocks').WPBlockSettings} WPBlockSettings */ + +/** + * Object whose keys are the names of block attributes, where each value + * represents the meta key to which the block attribute is intended to save. + * + * @see https://developer.wordpress.org/reference/functions/register_meta/ + * + * @typedef {Object<string,string>} WPMetaAttributeMapping + */ - if ( Object.values( attributeTypes ).some( ( type ) => type.source === 'meta' ) ) { - // eslint-disable-next-line react-hooks/rules-of-hooks - const type = useSelect( ( select ) => select( 'core/editor' ).getCurrentPostType(), [] ); - // eslint-disable-next-line react-hooks/rules-of-hooks - const [ meta, setMeta ] = useEntityProp( 'postType', type, 'meta' ); +/** + * Given a mapping of attribute names (meta source attributes) to their + * associated meta key, returns a higher order component that overrides its + * `attributes` and `setAttributes` props to sync any changes with the edited + * post's meta keys. + * + * @param {WPMetaAttributeMapping} metaAttributes Meta attribute mapping. + * + * @return {WPHigherOrderComponent} Higher-order component. + */ +const createWithMetaAttributeSource = ( metaAttributes ) => createHigherOrderComponent( + ( BlockEdit ) => ( { attributes, setAttributes, ...props } ) => { + const postType = useSelect( ( select ) => select( 'core/editor' ).getCurrentPostType(), [] ); + const [ meta, setMeta ] = useEntityProp( 'postType', postType, 'meta' ); - // eslint-disable-next-line react-hooks/rules-of-hooks - attributes = useMemo( + const mergedAttributes = useMemo( () => ( { - ..._attributes, - ...Object.keys( attributeTypes ).reduce( ( acc, key ) => { - if ( attributeTypes[ key ].source === 'meta' ) { - acc[ key ] = meta[ attributeTypes[ key ].meta ]; - } - return acc; - }, {} ), + ...attributes, + ...mapValues( metaAttributes, ( metaKey ) => meta[ metaKey ] ), } ), - [ attributeTypes, meta, _attributes ] + [ attributes, meta ] ); - // eslint-disable-next-line react-hooks/rules-of-hooks - setAttributes = useCallback( - ( ...args ) => { - Object.keys( args[ 0 ] ).forEach( ( key ) => { - if ( attributeTypes[ key ].source === 'meta' ) { - setMeta( { [ attributeTypes[ key ].meta ]: args[ 0 ][ key ] } ); + return ( + <BlockEdit + attributes={ mergedAttributes } + setAttributes={ ( nextAttributes ) => { + const nextMeta = mapKeys( + // Filter to intersection of keys between the updated + // attributes and those with an associated meta key. + pickBy( nextAttributes, ( value, key ) => metaAttributes[ key ] ), + + // Rename the keys to the expected meta key name. + ( value, attributeKey ) => metaAttributes[ attributeKey ], + ); + + if ( ! isEmpty( nextMeta ) ) { + setMeta( nextMeta ); } - } ); - return _setAttributes( ...args ); - }, - [ attributeTypes, setMeta, _setAttributes ] - ); - } - return [ attributes, setAttributes ]; -} -const withMetaAttributeSource = createHigherOrderComponent( - ( BlockListBlock ) => ( { attributes, setAttributes, name, ...props } ) => { - [ attributes, setAttributes ] = useMetaAttributeSource( - name, - attributes, - setAttributes - ); - return ( - <BlockListBlock - attributes={ attributes } - setAttributes={ setAttributes } - name={ name } + setAttributes( nextAttributes ); + } } { ...props } /> ); @@ -69,8 +73,26 @@ const withMetaAttributeSource = createHigherOrderComponent( 'withMetaAttributeSource' ); +/** + * Filters a registered block's settings to enhance a block's `edit` component + * to upgrade meta-sourced attributes to use the post's meta entity property. + * + * @param {WPBlockSettings} settings Registered block settings. + * + * @return {WPBlockSettings} Filtered block settings. + */ +function shimAttributeSource( settings ) { + /** @type {WPMetaAttributeMapping} */ + const metaAttributes = mapValues( pickBy( settings.attributes, { source: 'meta' } ), 'meta' ); + if ( ! isEmpty( metaAttributes ) ) { + settings.edit = createWithMetaAttributeSource( metaAttributes )( settings.edit ); + } + + return settings; +} + addFilter( - 'editor.BlockListBlock', - 'core/editor/custom-sources-backwards-compatibility/with-meta-attribute-source', - withMetaAttributeSource + 'blocks.registerBlockType', + 'core/editor/custom-sources-backwards-compatibility/shim-attribute-source', + shimAttributeSource ); diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index ad6a538c1b2620..82a91c3617a4d2 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -281,6 +281,11 @@ export function* savePost( options = {} ) { if ( args.length ) { yield dispatch( 'core/notices', 'createSuccessNotice', ...args ); } + // Make sure that any edits after saving create an undo level and are + // considered for change detection. + if ( ! options.isAutosave ) { + yield dispatch( 'core/block-editor', '__unstableMarkLastChangeAsPersistent' ); + } } } @@ -632,6 +637,46 @@ export function unlockPostSaving( lockName ) { }; } +/** + * Returns an action object used to signal that post autosaving is locked. + * + * @param {string} lockName The lock name. + * + * @example + * ``` + * // Lock post autosaving with the lock key `mylock`: + * wp.data.dispatch( 'core/editor' ).lockPostAutosaving( 'mylock' ); + * ``` + * + * @return {Object} Action object + */ +export function lockPostAutosaving( lockName ) { + return { + type: 'LOCK_POST_AUTOSAVING', + lockName, + }; +} + +/** + * Returns an action object used to signal that post autosaving is unlocked. + * + * @param {string} lockName The lock name. + * + * @example + * ``` + * // Unlock post saving with the lock key `mylock`: + * wp.data.dispatch( 'core/editor' ).unlockPostAutosaving( 'mylock' ); + * ``` + * + * @return {Object} Action object + */ +export function unlockPostAutosaving( lockName ) { + return { + type: 'UNLOCK_POST_AUTOSAVING', + lockName, + }; +} + /** * Returns an action object used to signal that the blocks have been updated. * @@ -641,8 +686,14 @@ export function unlockPostSaving( lockName ) { * @yield {Object} Action object */ export function* resetEditorBlocks( blocks, options = {} ) { - const edits = { blocks }; - if ( options.__unstableShouldCreateUndoLevel !== false ) { + const { + __unstableShouldCreateUndoLevel, + selectionStart, + selectionEnd, + } = options; + const edits = { blocks, selectionStart, selectionEnd }; + + if ( __unstableShouldCreateUndoLevel !== false ) { const { id, type } = yield select( STORE_KEY, 'getCurrentPost' ); const noChange = ( yield select( 'core', 'getEditedEntityRecord', 'postType', type, id ) ) diff --git a/packages/editor/src/store/actions.native.js b/packages/editor/src/store/actions.native.js index 17733a0e47b408..0154c92324640e 100644 --- a/packages/editor/src/store/actions.native.js +++ b/packages/editor/src/store/actions.native.js @@ -21,9 +21,7 @@ export function togglePostTitleSelection( isSelected = true ) { /** * Action generator used in signalling that the post should autosave. - * - * @param {Object?} options Extra flags to identify the autosave. */ -export function* autosave( ) { +export function* autosave() { RNReactNativeGutenbergBridge.editorDidAutosave(); } diff --git a/packages/editor/src/store/effects/test/utils.js b/packages/editor/src/store/effects/test/utils.js deleted file mode 100644 index 464edbc50d6759..00000000000000 --- a/packages/editor/src/store/effects/test/utils.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * WordPress dependencies - */ -import { registerStore, select } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { resolveSelector } from '../utils'; - -describe( 'resolveSelector', () => { - const storeConfig = { - reducer: ( state = 'no', action ) => { - if ( action.type === 'resolve' ) { - return 'yes'; - } - return state; - }, - selectors: { - selectAll: ( state, key ) => ( key === 'check' ) ? state : 'no-key', - }, - resolvers: { - selectAll: () => { - return new Promise( ( resolve ) => { - resolve( { type: 'resolve' } ); - } ); - }, - }, - }; - - it( 'Should wait for selector resolution', async () => { - registerStore( 'resolveStore', storeConfig ); - - expect( select( 'resolveStore' ).selectAll( 'check' ) ).toBe( 'no' ); - const value = await resolveSelector( 'resolveStore', 'selectAll', 'check' ); - expect( value ).toBe( 'yes' ); - } ); - - it( 'Should resolve already resolved selectors', async () => { - registerStore( 'resolveStore2', storeConfig ); - - // Trigger resolution - const value = await resolveSelector( 'resolveStore2', 'selectAll', 'check' ); - expect( value ).toBe( 'yes' ); - await resolveSelector( 'resolveStore2', 'selectAll', 'check' ); - expect( value ).toBe( 'yes' ); - } ); -} ); diff --git a/packages/editor/src/store/effects/utils.js b/packages/editor/src/store/effects/utils.js deleted file mode 100644 index 979d907a344324..00000000000000 --- a/packages/editor/src/store/effects/utils.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * WordPress dependencies - */ -import { select, subscribe } from '@wordpress/data'; - -/** - * Waits for the resolution of a selector before returning the selector's value. - * - * @param {string} namespace Store namespace. - * @param {string} selectorName Selector name. - * @param {Array} args Selector args. - * - * @return {Promise} Selector result. - */ -export function resolveSelector( namespace, selectorName, ...args ) { - return new Promise( ( resolve ) => { - const hasFinished = () => select( 'core/data' ).hasFinishedResolution( namespace, selectorName, args ); - const getResult = () => select( namespace )[ selectorName ].apply( null, args ); - - // We need to trigger the selector (to trigger the resolver) - const result = getResult(); - if ( hasFinished() ) { - return resolve( result ); - } - - const unsubscribe = subscribe( () => { - if ( hasFinished() ) { - unsubscribe(); - resolve( getResult() ); - } - } ); - } ); -} diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index 7014d8a3b1fe56..0bf0ddf3f1bb38 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -210,8 +210,8 @@ export function postLock( state = { isLocked: false }, action ) { * * When post saving is locked, the post cannot be published or updated. * - * @param {PostSavingLockState} state Current state. - * @param {Object} action Dispatched action. + * @param {PostLockState} state Current state. + * @param {Object} action Dispatched action. * * @return {PostLockState} Updated state. */ @@ -231,8 +231,8 @@ export function postSavingLock( state = {}, action ) { * * When post autosaving is locked, the post will not autosave. * - * @param {PostAutosavingLockState} state Current state. - * @param {Object} action Dispatched action. + * @param {PostLockState} state Current state. + * @param {Object} action Dispatched action. * * @return {PostLockState} Updated state. */ diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index a8c216d4b2083f..9cabf7bff4708e 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -1192,6 +1192,37 @@ export function getEditorBlocks( state ) { return getEditedPostAttribute( state, 'blocks' ) || EMPTY_ARRAY; } +/** + * A block selection object. + * + * @typedef {Object} WPBlockSelection + * + * @property {string} clientId A block client ID. + * @property {string} attributeKey A block attribute key. + * @property {number} offset An attribute value offset, based on the rich + * text value. See `wp.richText.create`. + */ + +/** + * Returns the current selection start. + * + * @param {Object} state + * @return {WPBlockSelection} The selection start. + */ +export function getEditorSelectionStart( state ) { + return getEditedPostAttribute( state, 'selectionStart' ); +} + +/** + * Returns the current selection end. + * + * @param {Object} state + * @return {WPBlockSelection} The selection end. + */ +export function getEditorSelectionEnd( state ) { + return getEditedPostAttribute( state, 'selectionEnd' ); +} + /** * Is the editor ready * diff --git a/packages/editor/src/store/test/actions.js b/packages/editor/src/store/test/actions.js index 255c0ea84af397..e1a4dbcbbe4648 100644 --- a/packages/editor/src/store/test/actions.js +++ b/packages/editor/src/store/test/actions.js @@ -225,6 +225,21 @@ describe( 'Post generator actions', () => { } }, ], + [ + 'yields an action for marking the last change as persistent', + () => true, + () => { + if ( ! isAutosave ) { + const { value } = fulfillment.next(); + expect( value ).toEqual( + dispatch( + 'core/block-editor', + '__unstableMarkLastChangeAsPersistent' + ) + ); + } + }, + ], [ 'implicitly returns undefined', () => true, @@ -619,4 +634,24 @@ describe( 'Editor actions', () => { } ); } ); } ); + + describe( 'lockPostAutosaving', () => { + it( 'should return the LOCK_POST_AUTOSAVING action', () => { + const result = actions.lockPostAutosaving( 'test' ); + expect( result ).toEqual( { + type: 'LOCK_POST_AUTOSAVING', + lockName: 'test', + } ); + } ); + } ); + + describe( 'unlockPostAutosaving', () => { + it( 'should return the UNLOCK_POST_AUTOSAVING action', () => { + const result = actions.unlockPostAutosaving( 'test' ); + expect( result ).toEqual( { + type: 'UNLOCK_POST_AUTOSAVING', + lockName: 'test', + } ); + } ); + } ); } ); diff --git a/packages/editor/src/store/utils/notice-builder.js b/packages/editor/src/store/utils/notice-builder.js index eb8172e9801dda..32ba7dacca5c25 100644 --- a/packages/editor/src/store/utils/notice-builder.js +++ b/packages/editor/src/store/utils/notice-builder.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { __, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies @@ -108,7 +108,7 @@ export function getNotificationArgumentsForSaveFail( data ) { // Check if message string contains HTML. Notice text is currently only // supported as plaintext, and stripping the tags may muddle the meaning. if ( error.message && ! ( /<\/?[^>]*>/.test( error.message ) ) ) { - noticeMessage = sprintf( __( '%1$s Error message: %2$s' ), noticeMessage, error.message ); + noticeMessage = [ noticeMessage, error.message ].join( ' ' ); } return [ noticeMessage, { id: SAVE_POST_NOTICE_ID, diff --git a/packages/editor/src/store/utils/test/notice-builder.js b/packages/editor/src/store/utils/test/notice-builder.js index 3e6abab360bca7..27113e6ee8cdb8 100644 --- a/packages/editor/src/store/utils/test/notice-builder.js +++ b/packages/editor/src/store/utils/test/notice-builder.js @@ -108,25 +108,25 @@ describe( 'getNotificationArgumentsForSaveFail()', () => { 'when post is not published and edits is published', '', [ 'draft', 'publish' ], - [ 'Publishing failed. Error message: Something went wrong.', defaultExpectedAction ], + [ 'Publishing failed. Something went wrong.', defaultExpectedAction ], ], [ 'when post is published and edits is privately published', '', [ 'draft', 'private' ], - [ 'Publishing failed. Error message: Something went wrong.', defaultExpectedAction ], + [ 'Publishing failed. Something went wrong.', defaultExpectedAction ], ], [ 'when post is published and edits is scheduled to be published', '', [ 'draft', 'future' ], - [ 'Scheduling failed. Error message: Something went wrong.', defaultExpectedAction ], + [ 'Scheduling failed. Something went wrong.', defaultExpectedAction ], ], [ 'when post is published and edits is published', '', [ 'publish', 'publish' ], - [ 'Updating failed. Error message: Something went wrong.', defaultExpectedAction ], + [ 'Updating failed. Something went wrong.', defaultExpectedAction ], ], ].forEach( ( [ description, diff --git a/packages/editor/src/utils/index.native.js b/packages/editor/src/utils/index.native.js index e69de29bb2d1d6..3a49f66b86c902 100644 --- a/packages/editor/src/utils/index.native.js +++ b/packages/editor/src/utils/index.native.js @@ -0,0 +1,6 @@ +/** + * Internal dependencies + */ +import mediaUpload from './media-upload'; + +export { mediaUpload }; diff --git a/packages/editor/src/utils/media-upload/index.native.js b/packages/editor/src/utils/media-upload/index.native.js new file mode 100644 index 00000000000000..9b7a53d3376d55 --- /dev/null +++ b/packages/editor/src/utils/media-upload/index.native.js @@ -0,0 +1 @@ +export default function() { } diff --git a/packages/element/CHANGELOG.md b/packages/element/CHANGELOG.md index de45ce3a90904d..f6a20d5840ab4a 100644 --- a/packages/element/CHANGELOG.md +++ b/packages/element/CHANGELOG.md @@ -1,3 +1,7 @@ +## Master + +- Added `__experimentalCreateInterpolateElement` function (see [17376](https://github.com/WordPress/gutenberg/pull/17376)) + ## 2.8.0 (2019-09-16) ### New Features diff --git a/packages/element/README.md b/packages/element/README.md index 0b96812eacb539..fb0c8089fae2b1 100755 --- a/packages/element/README.md +++ b/packages/element/README.md @@ -148,8 +148,8 @@ _Related_ _Parameters_ -- _component_ `Component`: Component -- _target_ `Element`: DOM node into which element should be rendered +- _child_ `WPElement`: Any renderable child, such as an element, string, or fragment. +- _container_ `HTMLElement`: DOM node into which element should be rendered. <a name="createRef" href="#createRef">#</a> **createRef** @@ -163,12 +163,11 @@ _Returns_ <a name="findDOMNode" href="#findDOMNode">#</a> **findDOMNode** -Finds the dom node of a React component +Finds the dom node of a React component. _Parameters_ -- _component_ `Component`: component's instance -- _target_ `Element`: DOM node into which element should be rendered +- _component_ `WPComponent`: Component's instance. <a name="forwardRef" href="#forwardRef">#</a> **forwardRef** @@ -203,7 +202,7 @@ _Returns_ <a name="isValidElement" href="#isValidElement">#</a> **isValidElement** -Checks if an object is a valid WPElement +Checks if an object is a valid WPElement. _Parameters_ @@ -225,6 +224,30 @@ _Related_ - <https://reactjs.org/docs/react-api.html#reactmemo> +<a name="Platform" href="#Platform">#</a> **Platform** + +Component used to detect the current Platform being used. +Use Platform.OS === 'web' to detect if running on web enviroment. + +This is the same concept as the React Native implementation. + +_Related_ + +- <https://facebook.github.io/react-native/docs/platform-specific-code#platform-module> + +Here is an example of how to use the select method: + +_Usage_ + +```js +import { Platform } from '@wordpress/element'; + +const placeholderLabel = Platform.select( { + native: __( 'Add media' ), + web: __( 'Drag images, upload new ones or select files from your library.' ), +} ); +``` + <a name="RawHTML" href="#RawHTML">#</a> **RawHTML** Component used as equivalent of Fragment with unescaped HTML, in cases where @@ -240,7 +263,7 @@ _Parameters_ _Returns_ -- `WPElement`: Dangerously-rendering element. +- `WPComponent`: Dangerously-rendering component. <a name="render" href="#render">#</a> **render** @@ -248,8 +271,8 @@ Renders a given element into the target DOM node. _Parameters_ -- _element_ `WPElement`: Element to render -- _target_ `Element`: DOM node into which element should be rendered +- _element_ `WPElement`: Element to render. +- _target_ `HTMLElement`: DOM node into which element should be rendered. <a name="renderToString" href="#renderToString">#</a> **renderToString** diff --git a/packages/element/package.json b/packages/element/package.json index 0cfaa1df9668d1..83f7dd79a267fe 100644 --- a/packages/element/package.json +++ b/packages/element/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/element", - "version": "2.8.0", + "version": "2.9.0", "description": "Element React module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -21,6 +21,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "sideEffects": false, "dependencies": { "@babel/runtime": "^7.4.4", "@wordpress/escape-html": "file:../escape-html", diff --git a/packages/element/src/create-interpolate-element.js b/packages/element/src/create-interpolate-element.js new file mode 100644 index 00000000000000..c32c3446ef98a4 --- /dev/null +++ b/packages/element/src/create-interpolate-element.js @@ -0,0 +1,325 @@ +/** + * External dependencies + */ +import { createElement, cloneElement, Fragment, isValidElement } from 'react'; + +let indoc, + offset, + output, + stack; + +/** + * Matches tags in the localized string + * + * This is used for extracting the tag pattern groups for parsing the localized + * string and along with the map converting it to a react element. + * + * There are four references extracted using this tokenizer: + * + * match: Full match of the tag (i.e. <strong>, </strong>, <br/>) + * isClosing: The closing slash, it it exists. + * name: The name portion of the tag (strong, br) (if ) + * isSelfClosed: The slash on a self closing tag, if it exists. + * + * @type {RegExp} + */ +const tokenizer = /<(\/)?(\w+)\s*(\/)?>/g; + +/** + * Tracks recursive-descent parse state. + * + * This is a Stack frame holding parent elements until all children have been + * parsed. + * + * @private + * @param {WPElement} element A parent element which may still have + * nested children not yet parsed. + * @param {number} tokenStart Offset at which parent element first + * appears. + * @param {number} tokenLength Length of string marking start of parent + * element. + * @param {number} prevOffset Running offset at which parsing should + * continue. + * @param {number} leadingTextStart Offset at which last closing element + * finished, used for finding text between + * elements + * + * @return {Frame} The stack frame tracking parse progress. + */ +function Frame( + element, + tokenStart, + tokenLength, + prevOffset, + leadingTextStart, +) { + return { + element, + tokenStart, + tokenLength, + prevOffset, + leadingTextStart, + children: [], + }; +} + +/** + * This function creates an interpolated element from a passed in string with + * specific tags matching how the string should be converted to an element via + * the conversion map value. + * + * @example + * For example, for the given string: + * + * "This is a <span>string</span> with <a>a link</a> and a self-closing + * <CustomComponentB/> tag" + * + * You would have something like this as the conversionMap value: + * + * ```js + * { + * span: <span />, + * a: <a href={ 'https://github.com' } />, + * CustomComponentB: <CustomComponent />, + * } + * ``` + * + * @param {string} interpolatedString The interpolation string to be parsed. + * @param {Object} conversionMap The map used to convert the string to + * a react element. + * @throws {TypeError} + * @return {WPElement} A wp element. + */ +const createInterpolateElement = ( interpolatedString, conversionMap ) => { + indoc = interpolatedString; + offset = 0; + output = []; + stack = []; + tokenizer.lastIndex = 0; + + if ( ! isValidConversionMap( conversionMap ) ) { + throw new TypeError( + 'The conversionMap provided is not valid. It must be an object with values that are WPElements' + ); + } + + do { + // twiddle our thumbs + } while ( proceed( conversionMap ) ); + return createElement( Fragment, null, ...output ); +}; + +/** + * Validate conversion map. + * + * A map is considered valid if it's an object and every value in the object + * is a WPElement + * + * @private + * + * @param {Object} conversionMap The map being validated. + * + * @return {boolean} True means the map is valid. + */ +const isValidConversionMap = ( conversionMap ) => { + const isObject = typeof conversionMap === 'object'; + const values = isObject && Object.values( conversionMap ); + return isObject && + values.length && + values.every( ( element ) => isValidElement( element ) ); +}; + +/** + * This is the iterator over the matches in the string. + * + * @private + * + * @param {Object} conversionMap The conversion map for the string. + * + * @return {boolean} true for continuing to iterate, false for finished. + */ +function proceed( conversionMap ) { + const next = nextToken(); + const [ tokenType, name, startOffset, tokenLength ] = next; + const stackDepth = stack.length; + const leadingTextStart = startOffset > offset ? offset : null; + if ( ! conversionMap[ name ] ) { + addText(); + return false; + } + switch ( tokenType ) { + case 'no-more-tokens': + if ( stackDepth !== 0 ) { + const { leadingTextStart: stackLeadingText, tokenStart } = stack.pop(); + output.push( indoc.substr( stackLeadingText, tokenStart ) ); + } + addText(); + return false; + + case 'self-closed': + if ( 0 === stackDepth ) { + if ( null !== leadingTextStart ) { + output.push( + indoc.substr( leadingTextStart, startOffset - leadingTextStart ) + ); + } + output.push( conversionMap[ name ] ); + offset = startOffset + tokenLength; + return true; + } + + // otherwise we found an inner element + addChild( + new Frame( conversionMap[ name ], startOffset, tokenLength ) + ); + offset = startOffset + tokenLength; + return true; + + case 'opener': + stack.push( + new Frame( + conversionMap[ name ], + startOffset, + tokenLength, + startOffset + tokenLength, + leadingTextStart + ) + ); + offset = startOffset + tokenLength; + return true; + + case 'closer': + // if we're not nesting then this is easy - close the block + if ( 1 === stackDepth ) { + closeOuterElement( startOffset ); + offset = startOffset + tokenLength; + return true; + } + + // otherwise we're nested and we have to close out the current + // block and add it as a innerBlock to the parent + const stackTop = stack.pop(); + const text = indoc.substr( + stackTop.prevOffset, + startOffset - stackTop.prevOffset + ); + stackTop.children.push( text ); + stackTop.prevOffset = startOffset + tokenLength; + const frame = new Frame( + stackTop.element, + stackTop.tokenStart, + stackTop.tokenLength, + startOffset + tokenLength, + ); + frame.children = stackTop.children; + addChild( frame ); + offset = startOffset + tokenLength; + return true; + + default: + addText(); + return false; + } +} + +/** + * Grabs the next token match in the string and returns it's details. + * + * @private + * + * @return {Array} An array of details for the token matched. + */ +function nextToken() { + const matches = tokenizer.exec( indoc ); + // we have no more tokens + if ( null === matches ) { + return [ 'no-more-tokens' ]; + } + const startedAt = matches.index; + const [ match, isClosing, name, isSelfClosed ] = matches; + const length = match.length; + if ( isSelfClosed ) { + return [ 'self-closed', name, startedAt, length ]; + } + if ( isClosing ) { + return [ 'closer', name, startedAt, length ]; + } + return [ 'opener', name, startedAt, length ]; +} + +/** + * Pushes text extracted from the indoc string to the output stack given the + * current rawLength value and offset (if rawLength is provided ) or the + * indoc.length and offset. + * + * @private + */ +function addText() { + const length = indoc.length - offset; + if ( 0 === length ) { + return; + } + output.push( indoc.substr( offset, length ) ); +} + +/** + * Pushes a child element to the associated parent element's children for the + * parent currently active in the stack. + * + * @private + * + * @param {Frame} frame The Frame containing the child element and it's + * token information. + */ +function addChild( frame ) { + const { element, tokenStart, tokenLength, prevOffset, children } = frame; + const parent = stack[ stack.length - 1 ]; + const text = indoc.substr( parent.prevOffset, tokenStart - parent.prevOffset ); + + if ( text ) { + parent.children.push( text ); + } + + parent.children.push( + cloneElement( element, null, ...children ) + ); + parent.prevOffset = prevOffset ? prevOffset : tokenStart + tokenLength; +} + +/** + * This is called for closing tags. It creates the element currently active in + * the stack. + * + * @private + * + * @param {number} endOffset Offset at which the closing tag for the element + * begins in the string. If this is greater than the + * prevOffset attached to the element, then this + * helps capture any remaining nested text nodes in + * the element. + */ +function closeOuterElement( endOffset ) { + const { element, leadingTextStart, prevOffset, tokenStart, children } = stack.pop(); + + const text = endOffset ? + indoc.substr( prevOffset, endOffset - prevOffset ) : + indoc.substr( prevOffset ); + + if ( text ) { + children.push( text ); + } + + if ( null !== leadingTextStart ) { + output.push( indoc.substr( leadingTextStart, tokenStart - leadingTextStart ) ); + } + + output.push( + cloneElement( + element, + null, + ...children + ) + ); +} + +export default createInterpolateElement; diff --git a/packages/element/src/index.js b/packages/element/src/index.js index d9d4bf87ecf05d..899a308c3b1c77 100644 --- a/packages/element/src/index.js +++ b/packages/element/src/index.js @@ -1,5 +1,7 @@ +export { default as __experimentalCreateInterpolateElement } from './create-interpolate-element'; export * from './react'; export * from './react-platform'; export * from './utils'; +export { default as Platform } from './platform'; export { default as renderToString } from './serialize'; export { default as RawHTML } from './raw-html'; diff --git a/packages/element/src/platform.android.js b/packages/element/src/platform.android.js new file mode 100644 index 00000000000000..9f9bd4c0d9e4c9 --- /dev/null +++ b/packages/element/src/platform.android.js @@ -0,0 +1,19 @@ +/** + * External dependencies + */ +import { Platform as OriginalPlatform } from 'react-native'; + +const Platform = { + ...OriginalPlatform, + OS: 'native', + select: ( spec ) => { + if ( 'android' in spec ) { + return spec.android; + } else if ( 'native' in spec ) { + return spec.native; + } + return spec.default; + }, +}; + +export default Platform; diff --git a/packages/element/src/platform.ios.js b/packages/element/src/platform.ios.js new file mode 100644 index 00000000000000..f5833141e33028 --- /dev/null +++ b/packages/element/src/platform.ios.js @@ -0,0 +1,19 @@ +/** + * External dependencies + */ +import { Platform as OriginalPlatform } from 'react-native'; + +const Platform = { + ...OriginalPlatform, + OS: 'native', + select: ( spec ) => { + if ( 'ios' in spec ) { + return spec.ios; + } else if ( 'native' in spec ) { + return spec.native; + } + return spec.default; + }, +}; + +export default Platform; diff --git a/packages/element/src/platform.js b/packages/element/src/platform.js new file mode 100644 index 00000000000000..328f5523b6f95f --- /dev/null +++ b/packages/element/src/platform.js @@ -0,0 +1,32 @@ +/** + * Parts of this source were derived and modified from react-native-web, + * released under the MIT license. + * + * Copyright (c) 2016-present, Nicolas Gallagher. + * Copyright (c) 2015-present, Facebook, Inc. + * + */ +const Platform = { + OS: 'web', + select: ( spec ) => ( 'web' in spec ? spec.web : spec.default ), +}; +/** + * Component used to detect the current Platform being used. + * Use Platform.OS === 'web' to detect if running on web enviroment. + * + * This is the same concept as the React Native implementation. + * + * @see https://facebook.github.io/react-native/docs/platform-specific-code#platform-module + * + * Here is an example of how to use the select method: + * @example + * ```js + * import { Platform } from '@wordpress/element'; + * + * const placeholderLabel = Platform.select( { + * native: __( 'Add media' ), + * web: __( 'Drag images, upload new ones or select files from your library.' ), + * } ); + * ``` + */ +export default Platform; diff --git a/packages/element/src/raw-html.js b/packages/element/src/raw-html.js index 2fa3618c417d88..0f56060e3057b6 100644 --- a/packages/element/src/raw-html.js +++ b/packages/element/src/raw-html.js @@ -13,7 +13,7 @@ import { createElement } from './react'; * @param {string} props.children HTML to render. * @param {Object} props.props Any additonal props to be set on the containing div. * - * @return {WPElement} Dangerously-rendering element. + * @return {WPComponent} Dangerously-rendering component. */ export default function RawHTML( { children, ...props } ) { // The DIV wrapper will be stripped by serializer, unless there are diff --git a/packages/element/src/react-platform.js b/packages/element/src/react-platform.js index e4ca37da7e11bc..44a3422ed79773 100644 --- a/packages/element/src/react-platform.js +++ b/packages/element/src/react-platform.js @@ -13,24 +13,24 @@ import { * * @see https://github.com/facebook/react/issues/10309#issuecomment-318433235 * - * @param {Component} component Component - * @param {Element} target DOM node into which element should be rendered + * @param {WPElement} child Any renderable child, such as an element, + * string, or fragment. + * @param {HTMLElement} container DOM node into which element should be rendered. */ export { createPortal }; /** - * Finds the dom node of a React component + * Finds the dom node of a React component. * - * @param {Component} component component's instance - * @param {Element} target DOM node into which element should be rendered + * @param {WPComponent} component Component's instance. */ export { findDOMNode }; /** * Renders a given element into the target DOM node. * - * @param {WPElement} element Element to render - * @param {Element} target DOM node into which element should be rendered + * @param {WPElement} element Element to render. + * @param {HTMLElement} target DOM node into which element should be rendered. */ export { render }; diff --git a/packages/element/src/react.js b/packages/element/src/react.js index 561776257f388e..b24f30c5fbafbb 100644 --- a/packages/element/src/react.js +++ b/packages/element/src/react.js @@ -28,6 +28,24 @@ import { } from 'react'; import { isString } from 'lodash'; +/** + * Object containing a React element. + * + * @typedef {import('react').ReactElement} WPElement + */ + +/** + * Object containing a React component. + * + * @typedef {import('react').Component} WPComponent + */ + +/** + * Object containing a React synthetic event. + * + * @typedef {import('react').SyntheticEvent} WPSyntheticEvent + */ + /** * Object that provides utilities for dealing with React children. */ @@ -63,8 +81,8 @@ export { createContext }; * * @param {?(string|Function)} type Tag name or element creator * @param {Object} props Element properties, either attribute - * set to apply to DOM node or values to - * pass through to element creator + * set to apply to DOM node or values to + * pass through to element creator * @param {...WPElement} children Descendant elements * * @return {WPElement} Element. @@ -99,7 +117,7 @@ export { forwardRef }; export { Fragment }; /** - * Checks if an object is a valid WPElement + * Checks if an object is a valid WPElement. * * @param {Object} objectToCheck The object to be checked. * @@ -185,7 +203,7 @@ export { Suspense }; * @return {Array} The concatenated value. */ export function concatChildren( ...childrenArguments ) { - return childrenArguments.reduce( ( result, children, i ) => { + return childrenArguments.reduce( ( accumulator, children, i ) => { Children.forEach( children, ( child, j ) => { if ( child && 'string' !== typeof child ) { child = cloneElement( child, { @@ -193,10 +211,10 @@ export function concatChildren( ...childrenArguments ) { } ); } - result.push( child ); + accumulator.push( child ); } ); - return result; + return accumulator; }, [] ); } diff --git a/packages/element/src/test/create-interpolate-element.js b/packages/element/src/test/create-interpolate-element.js new file mode 100644 index 00000000000000..006647d6b267a3 --- /dev/null +++ b/packages/element/src/test/create-interpolate-element.js @@ -0,0 +1,245 @@ +/** + * External dependencies + */ +import TestRenderer, { act } from 'react-test-renderer'; + +/** + * Internal dependencies + */ +import { createElement, Fragment, Component } from '../react'; +import createInterpolateElement from '../create-interpolate-element'; + +describe( 'createInterpolateElement', () => { + it( 'throws an error when there is no conversion map', () => { + const testString = 'This is a string'; + expect( + () => createInterpolateElement( testString, {} ) + ).toThrow( TypeError ); + } ); + it( 'returns same string when there are no tokens in the string', () => { + const testString = 'This is a string'; + const expectedElement = <>{ testString }</>; + expect( + createInterpolateElement( + testString, + { someValue: <em /> } + ) + ).toEqual( expectedElement ); + } ); + it( 'throws an error when there is an invalid conversion map', () => { + const testString = 'This is a <someValue/> string'; + expect( + () => createInterpolateElement( + testString, + [ 'someValue', { value: 10 } ], + ) + ).toThrow( TypeError ); + } ); + it( 'throws an error when there is an invalid entry in the conversion ' + + 'map', () => { + const testString = 'This is a <item /> string and <somethingElse/>'; + expect( + () => createInterpolateElement( + testString, + { + someValue: <em />, + somethingElse: 10, + } + ) + ).toThrow( TypeError ); + } ); + it( 'returns same string when there is an non matching token in the ' + + 'string', () => { + const testString = 'This is a <non_parsed/> string'; + const expectedElement = <>{ testString }</>; + expect( + createInterpolateElement( + testString, + { someValue: <strong /> } + ) + ).toEqual( expectedElement ); + } ); + it( 'returns same string when there is spaces in the token', () => { + const testString = 'This is a <spaced token/>string'; + const expectedElement = <>{ testString }</>; + expect( + createInterpolateElement( + testString, + { 'spaced token': <em /> } + ) + ).toEqual( expectedElement ); + } ); + it( 'returns expected react element for non nested components', () => { + const testString = 'This is a string with <a>a link</a>.'; + const expectedElement = createElement( + Fragment, + null, + 'This is a string with ', + createElement( + 'a', + { href: 'https://github.com', className: 'some_class' }, + 'a link' + ), + '.' + ); + const component = createInterpolateElement( + testString, + { + // eslint-disable-next-line jsx-a11y/anchor-has-content + a: <a href={ 'https://github.com' } className={ 'some_class' } />, + } + ); + expect( + JSON.stringify( component ) + ).toEqual( + JSON.stringify( expectedElement ) + ); + } ); + it( 'returns expected react element for nested components', () => { + const testString = 'This is a <a>string that is <em>linked</em></a>.'; + const expectedElement = createElement( + Fragment, + {}, + 'This is a ', + createElement( + 'a', + null, + 'string that is ', + createElement( + 'em', + null, + 'linked' + ), + ), + '.' + ); + expect( JSON.stringify( createInterpolateElement( + testString, + { + a: createElement( 'a' ), + em: <em />, + } + ) ) ).toEqual( JSON.stringify( expectedElement ) ); + } ); + it( 'returns expected output for a custom component with children ' + + 'replacement', () => { + const TestComponent = ( props ) => { + return <div { ...props } >{ props.children }</div>; + }; + const testString = 'This is a string with a <TestComponent>Custom Component</TestComponent>'; + const expectedElement = createElement( + Fragment, + null, + 'This is a string with a ', + createElement( + TestComponent, + null, + 'Custom Component' + ), + ); + expect( JSON.stringify( createInterpolateElement( + testString, + { + TestComponent: <TestComponent />, + } + ) ) ).toEqual( JSON.stringify( expectedElement ) ); + } ); + it( 'returns expected output for self closing custom component', () => { + const TestComponent = ( props ) => { + return <div { ...props } />; + }; + const testString = 'This is a string with a self closing custom component: <TestComponent/>'; + const expectedElement = createElement( + Fragment, + null, + 'This is a string with a self closing custom component: ', + createElement( TestComponent ), + ); + expect( JSON.stringify( createInterpolateElement( + testString, + { + TestComponent: <TestComponent />, + } + ) ) ).toEqual( JSON.stringify( expectedElement ) ); + } ); + it( 'throws an error with an invalid element in the conversion map', () => { + const test = () => ( + createInterpolateElement( 'This is a <invalid /> string', { invalid: 10 } ) + ); + expect( test ).toThrow( TypeError ); + } ); + it( 'returns expected output for complex replacement', () => { + class TestComponent extends Component { + render( props ) { + return <div { ...props } />; + } + } + const testString = 'This is a complex string with ' + + 'a <a1>nested <em1>emphasized string</em1> link</a1> and value: <TestComponent/>'; + const expectedElement = createElement( + Fragment, + null, + 'This is a complex string with a ', + createElement( + 'a', + null, + 'nested ', + createElement( + 'em', + null, + 'emphasized string' + ), + ' link', + ), + ' and value: ', + createElement( TestComponent ), + ); + expect( JSON.stringify( createInterpolateElement( + testString, + { + TestComponent: <TestComponent />, + em1: <em />, + a1: createElement( 'a' ), + } + ) ) ).toEqual( JSON.stringify( expectedElement ) ); + } ); + it( 'renders expected components across renders for keys in use', () => { + const TestComponent = ( { switchKey } ) => { + const elementConfig = switchKey ? { item: <em /> } : { item: <strong /> }; + return <div> + { createInterpolateElement( 'This is a <item>string!</item>', elementConfig ) } + </div>; + }; + let renderer; + act( () => { + renderer = TestRenderer.create( <TestComponent switchKey={ true } /> ); + } ); + expect( () => renderer.root.findByType( 'em' ) ).not.toThrow(); + expect( () => renderer.root.findByType( 'strong' ) ).toThrow(); + act( () => { + renderer.update( <TestComponent switchKey={ false } /> ); + } ); + expect( () => renderer.root.findByType( 'strong' ) ).not.toThrow(); + expect( () => renderer.root.findByType( 'em' ) ).toThrow(); + } ); + it( 'handles parsing emojii correctly', () => { + const testString = '👳‍♀️<icon>🚨🤷‍♂️⛈️fully</icon> here'; + const expectedElement = createElement( + Fragment, + null, + '👳‍♀️', + createElement( + 'strong', + null, + '🚨🤷‍♂️⛈️fully', + ), + ' here', + ); + expect( JSON.stringify( createInterpolateElement( + testString, + { + icon: <strong />, + } + ) ) ).toEqual( JSON.stringify( expectedElement ) ); + } ); +} ); diff --git a/packages/element/src/test/platform.js b/packages/element/src/test/platform.js new file mode 100644 index 00000000000000..fc98f4e19b22a9 --- /dev/null +++ b/packages/element/src/test/platform.js @@ -0,0 +1,19 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; +/** + * Internal dependencies + */ +import Platform from '../platform'; + +describe( 'Platform', () => { + it( 'is chooses the right thing', () => { + const element = Platform.select( { + web: shallow( <div></div> ), + native: shallow( <button></button> ), + } ); + + expect( element.type() ).toBe( 'div' ); + } ); +} ); diff --git a/packages/element/src/test/platform.native.js b/packages/element/src/test/platform.native.js new file mode 100644 index 00000000000000..da6717d66409e0 --- /dev/null +++ b/packages/element/src/test/platform.native.js @@ -0,0 +1,19 @@ +/** + * External dependencies + */ +import { shallow } from 'enzyme'; +/** + * Internal dependencies + */ +import Platform from '../platform'; + +describe( 'Platform', () => { + it( 'is chooses the right thing', () => { + const element = Platform.select( { + web: shallow( <div></div> ), + native: shallow( <button></button> ), + } ); + + expect( element.type() ).toBe( 'button' ); + } ); +} ); diff --git a/packages/env/.npmrc b/packages/env/.npmrc new file mode 100644 index 00000000000000..43c97e719a5a82 --- /dev/null +++ b/packages/env/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/packages/env/README.md b/packages/env/README.md index 45736ee1fa29bb..681f788c671bd6 100644 --- a/packages/env/README.md +++ b/packages/env/README.md @@ -7,7 +7,7 @@ ```sh $ npm -g i @wordpress/env -$ cd path/to/gutenberg # WordPress install will be in path/to/gutenberg-wordpress. +$ cd path/to/plugin-or-theme # WordPress install will be in path/to/plugin-or-theme-wordpress. $ wp-env --help @@ -15,11 +15,13 @@ wp-env <command> Commands: wp-env start [ref] Starts WordPress for development on port 8888 - (​http://localhost:8888​) and tests on port 8889 - (​http://localhost:8889​). If the current working + (​http://localhost:8888​) (override with + WP_ENV_PORT) and tests on port 8889 + (​http://localhost:8889​) (override with + WP_ENV_TESTS_PORT). If the current working directory is a plugin and/or has e2e-tests with plugins and/or mu-plugins, they will be mounted - appropiately. + appropriately. wp-env stop Stops running WordPress for development and tests and frees the ports. wp-env clean [environment] Cleans the WordPress databases. @@ -34,10 +36,11 @@ Options: ```sh wp-env start [ref] -Starts WordPress for development on port 8888 (​http://localhost:8888​) and -tests on port 8889 (​http://localhost:8889​). If the current working directory -is a plugin and/or has e2e-tests with plugins and/or mu-plugins, they will be -mounted appropiately. +Starts WordPress for development on port 8888 (​http://localhost:8888​) +(override with WP_ENV_PORT) and tests on port 8889 (​http://localhost:8889​) +(override with WP_ENV_TESTS_PORT). If the current working directory is a plugin +and/or has e2e-tests with plugins and/or mu-plugins, they will be mounted +appropriately. Positionals: ref A `https://github.com/WordPress/WordPress` git repo branch or commit for @@ -63,3 +66,29 @@ Positionals: environment Which environments' databases to clean. [string] [choices: "all", "development", "tests"] [default: "tests"] ``` + +<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p> + +## Additional Configuration and Running with Multiple Plugins/Themes + +`wp-env` also supports a configuration file. At the moment, this is only used for loading extra themes and plugins that you may be developing together with your main one. The script will attach the specified theme and plugin directories as volumes on the docker containers so that changes you make to them exist in the WordPress instance. + +### Example: + +`wp-env.json` +```json +{ + "themes": [ + "../path/to/theme/dir" + ], + "plugins": [ + "../path/to/plugin/dir" + ] +} +``` + +### Caveats: + +The file should be located in the same directory from which you run `wp-env` commands for a project. So if you are running `wp-env` in the root directory of a plugin, `wp-env.json` should also be located there. + +Each item in the `themes` or `plugins` array should be an absolute or relative path to the root of a different theme or plugin directory. Relative paths will be resolved from the current working directory, which means they will be resolved from the location of the `wp-env.json` file. diff --git a/packages/env/lib/cli.js b/packages/env/lib/cli.js index 6f134afd9ae421..bf7168b706652b 100644 --- a/packages/env/lib/cli.js +++ b/packages/env/lib/cli.js @@ -46,10 +46,10 @@ module.exports = function cli() { chalk`Starts WordPress for development on port {bold.underline ${ terminalLink( '8888', 'http://localhost:8888' - ) }} and tests on port {bold.underline ${ terminalLink( + ) }} (override with WP_ENV_PORT) and tests on port {bold.underline ${ terminalLink( '8889', 'http://localhost:8889' - ) }}. If the current working directory is a plugin and/or has e2e-tests with plugins and/or mu-plugins, they will be mounted appropiately.` + ) }} (override with WP_ENV_TESTS_PORT). If the current working directory is a plugin and/or has e2e-tests with plugins and/or mu-plugins, they will be mounted appropriately.` ), ( args ) => { args.positional( 'ref', { diff --git a/packages/env/lib/create-docker-compose-config.js b/packages/env/lib/create-docker-compose-config.js index 7a3dfc7d33b6aa..27c780309c771c 100644 --- a/packages/env/lib/create-docker-compose-config.js +++ b/packages/env/lib/create-docker-compose-config.js @@ -1,19 +1,25 @@ module.exports = function createDockerComposeConfig( - pluginPath, - pluginName, - pluginTestsPath + cwdTestsPath, + context, + dependencies ) { + const { path: cwd, pathBasename: cwdName } = context; + + const dependencyMappings = [ ...dependencies, context ] + .map( + ( { path, pathBasename, type } ) => + ` - ${ path }/:/var/www/html/wp-content/${ type }s/${ pathBasename }/\n` + ) + .join( '' ); const commonVolumes = ` - - ${ pluginPath }/:/var/www/html/wp-content/plugins/${ pluginName }/ - - ${ pluginPath }${ pluginTestsPath }/e2e-tests/mu-plugins/:/var/www/html/wp-content/mu-plugins/ - - ${ pluginPath }${ pluginTestsPath }/e2e-tests/plugins/:/var/www/html/wp-content/plugins/${ pluginName }-test-plugins/`; +${ dependencyMappings } + - ${ cwd }${ cwdTestsPath }/e2e-tests/mu-plugins/:/var/www/html/wp-content/mu-plugins/ + - ${ cwd }${ cwdTestsPath }/e2e-tests/plugins/:/var/www/html/wp-content/plugins/${ cwdName }-test-plugins/`; const volumes = ` - - ${ pluginPath }/../${ pluginName }-wordpress/:/var/www/html/${ commonVolumes }`; + - ${ cwd }/../${ cwdName }-wordpress/:/var/www/html/${ commonVolumes }`; const testsVolumes = ` - - tests-wordpress:/var/www/html/${ commonVolumes }`; - return `version: '2' -volumes: - tests-wordpress: + - ${ cwd }/../${ cwdName }-tests-wordpress/:/var/www/html/${ commonVolumes }`; + return `version: '2.1' services: mysql: environment: @@ -28,7 +34,7 @@ services: WORDPRESS_DB_PASSWORD: password image: wordpress ports: - - 8888:80 + - \${WP_ENV_PORT:-8888}:80 volumes:${ volumes } wordpress-cli: depends_on: @@ -44,7 +50,7 @@ services: WORDPRESS_DB_PASSWORD: password image: wordpress ports: - - 8889:80 + - \${WP_ENV_TESTS_PORT:-8889}:80 volumes:${ testsVolumes } tests-wordpress-cli: depends_on: diff --git a/packages/env/lib/detect-context.js b/packages/env/lib/detect-context.js new file mode 100644 index 00000000000000..8e1f9551b659d3 --- /dev/null +++ b/packages/env/lib/detect-context.js @@ -0,0 +1,67 @@ +'use strict'; +/** + * External dependencies + */ +const util = require( 'util' ); +const fs = require( 'fs' ); +const stream = require( 'stream' ); +const path = require( 'path' ); + +/** + * Promisified dependencies + */ +const readDir = util.promisify( fs.readdir ); +const finished = util.promisify( stream.finished ); + +/** + * @typedef Context + * @type {Object} + * @property {string} type + * @property {string} path + * @property {string} pathBasename + */ + +/** + * Detects the context of a given path. + * + * @param {string} [directoryPath=process.cwd()] The directory to detect. Should point to a directory, defaulting to the current working directory. + * + * @return {Context} The context of the directory. If a theme or plugin, the type property will contain 'theme' or 'plugin'. + */ +module.exports = async function detectContext( directoryPath = process.cwd() ) { + const context = {}; + + // Use absolute paths to files so that we can properly read + // dependencies not in the current working directory. + const absPath = path.resolve( directoryPath ); + + // Race multiple file read streams against each other until + // a plugin or theme header is found. + const files = ( await readDir( absPath ) ).filter( + ( file ) => path.extname( file ) === '.php' || path.basename( file ) === 'style.css' + ).map( ( fileName ) => path.join( absPath, fileName ) ); + + const streams = []; + for ( const file of files ) { + const fileStream = fs.createReadStream( file, 'utf8' ); + fileStream.on( 'data', ( text ) => { + const [ , type ] = text.match( /(Plugin|Theme) Name: .*[\r\n]/ ) || []; + if ( type ) { + context.type = type.toLowerCase(); + context.path = absPath; + context.pathBasename = path.basename( absPath ); + + // Stop the creation of new streams by mutating the iterated array. We can't `break`, because we are inside a function. + files.splice( 0 ); + fileStream.destroy(); + streams.forEach( ( otherFileStream ) => otherFileStream.destroy() ); + } + } ); + streams.push( fileStream ); + } + await Promise.all( + streams.map( ( fileStream ) => finished( fileStream ).catch( () => {} ) ) + ); + + return context; +}; diff --git a/packages/env/lib/env.js b/packages/env/lib/env.js index 159bb2ac2fc45f..a2f9e8ac035179 100644 --- a/packages/env/lib/env.js +++ b/packages/env/lib/env.js @@ -2,21 +2,29 @@ /** * External dependencies */ +const util = require( 'util' ); const path = require( 'path' ); const fs = require( 'fs' ); const dockerCompose = require( 'docker-compose' ); const NodeGit = require( 'nodegit' ); -const wait = require( 'util' ).promisify( setTimeout ); /** * Internal dependencies */ const createDockerComposeConfig = require( './create-docker-compose-config' ); +const detectContext = require( './detect-context' ); +const resolveDependencies = require( './resolve-dependencies' ); + +/** + * Promisified dependencies + */ +const copyDir = util.promisify( require( 'copy-dir' ) ); +const wait = util.promisify( setTimeout ); // Config Variables -const pluginPath = process.cwd(); -const pluginName = path.basename( pluginPath ); -const pluginTestsPath = fs.existsSync( './packages' ) ? '/packages' : ''; +const cwd = process.cwd(); +const cwdName = path.basename( cwd ); +const cwdTestsPath = fs.existsSync( './packages' ) ? '/packages' : ''; const dockerComposeOptions = { config: path.join( __dirname, 'docker-compose.yml' ), }; @@ -30,18 +38,23 @@ const wpCliRun = ( command, isTests = false ) => ); const setupSite = ( isTests = false ) => wpCliRun( - `wp core install --url=localhost:888${ - isTests ? '9' : '8' - } --title=Gutenberg --admin_user=admin --admin_password=password --admin_email=admin@wordpress.org`, + `wp core install --url=localhost:${ + isTests ? + process.env.WP_ENV_TESTS_PORT || 8889 : + process.env.WP_ENV_PORT || 8888 + } --title=${ cwdName } --admin_user=admin --admin_password=password --admin_email=admin@wordpress.org`, isTests ); -const activatePlugin = ( isTests = false ) => - wpCliRun( `wp plugin activate ${ pluginName }`, isTests ); +const activateContext = ( { type, pathBasename }, isTests = false ) => + wpCliRun( `wp ${ type } activate ${ pathBasename }`, isTests ); const resetDatabase = ( isTests = false ) => wpCliRun( 'wp db reset --yes', isTests ); module.exports = { async start( { ref, spinner = {} } ) { + const context = await detectContext(); + const dependencies = await resolveDependencies(); + spinner.text = `Downloading WordPress@${ ref } 0/100%.`; const gitFetchOptions = { fetchOpts: { @@ -62,7 +75,7 @@ module.exports = { }; // Clone or get the repo. - const repoPath = `../${ pluginName }-wordpress/`; + const repoPath = `../${ cwdName }-wordpress/`; const repo = await NodeGit.Clone( 'https://github.com/WordPress/WordPress.git', repoPath, @@ -90,12 +103,37 @@ module.exports = { // Some commit refs need to be set as detached. await repo.setHeadDetached( ref ); } + + // Duplicate repo for the tests container. + let stashed = true; // Stash to avoid copying config changes. + try { + await NodeGit.Stash.save( + repo, + await NodeGit.Signature.default( repo ), + null, + NodeGit.Stash.FLAGS.INCLUDE_UNTRACKED + ); + } catch ( err ) { + stashed = false; + } + await copyDir( repoPath, `../${ cwdName }-tests-wordpress/`, { + filter: ( stat, filepath ) => + stat !== 'symbolicLink' && + ( stat !== 'directory' || + ( filepath !== `${ repoPath }.git` && + ! filepath.endsWith( 'node_modules' ) ) ), + } ); + if ( stashed ) { + try { + await NodeGit.Stash.pop( repo, 0 ); + } catch ( err ) {} + } spinner.text = `Downloading WordPress@${ ref } 100/100%.`; - spinner.text = `Installing WordPress@${ ref }.`; + spinner.text = `Starting WordPress@${ ref }.`; fs.writeFileSync( dockerComposeOptions.config, - createDockerComposeConfig( pluginPath, pluginName, pluginTestsPath ) + createDockerComposeConfig( cwdTestsPath, context, dependencies ) ); // These will bring up the database container, @@ -119,11 +157,15 @@ module.exports = { .catch( retryableSiteSetup ) .catch( retryableSiteSetup ); - await Promise.all( [ activatePlugin(), activatePlugin( true ) ] ); + await Promise.all( [ + activateContext( context ), + activateContext( context, true ), + ...dependencies.map( activateContext ), + ] ); // Remove dangling containers and finish. await dockerCompose.rm( dockerComposeOptions ); - spinner.text = `Installed WordPress@${ ref }.`; + spinner.text = `Started WordPress@${ ref }.`; }, async stop( { spinner = {} } ) { @@ -133,6 +175,11 @@ module.exports = { }, async clean( { environment, spinner } ) { + const context = await detectContext(); + const dependencies = await resolveDependencies(); + const activateDependencies = () => + Promise.all( dependencies.map( activateContext ) ); + const description = `${ environment } environment${ environment === 'all' ? 's' : '' }`; @@ -144,7 +191,8 @@ module.exports = { tasks.push( resetDatabase() .then( setupSite ) - .then( activatePlugin ) + .then( activateContext.bind( null, context ) ) + .then( activateDependencies ) .catch( () => {} ) ); } @@ -152,7 +200,7 @@ module.exports = { tasks.push( resetDatabase( true ) .then( setupSite.bind( null, true ) ) - .then( activatePlugin.bind( null, true ) ) + .then( activateContext.bind( null, context, true ) ) .catch( () => {} ) ); } diff --git a/packages/env/lib/resolve-dependencies.js b/packages/env/lib/resolve-dependencies.js new file mode 100644 index 00000000000000..b812f6a6213041 --- /dev/null +++ b/packages/env/lib/resolve-dependencies.js @@ -0,0 +1,50 @@ +'use strict'; + +/** + * External dependencies + */ +const util = require( 'util' ); +const fs = require( 'fs' ); + +/** + * Internal dependencies + */ +const detectContext = require( './detect-context' ); + +/** + * Promisified dependencies + */ +const readFile = util.promisify( fs.readFile ); + +/** + * Returns an array of dependencies to be mounted in the Docker image. + * + * Reads from the wp-env.json file in the current directory and uses detect + * context to make sure the specified dependencies exist and are plugins + * and/or themes. + * + * @return {Array<detectContext.Context>} An array of dependencies in the context format. + */ +module.exports = async function resolveDependencies() { + let envFile; + try { + envFile = await readFile( './wp-env.json' ); + } catch ( error ) { + return []; + } + + const { themes, plugins } = JSON.parse( envFile ); + + const dependencyResolvers = []; + if ( Array.isArray( themes ) ) { + dependencyResolvers.push( ...themes.map( detectContext ) ); + } + + if ( Array.isArray( plugins ) ) { + dependencyResolvers.push( ...plugins.map( detectContext ) ); + } + + // Return all dependencies which have been detected to be a plugin or a theme. + const dependencies = await Promise.all( dependencyResolvers ); + return dependencies.filter( ( { type } ) => !! type ); +}; diff --git a/packages/env/package.json b/packages/env/package.json index 6be0539b3725cf..d37a159e64141d 100644 --- a/packages/env/package.json +++ b/packages/env/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/env", - "version": "1.0.0", + "version": "0.1.0", "description": "A zero-config, self contained local WordPress environment for development and testing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -33,6 +33,7 @@ }, "dependencies": { "chalk": "^2.4.2", + "copy-dir": "^1.2.0", "docker-compose": "^0.22.2", "nodegit": "^0.26.2", "ora": "^4.0.2", diff --git a/packages/env/tests/cli.test.js b/packages/env/test/cli.js similarity index 100% rename from packages/env/tests/cli.test.js rename to packages/env/test/cli.js diff --git a/packages/escape-html/README.md b/packages/escape-html/README.md index 764a6052d7e79c..6f112276748131 100644 --- a/packages/escape-html/README.md +++ b/packages/escape-html/README.md @@ -64,6 +64,20 @@ _Returns_ - `string`: Escaped attribute value. +<a name="escapeEditableHTML" href="#escapeEditableHTML">#</a> **escapeEditableHTML** + +Returns an escaped Editable HTML element value. This is different from +`escapeHTML`, because for editable HTML, ALL ampersands must be escaped in +order to render the content correctly on the page. + +_Parameters_ + +- _value_ `string`: Element value. + +_Returns_ + +- `string`: Escaped HTML element value. + <a name="escapeHTML" href="#escapeHTML">#</a> **escapeHTML** Returns an escaped HTML element value. diff --git a/packages/escape-html/package.json b/packages/escape-html/package.json index 2b3c55a0f80e93..410cedaf80d58d 100644 --- a/packages/escape-html/package.json +++ b/packages/escape-html/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/escape-html", - "version": "1.5.0", + "version": "1.6.0", "description": "Escape HTML utils.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -19,6 +19,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "sideEffects": false, "dependencies": { "@babel/runtime": "^7.4.4" }, diff --git a/packages/escape-html/src/index.js b/packages/escape-html/src/index.js index d31f43f63f70bb..8b72830a74c4b9 100644 --- a/packages/escape-html/src/index.js +++ b/packages/escape-html/src/index.js @@ -96,6 +96,19 @@ export function escapeHTML( value ) { return escapeLessThan( escapeAmpersand( value ) ); } +/** + * Returns an escaped Editable HTML element value. This is different from + * `escapeHTML`, because for editable HTML, ALL ampersands must be escaped in + * order to render the content correctly on the page. + * + * @param {string} value Element value. + * + * @return {string} Escaped HTML element value. + */ +export function escapeEditableHTML( value ) { + return escapeLessThan( value.replace( /&/g, '&amp;' ) ); +} + /** * Returns true if the given attribute name is valid, or false otherwise. * diff --git a/packages/escape-html/src/test/index.js b/packages/escape-html/src/test/index.js index 9558c58c7e23ab..2c564a771f4bdc 100644 --- a/packages/escape-html/src/test/index.js +++ b/packages/escape-html/src/test/index.js @@ -8,6 +8,7 @@ import { escapeAttribute, escapeHTML, isValidAttributeName, + escapeEditableHTML, } from '../'; import __unstableEscapeGreaterThan from '../escape-greater'; @@ -94,3 +95,11 @@ describe( 'isValidAttributeName', () => { expect( result ).toBe( true ); } ); } ); + +describe( 'escapeEditableHTML', () => { + it( 'should escape < and all ampersands', () => { + const result = escapeEditableHTML( '<a href="https://w.org">WP</a> & &lt;strong&gt;' ); + + expect( result ).toBe( '&lt;a href="https://w.org">WP&lt;/a> &amp; &amp;lt;strong&amp;gt;' ); + } ); +} ); diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 09d7afafa006ab..1f31e8f490ae40 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,3 +1,9 @@ +## Master + +### Bug Fixes + +- The React ruleset now correctly references the WordPress ESLint plugin, resolving an error about an unfound rule. + ## 3.0.0 (2019-08-29) ### Breaking Changes diff --git a/packages/eslint-plugin/configs/jsdoc.js b/packages/eslint-plugin/configs/jsdoc.js index 614816cb734a21..bcc4cc42fb4070 100644 --- a/packages/eslint-plugin/configs/jsdoc.js +++ b/packages/eslint-plugin/configs/jsdoc.js @@ -1,5 +1,41 @@ +/** + * External dependencies + */ const globals = require( 'globals' ); +/** + * The temporary list of types defined in Gutenberg which are whitelisted to avoid + * ESLint warnings. It should be removed once importing is going to be implemented + * in the tool which generates public APIs from JSDoc comments. Related issue to + * fix the root cause `@wordpress/docgen`: + * https://github.com/WordPress/gutenberg/issues/18045. + */ +const temporaryWordPressInternalTypes = [ + 'WPBlockChildren', + 'WPBlockNode', + 'WPBlockSelection', + 'WPBlockSerializationOptions', + 'WPBlock', + 'WPBlockPattern', + 'WPBlockTypeIcon', + 'WPBlockTypeIconRender', + 'WPBlockTypeIconDescriptor', + 'WPComponent', + 'WPElement', + 'WPIcon', +]; + +/** + * The temporary list of external types used in Gutenberg which are whitelisted + * to avoid ESLint warnings. It's similar to `wordpressInternalTypes` and it + * should be removed once the related issues is fixed: + * https://github.com/WordPress/gutenberg/issues/18045 + */ +const temporaryExternalTypes = [ + 'DOMHighResTimeStamp', + 'espree', +]; + /** * Helpful utilities that are globally defined and known to the TypeScript compiler. * @@ -50,6 +86,8 @@ module.exports = { // generally refer to window-level event listeners and are not a valid type to reference (e.g. `onclick`). ...Object.keys( globals.browser ).filter( ( k ) => /^[A-Z]/.test( k ) ), ...typescriptUtilityTypes, + ...temporaryWordPressInternalTypes, + ...temporaryExternalTypes, 'void', ], } ], diff --git a/packages/eslint-plugin/configs/react.js b/packages/eslint-plugin/configs/react.js index 20d0ccfc797b84..8541cb096cac44 100644 --- a/packages/eslint-plugin/configs/react.js +++ b/packages/eslint-plugin/configs/react.js @@ -8,6 +8,7 @@ module.exports = { }, }, plugins: [ + '@wordpress', 'react', 'react-hooks', ], diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 2846c228cd64eb..41d921106eaa5d 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/eslint-plugin", - "version": "3.1.0", + "version": "3.2.0", "description": "ESLint plugin for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/format-library/package.json b/packages/format-library/package.json index 50605fb08b7508..761e5097a2c7ce 100644 --- a/packages/format-library/package.json +++ b/packages/format-library/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/format-library", - "version": "1.9.0", + "version": "1.10.0", "description": "Format library for the WordPress editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/format-library/src/image/index.js b/packages/format-library/src/image/index.js index 15cea7ecee4e33..c7a46a804b6367 100644 --- a/packages/format-library/src/image/index.js +++ b/packages/format-library/src/image/index.js @@ -3,11 +3,10 @@ */ import { Path, SVG, TextControl, Popover, IconButton } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { Component, useMemo } from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { insertObject } from '@wordpress/rich-text'; import { MediaUpload, RichTextToolbarButton, MediaUploadCheck } from '@wordpress/block-editor'; import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; -import { computeCaretRect } from '@wordpress/dom'; const ALLOWED_MEDIA_TYPES = [ 'image' ]; @@ -16,16 +15,10 @@ const title = __( 'Inline image' ); const stopKeyPropagation = ( event ) => event.stopPropagation(); -const PopoverAtImage = ( { dependencies, ...props } ) => { - return ( - <Popover - position="bottom center" - focusOnMount={ false } - anchorRect={ useMemo( () => computeCaretRect(), dependencies ) } - { ...props } - /> - ); -}; +function getRange() { + const selection = window.getSelection(); + return selection.rangeCount ? selection.getRangeAt( 0 ) : null; +} export const image = { name, @@ -93,7 +86,6 @@ export const image = { render() { const { value, onChange, isObjectActive, activeObjectAttributes } = this.props; - const { style } = activeObjectAttributes; return ( <MediaUploadCheck> @@ -124,10 +116,10 @@ export const image = { } } /> } { isObjectActive && - <PopoverAtImage - // Reposition Popover when the selection changes or - // when the width changes. - dependencies={ [ style, value.start ] } + <Popover + position="bottom center" + focusOnMount={ false } + anchorRef={ getRange() } > { // Disable reason: KeyPress must be suppressed so the block doesn't hide the toolbar /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ } @@ -165,7 +157,7 @@ export const image = { <IconButton icon="editor-break" label={ __( 'Apply' ) } type="submit" /> </form> { /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ } - </PopoverAtImage> + </Popover> } </MediaUploadCheck> ); diff --git a/packages/format-library/src/image/style.scss b/packages/format-library/src/image/style.scss index 93bfc96ae7a166..5209377f6448e4 100644 --- a/packages/format-library/src/image/style.scss +++ b/packages/format-library/src/image/style.scss @@ -2,8 +2,11 @@ display: flex; .components-icon-button { - height: $icon-button-size + $grid-size + $grid-size; align-self: flex-end; + height: $grid-size * 4 - ($border-width * 2); + margin-bottom: $grid-size; + margin-right: $grid-size; + padding: 0 6px; } } @@ -15,7 +18,13 @@ min-width: 150px; max-width: 500px; - &.components-base-control .components-base-control__field { - margin-bottom: 0; + &.components-base-control { + .components-base-control__field { + margin-bottom: 0; + } + + .components-base-control__label { + display: block; + } } } diff --git a/packages/format-library/src/link/index.native.js b/packages/format-library/src/link/index.native.js index a804ebf1a15db5..5f0ad03034d2f3 100644 --- a/packages/format-library/src/link/index.native.js +++ b/packages/format-library/src/link/index.native.js @@ -2,6 +2,7 @@ * External dependencies */ import { find } from 'lodash'; +import { Clipboard } from 'react-native'; /** * WordPress dependencies @@ -43,6 +44,7 @@ export const link = { this.addLink = this.addLink.bind( this ); this.stopAddingLink = this.stopAddingLink.bind( this ); this.onRemoveFormat = this.onRemoveFormat.bind( this ); + this.getURLFromClipboard = this.getURLFromClipboard.bind( this ); this.state = { addingLink: false, }; @@ -56,6 +58,7 @@ export const link = { onChange( applyFormat( value, { type: name, attributes: { url: text } } ) ); } else { this.setState( { addingLink: true } ); + this.getURLFromClipboard(); } } @@ -108,10 +111,25 @@ export const link = { speak( __( 'Link removed.' ), 'assertive' ); } + async getURLFromClipboard() { + const clipboardText = await Clipboard.getString(); + if ( ! clipboardText ) { + return; + } + // Check if pasted text is URL + if ( ! isURL( clipboardText ) ) { + return; + } + this.setState( { clipboardURL: clipboardText } ); + } + render() { const { isActive, activeAttributes, onChange } = this.props; const linkSelection = this.getLinkSelection(); - + // If no URL is set and we have a clipboard URL let's use it + if ( ! activeAttributes.url && this.state.clipboardURL ) { + activeAttributes.url = this.state.clipboardURL; + } return ( <> <ModalLinkUI diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index bf1784db2132d7..34c05ad432c0cf 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -8,7 +8,6 @@ import { withSpokenMessages, } from '@wordpress/components'; import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; -import { getRectangleFromRange } from '@wordpress/dom'; import { prependHTTP } from '@wordpress/url'; import { create, @@ -32,15 +31,17 @@ function isShowingInput( props, state ) { } const URLPopoverAtLink = ( { isActive, addingLink, value, ...props } ) => { - const anchorRect = useMemo( () => { + const anchorRef = useMemo( () => { const selection = window.getSelection(); - const range = selection.rangeCount > 0 ? selection.getRangeAt( 0 ) : null; - if ( ! range ) { + + if ( ! selection.rangeCount ) { return; } + const range = selection.getRangeAt( 0 ); + if ( addingLink ) { - return getRectangleFromRange( range ); + return range; } let element = range.startContainer; @@ -52,17 +53,14 @@ const URLPopoverAtLink = ( { isActive, addingLink, value, ...props } ) => { element = element.parentNode; } - const closest = element.closest( 'a' ); - if ( closest ) { - return closest.getBoundingClientRect(); - } + return element.closest( 'a' ); }, [ isActive, addingLink, value.start, value.end ] ); - if ( ! anchorRect ) { + if ( ! anchorRef ) { return null; } - return <URLPopover anchorRect={ anchorRect } { ...props } />; + return <URLPopover anchorRef={ anchorRef } { ...props } />; }; class InlineLinkUI extends Component { diff --git a/packages/i18n/package.json b/packages/i18n/package.json index a09c6ab07cff6e..85996bcc155917 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/i18n", - "version": "3.6.0", + "version": "3.7.0", "description": "WordPress internationalization (i18n) library.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/is-shallow-equal/package.json b/packages/is-shallow-equal/package.json index e1bc54ac95eecb..a164a7cdbb44f8 100644 --- a/packages/is-shallow-equal/package.json +++ b/packages/is-shallow-equal/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/is-shallow-equal", - "version": "1.6.0", + "version": "1.6.1", "description": "Test for shallow equality between two objects or arrays.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -25,6 +25,7 @@ "objects.js" ], "main": "index.js", + "sideEffects": false, "dependencies": { "@babel/runtime": "^7.4.4" }, diff --git a/packages/jest-console/package.json b/packages/jest-console/package.json index 1d5a24df52e877..04a4386991a06e 100644 --- a/packages/jest-console/package.json +++ b/packages/jest-console/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-console", - "version": "3.3.0", + "version": "3.4.0", "description": "Custom Jest matchers for the Console object.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/jest-preset-default/CHANGELOG.md b/packages/jest-preset-default/CHANGELOG.md index b99f7bbcdbbb6e..e6c461bfd5b326 100644 --- a/packages/jest-preset-default/CHANGELOG.md +++ b/packages/jest-preset-default/CHANGELOG.md @@ -1,5 +1,3 @@ -## Master - ## 5.1.0 (2019-09-03) ### Bug Fixes diff --git a/packages/jest-preset-default/jest-preset.js b/packages/jest-preset-default/jest-preset.js new file mode 100644 index 00000000000000..badc6fc70efb9b --- /dev/null +++ b/packages/jest-preset-default/jest-preset.js @@ -0,0 +1,31 @@ +module.exports = { + moduleNameMapper: { + '\\.(scss|css)$': + '<rootDir>/node_modules/@wordpress/jest-preset-default/scripts/style-mock.js', + }, + modulePaths: [ '<rootDir>' ], + setupFiles: [ + '<rootDir>/node_modules/@wordpress/jest-preset-default/scripts/setup-globals.js', + ], + setupFilesAfterEnv: [ + '<rootDir>/node_modules/@wordpress/jest-preset-default/scripts/setup-test-framework.js', + ], + snapshotSerializers: [ '<rootDir>/node_modules/enzyme-to-json/serializer.js' ], + testMatch: [ + '**/__tests__/**/*.[jt]s', + '**/test/*.[jt]s', + '**/?(*.)test.[jt]s', + ], + testPathIgnorePatterns: [ '/node_modules/', '/wordpress/' ], + timers: 'fake', + transform: { + '^.+\\.[jt]sx?$': '<rootDir>/node_modules/babel-jest', + }, + verbose: true, + reporters: + 'TRAVIS' in process.env && 'CI' in process.env ? + [ + '../../../@wordpress/jest-preset-default/scripts/travis-fold-passes-reporter.js', + ] : + undefined, +}; diff --git a/packages/jest-preset-default/jest-preset.json b/packages/jest-preset-default/jest-preset.json deleted file mode 100644 index 54b62de584fcc9..00000000000000 --- a/packages/jest-preset-default/jest-preset.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "moduleNameMapper": { - "\\.(scss|css)$": "<rootDir>/node_modules/@wordpress/jest-preset-default/scripts/style-mock.js" - }, - "modulePaths": [ - "<rootDir>" - ], - "setupFiles": [ - "<rootDir>/node_modules/@wordpress/jest-preset-default/scripts/setup-globals.js" - ], - "setupFilesAfterEnv": [ - "<rootDir>/node_modules/@wordpress/jest-preset-default/scripts/setup-test-framework.js" - ], - "snapshotSerializers": [ - "<rootDir>/node_modules/enzyme-to-json/serializer.js" - ], - "testMatch": [ - "**/__tests__/**/*.[jt]s", - "**/test/*.[jt]s", - "**/?(*.)test.[jt]s" - ], - "testPathIgnorePatterns": [ - "/node_modules/", - "/wordpress/" - ], - "timers": "fake", - "transform": { - "^.+\\.[jt]sx?$": "<rootDir>/node_modules/babel-jest" - }, - "verbose": true -} diff --git a/packages/jest-preset-default/package.json b/packages/jest-preset-default/package.json index d36b03999ba225..f463f244d3487b 100644 --- a/packages/jest-preset-default/package.json +++ b/packages/jest-preset-default/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-preset-default", - "version": "5.1.0", + "version": "5.2.0", "description": "Default Jest preset for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -31,6 +31,7 @@ ], "main": "index.js", "dependencies": { + "@jest/reporters": "^24.8.0", "@wordpress/jest-console": "file:../jest-console", "babel-jest": "^24.7.1", "enzyme": "^3.9.0", diff --git a/packages/jest-preset-default/scripts/travis-fold-passes-reporter.js b/packages/jest-preset-default/scripts/travis-fold-passes-reporter.js new file mode 100644 index 00000000000000..a621f81c916ab5 --- /dev/null +++ b/packages/jest-preset-default/scripts/travis-fold-passes-reporter.js @@ -0,0 +1,42 @@ +/** + * External dependencies + */ +const { VerboseReporter } = require( '@jest/reporters' ); + +module.exports = class TravisFoldPassesReporter extends VerboseReporter { + constructor( ...args ) { + super( ...args ); + this.foldedTestResults = []; + } + + flushFoldedTestResults() { + if ( ! this.foldedTestResults.length ) { + return; + } + + this.log( 'travis_fold:start:TravisFoldPassesReporter' ); + this.log( + `...${ this.foldedTestResults.length } passing test${ + this.foldedTestResults.length === 1 ? '' : 's' + }.` + ); + this.foldedTestResults.forEach( ( args ) => super.onTestResult( ...args ) ); + this.log( 'travis_fold:end:TravisFoldPassesReporter' ); + this.foldedTestResults = []; + } + + onTestResult( ...args ) { + const testResult = args[ 1 ]; + if ( testResult.numFailingTests === 0 && ! testResult.failureMessage ) { + this.foldedTestResults.push( args ); + } else { + this.flushFoldedTestResults(); + super.onTestResult( ...args ); + } + } + + onRunComplete( ...args ) { + this.flushFoldedTestResults(); + super.onRunComplete( ...args ); + } +}; diff --git a/packages/jest-puppeteer-axe/README.md b/packages/jest-puppeteer-axe/README.md index c28b7ed96f752c..54d904cc5a313d 100644 --- a/packages/jest-puppeteer-axe/README.md +++ b/packages/jest-puppeteer-axe/README.md @@ -37,10 +37,12 @@ test( 'checks the test page with Axe', async () => { } ); ``` -It is also possible to pass optional Axe API options to perform customized check: +It is also possible to pass optional params which allow Axe API to perform customized checks: - `include` - CSS selector(s) to to add the list of elements to include in analysis. - `exclude` - CSS selector(s) to to add the list of elements to exclude from analysis. - `disabledRules` - the list of [Axe rules](https://github.com/dequelabs/axe-core/blob/master/doc/rule-descriptions.md) to skip from verification. +- `options` - a flexible way to configure how Axe run operates. See [axe-core API documentation](https://github.com/dequelabs/axe-core/blob/master/doc/API.md#options-parameter) for information on the object structure. +- `config` - Axe configuration object. See [axe-core API documentation](https://github.com/dequelabs/axe-core/blob/master/doc/API.md#api-name-axeconfigure) for documentation on the object structure. ```js test( 'checks the test component with Axe excluding some button', async () => { @@ -52,6 +54,8 @@ test( 'checks the test component with Axe excluding some button', async () => { include: '.test-component', exclude: '.some-button', disabledRules: [ 'aria-allowed-role' ], + options: { iframes: false }, + config: { reporter: 'raw' }, } ); } ); ``` diff --git a/packages/jest-puppeteer-axe/package.json b/packages/jest-puppeteer-axe/package.json index c5af52e08507bd..ad6c4a9299c605 100644 --- a/packages/jest-puppeteer-axe/package.json +++ b/packages/jest-puppeteer-axe/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/jest-puppeteer-axe", - "version": "1.3.0", + "version": "1.4.0", "description": "Axe API integration with Jest and Puppeteer.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -30,6 +30,7 @@ "main": "build/index.js", "module": "build-module/index.js", "dependencies": { + "@babel/runtime": "^7.4.4", "axe-puppeteer": "^1.0.0" }, "peerDependencies": { diff --git a/packages/jest-puppeteer-axe/src/index.js b/packages/jest-puppeteer-axe/src/index.js index 336cec9bcf8161..8ad73875e2ad69 100644 --- a/packages/jest-puppeteer-axe/src/index.js +++ b/packages/jest-puppeteer-axe/src/index.js @@ -3,6 +3,12 @@ */ import AxePuppeteer from 'axe-puppeteer'; +/** @typedef {import('puppeteer').Page} Page */ + +/** @typedef {import('axe-core').RunOptions} RunOptions */ + +/** @typedef {import('axe-core').Spec} Spec */ + /** * Formats the list of violations object returned by Axe analysis. * @@ -54,16 +60,20 @@ function formatViolations( violations ) { * @see https://github.com/dequelabs/axe-puppeteer * * @param {Page} page Puppeteer's page instance. - * @param {?Object} params Optional Axe API options. + * @param {?Object} params Optional params that allow better control over Axe API. * @param {?string|Array} params.include CSS selector(s) to add to the list of elements * to include in analysis. * @param {?string|Array} params.exclude CSS selector(s) to add to the list of elements * to exclude from analysis. * @param {?Array} params.disabledRules The list of Axe rules to skip from verification. + * @param {?RunOptions} params.options A flexible way to configure how Axe run operates, + * see https://github.com/dequelabs/axe-core/blob/master/doc/API.md#options-parameter. + * @param {?Spec} params.config Axe configuration object, + * see https://github.com/dequelabs/axe-core/blob/master/doc/API.md#api-name-axeconfigure. * * @return {Object} A matcher object with two keys `pass` and `message`. */ -async function toPassAxeTests( page, { include, exclude, disabledRules } = {} ) { +async function toPassAxeTests( page, { include, exclude, disabledRules, options, config } = {} ) { const axe = new AxePuppeteer( page ); if ( include ) { @@ -74,10 +84,18 @@ async function toPassAxeTests( page, { include, exclude, disabledRules } = {} ) axe.exclude( exclude ); } + if ( options ) { + axe.options( options ); + } + if ( disabledRules ) { axe.disableRules( disabledRules ); } + if ( config ) { + axe.configure( config ); + } + const { violations } = await axe.analyze(); const pass = violations.length === 0; diff --git a/packages/keycodes/package.json b/packages/keycodes/package.json index dee12c4a90a3ea..a42eeb08a960af 100644 --- a/packages/keycodes/package.json +++ b/packages/keycodes/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/keycodes", - "version": "2.6.0", + "version": "2.7.0", "description": "Keycodes utilities for WordPress. Used to check for keyboard events across browsers/operating systems.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -20,6 +20,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "sideEffects": false, "dependencies": { "@babel/runtime": "^7.4.4", "@wordpress/i18n": "file:../i18n", diff --git a/packages/library-export-default-webpack-plugin/package.json b/packages/library-export-default-webpack-plugin/package.json index e9baab81656501..6edc1347ea027b 100644 --- a/packages/library-export-default-webpack-plugin/package.json +++ b/packages/library-export-default-webpack-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/library-export-default-webpack-plugin", - "version": "1.4.0", + "version": "1.5.0", "description": "Webpack plugin for exporting default property for selected libraries.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/list-reusable-blocks/package.json b/packages/list-reusable-blocks/package.json index 6df9f6a7b996a0..00abbf86ab12ad 100644 --- a/packages/list-reusable-blocks/package.json +++ b/packages/list-reusable-blocks/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/list-reusable-blocks", - "version": "1.8.0", + "version": "1.9.0", "description": "Adding Export/Import support to the reusable blocks listing.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/list-reusable-blocks/src/components/import-dropdown/index.js b/packages/list-reusable-blocks/src/components/import-dropdown/index.js index 18d1ffe7b242df..f3a12cd90a7932 100644 --- a/packages/list-reusable-blocks/src/components/import-dropdown/index.js +++ b/packages/list-reusable-blocks/src/components/import-dropdown/index.js @@ -21,7 +21,6 @@ function ImportDropdown( { onUpload } ) { contentClassName="list-reusable-blocks-import-dropdown__content" renderToggle={ ( { isOpen, onToggle } ) => ( <Button - type="button" aria-expanded={ isOpen } onClick={ onToggle } isPrimary diff --git a/packages/media-utils/package.json b/packages/media-utils/package.json index 90ecc22a60a0c6..ad9e202d20223c 100644 --- a/packages/media-utils/package.json +++ b/packages/media-utils/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/media-utils", - "version": "1.2.0", + "version": "1.3.0", "description": "WordPress Media Upload Utils.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/media-utils/src/components/media-upload/index.js b/packages/media-utils/src/components/media-upload/index.js index 0ba3db65fd43b1..eac379f9ebb68d 100644 --- a/packages/media-utils/src/components/media-upload/index.js +++ b/packages/media-utils/src/components/media-upload/index.js @@ -125,9 +125,6 @@ class MediaUpload extends Component { } else { const frameConfig = { title, - button: { - text: __( 'Select' ), - }, multiple, }; if ( !! allowedTypes ) { diff --git a/packages/notices/package.json b/packages/notices/package.json index ed582e8ca7dc50..f9dbc17f19df5d 100644 --- a/packages/notices/package.json +++ b/packages/notices/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/notices", - "version": "1.8.0", + "version": "1.9.0", "description": "State management for notices.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/notices/src/store/actions.js b/packages/notices/src/store/actions.js index f1771b6ca8ae2f..0fed89a60e0f31 100644 --- a/packages/notices/src/store/actions.js +++ b/packages/notices/src/store/actions.js @@ -8,6 +8,17 @@ import { uniqueId } from 'lodash'; */ import { DEFAULT_CONTEXT, DEFAULT_STATUS } from './constants'; +/** + * @typedef {Object} WPNoticeAction Object describing a user action option associated with a notice. + * + * @property {string} label Message to use as action label. + * @property {?string} url Optional URL of resource if action incurs + * browser navigation. + * @property {?Function} onClick Optional function to invoke when action is + * triggered by user. + * + */ + /** * Yields action objects used in signalling that a notice is to be created. * diff --git a/packages/notices/src/store/selectors.js b/packages/notices/src/store/selectors.js index 3a0bc75ad0f02a..879f745ffe06d0 100644 --- a/packages/notices/src/store/selectors.js +++ b/packages/notices/src/store/selectors.js @@ -3,6 +3,8 @@ */ import { DEFAULT_CONTEXT } from './constants'; +/** @typedef {import('./actions').WPNoticeAction} WPNoticeAction */ + /** * The default empty set of notices to return when there are no notices * assigned for a given notices context. This can occur if the getNotices @@ -38,17 +40,6 @@ const DEFAULT_NOTICES = []; * */ -/** - * @typedef {Object} WPNoticeAction Object describing a user action option associated with a notice. - * - * @property {string} label Message to use as action label. - * @property {?string} url Optional URL of resource if action incurs - * browser navigation. - * @property {?Function} onClick Optional function to invoke when action is - * triggered by user. - * - */ - /** * Returns all notices as an array, optionally for a given context. Defaults to * the global context. diff --git a/packages/nux/package.json b/packages/nux/package.json index 858ddc289df131..f358034d79dfff 100644 --- a/packages/nux/package.json +++ b/packages/nux/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/nux", - "version": "3.7.0", + "version": "3.8.0", "description": "NUX (New User eXperience) module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/nux/src/components/dot-tip/index.js b/packages/nux/src/components/dot-tip/index.js index ed8119cb0d1158..5279ef41a1a60b 100644 --- a/packages/nux/src/components/dot-tip/index.js +++ b/packages/nux/src/components/dot-tip/index.js @@ -7,13 +7,6 @@ import { __ } from '@wordpress/i18n'; import { withSelect, withDispatch } from '@wordpress/data'; import { useCallback, useRef } from '@wordpress/element'; -function getAnchorRect( anchor ) { - // The default getAnchorRect() excludes an element's top and bottom padding - // from its calculation. We want tips to point to the outer margin of an - // element, so we override getAnchorRect() to include all padding. - return anchor.parentNode.getBoundingClientRect(); -} - function onClick( event ) { // Tips are often nested within buttons. We stop propagation so that clicking // on a tip doesn't result in the button being clicked. @@ -29,13 +22,6 @@ export function DotTip( { onDisable, } ) { const anchorParent = useRef( null ); - const getAnchorRectCallback = useCallback( - ( anchor ) => { - anchorParent.current = anchor.parentNode; - return getAnchorRect( anchor ); - }, - [ anchorParent ] - ); const onFocusOutsideCallback = useCallback( ( event ) => { if ( ! anchorParent.current ) { @@ -58,7 +44,7 @@ export function DotTip( { position={ position } noArrow focusOnMount="container" - getAnchorRect={ getAnchorRectCallback } + shouldAnchorIncludePadding role="dialog" aria-label={ __( 'Editor tips' ) } onClick={ onClick } diff --git a/packages/nux/src/components/dot-tip/style.scss b/packages/nux/src/components/dot-tip/style.scss index e8e25b423830ca..cd75537de3a98b 100644 --- a/packages/nux/src/components/dot-tip/style.scss +++ b/packages/nux/src/components/dot-tip/style.scss @@ -51,37 +51,37 @@ $dot-scale: 3; // How much the pulse animation should scale up by in size } // Position the dot right next to the edge of the button - &.is-top { + &[data-y-axis="top"] { margin-top: -$dot-size / 2; } - &.is-bottom { + &[data-y-axis="bottom"] { margin-top: $dot-size / 2; } - &.is-middle.is-left { + &[data-y-axis="middle"][data-y-axis="left"] { margin-left: -$dot-size / 2; } - &.is-middle.is-right { + &[data-y-axis="middle"][data-y-axis="right"] { margin-left: $dot-size / 2; } // Position the tip content away from the dot - &.is-top .components-popover__content { + &[data-y-axis="top"] .components-popover__content { margin-bottom: 20px; } - &.is-bottom .components-popover__content { + &[data-y-axis="bottom"] .components-popover__content { margin-top: 20px; } - &.is-middle.is-left .components-popover__content { + &[data-y-axis="middle"][data-y-axis="left"] .components-popover__content { margin-right: 20px; } - &.is-middle.is-right .components-popover__content { + &[data-y-axis="middle"][data-y-axis="right"] .components-popover__content { margin-left: 20px; } // Extra specificity so that we can override the styles in .component-popover - &:not(.is-mobile).is-left, - &:not(.is-mobile).is-center, - &:not(.is-mobile).is-right { + &[data-y-axis="left"], + &[data-y-axis="center"], + &[data-y-axis="right"] { // Position tips above popovers z-index: z-index(".nux-dot-tip"); @@ -99,22 +99,22 @@ $dot-scale: 3; // How much the pulse animation should scale up by in size } } - &.components-popover:not(.is-mobile):not(.is-middle).is-right .components-popover__content { + &.components-popover:not([data-y-axis="middle"])[data-y-axis="right"] .components-popover__content { /*!rtl:ignore*/ margin-left: 0; } - &.components-popover:not(.is-mobile):not(.is-middle).is-left .components-popover__content { + &.components-popover:not([data-y-axis="middle"])[data-y-axis="left"] .components-popover__content { /*!rtl:ignore*/ margin-right: 0; } - &.components-popover.edit-post-more-menu__content:not(.is-mobile):not(.is-middle).is-right .components-popover__content { + &.components-popover.edit-post-more-menu__content:not([data-y-axis="middle"])[data-y-axis="right"] .components-popover__content { /*!rtl:ignore*/ margin-left: -12px; } - &.components-popover.edit-post-more-menu__content:not(.is-mobile):not(.is-middle).is-left .components-popover__content { + &.components-popover.edit-post-more-menu__content:not([data-y-axis="middle"])[data-y-axis="left"] .components-popover__content { /*!rtl:ignore*/ margin-right: -12px; } diff --git a/packages/nux/src/components/dot-tip/test/__snapshots__/index.js.snap b/packages/nux/src/components/dot-tip/test/__snapshots__/index.js.snap index c7e04b79aaf56b..5c5917cb54f525 100644 --- a/packages/nux/src/components/dot-tip/test/__snapshots__/index.js.snap +++ b/packages/nux/src/components/dot-tip/test/__snapshots__/index.js.snap @@ -5,12 +5,12 @@ exports[`DotTip should render correctly 1`] = ` aria-label="Editor tips" className="nux-dot-tip" focusOnMount="container" - getAnchorRect={[Function]} noArrow={true} onClick={[Function]} onFocusOutside={[Function]} position="middle right" role="dialog" + shouldAnchorIncludePadding={true} > <p> It looks like you’re writing a letter. Would you like help? diff --git a/packages/nux/src/store/selectors.js b/packages/nux/src/store/selectors.js index 9225bd97077eee..58decf89455191 100644 --- a/packages/nux/src/store/selectors.js +++ b/packages/nux/src/store/selectors.js @@ -7,7 +7,7 @@ import { includes, difference, keys, has } from 'lodash'; /** * An object containing information about a guide. * - * @typedef {Object} NUX.GuideInfo + * @typedef {Object} NUXGuideInfo * @property {string[]} tipIds Which tips the guide contains. * @property {?string} currentTipId The guide's currently showing tip. * @property {?string} nextTipId The guide's next tip to show. @@ -20,7 +20,7 @@ import { includes, difference, keys, has } from 'lodash'; * @param {Object} state Global application state. * @param {string} tipId The tip to query. * - * @return {?NUX.GuideInfo} Information about the associated guide. + * @return {?NUXGuideInfo} Information about the associated guide. */ export const getAssociatedGuide = createSelector( ( state, tipId ) => { diff --git a/packages/plugins/README.md b/packages/plugins/README.md index 2875c76b0fa8da..9bf049e1bc6abc 100644 --- a/packages/plugins/README.md +++ b/packages/plugins/README.md @@ -26,7 +26,7 @@ _Parameters_ _Returns_ -- `?Object`: Plugin setting. +- `?WPPlugin`: Plugin setting. <a name="getPlugins" href="#getPlugins">#</a> **getPlugins** @@ -34,7 +34,7 @@ Returns all registered plugins. _Returns_ -- `Array`: Plugin settings. +- `Array<WPPlugin>`: Plugin settings. <a name="PluginArea" href="#PluginArea">#</a> **PluginArea** @@ -71,7 +71,7 @@ const Layout = () => ( _Returns_ -- `WPElement`: Plugin area. +- `WPComponent`: The component to be rendered. <a name="registerPlugin" href="#registerPlugin">#</a> **registerPlugin** @@ -143,14 +143,12 @@ registerPlugin( 'plugin-name', { _Parameters_ -- _name_ `string`: A string identifying the plugin. Must be unique across all registered plugins. -- _settings_ `Object`: The settings for this plugin. -- _settings.icon_ `(string|WPElement|Function)`: An icon to be shown in the UI. It can be a slug of the Dashicon, or an element (or function returning an element) if you choose to render your own SVG. -- _settings.render_ `Function`: A component containing the UI elements to be rendered. +- _name_ `string`: A string identifying the plugin.Must be unique across all registered plugins. +- _settings_ `WPPlugin`: The settings for this plugin. _Returns_ -- `Object`: The final plugin settings object. +- `WPPlugin`: The final plugin settings object. <a name="unregisterPlugin" href="#unregisterPlugin">#</a> **unregisterPlugin** @@ -191,7 +189,7 @@ _Parameters_ _Returns_ -- `Component`: Enhanced component with injected context as props. +- `WPComponent`: Enhanced component with injected context as props. <!-- END TOKEN(Autogenerated API docs) --> diff --git a/packages/plugins/package.json b/packages/plugins/package.json index f935e31b1b3de5..adce7fb07c8cb9 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/plugins", - "version": "2.7.0", + "version": "2.8.0", "description": "Plugins module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/plugins/src/api/index.js b/packages/plugins/src/api/index.js index 6e6ea102a80ffd..5e8fe1d19c851d 100644 --- a/packages/plugins/src/api/index.js +++ b/packages/plugins/src/api/index.js @@ -10,6 +10,22 @@ import { applyFilters, doAction } from '@wordpress/hooks'; */ import { isFunction } from 'lodash'; +/** + * Defined behavior of a plugin type. + * + * @typedef {Object} WPPlugin + * + * @property {string} name A string identifying the plugin. Must be + * unique across all registered plugins. + * unique across all registered plugins. + * @property {string|WPElement|Function} icon An icon to be shown in the UI. It can + * be a slug of the Dashicon, or an element + * (or function returning an element) if you + * choose to render your own SVG. + * @property {Function} render A component containing the UI elements + * to be rendered. + */ + /** * Plugin definitions keyed by plugin name. * @@ -20,11 +36,9 @@ const plugins = {}; /** * Registers a plugin to the editor. * - * @param {string} name A string identifying the plugin. Must be unique across all registered plugins. - * @param {Object} settings The settings for this plugin. - * @param {string|WPElement|Function} settings.icon An icon to be shown in the UI. It can be a slug of the Dashicon, - * or an element (or function returning an element) if you choose to render your own SVG. - * @param {Function} settings.render A component containing the UI elements to be rendered. + * @param {string} name A string identifying the plugin.Must be + * unique across all registered plugins. + * @param {WPPlugin} settings The settings for this plugin. * * @example <caption>ES5</caption> * ```js @@ -90,7 +104,7 @@ const plugins = {}; * } ); * ``` * - * @return {Object} The final plugin settings object. + * @return {WPPlugin} The final plugin settings object. */ export function registerPlugin( name, settings ) { if ( typeof settings !== 'object' ) { @@ -181,7 +195,7 @@ export function unregisterPlugin( name ) { * * @param {string} name Plugin name. * - * @return {?Object} Plugin setting. + * @return {?WPPlugin} Plugin setting. */ export function getPlugin( name ) { return plugins[ name ]; @@ -190,7 +204,7 @@ export function getPlugin( name ) { /** * Returns all registered plugins. * - * @return {Array} Plugin settings. + * @return {WPPlugin[]} Plugin settings. */ export function getPlugins() { return Object.values( plugins ); diff --git a/packages/plugins/src/components/plugin-area/index.js b/packages/plugins/src/components/plugin-area/index.js index 1cf6f15b528795..e03e5458d50ef4 100644 --- a/packages/plugins/src/components/plugin-area/index.js +++ b/packages/plugins/src/components/plugin-area/index.js @@ -47,7 +47,7 @@ import { getPlugins } from '../../api'; * ); * ``` * - * @return {WPElement} Plugin area. + * @return {WPComponent} The component to be rendered. */ class PluginArea extends Component { constructor() { diff --git a/packages/plugins/src/components/plugin-context/index.js b/packages/plugins/src/components/plugin-context/index.js index 75fb1b8f1dea01..586891c67eb4ee 100644 --- a/packages/plugins/src/components/plugin-context/index.js +++ b/packages/plugins/src/components/plugin-context/index.js @@ -19,7 +19,7 @@ export { Provider as PluginContextProvider }; * expected to return object of props to * merge with the component's own props. * - * @return {Component} Enhanced component with injected context as props. + * @return {WPComponent} Enhanced component with injected context as props. */ export const withPluginContext = ( mapContextToProps ) => createHigherOrderComponent( ( OriginalComponent ) => { return ( props ) => ( diff --git a/packages/priority-queue/package.json b/packages/priority-queue/package.json index 14026df5257147..de49d37c2fe9c3 100644 --- a/packages/priority-queue/package.json +++ b/packages/priority-queue/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/priority-queue", - "version": "1.3.0", + "version": "1.3.1", "description": "Generic browser priority queue.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -21,6 +21,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "sideEffects": false, "dependencies": { "@babel/runtime": "^7.4.4" }, diff --git a/packages/redux-routine/package.json b/packages/redux-routine/package.json index 7c24fc8f9023e7..29e53534cf6cd8 100644 --- a/packages/redux-routine/package.json +++ b/packages/redux-routine/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/redux-routine", - "version": "3.6.0", + "version": "3.6.2", "description": "Redux middleware for generator coroutines.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -22,6 +22,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "sideEffects": false, "dependencies": { "@babel/runtime": "^7.4.4", "is-promise": "^2.1.0", diff --git a/packages/rich-text/README.md b/packages/rich-text/README.md index 46a78502ff3ed4..30ed4296d162f7 100644 --- a/packages/rich-text/README.md +++ b/packages/rich-text/README.md @@ -84,6 +84,7 @@ _Parameters_ - _$1.range_ `[Range]`: Range to create value from. - _$1.multilineTag_ `[string]`: Multiline tag if the structure is multiline. - _$1.multilineWrapperTags_ `[Array]`: Tags where lines can be found if nesting is possible. +- _$1.preserveWhiteSpace_ `[?boolean]`: Whether or not to collapse white space characters. _Returns_ @@ -216,11 +217,7 @@ behavior. _Parameters_ - _name_ `string`: Format name. -- _settings_ `Object`: Format settings. -- _settings.tagName_ `string`: The HTML tag this format will wrap the selection with. -- _settings.className_ `[string]`: A class to match the format. -- _settings.title_ `string`: Name of the format. -- _settings.edit_ `Function`: Should return a component for the user to interact with the new registered format. +- _settings_ `WPFormat`: Format settings. _Returns_ @@ -332,6 +329,7 @@ _Parameters_ - _$1_ `Object`: Named argements. - _$1.value_ `Object`: Rich text value. - _$1.multilineTag_ `[string]`: Multiline tag. +- _$1.preserveWhiteSpace_ `[?boolean]`: Whether or not to use newline characters for line breaks. _Returns_ diff --git a/packages/rich-text/package.json b/packages/rich-text/package.json index 544d69294e90e4..5c41e6f4fe4fed 100644 --- a/packages/rich-text/package.json +++ b/packages/rich-text/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/rich-text", - "version": "3.7.0", + "version": "3.8.0", "description": "Rich text value and manipulation API.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/rich-text/src/can-indent-list-items.js b/packages/rich-text/src/can-indent-list-items.js new file mode 100644 index 00000000000000..824193f0c0a4da --- /dev/null +++ b/packages/rich-text/src/can-indent-list-items.js @@ -0,0 +1,29 @@ +/** + * Internal dependencies + */ +import { getLineIndex } from './get-line-index'; + +/** + * Checks if the selected list item can be indented. + * + * @param {Object} value Value to check. + * + * @return {boolean} Whether or not the selected list item can be indented. + */ +export function canIndentListItems( value ) { + const lineIndex = getLineIndex( value ); + + // There is only one line, so the line cannot be indented. + if ( lineIndex === undefined ) { + return false; + } + + const { replacements } = value; + const previousLineIndex = getLineIndex( value, lineIndex ); + const formatsAtLineIndex = replacements[ lineIndex ] || []; + const formatsAtPreviousLineIndex = replacements[ previousLineIndex ] || []; + + // If the indentation of the current line is greater than previous line, + // then the line cannot be furter indented. + return formatsAtLineIndex.length <= formatsAtPreviousLineIndex.length; +} diff --git a/packages/rich-text/src/can-outdent-list-items.js b/packages/rich-text/src/can-outdent-list-items.js new file mode 100644 index 00000000000000..0c56d227159a51 --- /dev/null +++ b/packages/rich-text/src/can-outdent-list-items.js @@ -0,0 +1,18 @@ +/** + * Internal dependencies + */ + +import { getLineIndex } from './get-line-index'; + +/** + * Checks if the selected list item can be outdented. + * + * @param {Object} value Value to check. + * + * @return {boolean} Whether or not the selected list item can be outdented. + */ +export function canOutdentListItems( value ) { + const { replacements, start } = value; + const startingLineIndex = getLineIndex( value, start ); + return replacements[ startingLineIndex ] !== undefined; +} diff --git a/packages/rich-text/src/component/aria.js b/packages/rich-text/src/component/aria.js deleted file mode 100644 index d563f7f42f9641..00000000000000 --- a/packages/rich-text/src/component/aria.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * External dependencies - */ - -import { - difference, - isEqual, - isNil, - keys, - pickBy, - startsWith, -} from 'lodash'; - -const isAriaPropName = ( name ) => - startsWith( name, 'aria-' ); - -export const pickAriaProps = ( props ) => - pickBy( props, ( value, key ) => isAriaPropName( key ) && ! isNil( value ) ); - -export const diffAriaProps = ( props, nextProps ) => { - const prevAriaKeys = keys( pickAriaProps( props ) ); - const nextAriaKeys = keys( pickAriaProps( nextProps ) ); - const removedKeys = difference( prevAriaKeys, nextAriaKeys ); - const updatedKeys = nextAriaKeys.filter( ( key ) => - ! isEqual( props[ key ], nextProps[ key ] ) ); - return { removedKeys, updatedKeys }; -}; diff --git a/packages/rich-text/src/component/editable.js b/packages/rich-text/src/component/editable.js deleted file mode 100644 index af9a2f79638b23..00000000000000 --- a/packages/rich-text/src/component/editable.js +++ /dev/null @@ -1,109 +0,0 @@ -/** - * External dependencies - */ -import { isEqual } from 'lodash'; - -/** - * WordPress dependencies - */ -import { Component, createElement, forwardRef } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import { diffAriaProps } from './aria'; - -class Editable extends Component { - // We must prevent rerenders because the browser will modify the DOM. React - // will rerender the DOM fine, but we're losing selection and it would be - // more expensive to do so as it would just set the inner HTML through - // `dangerouslySetInnerHTML`. Instead RichText does it's own diffing and - // selection setting. - // - // Because we never update the component, we have to look through props and - // update the attributes on the wrapper nodes here. `componentDidUpdate` - // will never be called. - shouldComponentUpdate( nextProps ) { - const element = this.props.forwardedRef.current; - - if ( ! isEqual( this.props.style, nextProps.style ) ) { - element.setAttribute( 'style', '' ); - Object.assign( element.style, { - ...( nextProps.style || {} ), - whiteSpace: 'pre-wrap', - } ); - } - - if ( ! isEqual( this.props.className, nextProps.className ) ) { - element.className = nextProps.className; - } - - if ( this.props.start !== nextProps.start ) { - element.setAttribute( 'start', nextProps.start ); - } - - if ( this.props.reversed !== nextProps.reversed ) { - element.reversed = nextProps.reversed; - } - - const { removedKeys, updatedKeys } = diffAriaProps( this.props, nextProps ); - removedKeys.forEach( ( key ) => - element.removeAttribute( key ) ); - updatedKeys.forEach( ( key ) => - element.setAttribute( key, nextProps[ key ] ) ); - - return false; - } - - render() { - const { - tagName = 'div', - style = {}, - record, - valueToEditableHTML, - className, - forwardedRef, - ...remainingProps - } = this.props; - - // In HTML, leading and trailing spaces are not visible, and multiple - // spaces elsewhere are visually reduced to one space. This rule - // prevents spaces from collapsing so all space is visible in the editor - // and can be removed. - // It also prevents some browsers from inserting non-breaking spaces at - // the end of a line to prevent the space from visually disappearing. - // Sometimes these non breaking spaces can linger in the editor causing - // unwanted non breaking spaces in between words. If also prevent - // Firefox from inserting a trailing `br` node to visualise any trailing - // space, causing the element to be saved. - // - // > Authors are encouraged to set the 'white-space' property on editing - // > hosts and on markup that was originally created through these - // > editing mechanisms to the value 'pre-wrap'. Default HTML whitespace - // > handling is not well suited to WYSIWYG editing, and line wrapping - // > will not work correctly in some corner cases if 'white-space' is - // > left at its default value. - // > - // > https://html.spec.whatwg.org/multipage/interaction.html#best-practices-for-in-page-editors - const whiteSpace = 'pre-wrap'; - - return createElement( tagName, { - role: 'textbox', - 'aria-multiline': true, - className, - contentEditable: true, - ref: forwardedRef, - style: { - ...style, - whiteSpace, - }, - suppressContentEditableWarning: true, - dangerouslySetInnerHTML: { __html: valueToEditableHTML( record ) }, - ...remainingProps, - } ); - } -} - -export default forwardRef( ( props, ref ) => { - return <Editable { ...props } forwardedRef={ ref } />; -} ); diff --git a/packages/rich-text/src/component/format-edit.js b/packages/rich-text/src/component/format-edit.js index 2bee3de1237823..0cac1897cec713 100644 --- a/packages/rich-text/src/component/format-edit.js +++ b/packages/rich-text/src/component/format-edit.js @@ -58,7 +58,7 @@ const FormatEdit = ( { const activeFormat = getActiveFormat( value, name ); const isActive = activeFormat !== undefined; const activeObject = getActiveObject( value ); - const isObjectActive = activeObject !== undefined; + const isObjectActive = activeObject !== undefined && activeObject.type === name; return ( <Edit diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index 0403dc8750de5b..48eddc2aead167 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -6,6 +6,7 @@ import { find, isNil, pickBy, + startsWith, } from 'lodash'; /** @@ -22,10 +23,8 @@ import deprecated from '@wordpress/deprecated'; * Internal dependencies */ import FormatEdit from './format-edit'; -import Editable from './editable'; -import { pickAriaProps } from './aria'; import { create } from '../create'; -import { apply, toDom } from '../to-dom'; +import { apply } from '../to-dom'; import { toHTMLString } from '../to-html-string'; import { remove } from '../remove'; import { removeFormat } from '../remove-format'; @@ -35,6 +34,7 @@ import { indentListItems } from '../indent-list-items'; import { getActiveFormats } from '../get-active-formats'; import { updateFormats } from '../update-formats'; import { removeLineSeparator } from '../remove-line-separator'; +import { isEmptyLine } from '../is-empty'; /** * Browser dependencies @@ -42,6 +42,8 @@ import { removeLineSeparator } from '../remove-line-separator'; const { getSelection, getComputedStyle } = window; +/** @typedef {import('@wordpress/element').WPSyntheticEvent} WPSyntheticEvent */ + /** * All inserting input types that would insert HTML into the DOM. * @@ -57,6 +59,35 @@ const INSERTION_INPUT_TYPES_TO_IGNORE = new Set( [ 'insertLink', ] ); +/** + * In HTML, leading and trailing spaces are not visible, and multiple spaces + * elsewhere are visually reduced to one space. This rule prevents spaces from + * collapsing so all space is visible in the editor and can be removed. It also + * prevents some browsers from inserting non-breaking spaces at the end of a + * line to prevent the space from visually disappearing. Sometimes these non + * breaking spaces can linger in the editor causing unwanted non breaking spaces + * in between words. If also prevent Firefox from inserting a trailing `br` node + * to visualise any trailing space, causing the element to be saved. + * + * > Authors are encouraged to set the 'white-space' property on editing hosts + * > and on markup that was originally created through these editing mechanisms + * > to the value 'pre-wrap'. Default HTML whitespace handling is not well + * > suited to WYSIWYG editing, and line wrapping will not work correctly in + * > some corner cases if 'white-space' is left at its default value. + * + * https://html.spec.whatwg.org/multipage/interaction.html#best-practices-for-in-page-editors + * + * @type {string} + */ +const whiteSpace = 'pre-wrap'; + +/** + * Default style object for the editable element. + * + * @type {Object<string,string>} + */ +const defaultStyle = { whiteSpace }; + /** * Global stylesheet. */ @@ -78,6 +109,31 @@ function createPrepareEditableTree( props, prefix ) { }, value.formats ); } +/** + * If the selection is set on the placeholder element, collapse the selection to + * the start (before the placeholder). + */ +function fixPlaceholderSelection() { + const selection = window.getSelection(); + const { anchorNode, anchorOffset } = selection; + + if ( anchorNode.nodeType !== anchorNode.ELEMENT_NODE ) { + return; + } + + const targetNode = anchorNode.childNodes[ anchorOffset ]; + + if ( + ! targetNode || + targetNode.nodeType !== targetNode.ELEMENT_NODE || + ! targetNode.getAttribute( 'data-rich-text-placeholder' ) + ) { + return; + } + + selection.collapseToStart(); +} + /** * See export statement below. */ @@ -104,7 +160,6 @@ class RichText extends Component { this.createRecord = this.createRecord.bind( this ); this.applyRecord = this.applyRecord.bind( this ); this.valueToFormat = this.valueToFormat.bind( this ); - this.valueToEditableHTML = this.valueToEditableHTML.bind( this ); this.onPointerDown = this.onPointerDown.bind( this ); this.formatToValue = this.formatToValue.bind( this ); this.Editable = this.Editable.bind( this ); @@ -144,10 +199,16 @@ class RichText extends Component { console.warn( 'RichText cannot be used with an inline container. Please use a different tagName.' ); } } + + this.applyRecord( this.record, { domOnly: true } ); } createRecord() { - const { __unstableMultilineTag: multilineTag, forwardedRef } = this.props; + const { + __unstableMultilineTag: multilineTag, + forwardedRef, + preserveWhiteSpace, + } = this.props; const selection = getSelection(); const range = selection.rangeCount > 0 ? selection.getRangeAt( 0 ) : null; @@ -157,6 +218,7 @@ class RichText extends Component { multilineTag, multilineWrapperTags: multilineTag === 'li' ? [ 'ul', 'ol' ] : undefined, __unstableIsEditableTree: true, + preserveWhiteSpace, } ); } @@ -179,10 +241,20 @@ class RichText extends Component { * * Saves the pasted data as plain text in `pastedPlainText`. * - * @param {PasteEvent} event The paste event. + * @param {ClipboardEvent} event The paste event. */ onPaste( event ) { - const { formatTypes, onPaste } = this.props; + const { + formatTypes, + onPaste, + __unstableIsSelected: isSelected, + } = this.props; + + if ( ! isSelected ) { + event.preventDefault(); + return; + } + const clipboardData = event.clipboardData; let { items, files } = clipboardData; @@ -233,18 +305,32 @@ class RichText extends Component { } if ( onPaste ) { - // Only process file if no HTML is present. - // Note: a pasted file may have the URL as plain text. - const image = find( [ ...items, ...files ], ( { type } ) => - /^image\/(?:jpe?g|png|gif)$/.test( type ) - ); + files = Array.from( files ); + + Array.from( items ).forEach( ( item ) => { + if ( ! item.getAsFile ) { + return; + } + + const file = item.getAsFile(); + + if ( ! file ) { + return; + } + + const { name, type, size } = file; + + if ( ! find( files, { name, type, size } ) ) { + files.push( file ); + } + } ); onPaste( { value: this.removeEditorOnlyFormats( record ), onChange: this.onChange, html, plainText, - image, + files, } ); } } @@ -312,7 +398,7 @@ class RichText extends Component { /** * Handle input on the next selection change event. * - * @param {SyntheticEvent} event Synthetic input event. + * @param {WPSyntheticEvent} event Synthetic input event. */ onInput( event ) { // For Input Method Editor (IME), used in Chinese, Japanese, and Korean @@ -412,7 +498,7 @@ class RichText extends Component { * native events, `keyup`, `mouseup` and `touchend` synthetic events, and * animation frames after the `focus` event. * - * @param {Event|SyntheticEvent|DOMHighResTimeStamp} event + * @param {Event|WPSyntheticEvent|DOMHighResTimeStamp} event */ onSelectionChange( event ) { if ( @@ -422,6 +508,10 @@ class RichText extends Component { return; } + if ( this.props.disabled ) { + return; + } + // In case of a keyboard event, ignore selection changes during // composition. if ( @@ -442,14 +532,11 @@ class RichText extends Component { } if ( start === value.start && end === value.end ) { - // If a placeholder is set, some browsers seems to place the - // selection after the placeholder instead of the text node that is - // padding the empty container element. The internal selection is - // set correctly to zero, but the caret is not visible. By - // reapplying the value to the DOM we reset the selection to the - // right node, making the caret visible again. + // Sometimes the browser may set the selection on the placeholder + // element, in which case the caret is not visible. We need to set + // the caret before the placeholder if that's the case. if ( value.text.length === 0 && start === 0 ) { - this.applyRecord( value ); + fixPlaceholderSelection(); } return; @@ -532,8 +619,10 @@ class RichText extends Component { this.value = this.valueToFormat( record ); this.record = record; - this.props.onChange( this.value ); + // Selection must be updated first, so it is recorded in history when + // the content change happens. this.props.onSelectionChange( start, end ); + this.props.onChange( this.value ); this.setState( { activeFormats } ); if ( ! withoutHistory ) { @@ -557,7 +646,7 @@ class RichText extends Component { * - delete content if everything is selected, * - trigger the onDelete prop when selection is uncollapsed and at an edge. * - * @param {SyntheticEvent} event A synthetic keyboard event. + * @param {WPSyntheticEvent} event A synthetic keyboard event. */ handleDelete( event ) { const { keyCode } = event; @@ -582,14 +671,6 @@ class RichText extends Component { const { start, end, text } = value; const isReverse = keyCode === BACKSPACE; - if ( multilineTag ) { - const newValue = removeLineSeparator( value, isReverse ); - if ( newValue ) { - this.onChange( newValue ); - event.preventDefault(); - } - } - // Always handle full content deletion ourselves. if ( start === 0 && end !== 0 && end === text.length ) { this.onChange( remove( value ) ); @@ -597,6 +678,23 @@ class RichText extends Component { return; } + if ( multilineTag ) { + let newValue; + + // Check to see if we should remove the first item if empty. + if ( isReverse && value.start === 0 && value.end === 0 && isEmptyLine( value ) ) { + newValue = removeLineSeparator( value, ! isReverse ); + } else { + newValue = removeLineSeparator( value, isReverse ); + } + + if ( newValue ) { + this.onChange( newValue ); + event.preventDefault(); + return; + } + } + // Only process delete if the key press occurs at an uncollapsed edge. if ( ! onDelete || @@ -615,7 +713,7 @@ class RichText extends Component { /** * Triggers the `onEnter` prop on keydown. * - * @param {SyntheticEvent} event A synthetic keyboard event. + * @param {WPSyntheticEvent} event A synthetic keyboard event. */ handleEnter( event ) { if ( event.keyCode !== ENTER ) { @@ -640,7 +738,7 @@ class RichText extends Component { /** * Indents list items on space keydown. * - * @param {SyntheticEvent} event A synthetic keyboard event. + * @param {WPSyntheticEvent} event A synthetic keyboard event. */ handleSpace( event ) { const { keyCode, shiftKey, altKey, metaKey, ctrlKey } = event; @@ -677,7 +775,7 @@ class RichText extends Component { * navigation is handled separately to move correctly around format * boundaries. * - * @param {SyntheticEvent} event A synthetic keyboard event. + * @param {WPSyntheticEvent} event A synthetic keyboard event. */ handleHorizontalNavigation( event ) { const { keyCode, shiftKey, altKey, metaKey, ctrlKey } = event; @@ -783,7 +881,7 @@ class RichText extends Component { * Select object when they are clicked. The browser will not set any * selection when clicking e.g. an image. * - * @param {SyntheticEvent} event Synthetic mousedown or touchstart event. + * @param {WPSyntheticEvent} event Synthetic mousedown or touchstart event. */ onPointerDown( event ) { const { target } = event; @@ -815,19 +913,26 @@ class RichText extends Component { __unstableIsSelected: isSelected, } = this.props; + // Check if tag name changed. + let shouldReapply = tagName !== prevProps.tagName; + // Check if the content changed. - let shouldReapply = ( - tagName === prevProps.tagName && + shouldReapply = shouldReapply || ( value !== prevProps.value && value !== this.value ); + const selectionChanged = ( + selectionStart !== prevProps.selectionStart && + selectionStart !== this.record.start + ) || ( + selectionEnd !== prevProps.selectionEnd && + selectionEnd !== this.record.end + ); + // Check if the selection changed. shouldReapply = shouldReapply || ( - isSelected && ! prevProps.isSelected && ( - this.record.start !== selectionStart || - this.record.end !== selectionEnd - ) + isSelected && ! prevProps.isSelected && selectionChanged ); const prefix = 'format_prepare_props_'; @@ -843,26 +948,13 @@ class RichText extends Component { shouldReapply = shouldReapply || placeholder !== prevProps.placeholder; - const { activeFormats = [] } = this.record; - if ( shouldReapply ) { this.value = value; this.record = this.formatToValue( value ); this.record.start = selectionStart; this.record.end = selectionEnd; - - updateFormats( { - value: this.record, - start: this.record.start, - end: this.record.end, - formats: activeFormats, - } ); - this.applyRecord( this.record ); - } else if ( - this.record.start !== selectionStart || - this.record.end !== selectionEnd - ) { + } else if ( selectionChanged ) { this.record = { ...this.record, start: selectionStart, @@ -878,7 +970,11 @@ class RichText extends Component { * @return {Object} An internal rich-text value. */ formatToValue( value ) { - const { format, __unstableMultilineTag: multilineTag } = this.props; + const { + format, + __unstableMultilineTag: multilineTag, + preserveWhiteSpace, + } = this.props; if ( format !== 'string' ) { return value; @@ -890,23 +986,13 @@ class RichText extends Component { html: value, multilineTag, multilineWrapperTags: multilineTag === 'li' ? [ 'ul', 'ol' ] : undefined, + preserveWhiteSpace, } ); value.formats = prepare( value ); return value; } - valueToEditableHTML( value ) { - const { __unstableMultilineTag: multilineTag } = this.props; - - return toDom( { - value, - multilineTag, - prepareEditableTree: createPrepareEditableTree( this.props, 'format_prepare_functions' ), - placeholder: this.props.placeholder, - } ).body.innerHTML; - } - /** * Removes editor only formats from the value. * @@ -934,7 +1020,11 @@ class RichText extends Component { * @return {*} The external data format, data type depends on props. */ valueToFormat( value ) { - const { format, __unstableMultilineTag: multilineTag } = this.props; + const { + format, + __unstableMultilineTag: multilineTag, + preserveWhiteSpace, + } = this.props; value = this.removeEditorOnlyFormats( value ); @@ -942,34 +1032,32 @@ class RichText extends Component { return; } - return toHTMLString( { value, multilineTag } ); + return toHTMLString( { value, multilineTag, preserveWhiteSpace } ); } Editable( props ) { const { - tagName: Tagname = 'div', + tagName: TagName = 'div', style, className, placeholder, forwardedRef, + disabled, } = this.props; - // Generating a key that includes `tagName` ensures that if the tag - // changes, we replace the relevant element. This is needed because we - // prevent Editable component updates. - const key = Tagname; + const ariaProps = pickBy( this.props, ( value, key ) => + startsWith( key, 'aria-' ) ); return ( - <Editable + <TagName + // Overridable props. + role="textbox" + aria-multiline + aria-label={ placeholder } { ...props } + { ...ariaProps } ref={ forwardedRef } - tagName={ Tagname } - style={ style } - record={ this.record } - valueToEditableHTML={ this.valueToEditableHTML } - aria-label={ placeholder } - { ...pickAriaProps( this.props ) } + style={ style ? { ...style, whiteSpace } : defaultStyle } className={ classnames( 'rich-text', className ) } - key={ key } onPaste={ this.onPaste } onInput={ this.onInput } onCompositionEnd={ this.onCompositionEnd } @@ -988,6 +1076,9 @@ class RichText extends Component { onKeyUp={ this.onSelectionChange } onMouseUp={ this.onSelectionChange } onTouchEnd={ this.onSelectionChange } + // Do not set the attribute if disabled. + contentEditable={ disabled ? undefined : true } + suppressContentEditableWarning={ ! disabled } /> ); } diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js index 206680d3c87668..7415ebfe275977 100644 --- a/packages/rich-text/src/component/index.native.js +++ b/packages/rich-text/src/component/index.native.js @@ -30,10 +30,7 @@ import { getActiveFormat } from '../get-active-format'; import { getActiveFormats } from '../get-active-formats'; import { isEmpty, isEmptyLine } from '../is-empty'; import { create } from '../create'; -import { split } from '../split'; import { toHTMLString } from '../to-html-string'; -import { insert } from '../insert'; -import { insertLineSeparator } from '../insert-line-separator'; import { removeLineSeparator } from '../remove-line-separator'; import { isCollapsed } from '../is-collapsed'; import { remove } from '../remove'; @@ -43,28 +40,6 @@ const unescapeSpaces = ( text ) => { return text.replace( /&nbsp;|&#160;/gi, ' ' ); }; -/** - * Calls {@link pasteHandler} with a fallback to plain text when HTML processing - * results in errors - * - * @param {Function} originalPasteHandler The original handler function - * @param {Object} [options] The options to pass to {@link pasteHandler} - * - * @return {Array|string} A list of blocks or a string, depending on - * `handlerMode`. - */ -const saferPasteHandler = ( originalPasteHandler, options ) => { - try { - return originalPasteHandler( options ); - } catch ( error ) { - window.console.log( 'Pasting HTML failed:', error ); - window.console.log( 'HTML:', options.HTML ); - window.console.log( 'Falling back to plain text.' ); - // fallback to plain text - return originalPasteHandler( { ...options, HTML: '' } ); - } -}; - const gutenbergFormatNamesToAztec = { 'core/bold': 'bold', 'core/italic': 'italic', @@ -72,7 +47,7 @@ const gutenbergFormatNamesToAztec = { }; export class RichText extends Component { - constructor( { value, __unstableMultiline: multiline, selectionStart, selectionEnd } ) { + constructor( { value, selectionStart, selectionEnd, __unstableMultilineTag: multiline } ) { super( ...arguments ); this.isMultiline = false; @@ -84,12 +59,12 @@ export class RichText extends Component { if ( this.multilineTag === 'li' ) { this.multilineWrapperTags = [ 'ul', 'ol' ]; } - this.onSplit = this.onSplit.bind( this ); + this.isIOS = Platform.OS === 'ios'; this.createRecord = this.createRecord.bind( this ); this.onChange = this.onChange.bind( this ); - this.onEnter = this.onEnter.bind( this ); - this.onBackspace = this.onBackspace.bind( this ); + this.handleEnter = this.handleEnter.bind( this ); + this.handleDelete = this.handleDelete.bind( this ); this.onPaste = this.onPaste.bind( this ); this.onFocus = this.onFocus.bind( this ); this.onBlur = this.onBlur.bind( this ); @@ -154,6 +129,9 @@ export class RichText extends Component { * @return {Object} A RichText value with formats and selection. */ createRecord() { + const { + preserveWhiteSpace, + } = this.props; const value = { start: this.selectionStart, end: this.selectionEnd, @@ -162,6 +140,7 @@ export class RichText extends Component { range: null, multilineTag: this.multilineTag, multilineWrapperTags: this.multilineWrapperTags, + preserveWhiteSpace, } ), }; const start = Math.min( this.selectionStart, value.text.length ); @@ -169,63 +148,6 @@ export class RichText extends Component { return { ...value, start, end }; } - /** - * Signals to the RichText owner that the block can be replaced with two - * blocks as a result of splitting the block by pressing enter, or with - * blocks as a result of splitting the block by pasting block content in the - * instance. - * - * @param {Object} record The rich text value to split. - * @param {Array} pastedBlocks The pasted blocks to insert, if any. - */ - onSplit( record, pastedBlocks = [] ) { - const { - __unstableOnReplace: onReplace, - __unstableOnSplit: onSplit, - __unstableOnSplitMiddle: onSplitMiddle, - } = this.props; - - if ( ! onReplace || ! onSplit ) { - return; - } - - const blocks = []; - const [ before, after ] = split( record ); - const hasPastedBlocks = pastedBlocks.length > 0; - - // Create a block with the content before the caret if there's no pasted - // blocks, or if there are pasted blocks and the value is not empty. - // We do not want a leading empty block on paste, but we do if split - // with e.g. the enter key. - if ( ! hasPastedBlocks || ! isEmpty( before ) ) { - blocks.push( onSplit( this.valueToFormat( before ) ) ); - } - - if ( hasPastedBlocks ) { - blocks.push( ...pastedBlocks ); - } else if ( onSplitMiddle ) { - blocks.push( onSplitMiddle() ); - } - - // If there's pasted blocks, append a block with the content after the - // caret. Otherwise, do append and empty block if there is no - // `onSplitMiddle` prop, but if there is and the content is empty, the - // middle block is enough to set focus in. - if ( hasPastedBlocks || ! onSplitMiddle || ! isEmpty( after ) ) { - blocks.push( onSplit( this.valueToFormat( after ) ) ); - } - - // If there are pasted blocks, set the selection to the last one. - // Otherwise, set the selection to the second block. - const indexToSelect = hasPastedBlocks ? blocks.length - 1 : 1; - // The onSplit event can cause a content update event for this block. Such event should - // definitely be processed by our native components, since they have no knowledge of - // how the split works. Setting lastEventCount to undefined forces the native component to - // always update when provided with new content. - this.lastEventCount = undefined; - onReplace( blocks, indexToSelect ); - } - valueToFormat( value ) { // remove the outer root tags return this.removeRootTagsProduceByAztec( toHTMLString( { @@ -245,6 +167,7 @@ export class RichText extends Component { } onFormatChange( record ) { + this.getRecord( record ); const { start, end, activeFormats = [] } = record; const changeHandlers = pickBy( this.props, ( v, key ) => key.startsWith( 'format_on_change_functions_' ) @@ -299,7 +222,7 @@ export class RichText extends Component { } removeRootTag( tag, html ) { - const openingTagRegexp = RegExp( '^<' + tag + '>', 'gim' ); + const openingTagRegexp = RegExp( '^<' + tag + '[^>]*>', 'gim' ); const closingTagRegexp = RegExp( '</' + tag + '>$', 'gim' ); return html.replace( openingTagRegexp, '' ).replace( closingTagRegexp, '' ); } @@ -346,92 +269,67 @@ export class RichText extends Component { this.lastAztecEventType = 'content size change'; } - onEnter( event ) { - if ( this.props.onEnter ) { - this.props.onEnter(); + handleEnter( event ) { + const { onEnter } = this.props; + + if ( ! onEnter ) { return; } - const { - __unstableOnReplace: onReplace, - __unstableOnSplit: onSplit, - } = this.props; - - this.lastEventCount = event.nativeEvent.eventCount; - this.comesFromAztec = true; - this.firedAfterTextChanged = event.nativeEvent.firedAfterTextChanged; - const canSplit = onReplace && onSplit; - const currentRecord = this.createRecord(); - if ( this.multilineTag ) { - if ( event.shiftKey ) { - this.needsSelectionUpdate = true; - const insertedLineBreak = { ...insert( currentRecord, '\n' ) }; - this.onFormatChange( insertedLineBreak ); - } else if ( canSplit && isEmptyLine( currentRecord ) ) { - this.onSplit( currentRecord ); - } else { - this.needsSelectionUpdate = true; - const insertedLineSeparator = { ...insertLineSeparator( currentRecord ) }; - this.onFormatChange( insertedLineSeparator ); - } - } else if ( event.shiftKey || ! onSplit ) { - this.needsSelectionUpdate = true; - const insertedLineBreak = { ...insert( currentRecord, '\n' ) }; - this.onFormatChange( insertedLineBreak ); - } else { - this.onSplit( currentRecord ); - } + onEnter( { + value: this.createRecord(), + onChange: this.onFormatChange, + shiftKey: event.shiftKey, + } ); this.lastAztecEventType = 'input'; } - onBackspace( event ) { - const { - __unstableOnMerge: onMerge, - __unstableOnRemove: onRemove, - onChange, - } = this.props; - if ( ! onMerge && ! onRemove ) { - return; - } - + handleDelete( event ) { const keyCode = BACKSPACE; // TODO : should we differentiate BACKSPACE and DELETE? const isReverse = keyCode === BACKSPACE; + const { onDelete, __unstableMultilineTag: multilineTag } = this.props; + const { activeFormats = [] } = this.state; this.lastEventCount = event.nativeEvent.eventCount; this.comesFromAztec = true; this.firedAfterTextChanged = event.nativeEvent.firedAfterTextChanged; const value = this.createRecord(); - const { start, end } = value; + const { start, end, text } = value; let newValue; // Always handle full content deletion ourselves. - if ( start === 0 && end !== 0 && end >= value.text.length ) { - newValue = remove( value, start, end ); - onChange( newValue ); + if ( start === 0 && end !== 0 && end >= text.length ) { + newValue = remove( value ); + this.onFormatChange( newValue ); + event.preventDefault(); return; } - if ( this.multilineTag ) { - newValue = removeLineSeparator( value, keyCode === BACKSPACE ); + if ( multilineTag ) { + if ( isReverse && value.start === 0 && value.end === 0 && isEmptyLine( value ) ) { + newValue = removeLineSeparator( value, ! isReverse ); + } else { + newValue = removeLineSeparator( value, isReverse ); + } if ( newValue ) { this.onFormatChange( newValue ); + event.preventDefault(); return; } } - const empty = this.isEmpty(); - - if ( onMerge ) { - onMerge( ! isReverse ); + // Only process delete if the key press occurs at an uncollapsed edge. + if ( + ! onDelete || + ! isCollapsed( value ) || + activeFormats.length || + ( isReverse && start !== 0 ) || + ( ! isReverse && end !== text.length ) + ) { + return; } - // Only handle remove on Backspace. This serves dual-purpose of being - // an intentional user interaction distinguishing between Backspace and - // Delete to remove the empty field, but also to avoid merge & remove - // causing destruction of two fields (merge, then removed merged). - if ( onRemove && empty && isReverse ) { - onRemove( ! isReverse ); - } + onDelete( { isReverse, value } ); event.preventDefault(); this.lastAztecEventType = 'input'; @@ -440,14 +338,11 @@ export class RichText extends Component { /** * Handles a paste event from the native Aztec Wrapper. * - * @param {PasteEvent} event The paste event which wraps `nativeEvent`. + * @param {Object} event The paste event which wraps `nativeEvent`. */ onPaste( event ) { const { - tagName, - __unstablePasteHandler: pasteHandler, - __unstableOnReplace: onReplace, - __unstableOnSplit: onSplit, + onPaste, onChange, } = this.props; @@ -456,30 +351,6 @@ export class RichText extends Component { event.preventDefault(); - // Only process file if no HTML is present. - // Note: a pasted file may have the URL as plain text. - if ( files && files.length > 0 ) { - const uploadId = Number.MAX_SAFE_INTEGER; - let html = ''; - files.forEach( ( file ) => { - html += `<img src="${ file }" class="wp-image-${ uploadId }">`; - } ); - const content = pasteHandler( { - HTML: html, - mode: 'BLOCKS', - tagName, - } ); - const shouldReplace = onReplace && this.isEmpty(); - - if ( shouldReplace ) { - onReplace( content ); - } else { - this.onSplit( currentRecord, content ); - } - - return; - } - // There is a selection, check if a URL is pasted. if ( ! isCollapsed( currentRecord ) ) { const trimmedText = ( pastedHtml || pastedText ).replace( /<[^>]+>/g, '' ) @@ -503,46 +374,14 @@ export class RichText extends Component { } } - const shouldReplace = this.props.onReplace && this.isEmpty(); - - let mode = 'INLINE'; - - if ( shouldReplace ) { - mode = 'BLOCKS'; - } else if ( onSplit ) { - mode = 'AUTO'; - } - - const pastedContent = saferPasteHandler( pasteHandler, { - HTML: pastedHtml, - plainText: pastedText, - mode, - tagName: this.props.tagName, - canUserUseUnfilteredHTML: this.props.canUserUseUnfilteredHTML, - } ); - - if ( typeof pastedContent === 'string' ) { - const recordToInsert = create( { html: pastedContent } ); - const resultingRecord = insert( currentRecord, recordToInsert ); - const resultingContent = this.valueToFormat( resultingRecord ); - - this.lastEventCount = undefined; - this.value = resultingContent; - - // explicitly set selection after inline paste - this.onSelectionChange( resultingRecord.start, resultingRecord.end ); - - onChange( this.value ); - } else if ( onSplit ) { - if ( ! pastedContent.length ) { - return; - } - - if ( shouldReplace ) { - onReplace( pastedContent ); - } else { - this.onSplit( currentRecord, pastedContent ); - } + if ( onPaste ) { + onPaste( { + value: currentRecord, + onChange: this.onFormatChange, + html: pastedHtml, + plainText: pastedText, + files, + } ); } } @@ -629,12 +468,16 @@ export class RichText extends Component { } formatToValue( value ) { + const { + preserveWhiteSpace, + } = this.props; // Handle deprecated `children` and `node` sources. if ( Array.isArray( value ) ) { return create( { html: childrenBlock.toHTML( value ), multilineTag: this.multilineTag, multilineWrapperTags: this.multilineWrapperTags, + preserveWhiteSpace, } ); } @@ -643,6 +486,7 @@ export class RichText extends Component { html: value, multilineTag: this.multilineTag, multilineWrapperTags: this.multilineWrapperTags, + preserveWhiteSpace, } ); } @@ -656,7 +500,9 @@ export class RichText extends Component { } shouldComponentUpdate( nextProps ) { - if ( nextProps.tagName !== this.props.tagName ) { + if ( nextProps.tagName !== this.props.tagName || + nextProps.reversed !== this.props.reversed || + nextProps.start !== this.props.start ) { this.lastEventCount = undefined; this.value = undefined; return true; @@ -740,6 +586,15 @@ export class RichText extends Component { } willTrimSpaces( html ) { + const { + tagName, + } = this.props; + + // aztec won't trim spaces in a case of <pre> block, so we are excluding it + if ( tagName === 'pre' ) { + return false; + } + // regex for detecting spaces around block element html tags const blockHtmlElements = '(div|br|blockquote|ul|ol|li|p|pre|h1|h2|h3|h4|h5|h6|iframe|hr)'; const leadingOrTrailingSpaces = new RegExp( `(\\s+)<\/?${ blockHtmlElements }>|<\/?${ blockHtmlElements }>(\\s+)`, 'g' ); @@ -753,13 +608,28 @@ export class RichText extends Component { getHtmlToRender( record, tagName ) { // Save back to HTML from React tree - const value = this.valueToFormat( record ); + let value = this.valueToFormat( record ); - if ( value === undefined || value === '' ) { + if ( value === undefined ) { this.lastEventCount = undefined; // force a refresh on the native side - return ''; - } else if ( tagName ) { - return `<${ tagName }>${ value }</${ tagName }>`; + value = ''; + } + // On android if content is empty we need to send no content or else the placeholder will not show. + if ( ! this.isIOS && value === '' ) { + return value; + } + + if ( tagName ) { + let extraAttributes = ``; + if ( tagName === `ol` ) { + if ( this.props.reversed ) { + extraAttributes += ` reversed`; + } + if ( this.props.start ) { + extraAttributes += ` start=${ this.props.start }`; + } + } + value = `<${ tagName } ${ extraAttributes }>${ value }</${ tagName }>`; } return value; } @@ -811,6 +681,25 @@ export class RichText extends Component { } else if ( this.props.selectionStart > record.text.length || this.props.selectionEnd > record.text.length ) { console.warn( 'Oops, selection will land outside the text, skipping setting it...' ); selection = null; + } else { + // The following regular expression is used in Aztec here: + // https://github.com/wordpress-mobile/AztecEditor-Android/blob/b1fad439d56fa6d4aa0b78526fef355c59d00dd3/aztec/src/main/kotlin/org/wordpress/aztec/AztecParser.kt#L656 + const brBeforeParaMatches = html.match( /(<br>)+<\/p>$/g ); + if ( brBeforeParaMatches ) { + console.warn( 'Oops, BR tag(s) at the end of content. Aztec will remove them, adapting the selection...' ); + const count = ( brBeforeParaMatches[ 0 ].match( /br/g ) || [] ).length; + if ( count > 0 ) { + let newSelectionStart = this.props.selectionStart - count; + if ( newSelectionStart < 0 ) { + newSelectionStart = 0; + } + let newSelectionEnd = this.props.selectionEnd - count; + if ( newSelectionEnd < 0 ) { + newSelectionEnd = 0; + } + selection = { start: newSelectionStart, end: newSelectionEnd }; + } + } } } } @@ -820,8 +709,6 @@ export class RichText extends Component { this.firedAfterTextChanged = false; } - const dynamicStyle = getStylesFromColorScheme( style, styles.richTextDark ); - return ( <View> { children && children( { @@ -838,7 +725,7 @@ export class RichText extends Component { } } } style={ { - ...dynamicStyle, + ...style, minHeight: Math.max( minHeight, this.state.height ), } } text={ { text: html, eventCount: this.lastEventCount, selection } } @@ -848,15 +735,15 @@ export class RichText extends Component { onChange={ this.onChange } onFocus={ this.onFocus } onBlur={ this.onBlur } - onEnter={ this.onEnter } - onBackspace={ this.onBackspace } + onEnter={ this.handleEnter } + onBackspace={ this.handleDelete } onPaste={ this.onPaste } activeFormats={ this.getActiveFormatNames( record ) } onContentSizeChange={ this.onContentSizeChange } onCaretVerticalPositionChange={ this.props.onCaretVerticalPositionChange } onSelectionChange={ this.onSelectionChangeFromAztec } blockType={ { tag: tagName } } - color={ defaultColor } + color={ ( style && style.color ) || defaultColor } linkTextColor={ defaultTextDecorationColor } maxImagesWidth={ 200 } fontFamily={ this.props.fontFamily || defaultFontFamily } diff --git a/packages/rich-text/src/component/style.native.scss b/packages/rich-text/src/component/style.native.scss index 4ed93f7f70239d..b6e72919a59946 100644 --- a/packages/rich-text/src/component/style.native.scss +++ b/packages/rich-text/src/component/style.native.scss @@ -9,7 +9,6 @@ .richTextDark { color: $white; text-decoration-color: $blue-30; - background-color: $black; } .richTextPlaceholder { diff --git a/packages/rich-text/src/component/test/index.js b/packages/rich-text/src/component/test/index.js deleted file mode 100644 index 01b14cfce1f88c..00000000000000 --- a/packages/rich-text/src/component/test/index.js +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Internal dependencies - */ -import { diffAriaProps, pickAriaProps } from '../aria'; - -describe( 'RichText', () => { - describe( 'pickAriaProps()', () => { - it( 'should should filter all properties to only those begining with "aria-"', () => { - expect( pickAriaProps( { - tagName: 'p', - className: 'class1 class2', - 'aria-label': 'my label', - style: { - backgroundColor: 'white', - color: 'black', - fontSize: '12px', - textAlign: 'left', - }, - 'aria-owns': 'some-id', - 'not-aria-prop': 'value', - ariaWithoutDash: 'value', - } ) ).toEqual( { - 'aria-label': 'my label', - 'aria-owns': 'some-id', - } ); - } ); - } ); - describe( 'diffAriaProps()', () => { - it( 'should report empty arrays for no props', () => { - expect( diffAriaProps( {}, {} ) ).toEqual( { - removedKeys: [], - updatedKeys: [], - } ); - } ); - it( 'should report empty arrays for non-aria props', () => { - expect( diffAriaProps( { - 'non-aria-prop': 'old value', - 'removed-prop': 'value', - }, { - 'non-aria-prop': 'new value', - 'added-prop': 'value', - } ) ).toEqual( { - removedKeys: [], - updatedKeys: [], - } ); - } ); - it( 'should report added aria props', () => { - expect( diffAriaProps( { - }, { - 'aria-prop': 'value', - } ) ).toEqual( { - removedKeys: [], - updatedKeys: [ 'aria-prop' ], - } ); - } ); - it( 'should report removed aria props', () => { - expect( diffAriaProps( { - 'aria-prop': 'value', - }, { - } ) ).toEqual( { - removedKeys: [ 'aria-prop' ], - updatedKeys: [], - } ); - } ); - it( 'should report changed aria props', () => { - expect( diffAriaProps( { - 'aria-prop': 'old value', - }, { - 'aria-prop': 'new value', - } ) ).toEqual( { - removedKeys: [], - updatedKeys: [ 'aria-prop' ], - } ); - } ); - it( 'should not report unchanged aria props', () => { - expect( diffAriaProps( { - 'aria-prop': 'value', - }, { - 'aria-prop': 'value', - } ) ).toEqual( { - removedKeys: [], - updatedKeys: [], - } ); - } ); - it( 'should work with a mixture of aria and non-aria props', () => { - expect( diffAriaProps( { - tagName: 'p', - className: 'class1 class2', - 'aria-label': 'my label', - style: { - backgroundColor: 'white', - color: 'black', - fontSize: '12px', - textAlign: 'left', - }, - 'aria-owns': 'some-id', - 'aria-active': 'some-active-id', - 'not-aria-prop': 'old value', - }, { - tagName: 'div', - className: 'class1 class2', - style: { - backgroundColor: 'red', - color: 'black', - fontSize: '12px', - }, - 'aria-active': 'some-other-active-id', - 'not-aria-prop': 'new value', - 'aria-label': 'my label', - } ) ).toEqual( { - removedKeys: [ 'aria-owns' ], - updatedKeys: [ 'aria-active' ], - } ); - } ); - } ); -} ); diff --git a/packages/rich-text/src/component/test/index.native.js b/packages/rich-text/src/component/test/index.native.js index 6b2bc12f855ffa..f3d35bff0501c7 100644 --- a/packages/rich-text/src/component/test/index.native.js +++ b/packages/rich-text/src/component/test/index.native.js @@ -1,17 +1,8 @@ -/** - * External dependencies - */ -import { shallow } from 'enzyme'; - /** * Internal dependencies */ import { RichText } from '../index'; -const getStylesFromColorScheme = () => { - return { color: 'white' }; -}; - describe( 'RichText Native', () => { let richText; @@ -32,30 +23,11 @@ describe( 'RichText Native', () => { const html = '<p><b>Hello</b> <strong>Hello</strong> WorldWorld!</p>'; expect( richText.willTrimSpaces( html ) ).toBe( false ); } ); - } ); - - describe( 'Adds new line on Enter', () => { - let newValue; - const wrapper = shallow( <RichText - rootTagsToEliminate={ [ 'p' ] } - value="" - onChange={ ( value ) => { - newValue = value; - } } - formatTypes={ [] } - onSelectionChange={ jest.fn() } - getStylesFromColorScheme={ getStylesFromColorScheme } - /> ); - const event = { - nativeEvent: { - eventCount: 0, - }, - }; - wrapper.instance().onEnter( event ); - - it( ' Adds <br> tag to content after pressing Enter key', () => { - expect( newValue ).toEqual( '<br>' ); + it( 'reports false for Preformatted block', () => { + const html = '<pre>Hello World <br><br><br></pre>'; + richText.props.tagName = 'pre'; + expect( richText.willTrimSpaces( html ) ).toBe( false ); } ); } ); } ); diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index 92251fc547b8e6..3241552dbd26d0 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -130,6 +130,8 @@ function toFormat( { type, attributes } ) { * multiline. * @param {Array} [$1.multilineWrapperTags] Tags where lines can be found if * nesting is possible. + * @param {?boolean} [$1.preserveWhiteSpace] Whether or not to collapse white + * space characters. * * @return {Object} A rich text value. */ @@ -141,6 +143,7 @@ export function create( { multilineTag, multilineWrapperTags, __unstableIsEditableTree: isEditableTree, + preserveWhiteSpace, } = {} ) { if ( typeof text === 'string' && text.length > 0 ) { return { @@ -163,6 +166,7 @@ export function create( { element, range, isEditableTree, + preserveWhiteSpace, } ); } @@ -172,6 +176,7 @@ export function create( { multilineTag, multilineWrapperTags, isEditableTree, + preserveWhiteSpace, } ); } @@ -268,14 +273,25 @@ function filterRange( node, range, filter ) { return { startContainer, startOffset, endContainer, endOffset }; } +/** + * Collapse any whitespace used for HTML formatting to one space character, + * because it will also be displayed as such by the browser. + * + * @param {string} string + */ +function collapseWhiteSpace( string ) { + return string.replace( /[\n\r\t]+/g, ' ' ); +} + const ZWNBSPRegExp = new RegExp( ZWNBSP, 'g' ); -function filterString( string ) { - // Reduce any whitespace used for HTML formatting to one space - // character, because it will also be displayed as such by the browser. - return string.replace( /[\n\r\t]+/g, ' ' ) - // Remove padding added by `toTree`. - .replace( ZWNBSPRegExp, '' ); +/** + * Removes padding (zero width non breaking spaces) added by `toTree`. + * + * @param {string} string + */ +function removePadding( string ) { + return string.replace( ZWNBSPRegExp, '' ); } /** @@ -288,6 +304,8 @@ function filterString( string ) { * multiline. * @param {?Array} $1.multilineWrapperTags Tags where lines can be found if * nesting is possible. + * @param {?boolean} $1.preserveWhiteSpace Whether or not to collapse white + * space characters. * * @return {Object} A rich text value. */ @@ -298,6 +316,7 @@ function createFromElement( { multilineWrapperTags, currentWrapperTags = [], isEditableTree, + preserveWhiteSpace, } ) { const accumulator = createEmptyValue(); @@ -318,8 +337,15 @@ function createFromElement( { const type = node.nodeName.toLowerCase(); if ( node.nodeType === TEXT_NODE ) { - const text = filterString( node.nodeValue ); - range = filterRange( node, range, filterString ); + let filter = removePadding; + + if ( ! preserveWhiteSpace ) { + filter = ( string ) => + removePadding( collapseWhiteSpace( string ) ); + } + + const text = filter( node.nodeValue ); + range = filterRange( node, range, filter ); accumulateSelection( accumulator, node, range, { text } ); // Create a sparse array of the same length as `text`, in which // formats can be added. @@ -365,6 +391,7 @@ function createFromElement( { multilineWrapperTags, currentWrapperTags: [ ...currentWrapperTags, format ], isEditableTree, + preserveWhiteSpace, } ); accumulateSelection( accumulator, node, range, value ); @@ -378,6 +405,7 @@ function createFromElement( { multilineTag, multilineWrapperTags, isEditableTree, + preserveWhiteSpace, } ); accumulateSelection( accumulator, node, range, value ); @@ -409,15 +437,17 @@ function createFromElement( { * Creates a rich text value from a DOM element and range that should be * multiline. * - * @param {Object} $1 Named argements. - * @param {?Element} $1.element Element to create value from. - * @param {?Range} $1.range Range to create value from. - * @param {?string} $1.multilineTag Multiline tag if the structure is - * multiline. - * @param {?Array} $1.multilineWrapperTags Tags where lines can be found if - * nesting is possible. - * @param {boolean} $1.currentWrapperTags Whether to prepend a line - * separator. + * @param {Object} $1 Named argements. + * @param {?Element} $1.element Element to create value from. + * @param {?Range} $1.range Range to create value from. + * @param {?string} $1.multilineTag Multiline tag if the structure is + * multiline. + * @param {?Array} $1.multilineWrapperTags Tags where lines can be found if + * nesting is possible. + * @param {boolean} $1.currentWrapperTags Whether to prepend a line + * separator. + * @param {?boolean} $1.preserveWhiteSpace Whether or not to collapse white + * space characters. * * @return {Object} A rich text value. */ @@ -428,6 +458,7 @@ function createFromMultilineElement( { multilineWrapperTags, currentWrapperTags = [], isEditableTree, + preserveWhiteSpace, } ) { const accumulator = createEmptyValue(); @@ -452,6 +483,7 @@ function createFromMultilineElement( { multilineWrapperTags, currentWrapperTags, isEditableTree, + preserveWhiteSpace, } ); // Multiline value text should be separated by a line separator. diff --git a/packages/rich-text/src/indent-list-items.js b/packages/rich-text/src/indent-list-items.js index 985d1062c76ed1..bee5d96f1baca2 100644 --- a/packages/rich-text/src/indent-list-items.js +++ b/packages/rich-text/src/indent-list-items.js @@ -4,6 +4,7 @@ import { LINE_SEPARATOR } from './special-characters'; import { getLineIndex } from './get-line-index'; +import { canIndentListItems } from './can-indent-list-items'; /** * Gets the line index of the first previous list item with higher indentation. @@ -44,24 +45,13 @@ function getTargetLevelLineIndex( { text, replacements }, lineIndex ) { * @return {Object} The changed value. */ export function indentListItems( value, rootFormat ) { - const lineIndex = getLineIndex( value ); - - // There is only one line, so the line cannot be indented. - if ( lineIndex === undefined ) { + if ( ! canIndentListItems( value ) ) { return value; } - const { text, replacements, end } = value; + const lineIndex = getLineIndex( value ); const previousLineIndex = getLineIndex( value, lineIndex ); - const formatsAtLineIndex = replacements[ lineIndex ] || []; - const formatsAtPreviousLineIndex = replacements[ previousLineIndex ] || []; - - // The the indentation of the current line is greater than previous line, - // then the line cannot be furter indented. - if ( formatsAtLineIndex.length > formatsAtPreviousLineIndex.length ) { - return value; - } - + const { text, replacements, end } = value; const newFormats = replacements.slice(); const targetLevelLineIndex = getTargetLevelLineIndex( value, lineIndex ); diff --git a/packages/rich-text/src/index.js b/packages/rich-text/src/index.js index eb249f1240054d..ac4cea67d4757c 100644 --- a/packages/rich-text/src/index.js +++ b/packages/rich-text/src/index.js @@ -29,6 +29,8 @@ export { toHTMLString } from './to-html-string'; export { toggleFormat } from './toggle-format'; export { LINE_SEPARATOR as __UNSTABLE_LINE_SEPARATOR } from './special-characters'; export { unregisterFormatType } from './unregister-format-type'; +export { canIndentListItems as __unstableCanIndentListItems } from './can-indent-list-items'; +export { canOutdentListItems as __unstableCanOutdentListItems } from './can-outdent-list-items'; export { indentListItems as __unstableIndentListItems } from './indent-list-items'; export { outdentListItems as __unstableOutdentListItems } from './outdent-list-items'; export { changeListType as __unstableChangeListType } from './change-list-type'; diff --git a/packages/rich-text/src/outdent-list-items.js b/packages/rich-text/src/outdent-list-items.js index 19fac90515dfb2..99cae6c12944f3 100644 --- a/packages/rich-text/src/outdent-list-items.js +++ b/packages/rich-text/src/outdent-list-items.js @@ -6,6 +6,7 @@ import { LINE_SEPARATOR } from './special-characters'; import { getLineIndex } from './get-line-index'; import { getParentLineIndex } from './get-parent-line-index'; import { getLastChildIndex } from './get-last-child-index'; +import { canOutdentListItems } from './can-outdent-list-items'; /** * Outdents any selected list items if possible. @@ -15,14 +16,12 @@ import { getLastChildIndex } from './get-last-child-index'; * @return {Object} The changed value. */ export function outdentListItems( value ) { - const { text, replacements, start, end } = value; - const startingLineIndex = getLineIndex( value, start ); - - // Return early if the starting line index cannot be further outdented. - if ( replacements[ startingLineIndex ] === undefined ) { + if ( ! canOutdentListItems( value ) ) { return value; } + const { text, replacements, start, end } = value; + const startingLineIndex = getLineIndex( value, start ); const newFormats = replacements.slice( 0 ); const parentFormats = replacements[ getParentLineIndex( value, startingLineIndex ) ] || []; const endingLineIndex = getLineIndex( value, end ); diff --git a/packages/rich-text/src/register-format-type.js b/packages/rich-text/src/register-format-type.js index e6efc99f5fec97..84a158167b860f 100644 --- a/packages/rich-text/src/register-format-type.js +++ b/packages/rich-text/src/register-format-type.js @@ -10,16 +10,25 @@ import { select, dispatch, withSelect, withDispatch } from '@wordpress/data'; import { addFilter } from '@wordpress/hooks'; import { compose } from '@wordpress/compose'; +/** + * @typedef {Object} WPFormat + * + * @property {string} name A string identifying the format. Must be + * unique across all registered formats. + * @property {string} tagName The HTML tag this format will wrap the + * selection with. + * @property {string} [className] A class to match the format. + * @property {string} title Name of the format. + * @property {Function} edit Should return a component for the user to + * interact with the new registered format. + */ + /** * Registers a new format provided a unique name and an object defining its * behavior. * * @param {string} name Format name. - * @param {Object} settings Format settings. - * @param {string} settings.tagName The HTML tag this format will wrap the selection with. - * @param {string} [settings.className] A class to match the format. - * @param {string} settings.title Name of the format. - * @param {Function} settings.edit Should return a component for the user to interact with the new registered format. + * @param {WPFormat} settings Format settings. * * @return {WPFormat|undefined} The format, if it has been successfully registered; * otherwise `undefined`. diff --git a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap index 755cfb068aaddd..be2a72211e428b 100644 --- a/packages/rich-text/src/test/__snapshots__/to-dom.js.snap +++ b/packages/rich-text/src/test/__snapshots__/to-dom.js.snap @@ -321,6 +321,46 @@ exports[`recordToDom should ignore formats at line separator 1`] = ` </body> `; +exports[`recordToDom should not error with overlapping formats (1) 1`] = ` +<body> + <a + href="#" + > + <em> + 1 + </em> + <strong + data-rich-text-format-boundary="true" + > + 2 + </strong> + </a> + +</body> +`; + +exports[`recordToDom should not error with overlapping formats (2) 1`] = ` +<body> + <em> + <a + data-rich-text-format-boundary="true" + href="#" + > + 1 + </a> + </em> + <strong> + <a + data-rich-text-format-boundary="true" + href="#" + > + 2 + </a> + </strong> + +</body> +`; + exports[`recordToDom should preserve emoji 1`] = ` <body> 🍒 diff --git a/packages/rich-text/src/test/can-indent-list-items.js b/packages/rich-text/src/test/can-indent-list-items.js new file mode 100644 index 00000000000000..12badf08bebb3b --- /dev/null +++ b/packages/rich-text/src/test/can-indent-list-items.js @@ -0,0 +1,51 @@ +/** + * External dependencies + */ +import deepFreeze from 'deep-freeze'; + +/** + * Internal dependencies + */ + +import { canIndentListItems } from '../can-indent-list-items'; +import { LINE_SEPARATOR } from '../special-characters'; + +describe( 'indentListItems', () => { + const ul = { type: 'ul' }; + + it( 'should not be able to indent if no previous item', () => { + const record = { + replacements: [ , ], + text: '1', + start: 1, + end: 1, + }; + const result = canIndentListItems( deepFreeze( record ) ); + + expect( result ).toBe( false ); + } ); + + it( 'should be able to indent if previous item', () => { + const record = { + replacements: [ , , ], + text: `1${ LINE_SEPARATOR }`, + start: 2, + end: 2, + }; + const result = canIndentListItems( deepFreeze( record ) ); + + expect( result ).toBe( true ); + } ); + + it( 'should not be able to indent already far indented item', () => { + const record = { + replacements: [ , [ ul ] ], + text: `1${ LINE_SEPARATOR }`, + start: 2, + end: 2, + }; + const result = canIndentListItems( deepFreeze( record ) ); + + expect( result ).toBe( false ); + } ); +} ); diff --git a/packages/rich-text/src/test/can-outdent-list-items.js b/packages/rich-text/src/test/can-outdent-list-items.js new file mode 100644 index 00000000000000..d038d0c8422eb9 --- /dev/null +++ b/packages/rich-text/src/test/can-outdent-list-items.js @@ -0,0 +1,39 @@ +/** + * External dependencies + */ +import deepFreeze from 'deep-freeze'; + +/** + * Internal dependencies + */ + +import { canOutdentListItems } from '../can-outdent-list-items'; +import { LINE_SEPARATOR } from '../special-characters'; + +describe( 'outdentListItems', () => { + const ul = { type: 'ul' }; + + it( 'should not be able to outdent if not indented', () => { + const record = { + replacements: [ , ], + text: '1', + start: 1, + end: 1, + }; + const result = canOutdentListItems( deepFreeze( record ) ); + + expect( result ).toBe( false ); + } ); + + it( 'should be able to outdent if indented', () => { + const record = { + replacements: [ , [ ul ] ], + text: `1${ LINE_SEPARATOR }`, + start: 2, + end: 2, + }; + const result = canOutdentListItems( deepFreeze( record ) ); + + expect( result ).toBe( true ); + } ); +} ); diff --git a/packages/rich-text/src/test/helpers/index.js b/packages/rich-text/src/test/helpers/index.js index fe97f00d245f53..45206a6d3e9a09 100644 --- a/packages/rich-text/src/test/helpers/index.js +++ b/packages/rich-text/src/test/helpers/index.js @@ -4,7 +4,7 @@ import { ZWNBSP } from '../../special-characters'; export function getSparseArrayLength( array ) { - return array.reduce( ( i ) => i + 1, 0 ); + return array.reduce( ( accumulator ) => accumulator + 1, 0 ); } const em = { type: 'em' }; @@ -609,6 +609,44 @@ export const spec = [ text: 'test', }, }, + { + description: 'should not error with overlapping formats (1)', + html: '<a href="#"><em>1</em><strong>2</strong></a>', + createRange: ( element ) => ( { + startOffset: 1, + startContainer: element.firstChild, + endOffset: 1, + endContainer: element.firstChild, + } ), + startPath: [ 0, 0, 0, 1 ], + endPath: [ 0, 0, 0, 1 ], + record: { + start: 1, + end: 1, + formats: [ [ a, em ], [ a, strong ] ], + replacements: [ , , ], + text: '12', + }, + }, + { + description: 'should not error with overlapping formats (2)', + html: '<em><a href="#">1</a></em><strong><a href="#">2</a></strong>', + createRange: ( element ) => ( { + startOffset: 1, + startContainer: element.firstChild, + endOffset: 1, + endContainer: element.firstChild, + } ), + startPath: [ 0, 0, 0, 1 ], + endPath: [ 0, 0, 0, 1 ], + record: { + start: 1, + end: 1, + formats: [ [ em, a ], [ strong, a ] ], + replacements: [ , , ], + text: '12', + }, + }, ]; export const specWithRegistration = [ diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js index 9d7aed3cfdfd00..730a38587114c4 100644 --- a/packages/rich-text/src/to-dom.js +++ b/packages/rich-text/src/to-dom.js @@ -67,7 +67,7 @@ function getNodeByPath( node, path ) { * each call to `createEmpty`. Therefore, you should not hold a reference to * the value to operate upon asynchronously, as it may have unexpected results. * - * @return {WPRichTextTree} RichText tree. + * @return {Object} RichText tree. */ const createEmpty = () => createElement( document, '' ); diff --git a/packages/rich-text/src/to-html-string.js b/packages/rich-text/src/to-html-string.js index bf6c60fda0442d..bf18f3346cc604 100644 --- a/packages/rich-text/src/to-html-string.js +++ b/packages/rich-text/src/to-html-string.js @@ -3,7 +3,7 @@ */ import { - escapeHTML, + escapeEditableHTML, escapeAttribute, isValidAttributeName, } from '@wordpress/escape-html'; @@ -18,16 +18,19 @@ import { toTree } from './to-tree'; * Create an HTML string from a Rich Text value. If a `multilineTag` is * provided, text separated by a line separator will be wrapped in it. * - * @param {Object} $1 Named argements. - * @param {Object} $1.value Rich text value. - * @param {string} [$1.multilineTag] Multiline tag. + * @param {Object} $1 Named argements. + * @param {Object} $1.value Rich text value. + * @param {string} [$1.multilineTag] Multiline tag. + * @param {?boolean} [$1.preserveWhiteSpace] Whether or not to use newline + * characters for line breaks. * * @return {string} HTML string. */ -export function toHTMLString( { value, multilineTag } ) { +export function toHTMLString( { value, multilineTag, preserveWhiteSpace } ) { const tree = toTree( { value, multilineTag, + preserveWhiteSpace, createEmpty, append, getLastChild, @@ -106,6 +109,6 @@ function createElementHTML( { type, attributes, object, children } ) { function createChildrenHTML( children = [] ) { return children.map( ( child ) => { - return child.text === undefined ? createElementHTML( child ) : escapeHTML( child.text ); + return child.text === undefined ? createElementHTML( child ) : escapeEditableHTML( child.text ); } ).join( '' ); } diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js index 6e50be6a42cdd5..dc352bd23f02b3 100644 --- a/packages/rich-text/src/to-tree.js +++ b/packages/rich-text/src/to-tree.js @@ -70,9 +70,27 @@ function fromFormat( { type, attributes, unregisteredAttributes, object, boundar }; } +/** + * Checks if both arrays of formats up until a certain index are equal. + * + * @param {Array} a Array of formats to compare. + * @param {Array} b Array of formats to compare. + * @param {number} index Index to check until. + */ +function isEqualUntil( a, b, index ) { + do { + if ( a[ index ] !== b[ index ] ) { + return false; + } + } while ( index-- ); + + return true; +} + export function toTree( { value, multilineTag, + preserveWhiteSpace, createEmpty, append, getLastChild, @@ -164,7 +182,8 @@ export function toTree( { if ( pointer && lastCharacterFormats && - format === lastCharacterFormats[ formatIndex ] && + // Reuse the last element if all formats remain the same. + isEqualUntil( characterFormats, lastCharacterFormats, formatIndex ) && // Do not reuse the last element if the character is a // line separator. ( character !== LINE_SEPARATOR || @@ -223,7 +242,7 @@ export function toTree( { } ) ); // Ensure pointer is text node. pointer = append( getParent( pointer ), '' ); - } else if ( character === '\n' ) { + } else if ( ! preserveWhiteSpace && character === '\n' ) { pointer = append( getParent( pointer ), { type: 'br', attributes: isEditableTree ? { diff --git a/packages/rich-text/src/unregister-format-type.js b/packages/rich-text/src/unregister-format-type.js index a70c1d1d417f2b..aa0d4963b16bba 100644 --- a/packages/rich-text/src/unregister-format-type.js +++ b/packages/rich-text/src/unregister-format-type.js @@ -4,6 +4,8 @@ import { select, dispatch } from '@wordpress/data'; import { removeFilter } from '@wordpress/hooks'; +/** @typedef {import('./register-format-type').WPFormat} WPFormat */ + /** * Unregisters a format. * diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 96e7ae747107c8..f40762f187869a 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -1,4 +1,11 @@ -## Master +## 6.0.0 (2019-11-14) + +### Breaking Changes + +- The bundled `npm-package-json-lint` dependency has been updated from requiring `^3.6.0` to requiring `^4.0.3` ([#18054](https://github.com/WordPress/gutenberg/pull/18054)). Please see the [migration guide](https://npmpackagejsonlint.org/docs/en/v3-to-v4). Note: `npmPackageJsonLintConfig` prop in the `package.json` file needs to be renamed to `npmpackagejsonlint`. +- The bundled `puppeteer` dependency has been updated from requiring `^1.19.0` to requiring `^2.0.0` ([#18205](https://github.com/WordPress/gutenberg/pull/18205)). It uses Chromium v79 instead of Chromium v77. See the [full list of changes](https://github.com/GoogleChrome/puppeteer/releases/tag/v2.0.0). + +## 5.1.0 ### New Features diff --git a/packages/scripts/README.md b/packages/scripts/README.md index ff62c86c3352d4..cee0e05c9daf7a 100644 --- a/packages/scripts/README.md +++ b/packages/scripts/README.md @@ -42,9 +42,9 @@ It might also be a good idea to get familiar with the [JavaScript Build Setup tu ## Updating to New Release -To update an existing project to a new version of `@wordpress/scripts`, open the [changelog](/packages/scripts/CHANGELOG.md), find the version you’re currently on (check `package.json` in the top-level directory of your project), and apply the migration instructions for the newer versions. +To update an existing project to a new version of `@wordpress/scripts`, open the [changelog](https://github.com/WordPress/gutenberg/blob/master/packages/scripts/CHANGELOG.md), find the version you’re currently on (check `package.json` in the top-level directory of your project), and apply the migration instructions for the newer versions. -In most cases bumping the `@wordpress/scripts` version in `package.json` and running `npm install` in the root folder of your project should be enough, but it’s good to check the [changelog](/packages/scripts/CHANGELOG.md) for potential breaking changes. +In most cases bumping the `@wordpress/scripts` version in `package.json` and running `npm install` in the root folder of your project should be enough, but it’s good to check the [changelog](https://github.com/WordPress/gutenberg/blob/master/packages/scripts/CHANGELOG.md) for potential breaking changes. We commit to keeping the breaking changes minimal so you can upgrade `@wordpress/scripts` as seamless as possible. diff --git a/packages/scripts/config/jest-e2e.config.js b/packages/scripts/config/jest-e2e.config.js index df119629454633..e5243e1af32c2a 100644 --- a/packages/scripts/config/jest-e2e.config.js +++ b/packages/scripts/config/jest-e2e.config.js @@ -10,14 +10,14 @@ const { hasBabelConfig } = require( '../utils' ); const jestE2EConfig = { preset: 'jest-puppeteer', - testMatch: [ - '**/specs/**/*.[jt]s', - '**/?(*.)spec.[jt]s', - ], - testPathIgnorePatterns: [ - '/node_modules/', - '/wordpress/', - ], + testMatch: [ '**/specs/**/*.[jt]s', '**/?(*.)spec.[jt]s' ], + testPathIgnorePatterns: [ '/node_modules/', '/wordpress/' ], + reporters: + 'TRAVIS' in process.env && 'CI' in process.env ? + [ + '@wordpress/jest-preset-default/scripts/travis-fold-passes-reporter.js', + ] : + undefined, }; if ( ! hasBabelConfig() ) { diff --git a/packages/scripts/package.json b/packages/scripts/package.json index a50c307eeeee68..f06c00e125e841 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/scripts", - "version": "5.0.0", + "version": "6.0.0", "description": "Collection of reusable scripts for WordPress development.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -38,7 +38,7 @@ "@wordpress/jest-preset-default": "file:../jest-preset-default", "@wordpress/npm-package-json-lint-config": "file:../npm-package-json-lint-config", "babel-jest": "^24.7.1", - "babel-loader": "^8.0.5", + "babel-loader": "^8.0.6", "chalk": "^2.4.2", "check-node-version": "^3.1.1", "command-exists": "^1.2.8", @@ -50,8 +50,8 @@ "js-yaml": "^3.13.1", "lodash": "^4.17.15", "minimist": "^1.2.0", - "npm-package-json-lint": "^3.6.0", - "puppeteer": "^1.19.0", + "npm-package-json-lint": "^4.0.3", + "puppeteer": "^2.0.0", "read-pkg-up": "^1.0.1", "request": "^2.88.0", "resolve-bin": "^0.4.0", diff --git a/packages/scripts/scripts/lint-pkg-json.js b/packages/scripts/scripts/lint-pkg-json.js index 487a8191067dbc..53e9c42b366437 100644 --- a/packages/scripts/scripts/lint-pkg-json.js +++ b/packages/scripts/scripts/lint-pkg-json.js @@ -25,6 +25,8 @@ const hasLintConfig = hasArgInCLI( '-c' ) || hasArgInCLI( '--configFile' ) || hasProjectFile( '.npmpackagejsonlintrc.json' ) || hasProjectFile( 'npmpackagejsonlint.config.js' ) || + hasPackageProp( 'npmpackagejsonlint' ) || + // npm-package-json-lint v3.x used a different prop name. hasPackageProp( 'npmPackageJsonLintConfig' ); const defaultConfigArgs = ! hasLintConfig ? diff --git a/packages/server-side-render/README.md b/packages/server-side-render/README.md index 49bb3a7c94bb16..dcd3d6b2bc5906 100644 --- a/packages/server-side-render/README.md +++ b/packages/server-side-render/README.md @@ -79,7 +79,7 @@ This is a [render prop](https://reactjs.org/docs/render-props.html). While the r Render core/archives preview. ```jsx -import { ServerSideRender } from '@wordpress/server-side-render'; +import ServerSideRender from '@wordpress/server-side-render'; const MyServerSideRender = () => ( <ServerSideRender diff --git a/packages/server-side-render/package.json b/packages/server-side-render/package.json index 99aa8807a8bf6c..20d8e6dc3210e2 100644 --- a/packages/server-side-render/package.json +++ b/packages/server-side-render/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/server-side-render", - "version": "1.3.0", + "version": "1.4.0", "description": "The component used with WordPress to server-side render a preview of dynamic blocks to display in the editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/shortcode/package.json b/packages/shortcode/package.json index f50b996609dd7d..257150e48cb9bd 100644 --- a/packages/shortcode/package.json +++ b/packages/shortcode/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/shortcode", - "version": "2.4.0", + "version": "2.5.0", "description": "Shortcode module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/token-list/package.json b/packages/token-list/package.json index 09b2244b92c96a..1048b9be13de93 100644 --- a/packages/token-list/package.json +++ b/packages/token-list/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/token-list", - "version": "1.6.0", + "version": "1.7.0", "description": "Constructable, plain JavaScript DOMTokenList implementation, supporting non-browser runtimes.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/token-list/src/index.js b/packages/token-list/src/index.js index e97bfabaa77cf1..91bfe60c16a5ec 100644 --- a/packages/token-list/src/index.js +++ b/packages/token-list/src/index.js @@ -18,9 +18,7 @@ export default class TokenList { this.value = initialValue; [ 'entries', 'forEach', 'keys', 'values' ].forEach( ( fn ) => { - this[ fn ] = ( function() { - return this._valueAsArray[ fn ]( ...arguments ); - } ).bind( this ); + this[ fn ] = ( ...args ) => this._valueAsArray[ fn ]( ...args ); } ); } @@ -89,7 +87,7 @@ export default class TokenList { * * @param {number} index Index at which to return token. * - * @return {?string} Token at index. + * @return {string|undefined} Token at index. */ item( index ) { return this._valueAsArray[ index ]; @@ -138,8 +136,8 @@ export default class TokenList { * * @see https://dom.spec.whatwg.org/#dom-domtokenlist-toggle * - * @param {string} token Token to toggle. - * @param {?boolean} force Presence to force. + * @param {string} token Token to toggle. + * @param {boolean} [force] Presence to force. * * @return {boolean} Whether token is present after toggle. */ diff --git a/packages/url/package.json b/packages/url/package.json index 062daaf67559e2..bc1e8bf3587eb0 100644 --- a/packages/url/package.json +++ b/packages/url/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/url", - "version": "2.8.0", + "version": "2.8.2", "description": "WordPress URL utilities.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -20,6 +20,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "sideEffects": false, "dependencies": { "@babel/runtime": "^7.4.4", "qs": "^6.5.2" diff --git a/packages/url/src/add-query-args.js b/packages/url/src/add-query-args.js new file mode 100644 index 00000000000000..9a1add3af409be --- /dev/null +++ b/packages/url/src/add-query-args.js @@ -0,0 +1,44 @@ +/** + * External dependencies + */ +import { parse, stringify } from 'qs'; + +/** + * Appends arguments as querystring to the provided URL. If the URL already + * includes query arguments, the arguments are merged with (and take precedent + * over) the existing set. + * + * @param {string} [url=''] URL to which arguments should be appended. If omitted, + * only the resulting querystring is returned. + * @param {Object} args Query arguments to apply to URL. + * + * @example + * ```js + * const newURL = addQueryArgs( 'https://google.com', { q: 'test' } ); // https://google.com/?q=test + * ``` + * + * @return {string} URL with arguments applied. + */ +export function addQueryArgs( url = '', args ) { + // If no arguments are to be appended, return original URL. + if ( ! args || ! Object.keys( args ).length ) { + return url; + } + + let baseUrl = url; + + // Determine whether URL already had query arguments. + const queryStringIndex = url.indexOf( '?' ); + if ( queryStringIndex !== -1 ) { + // Merge into existing query arguments. + args = Object.assign( + parse( url.substr( queryStringIndex + 1 ) ), + args + ); + + // Change working base URL to omit previous query arguments. + baseUrl = baseUrl.substr( 0, queryStringIndex ); + } + + return baseUrl + '?' + stringify( args ); +} diff --git a/packages/url/src/filter-url-for-display.js b/packages/url/src/filter-url-for-display.js new file mode 100644 index 00000000000000..978594aced536c --- /dev/null +++ b/packages/url/src/filter-url-for-display.js @@ -0,0 +1,23 @@ +/** + * Returns a URL for display. + * + * @param {string} url Original URL. + * + * @example + * ```js + * const displayUrl = filterURLForDisplay( 'https://www.wordpress.org/gutenberg/' ); // wordpress.org/gutenberg + * ``` + * + * @return {string} Displayed URL. + */ +export function filterURLForDisplay( url ) { + // Remove protocol and www prefixes. + const filteredURL = url.replace( /^(?:https?:)\/\/(?:www\.)?/, '' ); + + // Ends with / and only has that single slash, strip it. + if ( filteredURL.match( /^[^\/]+\/$/ ) ) { + return filteredURL.replace( '/', '' ); + } + + return filteredURL; +} diff --git a/packages/url/src/get-authority.js b/packages/url/src/get-authority.js new file mode 100644 index 00000000000000..68315432f07cde --- /dev/null +++ b/packages/url/src/get-authority.js @@ -0,0 +1,19 @@ +/** + * Returns the authority part of the URL. + * + * @param {string} url The full URL. + * + * @example + * ```js + * const authority1 = getAuthority( 'https://wordpress.org/help/' ); // 'wordpress.org' + * const authority2 = getAuthority( 'https://localhost:8080/test/' ); // 'localhost:8080' + * ``` + * + * @return {string|void} The authority part of the URL. + */ +export function getAuthority( url ) { + const matches = /^[^\/\s:]+:(?:\/\/)?\/?([^\/\s#?]+)[\/#?]{0,1}\S*$/.exec( url ); + if ( matches ) { + return matches[ 1 ]; + } +} diff --git a/packages/url/src/get-fragment.js b/packages/url/src/get-fragment.js new file mode 100644 index 00000000000000..4f15ef0ce06af8 --- /dev/null +++ b/packages/url/src/get-fragment.js @@ -0,0 +1,19 @@ +/** + * Returns the fragment part of the URL. + * + * @param {string} url The full URL + * + * @example + * ```js + * const fragment1 = getFragment( 'http://localhost:8080/this/is/a/test?query=true#fragment' ); // '#fragment' + * const fragment2 = getFragment( 'https://wordpress.org#another-fragment?query=true' ); // '#another-fragment' + * ``` + * + * @return {string|void} The fragment part of the URL. + */ +export function getFragment( url ) { + const matches = /^\S+?(#[^\s\?]*)/.exec( url ); + if ( matches ) { + return matches[ 1 ]; + } +} diff --git a/packages/url/src/get-path.js b/packages/url/src/get-path.js new file mode 100644 index 00000000000000..ddc79b4963c212 --- /dev/null +++ b/packages/url/src/get-path.js @@ -0,0 +1,19 @@ +/** + * Returns the path part of the URL. + * + * @param {string} url The full URL. + * + * @example + * ```js + * const path1 = getPath( 'http://localhost:8080/this/is/a/test?query=true' ); // 'this/is/a/test' + * const path2 = getPath( 'https://wordpress.org/help/faq/' ); // 'help/faq' + * ``` + * + * @return {string|void} The path part of the URL. + */ +export function getPath( url ) { + const matches = /^[^\/\s:]+:(?:\/\/)?[^\/\s#?]+[\/]([^\s#?]+)[#?]{0,1}\S*$/.exec( url ); + if ( matches ) { + return matches[ 1 ]; + } +} diff --git a/packages/url/src/get-protocol.js b/packages/url/src/get-protocol.js new file mode 100644 index 00000000000000..07b375c10f841b --- /dev/null +++ b/packages/url/src/get-protocol.js @@ -0,0 +1,19 @@ +/** + * Returns the protocol part of the URL. + * + * @param {string} url The full URL. + * + * @example + * ```js + * const protocol1 = getProtocol( 'tel:012345678' ); // 'tel:' + * const protocol2 = getProtocol( 'https://wordpress.org' ); // 'https:' + * ``` + * + * @return {string|void} The protocol part of the URL. + */ +export function getProtocol( url ) { + const matches = /^([^\s:]+:)/.exec( url ); + if ( matches ) { + return matches[ 1 ]; + } +} diff --git a/packages/url/src/get-query-arg.js b/packages/url/src/get-query-arg.js new file mode 100644 index 00000000000000..2dffc6fd4135a7 --- /dev/null +++ b/packages/url/src/get-query-arg.js @@ -0,0 +1,32 @@ +/** + * External dependencies + */ +import { parse } from 'qs'; + +/** + * @typedef {{[key: string]: QueryArgParsed}} QueryArgObject + */ + +/** + * @typedef {string|string[]|QueryArgObject} QueryArgParsed + */ + +/** + * Returns a single query argument of the url + * + * @param {string} url URL. + * @param {string} arg Query arg name. + * + * @example + * ```js + * const foo = getQueryArg( 'https://wordpress.org?foo=bar&bar=baz', 'foo' ); // bar + * ``` + * + * @return {QueryArgParsed|undefined} Query arg value. + */ +export function getQueryArg( url, arg ) { + const queryStringIndex = url.indexOf( '?' ); + const query = queryStringIndex !== -1 ? parse( url.substr( queryStringIndex + 1 ) ) : {}; + + return query[ arg ]; +} diff --git a/packages/url/src/get-query-string.js b/packages/url/src/get-query-string.js new file mode 100644 index 00000000000000..8341da1f9fa1bd --- /dev/null +++ b/packages/url/src/get-query-string.js @@ -0,0 +1,19 @@ +/** + * Returns the query string part of the URL. + * + * @param {string} url The full URL. + * + * @example + * ```js + * const queryString1 = getQueryString( 'http://localhost:8080/this/is/a/test?query=true#fragment' ); // 'query=true' + * const queryString2 = getQueryString( 'https://wordpress.org#fragment?query=false&search=hello' ); // 'query=false&search=hello' + * ``` + * + * @return {string|void} The query string part of the URL. + */ +export function getQueryString( url ) { + const matches = /^\S+?\?([^\s#]+)/.exec( url ); + if ( matches ) { + return matches[ 1 ]; + } +} diff --git a/packages/url/src/has-query-arg.js b/packages/url/src/has-query-arg.js new file mode 100644 index 00000000000000..a940338dd16655 --- /dev/null +++ b/packages/url/src/has-query-arg.js @@ -0,0 +1,21 @@ +/** + * Internal dependencies + */ +import { getQueryArg } from './get-query-arg'; + +/** + * Determines whether the URL contains a given query arg. + * + * @param {string} url URL. + * @param {string} arg Query arg name. + * + * @example + * ```js + * const hasBar = hasQueryArg( 'https://wordpress.org?foo=bar&bar=baz', 'bar' ); // true + * ``` + * + * @return {boolean} Whether or not the URL contains the query arg. + */ +export function hasQueryArg( url, arg ) { + return getQueryArg( url, arg ) !== undefined; +} diff --git a/packages/url/src/index.js b/packages/url/src/index.js index b4fa34efac51a6..e39d95f2804867 100644 --- a/packages/url/src/index.js +++ b/packages/url/src/index.js @@ -1,430 +1,20 @@ -/** - * External dependencies - */ -import { parse, stringify } from 'qs'; - -const URL_REGEXP = /^(?:https?:)?\/\/\S+$/i; -const EMAIL_REGEXP = /^(mailto:)?[a-z0-9._%+-]+@[a-z0-9][a-z0-9.-]*\.[a-z]{2,63}$/i; -const USABLE_HREF_REGEXP = /^(?:[a-z]+:|#|\?|\.|\/)/i; - -/** - * @typedef {{[key: string]: QueryArgParsed}} QueryArgObject - */ - -/** - * @typedef {string|string[]|QueryArgObject} QueryArgParsed - */ - -/** - * Determines whether the given string looks like a URL. - * - * @param {string} url The string to scrutinise. - * - * @example - * ```js - * const isURL = isURL( 'https://wordpress.org' ); // true - * ``` - * - * @return {boolean} Whether or not it looks like a URL. - */ -export function isURL( url ) { - return URL_REGEXP.test( url ); -} - -/** - * Determines whether the given string looks like an email. - * - * @param {string} email The string to scrutinise. - * - * @example - * ```js - * const isEmail = isEmail( 'hello@wordpress.org' ); // true - * ``` - * - * @return {boolean} Whether or not it looks like an email. - */ -export function isEmail( email ) { - return EMAIL_REGEXP.test( email ); -} - -/** - * Returns the protocol part of the URL. - * - * @param {string} url The full URL. - * - * @example - * ```js - * const protocol1 = getProtocol( 'tel:012345678' ); // 'tel:' - * const protocol2 = getProtocol( 'https://wordpress.org' ); // 'https:' - * ``` - * - * @return {string|void} The protocol part of the URL. - */ -export function getProtocol( url ) { - const matches = /^([^\s:]+:)/.exec( url ); - if ( matches ) { - return matches[ 1 ]; - } -} - -/** - * Tests if a url protocol is valid. - * - * @param {string} protocol The url protocol. - * - * @example - * ```js - * const isValid = isValidProtocol( 'https:' ); // true - * const isNotValid = isValidProtocol( 'https :' ); // false - * ``` - * - * @return {boolean} True if the argument is a valid protocol (e.g. http:, tel:). - */ -export function isValidProtocol( protocol ) { - if ( ! protocol ) { - return false; - } - return /^[a-z\-.\+]+[0-9]*:$/i.test( protocol ); -} - -/** - * Returns the authority part of the URL. - * - * @param {string} url The full URL. - * - * @example - * ```js - * const authority1 = getAuthority( 'https://wordpress.org/help/' ); // 'wordpress.org' - * const authority2 = getAuthority( 'https://localhost:8080/test/' ); // 'localhost:8080' - * ``` - * - * @return {string|void} The authority part of the URL. - */ -export function getAuthority( url ) { - const matches = /^[^\/\s:]+:(?:\/\/)?\/?([^\/\s#?]+)[\/#?]{0,1}\S*$/.exec( url ); - if ( matches ) { - return matches[ 1 ]; - } -} - -/** - * Checks for invalid characters within the provided authority. - * - * @param {string} authority A string containing the URL authority. - * - * @example - * ```js - * const isValid = isValidAuthority( 'wordpress.org' ); // true - * const isNotValid = isValidAuthority( 'wordpress#org' ); // false - * ``` - * - * @return {boolean} True if the argument contains a valid authority. - */ -export function isValidAuthority( authority ) { - if ( ! authority ) { - return false; - } - return /^[^\s#?]+$/.test( authority ); -} - -/** - * Returns the path part of the URL. - * - * @param {string} url The full URL. - * - * @example - * ```js - * const path1 = getPath( 'http://localhost:8080/this/is/a/test?query=true' ); // 'this/is/a/test' - * const path2 = getPath( 'https://wordpress.org/help/faq/' ); // 'help/faq' - * ``` - * - * @return {string|void} The path part of the URL. - */ -export function getPath( url ) { - const matches = /^[^\/\s:]+:(?:\/\/)?[^\/\s#?]+[\/]([^\s#?]+)[#?]{0,1}\S*$/.exec( url ); - if ( matches ) { - return matches[ 1 ]; - } -} - -/** - * Checks for invalid characters within the provided path. - * - * @param {string} path The URL path. - * - * @example - * ```js - * const isValid = isValidPath( 'test/path/' ); // true - * const isNotValid = isValidPath( '/invalid?test/path/' ); // false - * ``` - * - * @return {boolean} True if the argument contains a valid path - */ -export function isValidPath( path ) { - if ( ! path ) { - return false; - } - return /^[^\s#?]+$/.test( path ); -} - -/** - * Returns the query string part of the URL. - * - * @param {string} url The full URL. - * - * @example - * ```js - * const queryString1 = getQueryString( 'http://localhost:8080/this/is/a/test?query=true#fragment' ); // 'query=true' - * const queryString2 = getQueryString( 'https://wordpress.org#fragment?query=false&search=hello' ); // 'query=false&search=hello' - * ``` - * - * @return {string|void} The query string part of the URL. - */ -export function getQueryString( url ) { - const matches = /^\S+?\?([^\s#]+)/.exec( url ); - if ( matches ) { - return matches[ 1 ]; - } -} - -/** - * Checks for invalid characters within the provided query string. - * - * @param {string} queryString The query string. - * - * @example - * ```js - * const isValid = isValidQueryString( 'query=true&another=false' ); // true - * const isNotValid = isValidQueryString( 'query=true?another=false' ); // false - * ``` - * - * @return {boolean} True if the argument contains a valid query string. - */ -export function isValidQueryString( queryString ) { - if ( ! queryString ) { - return false; - } - return /^[^\s#?\/]+$/.test( queryString ); -} - -/** - * Returns the fragment part of the URL. - * - * @param {string} url The full URL - * - * @example - * ```js - * const fragment1 = getFragment( 'http://localhost:8080/this/is/a/test?query=true#fragment' ); // '#fragment' - * const fragment2 = getFragment( 'https://wordpress.org#another-fragment?query=true' ); // '#another-fragment' - * ``` - * - * @return {string|void} The fragment part of the URL. - */ -export function getFragment( url ) { - const matches = /^\S+?(#[^\s\?]*)/.exec( url ); - if ( matches ) { - return matches[ 1 ]; - } -} - -/** - * Checks for invalid characters within the provided fragment. - * - * @param {string} fragment The url fragment. - * - * @example - * ```js - * const isValid = isValidFragment( '#valid-fragment' ); // true - * const isNotValid = isValidFragment( '#invalid-#fragment' ); // false - * ``` - * - * @return {boolean} True if the argument contains a valid fragment. - */ -export function isValidFragment( fragment ) { - if ( ! fragment ) { - return false; - } - return /^#[^\s#?\/]*$/.test( fragment ); -} - -/** - * Appends arguments as querystring to the provided URL. If the URL already - * includes query arguments, the arguments are merged with (and take precedent - * over) the existing set. - * - * @param {string} [url=''] URL to which arguments should be appended. If omitted, - * only the resulting querystring is returned. - * @param {Object} args Query arguments to apply to URL. - * - * @example - * ```js - * const newURL = addQueryArgs( 'https://google.com', { q: 'test' } ); // https://google.com/?q=test - * ``` - * - * @return {string} URL with arguments applied. - */ -export function addQueryArgs( url = '', args ) { - // If no arguments are to be appended, return original URL. - if ( ! args || ! Object.keys( args ).length ) { - return url; - } - - let baseUrl = url; - - // Determine whether URL already had query arguments. - const queryStringIndex = url.indexOf( '?' ); - if ( queryStringIndex !== -1 ) { - // Merge into existing query arguments. - args = Object.assign( - parse( url.substr( queryStringIndex + 1 ) ), - args - ); - - // Change working base URL to omit previous query arguments. - baseUrl = baseUrl.substr( 0, queryStringIndex ); - } - - return baseUrl + '?' + stringify( args ); -} - -/** - * Returns a single query argument of the url - * - * @param {string} url URL. - * @param {string} arg Query arg name. - * - * @example - * ```js - * const foo = getQueryArg( 'https://wordpress.org?foo=bar&bar=baz', 'foo' ); // bar - * ``` - * - * @return {QueryArgParsed|undefined} Query arg value. - */ -export function getQueryArg( url, arg ) { - const queryStringIndex = url.indexOf( '?' ); - const query = queryStringIndex !== -1 ? parse( url.substr( queryStringIndex + 1 ) ) : {}; - - return query[ arg ]; -} - -/** - * Determines whether the URL contains a given query arg. - * - * @param {string} url URL. - * @param {string} arg Query arg name. - * - * @example - * ```js - * const hasBar = hasQueryArg( 'https://wordpress.org?foo=bar&bar=baz', 'bar' ); // true - * ``` - * - * @return {boolean} Whether or not the URL contains the query arg. - */ -export function hasQueryArg( url, arg ) { - return getQueryArg( url, arg ) !== undefined; -} - -/** - * Removes arguments from the query string of the url - * - * @param {string} url URL. - * @param {...string} args Query Args. - * - * @example - * ```js - * const newUrl = removeQueryArgs( 'https://wordpress.org?foo=bar&bar=baz&baz=foobar', 'foo', 'bar' ); // https://wordpress.org?baz=foobar - * ``` - * - * @return {string} Updated URL. - */ -export function removeQueryArgs( url, ...args ) { - const queryStringIndex = url.indexOf( '?' ); - const query = queryStringIndex !== -1 ? parse( url.substr( queryStringIndex + 1 ) ) : {}; - const baseUrl = queryStringIndex !== -1 ? url.substr( 0, queryStringIndex ) : url; - - args.forEach( ( arg ) => delete query[ arg ] ); - - return baseUrl + '?' + stringify( query ); -} - -/** - * Prepends "http://" to a url, if it looks like something that is meant to be a TLD. - * - * @param {string} url The URL to test. - * - * @example - * ```js - * const actualURL = prependHTTP( 'wordpress.org' ); // http://wordpress.org - * ``` - * - * @return {string} The updated URL. - */ -export function prependHTTP( url ) { - url = url.trim(); - if ( ! USABLE_HREF_REGEXP.test( url ) && ! EMAIL_REGEXP.test( url ) ) { - return 'http://' + url; - } - - return url; -} - -/** - * Safely decodes a URI with `decodeURI`. Returns the URI unmodified if - * `decodeURI` throws an error. - * - * @param {string} uri URI to decode. - * - * @example - * ```js - * const badUri = safeDecodeURI( '%z' ); // does not throw an Error, simply returns '%z' - * ``` - * - * @return {string} Decoded URI if possible. - */ -export function safeDecodeURI( uri ) { - try { - return decodeURI( uri ); - } catch ( uriError ) { - return uri; - } -} - -/** - * Returns a URL for display. - * - * @param {string} url Original URL. - * - * @example - * ```js - * const displayUrl = filterURLForDisplay( 'https://www.wordpress.org/gutenberg/' ); // wordpress.org/gutenberg - * ``` - * - * @return {string} Displayed URL. - */ -export function filterURLForDisplay( url ) { - // Remove protocol and www prefixes. - const filteredURL = url.replace( /^(?:https?:)\/\/(?:www\.)?/, '' ); - - // Ends with / and only has that single slash, strip it. - if ( filteredURL.match( /^[^\/]+\/$/ ) ) { - return filteredURL.replace( '/', '' ); - } - - return filteredURL; -} - -/** - * Safely decodes a URI component with `decodeURIComponent`. Returns the URI component unmodified if - * `decodeURIComponent` throws an error. - * - * @param {string} uriComponent URI component to decode. - * - * @return {string} Decoded URI component if possible. - */ -export function safeDecodeURIComponent( uriComponent ) { - try { - return decodeURIComponent( uriComponent ); - } catch ( uriComponentError ) { - return uriComponent; - } -} +export { isURL } from './is-url'; +export { isEmail } from './is-email'; +export { getProtocol } from './get-protocol'; +export { isValidProtocol } from './is-valid-protocol'; +export { getAuthority } from './get-authority'; +export { isValidAuthority } from './is-valid-authority'; +export { getPath } from './get-path'; +export { isValidPath } from './is-valid-path'; +export { getQueryString } from './get-query-string'; +export { isValidQueryString } from './is-valid-query-string'; +export { getFragment } from './get-fragment'; +export { isValidFragment } from './is-valid-fragment'; +export { addQueryArgs } from './add-query-args'; +export { getQueryArg } from './get-query-arg'; +export { hasQueryArg } from './has-query-arg'; +export { removeQueryArgs } from './remove-query-args'; +export { prependHTTP } from './prepend-http'; +export { safeDecodeURI } from './safe-decode-uri'; +export { safeDecodeURIComponent } from './safe-decode-uri-component'; +export { filterURLForDisplay } from './filter-url-for-display'; diff --git a/packages/url/src/is-email.js b/packages/url/src/is-email.js new file mode 100644 index 00000000000000..85f007d5d2713d --- /dev/null +++ b/packages/url/src/is-email.js @@ -0,0 +1,17 @@ +const EMAIL_REGEXP = /^(mailto:)?[a-z0-9._%+-]+@[a-z0-9][a-z0-9.-]*\.[a-z]{2,63}$/i; + +/** + * Determines whether the given string looks like an email. + * + * @param {string} email The string to scrutinise. + * + * @example + * ```js + * const isEmail = isEmail( 'hello@wordpress.org' ); // true + * ``` + * + * @return {boolean} Whether or not it looks like an email. + */ +export function isEmail( email ) { + return EMAIL_REGEXP.test( email ); +} diff --git a/packages/url/src/is-url.js b/packages/url/src/is-url.js new file mode 100644 index 00000000000000..5f3c854f93ad7d --- /dev/null +++ b/packages/url/src/is-url.js @@ -0,0 +1,17 @@ +const URL_REGEXP = /^(?:https?:)?\/\/\S+$/i; + +/** + * Determines whether the given string looks like a URL. + * + * @param {string} url The string to scrutinise. + * + * @example + * ```js + * const isURL = isURL( 'https://wordpress.org' ); // true + * ``` + * + * @return {boolean} Whether or not it looks like a URL. + */ +export function isURL( url ) { + return URL_REGEXP.test( url ); +} diff --git a/packages/url/src/is-valid-authority.js b/packages/url/src/is-valid-authority.js new file mode 100644 index 00000000000000..4734259b34b19c --- /dev/null +++ b/packages/url/src/is-valid-authority.js @@ -0,0 +1,19 @@ +/** + * Checks for invalid characters within the provided authority. + * + * @param {string} authority A string containing the URL authority. + * + * @example + * ```js + * const isValid = isValidAuthority( 'wordpress.org' ); // true + * const isNotValid = isValidAuthority( 'wordpress#org' ); // false + * ``` + * + * @return {boolean} True if the argument contains a valid authority. + */ +export function isValidAuthority( authority ) { + if ( ! authority ) { + return false; + } + return /^[^\s#?]+$/.test( authority ); +} diff --git a/packages/url/src/is-valid-fragment.js b/packages/url/src/is-valid-fragment.js new file mode 100644 index 00000000000000..ce19c0f859e5a7 --- /dev/null +++ b/packages/url/src/is-valid-fragment.js @@ -0,0 +1,19 @@ +/** + * Checks for invalid characters within the provided fragment. + * + * @param {string} fragment The url fragment. + * + * @example + * ```js + * const isValid = isValidFragment( '#valid-fragment' ); // true + * const isNotValid = isValidFragment( '#invalid-#fragment' ); // false + * ``` + * + * @return {boolean} True if the argument contains a valid fragment. + */ +export function isValidFragment( fragment ) { + if ( ! fragment ) { + return false; + } + return /^#[^\s#?\/]*$/.test( fragment ); +} diff --git a/packages/url/src/is-valid-path.js b/packages/url/src/is-valid-path.js new file mode 100644 index 00000000000000..7ab5d9d95eeca7 --- /dev/null +++ b/packages/url/src/is-valid-path.js @@ -0,0 +1,19 @@ +/** + * Checks for invalid characters within the provided path. + * + * @param {string} path The URL path. + * + * @example + * ```js + * const isValid = isValidPath( 'test/path/' ); // true + * const isNotValid = isValidPath( '/invalid?test/path/' ); // false + * ``` + * + * @return {boolean} True if the argument contains a valid path + */ +export function isValidPath( path ) { + if ( ! path ) { + return false; + } + return /^[^\s#?]+$/.test( path ); +} diff --git a/packages/url/src/is-valid-protocol.js b/packages/url/src/is-valid-protocol.js new file mode 100644 index 00000000000000..689768114318c4 --- /dev/null +++ b/packages/url/src/is-valid-protocol.js @@ -0,0 +1,19 @@ +/** + * Tests if a url protocol is valid. + * + * @param {string} protocol The url protocol. + * + * @example + * ```js + * const isValid = isValidProtocol( 'https:' ); // true + * const isNotValid = isValidProtocol( 'https :' ); // false + * ``` + * + * @return {boolean} True if the argument is a valid protocol (e.g. http:, tel:). + */ +export function isValidProtocol( protocol ) { + if ( ! protocol ) { + return false; + } + return /^[a-z\-.\+]+[0-9]*:$/i.test( protocol ); +} diff --git a/packages/url/src/is-valid-query-string.js b/packages/url/src/is-valid-query-string.js new file mode 100644 index 00000000000000..039cede44de959 --- /dev/null +++ b/packages/url/src/is-valid-query-string.js @@ -0,0 +1,19 @@ +/** + * Checks for invalid characters within the provided query string. + * + * @param {string} queryString The query string. + * + * @example + * ```js + * const isValid = isValidQueryString( 'query=true&another=false' ); // true + * const isNotValid = isValidQueryString( 'query=true?another=false' ); // false + * ``` + * + * @return {boolean} True if the argument contains a valid query string. + */ +export function isValidQueryString( queryString ) { + if ( ! queryString ) { + return false; + } + return /^[^\s#?\/]+$/.test( queryString ); +} diff --git a/packages/url/src/prepend-http.js b/packages/url/src/prepend-http.js new file mode 100644 index 00000000000000..e76488c51a4266 --- /dev/null +++ b/packages/url/src/prepend-http.js @@ -0,0 +1,31 @@ +/** + * Internal dependencies + */ +import { isEmail } from './is-email'; + +const USABLE_HREF_REGEXP = /^(?:[a-z]+:|#|\?|\.|\/)/i; + +/** + * Prepends "http://" to a url, if it looks like something that is meant to be a TLD. + * + * @param {string} url The URL to test. + * + * @example + * ```js + * const actualURL = prependHTTP( 'wordpress.org' ); // http://wordpress.org + * ``` + * + * @return {string} The updated URL. + */ +export function prependHTTP( url ) { + if ( ! url ) { + return url; + } + + url = url.trim(); + if ( ! USABLE_HREF_REGEXP.test( url ) && ! isEmail( url ) ) { + return 'http://' + url; + } + + return url; +} diff --git a/packages/url/src/remove-query-args.js b/packages/url/src/remove-query-args.js new file mode 100644 index 00000000000000..58dc1edd8ebab2 --- /dev/null +++ b/packages/url/src/remove-query-args.js @@ -0,0 +1,27 @@ +/** + * External dependencies + */ +import { parse, stringify } from 'qs'; + +/** + * Removes arguments from the query string of the url + * + * @param {string} url URL. + * @param {...string} args Query Args. + * + * @example + * ```js + * const newUrl = removeQueryArgs( 'https://wordpress.org?foo=bar&bar=baz&baz=foobar', 'foo', 'bar' ); // https://wordpress.org?baz=foobar + * ``` + * + * @return {string} Updated URL. + */ +export function removeQueryArgs( url, ...args ) { + const queryStringIndex = url.indexOf( '?' ); + const query = queryStringIndex !== -1 ? parse( url.substr( queryStringIndex + 1 ) ) : {}; + const baseUrl = queryStringIndex !== -1 ? url.substr( 0, queryStringIndex ) : url; + + args.forEach( ( arg ) => delete query[ arg ] ); + + return baseUrl + '?' + stringify( query ); +} diff --git a/packages/url/src/safe-decode-uri-component.js b/packages/url/src/safe-decode-uri-component.js new file mode 100644 index 00000000000000..2bf2047803fba3 --- /dev/null +++ b/packages/url/src/safe-decode-uri-component.js @@ -0,0 +1,15 @@ +/** + * Safely decodes a URI component with `decodeURIComponent`. Returns the URI component unmodified if + * `decodeURIComponent` throws an error. + * + * @param {string} uriComponent URI component to decode. + * + * @return {string} Decoded URI component if possible. + */ +export function safeDecodeURIComponent( uriComponent ) { + try { + return decodeURIComponent( uriComponent ); + } catch ( uriComponentError ) { + return uriComponent; + } +} diff --git a/packages/url/src/safe-decode-uri.js b/packages/url/src/safe-decode-uri.js new file mode 100644 index 00000000000000..f5f6379e19164a --- /dev/null +++ b/packages/url/src/safe-decode-uri.js @@ -0,0 +1,20 @@ +/** + * Safely decodes a URI with `decodeURI`. Returns the URI unmodified if + * `decodeURI` throws an error. + * + * @param {string} uri URI to decode. + * + * @example + * ```js + * const badUri = safeDecodeURI( '%z' ); // does not throw an Error, simply returns '%z' + * ``` + * + * @return {string} Decoded URI if possible. + */ +export function safeDecodeURI( uri ) { + try { + return decodeURI( uri ); + } catch ( uriError ) { + return uri; + } +} diff --git a/packages/viewport/package.json b/packages/viewport/package.json index 5dd1af95cbe842..98e8fcf03723f1 100644 --- a/packages/viewport/package.json +++ b/packages/viewport/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/viewport", - "version": "2.8.0", + "version": "2.9.0", "description": "Viewport module for WordPress.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", diff --git a/packages/viewport/src/index.js b/packages/viewport/src/index.js index bd65fd4e6da095..b2faa967569480 100644 --- a/packages/viewport/src/index.js +++ b/packages/viewport/src/index.js @@ -1,17 +1,8 @@ -/** - * External dependencies - */ -import { reduce, forEach, debounce, mapValues } from 'lodash'; - -/** - * WordPress dependencies - */ -import { dispatch } from '@wordpress/data'; - /** * Internal dependencies */ import './store'; +import addDimensionsEventListener from './listener'; export { default as ifViewportMatches } from './if-viewport-matches'; export { default as withViewportMatch } from './with-viewport-match'; @@ -42,38 +33,4 @@ const OPERATORS = { '>=': 'min-width', }; -/** - * Callback invoked when media query state should be updated. Is invoked a - * maximum of one time per call stack. - */ -const setIsMatching = debounce( () => { - const values = mapValues( queries, ( query ) => query.matches ); - dispatch( 'core/viewport' ).setIsMatching( values ); -}, { leading: true } ); - -/** - * Hash of breakpoint names with generated MediaQueryList for corresponding - * media query. - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia - * @see https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList - * - * @type {Object<string,MediaQueryList>} - */ -const queries = reduce( BREAKPOINTS, ( result, width, name ) => { - forEach( OPERATORS, ( condition, operator ) => { - const list = window.matchMedia( `(${ condition }: ${ width }px)` ); - list.addListener( setIsMatching ); - - const key = [ operator, name ].join( ' ' ); - result[ key ] = list; - } ); - - return result; -}, {} ); - -window.addEventListener( 'orientationchange', setIsMatching ); - -// Set initial values -setIsMatching(); -setIsMatching.flush(); +addDimensionsEventListener( BREAKPOINTS, OPERATORS ); diff --git a/packages/viewport/src/index.native.js b/packages/viewport/src/index.native.js deleted file mode 100644 index 605b511ac9d686..00000000000000 --- a/packages/viewport/src/index.native.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Internal dependencies - */ -import './store'; - -export { default as ifViewportMatches } from './if-viewport-matches'; -export { default as withViewportMatch } from './with-viewport-match'; - diff --git a/packages/viewport/src/listener.js b/packages/viewport/src/listener.js new file mode 100644 index 00000000000000..c8cbcaafdc5a0d --- /dev/null +++ b/packages/viewport/src/listener.js @@ -0,0 +1,50 @@ +/** + * External dependencies + */ +import { reduce, forEach, debounce, mapValues } from 'lodash'; + +/** + * WordPress dependencies + */ +import { dispatch } from '@wordpress/data'; + +const addDimensionsEventListener = ( breakpoints, operators ) => { + /** + * Callback invoked when media query state should be updated. Is invoked a + * maximum of one time per call stack. + */ + const setIsMatching = debounce( () => { + const values = mapValues( queries, ( query ) => query.matches ); + dispatch( 'core/viewport' ).setIsMatching( values ); + }, { leading: true } ); + + /** + * Hash of breakpoint names with generated MediaQueryList for corresponding + * media query. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia + * @see https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList + * + * @type {Object<string,MediaQueryList>} + */ + const queries = reduce( breakpoints, ( result, width, name ) => { + forEach( operators, ( condition, operator ) => { + const list = window.matchMedia( `(${ condition }: ${ width }px)` ); + list.addListener( setIsMatching ); + + const key = [ operator, name ].join( ' ' ); + result[ key ] = list; + } ); + + return result; + }, {} ); + + window.addEventListener( 'orientationchange', setIsMatching ); + + // Set initial values + setIsMatching(); + setIsMatching.flush(); +}; + +export default addDimensionsEventListener; + diff --git a/packages/viewport/src/listener.native.js b/packages/viewport/src/listener.native.js new file mode 100644 index 00000000000000..16f49bb84b16f0 --- /dev/null +++ b/packages/viewport/src/listener.native.js @@ -0,0 +1,42 @@ +/** + * External dependencies + */ +import { forEach, reduce } from 'lodash'; +import { Dimensions } from 'react-native'; + +/** + * WordPress dependencies + */ +import { dispatch } from '@wordpress/data'; + +const matchWidth = ( operator, breakpoint ) => { + const { width } = Dimensions.get( 'window' ); + if ( operator === 'max-width' ) { + return width < breakpoint; + } else if ( operator === 'min-width' ) { + return width >= breakpoint; + } + throw new Error( `Unsupported viewport operator: ${ operator }` ); +}; + +const addDimensionsEventListener = ( breakpoints, operators ) => { + const setIsMatching = () => { + const matches = reduce( breakpoints, ( result, width, name ) => { + forEach( operators, ( condition, operator ) => { + const key = [ operator, name ].join( ' ' ); + result[ key ] = matchWidth( condition, width ); + } ); + + return result; + }, {} ); + + dispatch( 'core/viewport' ).setIsMatching( matches ); + }; + + Dimensions.addEventListener( 'change', setIsMatching ); + + // Set initial values + setIsMatching(); +}; + +export default addDimensionsEventListener; diff --git a/packages/viewport/src/test/if-viewport-matches.js b/packages/viewport/src/test/if-viewport-matches.js index 4a57039e9c7f1b..25a8f6b5b95dcb 100644 --- a/packages/viewport/src/test/if-viewport-matches.js +++ b/packages/viewport/src/test/if-viewport-matches.js @@ -6,7 +6,7 @@ import TestRenderer, { act } from 'react-test-renderer'; /** * WordPress dependencies */ -import { dispatch } from '@wordpress/data'; +import { useViewportMatch as useViewportMatchMock } from '@wordpress/compose'; /** * Internal dependencies @@ -17,27 +17,34 @@ import ifViewportMatches from '../if-viewport-matches'; describe( 'ifViewportMatches()', () => { const Component = () => <div>Hello</div>; + afterEach( () => { + useViewportMatchMock.mockClear(); + } ); + it( 'should not render if query does not match', () => { - dispatch( 'core/viewport' ).setIsMatching( { '> wide': false } ); - const EnhancedComponent = ifViewportMatches( '> wide' )( Component ); + useViewportMatchMock.mockReturnValueOnce( false ); + const EnhancedComponent = ifViewportMatches( '< wide' )( Component ); let testRenderer; act( () => { testRenderer = TestRenderer.create( <EnhancedComponent /> ); } ); + expect( useViewportMatchMock.mock.calls ).toEqual( [ [ 'wide', '<' ] ] ); + expect( testRenderer.root.findAllByType( Component ) ).toHaveLength( 0 ); } ); it( 'should render if query does match', () => { - act( () => { - dispatch( 'core/viewport' ).setIsMatching( { '> wide': true } ); - } ); - const EnhancedComponent = ifViewportMatches( '> wide' )( Component ); + useViewportMatchMock.mockReturnValueOnce( true ); + const EnhancedComponent = ifViewportMatches( '>= wide' )( Component ); let testRenderer; act( () => { testRenderer = TestRenderer.create( <EnhancedComponent /> ); } ); + + expect( useViewportMatchMock.mock.calls ).toEqual( [ [ 'wide', '>=' ] ] ); + expect( testRenderer.root.findAllByType( Component ) ).toHaveLength( 1 ); } ); } ); diff --git a/packages/viewport/src/test/with-viewport-match.js b/packages/viewport/src/test/with-viewport-match.js index 8744178e2c2106..1cf40db7b0e344 100644 --- a/packages/viewport/src/test/with-viewport-match.js +++ b/packages/viewport/src/test/with-viewport-match.js @@ -6,8 +6,8 @@ import renderer from 'react-test-renderer'; /** * WordPress dependencies */ -import { dispatch } from '@wordpress/data'; import { Component } from '@wordpress/element'; +import { useViewportMatch as useViewportMatchMock } from '@wordpress/compose'; /** * Internal dependencies @@ -16,6 +16,10 @@ import '../store'; import withViewportMatch from '../with-viewport-match'; describe( 'withViewportMatch()', () => { + afterEach( () => { + useViewportMatchMock.mockClear(); + } ); + const ChildComponent = () => <div>Hello</div>; // this is needed because TestUtils does not accept a stateless component. @@ -30,14 +34,33 @@ describe( 'withViewportMatch()', () => { }; it( 'should render with result of query as custom prop name', () => { - dispatch( 'core/viewport' ).setIsMatching( { '> wide': true } ); - const EnhancedComponent = withViewportMatch( - { isWide: '> wide' } + const EnhancedComponent = withViewportMatch( { + isWide: '>= wide', + isSmall: '>= small', + isLarge: 'large', + isLessThanSmall: '< small', + } )( ChildComponent ); + + useViewportMatchMock.mockReturnValueOnce( false ); + useViewportMatchMock.mockReturnValueOnce( true ); + useViewportMatchMock.mockReturnValueOnce( true ); + useViewportMatchMock.mockReturnValueOnce( false ); + const wrapper = renderer.create( getTestComponent( EnhancedComponent ) ); - expect( wrapper.root.findByType( ChildComponent ).props.isWide ) - .toBe( true ); + expect( useViewportMatchMock.mock.calls ).toEqual( [ + [ 'wide', '>=' ], + [ 'small', '>=' ], + [ 'large', '>=' ], + [ 'small', '<' ], + ] ); + + const { props } = wrapper.root.findByType( ChildComponent ); + expect( props.isWide ).toBe( false ); + expect( props.isSmall ).toBe( true ); + expect( props.isLarge ).toBe( true ); + expect( props.isLessThanSmall ).toBe( false ); wrapper.unmount(); } ); diff --git a/packages/viewport/src/with-viewport-match.js b/packages/viewport/src/with-viewport-match.js index 3398fda7e4f3f5..0d67062b438a0a 100644 --- a/packages/viewport/src/with-viewport-match.js +++ b/packages/viewport/src/with-viewport-match.js @@ -6,8 +6,7 @@ import { mapValues } from 'lodash'; /** * WordPress dependencies */ -import { createHigherOrderComponent } from '@wordpress/compose'; -import { withSelect } from '@wordpress/data'; +import { createHigherOrderComponent, pure, useViewportMatch } from '@wordpress/compose'; /** * Higher-order component creator, creating a new component which renders with @@ -32,13 +31,33 @@ import { withSelect } from '@wordpress/data'; * * @return {Function} Higher-order component. */ -const withViewportMatch = ( queries ) => createHigherOrderComponent( - withSelect( ( select ) => { - return mapValues( queries, ( query ) => { - return select( 'core/viewport' ).isViewportMatch( query ); - } ); - } ), - 'withViewportMatch' -); +const withViewportMatch = ( queries ) => { + const useViewPortQueriesResult = () => mapValues( queries, ( query ) => { + let [ operator, breakpointName ] = query.split( ' ' ); + if ( breakpointName === undefined ) { + breakpointName = operator; + operator = '>='; + } + // Hooks should unconditionally execute in the same order, + // we are respecting that as from the static query of the HOC we generate + // a hook that calls other hooks always in the same order (because the query never changes). + // eslint-disable-next-line react-hooks/rules-of-hooks + return useViewportMatch( breakpointName, operator ); + } ); + return createHigherOrderComponent( + ( WrappedComponent ) => { + return pure( ( props ) => { + const queriesResult = useViewPortQueriesResult(); + return ( + <WrappedComponent + { ...props } + { ...queriesResult } + /> + ); + } ); + }, + 'withViewportMatch' + ); +}; export default withViewportMatch; diff --git a/packages/wordcount/package.json b/packages/wordcount/package.json index 73ad190cc7aa1d..7664cf8b99a0fc 100644 --- a/packages/wordcount/package.json +++ b/packages/wordcount/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/wordcount", - "version": "2.6.0", + "version": "2.6.2", "description": "WordPress word count utility.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", @@ -20,6 +20,7 @@ "main": "build/index.js", "module": "build-module/index.js", "react-native": "src/index", + "sideEffects": false, "dependencies": { "@babel/runtime": "^7.4.4", "lodash": "^4.17.15" diff --git a/phpcs.xml.dist b/phpcs.xml.dist index e039c8724c9485..87c4674f45e6e1 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -3,7 +3,7 @@ <description>Sniffs for WordPress plugins, with minor modifications for Gutenberg</description> <rule ref="PHPCompatibility"/> - <config name="testVersion" value="5.2-"/> + <config name="testVersion" value="5.6-"/> <rule ref="WordPress-Core"/> <rule ref="WordPress-Docs"/> diff --git a/phpunit/class-override-script-test.php b/phpunit/class-override-script-test.php index 034fa2f25a618b..074de00b649ba6 100644 --- a/phpunit/class-override-script-test.php +++ b/phpunit/class-override-script-test.php @@ -28,7 +28,10 @@ function tearDown() { * Tests that script is localized. */ function test_localizes_script() { + global $wp_scripts; + gutenberg_override_script( + $wp_scripts, 'gutenberg-dummy-script', 'https://example.com/', array( 'dependency' ), @@ -36,7 +39,6 @@ function test_localizes_script() { false ); - global $wp_scripts; $script = $wp_scripts->query( 'gutenberg-dummy-script', 'registered' ); $this->assertEquals( array( 'dependency', 'wp-i18n' ), $script->deps ); } @@ -45,7 +47,10 @@ function test_localizes_script() { * Tests that script properties are overridden. */ function test_replaces_registered_properties() { + global $wp_scripts; + gutenberg_override_script( + $wp_scripts, 'gutenberg-dummy-script', 'https://example.com/updated', array( 'updated-dependency' ), @@ -53,19 +58,21 @@ function test_replaces_registered_properties() { true ); - global $wp_scripts; $script = $wp_scripts->query( 'gutenberg-dummy-script', 'registered' ); $this->assertEquals( 'https://example.com/updated', $script->src ); $this->assertEquals( array( 'updated-dependency', 'wp-i18n' ), $script->deps ); $this->assertEquals( 'updated-version', $script->ver ); - $this->assertEquals( 1, $script->extra['group'] ); + $this->assertTrue( $script->args ); } /** * Tests that new script registers normally if no handle by the name. */ function test_registers_new_script() { + global $wp_scripts; + gutenberg_override_script( + $wp_scripts, 'gutenberg-second-dummy-script', 'https://example.com/', array( 'dependency' ), @@ -73,11 +80,10 @@ function test_registers_new_script() { true ); - global $wp_scripts; $script = $wp_scripts->query( 'gutenberg-second-dummy-script', 'registered' ); $this->assertEquals( 'https://example.com/', $script->src ); $this->assertEquals( array( 'dependency', 'wp-i18n' ), $script->deps ); $this->assertEquals( 'version', $script->ver ); - $this->assertEquals( 1, $script->extra['group'] ); + $this->assertTrue( $script->args ); } } diff --git a/playground/.babelrc b/playground/.babelrc deleted file mode 100644 index 5b8ba9724e13af..00000000000000 --- a/playground/.babelrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "presets": ["@wordpress/babel-preset-default"], - "plugins": [ - [ "@babel/plugin-transform-react-jsx", { - "pragma": "createElement" - } ], - "babel-plugin-inline-json-import" - ] -} diff --git a/playground/src/index.html b/playground/src/index.html deleted file mode 100644 index 11576c6aa41182..00000000000000 --- a/playground/src/index.html +++ /dev/null @@ -1,13 +0,0 @@ -<!doctype html> -<html lang="en"> -<head> - <meta charset="utf-8"> - <title>The Gutenberg Playground</title> - <link href="https://fonts.googleapis.com/css?family=Noto+Serif:400,700" rel="stylesheet"> -</head> - -<body> - <div id="app"></div> - <script src="./index.js"></script> -</body> -</html> diff --git a/playground/src/index.js b/playground/src/index.js deleted file mode 100644 index b74e30091c1739..00000000000000 --- a/playground/src/index.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * WordPress dependencies - */ -import '@wordpress/editor'; // This shouldn't be necessary - -import { render, useState, Fragment } from '@wordpress/element'; -import { - BlockEditorKeyboardShortcuts, - BlockEditorProvider, - BlockList, - WritingFlow, - ObserveTyping, -} from '@wordpress/block-editor'; -import { - Popover, - SlotFillProvider, - DropZoneProvider, -} from '@wordpress/components'; -import { registerCoreBlocks } from '@wordpress/block-library'; -import '@wordpress/format-library'; - -/** - * Internal dependencies - */ -import './style.scss'; - -/* eslint-disable no-restricted-syntax */ -import '@wordpress/components/build-style/style.css'; -import '@wordpress/block-editor/build-style/style.css'; -import '@wordpress/block-library/build-style/style.css'; -import '@wordpress/block-library/build-style/editor.css'; -import '@wordpress/block-library/build-style/theme.css'; -import '@wordpress/format-library/build-style/style.css'; -/* eslint-enable no-restricted-syntax */ - -function App() { - const [ blocks, updateBlocks ] = useState( [] ); - - return ( - <Fragment> - <div className="playground__header"> - <h1 className="playground__logo">Gutenberg Playground</h1> - </div> - <div className="playground__body"> - <SlotFillProvider> - <DropZoneProvider> - <BlockEditorProvider - value={ blocks } - onInput={ updateBlocks } - onChange={ updateBlocks } - > - <div className="editor-styles-wrapper"> - <BlockEditorKeyboardShortcuts /> - <WritingFlow> - <ObserveTyping> - <BlockList /> - </ObserveTyping> - </WritingFlow> - </div> - <Popover.Slot /> - </BlockEditorProvider> - </DropZoneProvider> - </SlotFillProvider> - </div> - </Fragment> - ); -} - -registerCoreBlocks(); -render( - <App />, - document.querySelector( '#app' ) -); diff --git a/playground/src/style.scss b/playground/src/style.scss deleted file mode 100644 index 63a330e00a2d6f..00000000000000 --- a/playground/src/style.scss +++ /dev/null @@ -1,53 +0,0 @@ -@import "../../assets/stylesheets/colors"; -@import "../../assets/stylesheets/variables"; -@import "../../assets/stylesheets/mixins"; -@import "../../assets/stylesheets/breakpoints"; -@import "../../assets/stylesheets/animations"; -@import "../../assets/stylesheets/z-index"; - -@import "./reset"; -@import "./editor-styles"; - - -.playground__header { - padding: 20px; - border-bottom: 1px solid #ddd; -} - -.playground__logo { - font-size: 20px; - font-weight: 300; -} - -.playground__body { - padding-top: 20px; - - img { - max-width: 100%; - height: auto; - } - - iframe { - width: 100%; - } - - .components-navigate-regions { - height: 100%; - } -} - -/** - * Animations - */ - -// These keyframes should not be part of the _animations.scss mixins file. -// Because keyframe animations can't be defined as mixins properly, they are duplicated. -// Since hey are intended only for the editor, we add them here instead. -@keyframes edit-post__fade-in-animation { - from { - opacity: 0; - } - to { - opacity: 1; - } -} diff --git a/readme.txt b/readme.txt new file mode 100644 index 00000000000000..64e42984cc2718 --- /dev/null +++ b/readme.txt @@ -0,0 +1,210 @@ +=== Gutenberg === +Contributors: matveb, joen, karmatosed +Requires at least: 5.2.0 +Tested up to: 5.3 +Stable tag: V.V.V +License: GPLv2 or later +License URI: http://www.gnu.org/licenses/gpl-2.0.html + +The block editor was introduced in core WordPress with version 5.0. This beta plugin allows you to test bleeding-edge features around editing and customization projects before they land in future WordPress releases. + +== Description == + +The block editor was introduced in core WordPress with version 5.0 but the Gutenberg project will ultimately impact the entire publishing experience including customization (the next focus area). This beta plugin allows you to test bleeding-edge features around editing and customization projects before they land in future WordPress releases. + +<a href="https://wordpress.org/gutenberg">Discover more about the project</a>. + += Editing focus = + +> The editor will create a new page- and post-building experience that makes writing rich posts effortless, and has “blocks” to make it easy what today might take shortcodes, custom HTML, or “mystery meat” embed discovery. — Matt Mullenweg + +One thing that sets WordPress apart from other systems is that it allows you to create as rich a post layout as you can imagine -- but only if you know HTML and CSS and build your own custom theme. By thinking of the editor as a tool to let you write rich posts and create beautiful layouts, we can transform WordPress into something users _love_ WordPress, as opposed something they pick it because it's what everyone else uses. + +Gutenberg looks at the editor as more than a content field, revisiting a layout that has been largely unchanged for almost a decade.This allows us to holistically design a modern editing experience and build a foundation for things to come. + +Here's why we're looking at the whole editing screen, as opposed to just the content field: + +1. The block unifies multiple interfaces. If we add that on top of the existing interface, it would _add_ complexity, as opposed to remove it. +2. By revisiting the interface, we can modernize the writing, editing, and publishing experience, with usability and simplicity in mind, benefitting both new and casual users. +3. When singular block interface takes center stage, it demonstrates a clear path forward for developers to create premium blocks, superior to both shortcodes and widgets. +4. Considering the whole interface lays a solid foundation for the next focus, full site customization. +5. Looking at the full editor screen also gives us the opportunity to drastically modernize the foundation, and take steps towards a more fluid and JavaScript powered future that fully leverages the WordPress REST API. + += Blocks = + +Blocks are the unifying evolution of what is now covered, in different ways, by shortcodes, embeds, widgets, post formats, custom post types, theme options, meta-boxes, and other formatting elements. They embrace the breadth of functionality WordPress is capable of, with the clarity of a consistent user experience. + +Imagine a custom “employee” block that a client can drag to an About page to automatically display a picture, name, and bio. A whole universe of plugins that all extend WordPress in the same way. Simplified menus and widgets. Users who can instantly understand and use WordPress -- and 90% of plugins. This will allow you to easily compose beautiful posts like <a href="http://moc.co/sandbox/example-post/">this example</a>. + +Check out the <a href="https://wordpress.org/gutenberg/handbook/reference/faq/">FAQ</a> for answers to the most common questions about the project. + += Compatibility = + +Posts are backwards compatible, and shortcodes will still work. We are continuously exploring how highly-tailored metaboxes can be accommodated, and are looking at solutions ranging from a plugin to disable Gutenberg to automatically detecting whether to load Gutenberg or not. While we want to make sure the new editing experience from writing to publishing is user-friendly, we’re committed to finding a good solution for highly-tailored existing sites. + += The stages of Gutenberg = + +Gutenberg has three planned stages. The first, aimed for inclusion in WordPress 5.0, focuses on the post editing experience and the implementation of blocks. This initial phase focuses on a content-first approach. The use of blocks, as detailed above, allows you to focus on how your content will look without the distraction of other configuration options. This ultimately will help all users present their content in a way that is engaging, direct, and visual. + +These foundational elements will pave the way for stages two and three, planned for the next year, to go beyond the post into page templates and ultimately, full site customization. + +Gutenberg is a big change, and there will be ways to ensure that existing functionality (like shortcodes and meta-boxes) continue to work while allowing developers the time and paths to transition effectively. Ultimately, it will open new opportunities for plugin and theme developers to better serve users through a more engaging and visual experience that takes advantage of a toolset supported by core. + += Contributors = + +Gutenberg is built by many contributors and volunteers. Please see the full list in <a href="https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTORS.md">CONTRIBUTORS.md</a>. + +== Frequently Asked Questions == + += How can I send feedback or get help with a bug? = + +We'd love to hear your bug reports, feature suggestions and any other feedback! Please head over to <a href="https://github.com/WordPress/gutenberg/issues">the GitHub issues page</a> to search for existing issues or open a new one. While we'll try to triage issues reported here on the plugin forum, you'll get a faster response (and reduce duplication of effort) by keeping everything centralized in the GitHub repository. + += How can I contribute? = + +We’re calling this editor project "Gutenberg" because it's a big undertaking. We are working on it every day in GitHub, and we'd love your help building it.You’re also welcome to give feedback, the easiest is to join us in <a href="https://make.wordpress.org/chat/">our Slack channel</a>, `#core-editor`. + +See also <a href="https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTING.md">CONTRIBUTING.md</a>. + += Where can I read more about Gutenberg? = + +- <a href="http://matiasventura.com/post/gutenberg-or-the-ship-of-theseus/">Gutenberg, or the Ship of Theseus</a>, with examples of what Gutenberg might do in the future +- <a href="https://make.wordpress.org/core/2017/01/17/editor-technical-overview/">Editor Technical Overview</a> +- <a href="https://wordpress.org/gutenberg/handbook/reference/design-principles/">Design Principles and block design best practices</a> +- <a href="https://github.com/Automattic/wp-post-grammar">WP Post Grammar Parser</a> +- <a href="https://make.wordpress.org/core/tag/gutenberg/">Development updates on make.wordpress.org</a> +- <a href="https://wordpress.org/gutenberg/handbook/">Documentation: Creating Blocks, Reference, and Guidelines</a> +- <a href="https://wordpress.org/gutenberg/handbook/reference/faq/">Additional frequently asked questions</a> + + +== Changelog == + +### Features + +* Adding a menu to visually [switch between edit and](https://github.com/WordPress/gutenberg/pull/18624) [navigation](https://github.com/WordPress/gutenberg/pull/18829) [modes](https://github.com/WordPress/gutenberg/pull/18805) and announce the mode changes to screen reader users. +* Support adding [a caption to the Table block](https://github.com/WordPress/gutenberg/pull/15554). +* Implement a [Welcome Guide](https://github.com/WordPress/gutenberg/pull/18041) modal. + +### Enhancements + +* Use a [Fixed Block](https://github.com/WordPress/gutenberg/pull/18686) [Toolbar](https://github.com/WordPress/gutenberg/pull/18945https://github.com/WordPress/gutenberg/pull/18945) [on](https://github.com/WordPress/gutenberg/pull/19014) Mobile Web. +* Block UI: + * [Remove the parent block from the block title](https://github.com/WordPress/gutenberg/pull/18795) component. + * [Remove dashed](https://github.com/WordPress/gutenberg/pull/18105) [outlines](https://github.com/WordPress/gutenberg/pull/18865) for child and parent blocks. + * Remove [hover](https://github.com/WordPress/gutenberg/pull/18862) [styles](https://github.com/WordPress/gutenberg/pull/18904). +* Navigation block: + * Keep a single place to trigger the “[Open in a new tab](https://github.com/WordPress/gutenberg/pull/18532)” option. + * Fix overflow by allowing [wrapping of menu items](https://github.com/WordPress/gutenberg/pull/18431). + * Fix [double click to open the appender](https://github.com/WordPress/gutenberg/pull/18379). + * Add a **type=submit** to the [search suggestion buttons](https://github.com/WordPress/gutenberg/pull/18933). + * Support [justifying the menu items](https://github.com/WordPress/gutenberg/pull/18909). + * Use [correct classnames](https://github.com/WordPress/gutenberg/pull/18926) for navigation link block save output. + * Remove the [inspector controls](https://github.com/WordPress/gutenberg/pull/18948). +* Improve the block multi-selection: + * A11y: [Use the browser’s selection](https://github.com/WordPress/gutenberg/pull/16835) default color.  + * Polish the [styles](https://github.com/WordPress/gutenberg/pull/18867). + * [Responsive](https://github.com/WordPress/gutenberg/pull/18915) multi-selection. + * [Allow pasting](https://github.com/WordPress/gutenberg/pull/18746) on multi-selection. +* Improve the [Image blocks replacement flow/UI](https://github.com/WordPress/gutenberg/pull/16200). +* Disable the [HTML mode in the Cover block](https://github.com/WordPress/gutenberg/pull/18730). +* Add [friendly offline error messages](https://github.com/WordPress/gutenberg/pull/17961) on Rest API request failures. +* [Round the focal point](https://github.com/WordPress/gutenberg/pull/18765) coordinates. +* Popover & Dropdowns: Consistently and [smoothly](https://github.com/WordPress/gutenberg/pull/18813) [adjust](https://github.com/WordPress/gutenberg/pull/18936) the [position on scroll](https://github.com/WordPress/gutenberg/pull/17867). +* Remove [clearing the block selection](https://github.com/WordPress/gutenberg/pull/18621) on sidebar tab switch. +* [Separate editor notices](https://github.com/WordPress/gutenberg/pull/18871) by border instead of margin. +* Allow [drag and dropping images into the featured image](https://github.com/WordPress/gutenberg/pull/17486https://github.com/WordPress/gutenberg/pull/17486) box. + +### Bug Fixes + +* Prevent [resized Image blocks](https://github.com/WordPress/gutenberg/pull/18728) from overlapping the boundaries of the block. +* Fix [wrong link to attachment page](https://github.com/WordPress/gutenberg/pull/18731) after replacing images. +* Fix Media & Text block: "[Crop image to fill entire column](https://github.com/WordPress/gutenberg/pull/18729)" reset on image change. +* Fix the [Snackbar notices position](https://github.com/WordPress/gutenberg/pull/18801). +* Save the [Verse block line breaks](https://github.com/WordPress/gutenberg/pull/18372) as single characters. +* [Remove has-background-dim-NaN classname](https://github.com/WordPress/gutenberg/pull/18011) from the Cover block. +* Normalize the keys of the [apiFetch preloaded data](https://github.com/WordPress/gutenberg/pull/18724) to avoid unnecessary Rest API calls. +* Fix [CSS styles of the ColorPicker](https://github.com/WordPress/gutenberg/pull/18448) component. +* Update the Inspector slots to use the bubblesVirtually slots Fixing [RichText usage in Inspector controls](https://github.com/WordPress/gutenberg/pull/16807). +* Move the [Modals and Popovers](https://github.com/WordPress/gutenberg/pull/18775) to the right position in the DOM. +* Fix [alignment of date picker days](https://github.com/WordPress/gutenberg/pull/18856) when used in block. +* Fix alignment of [ToggleControl label](https://github.com/WordPress/gutenberg/pull/18815). +* Fix [the toggled state](https://github.com/WordPress/gutenberg/pull/18868) in the block toolbar buttons. +* Fix the [multi-select inspector padding](https://github.com/WordPress/gutenberg/pull/18847). +* Fix the behavior that allows writing by [clicking anywhere in the canvas](https://github.com/WordPress/gutenberg/pull/18732). +* Prevent [private posts with a future date](https://github.com/WordPress/gutenberg/pull/18834) from becoming public on update. +* Fix [useColors crashes if contrast checkers](https://github.com/WordPress/gutenberg/pull/18884) are not specified. +* Render [metaboxes as a single seemless unit](https://github.com/WordPress/gutenberg/pull/18873) to fix styling issues for themes with colored backgrounds. +* Fix the [FontSizePicker custom option](https://github.com/WordPress/gutenberg/pull/18842). +* Fix [reusable blocks](https://github.com/WordPress/gutenberg/pull/18902) showing up as too tall. +* Fix [Drop Cap + alignment](https://github.com/WordPress/gutenberg/pull/18831) producing a gap between paragraphs. +* Fix [Cover to Image block transform](https://github.com/WordPress/gutenberg/pull/18023) when no image is used in the Cover block. +* Ensure [empty classname is not output](https://github.com/WordPress/gutenberg/pull/18861) onto table element. +* Fix [scrolling the sidebar on mobile](https://github.com/WordPress/gutenberg/pull/18937). +* I18: Fix the [Code block](https://github.com/WordPress/gutenberg/pull/18964) [example](https://github.com/WordPress/gutenberg/pull/18993) [string](https://github.com/WordPress/gutenberg/pull/18973). + +### APIs + +* Support a [**disabled** prop in the RichText](https://github.com/WordPress/gutenberg/pull/18792) component. +* Add a [new](https://github.com/WordPress/gutenberg/pull/18827) [CustomSelectControl](https://github.com/WordPress/gutenberg/pull/17926) [component](https://github.com/WordPress/gutenberg/pull/18944). +* Add a new [TextHighlight](https://github.com/WordPress/gutenberg/pull/18609) component. +* Add a new [CustomGradientPicker](https://github.com/WordPress/gutenberg/pull/17603) component. +* Add [useViewportMatch](https://github.com/WordPress/gutenberg/pull/18816) [React hook](https://github.com/WordPress/gutenberg/pull/18950) to the @wordpress/compose package. +* Allowing [changing the aXe config](https://github.com/WordPress/gutenberg/pull/18712) in the @wordpress/just-puppeteer-axe package. + +### Experiments + +* Block Content Areas: + * Add a [demo templates](https://github.com/WordPress/gutenberg/pull/18554) directory.  + * Add the [Template Part](https://github.com/WordPress/gutenberg/pull/18736) block. + * Add [documentation](https://github.com/WordPress/gutenberg/pull/18890) for the current state of the experiment. +* Widgets screen: + * Clear the block selection when [clicking outside the widget areas](https://github.com/WordPress/gutenberg/pull/17851). +* APIs: + * Add a new [\_\_experimentalResolveSelect](https://github.com/WordPress/gutenberg/pull/17558https://github.com/WordPress/gutenberg/pull/17558) API to the data package. + * Add [color detection and contrast checks support](https://github.com/WordPress/gutenberg/pull/18547) to the useColors hook. + +### Documentation + +* Improvements to the [Getting Started](https://github.com/WordPress/gutenberg/pull/18769) documentation. +* Include [TypeScript type checking](https://github.com/WordPress/gutenberg/pull/18879) in Testing Overview. +* Add [JSDoc recommendations](https://github.com/WordPress/gutenberg/pull/18920). +* Reintroduce [NodeJS LTS](https://github.com/WordPress/gutenberg/pull/18923) support commitment. +* Typos and tweaks: [1](https://github.com/WordPress/gutenberg/pull/18752), [2](https://github.com/WordPress/gutenberg/pull/18882), [3](https://github.com/WordPress/gutenberg/pull/18882), [4](https://github.com/WordPress/gutenberg/pull/18916), [5](https://github.com/WordPress/gutenberg/pull/18961), [6](https://github.com/WordPress/gutenberg/pull/19012). + +### Performance + +* Avoid [rerendering the EditorRegions component](https://github.com/WordPress/gutenberg/pull/18776) on each click. +* Flatten and simplify the [align hook](https://github.com/WordPress/gutenberg/pull/18963) [rendering](https://github.com/WordPress/gutenberg/pull/19008). +* Shim the [meta attribute source](https://github.com/WordPress/gutenberg/pull/18960) on block registration. + +### Various + +* Storybook: Add [StoryShots integration](https://github.com/WordPress/gutenberg/pull/18031) to generate unit tests. +* Work on the stability of e2e tests: [1](https://github.com/WordPress/gutenberg/pull/18662), [2](https://github.com/WordPress/gutenberg/pull/18754), [3](https://github.com/WordPress/gutenberg/pull/18753), [4](https://github.com/WordPress/gutenberg/pull/18773), [5](https://github.com/WordPress/gutenberg/pull/18771). +* Use [consistent theme colors and font sizes](https://github.com/WordPress/gutenberg/pull/18761) in e2e tests. +* Travis: [Skip the deploy stage](https://github.com/WordPress/gutenberg/pull/18788) on PRs. +* And a Travis job to check the [IE11 compatibility of the produced JavaScript builds](https://github.com/WordPress/gutenberg/pull/18774). +* Avoid usage of [editor store on block editor](https://github.com/WordPress/gutenberg/pull/18784) reusable blocks inserter. +* Replace the [fs-extra dependency with rimraf](https://github.com/WordPress/gutenberg/pull/18790). +* RSS block: Remove [PHP 5.2 compatibility code](https://github.com/WordPress/gutenberg/pull/15806). +* Update the [Columns block to use the Patterns API](https://github.com/WordPress/gutenberg/pull/18283). +* Refactor the [BlockToolbar component](https://github.com/WordPress/gutenberg/pull/18843) to use React hooks. +* Refactor the [BlockDraggable](https://github.com/WordPress/gutenberg/pull/18756) component for a simpler React tree. +* Refactor the [BlockHTML](https://github.com/WordPress/gutenberg/pull/18968) component to use React hooks. +* Refactor the [BlockList](https://github.com/WordPress/gutenberg/pull/18821) component to use React hooks. +* Refactor the [BlockInsertionPoint](https://github.com/WordPress/gutenberg/pull/18821) component to use React hooks. +* [Split @wordpress/urls into multiple modules](https://github.com/WordPress/gutenberg/pull/18689https://github.com/WordPress/gutenberg/pull/18689)/files to allow better tree-shaking. +* Improve the Storybook setup to allow [updates on style changes](https://github.com/WordPress/gutenberg/pull/18676). +* Enforce consistent usage of [Button and ToolbarGroup](https://github.com/WordPress/gutenberg/pull/18817) components. +* Use the [colors hook in the Paragraph block](https://github.com/WordPress/gutenberg/pull/18148). +* Add missing actions and tests for [lockPostAutosaving, unlockPostAutosaving](https://github.com/WordPress/gutenberg/pull/18854). +* [Collapse passed](https://github.com/WordPress/gutenberg/pull/16755) [tests](https://github.com/WordPress/gutenberg/pull/18896) in Travis jobs. +* Add [side effects property to the @wordpress/components package](https://github.com/WordPress/gutenberg/pull/18911) to allow tree-shaking. +* Add a [script to perform patch releases](https://github.com/WordPress/gutenberg/pull/18938) for old npm package versions. +* Reuse the [URLInput component in the Social Links](https://github.com/WordPress/gutenberg/pull/18905) block and [disable suggestions](https://github.com/WordPress/gutenberg/pull/18946). +* Improve and simplify [reusable block](https://github.com/WordPress/gutenberg/pull/18903) [styles](https://github.com/WordPress/gutenberg/pull/18958). +* Refactor the [Gallery edit component](https://github.com/WordPress/gutenberg/pull/18265) to be semi-cross-platform. +* Run tests using the same [environment](https://github.com/WordPress/gutenberg/pull/18703) version used for development. +* Add [CPU/Network slowdown configuration](https://github.com/WordPress/gutenberg/pull/18770) options to the e2e tests setup. +* Enable [Type checking for the @wordpress/token-list](https://github.com/WordPress/gutenberg/pull/18839) package. +* Move the [changelog.txt and readme.txt files](https://github.com/WordPress/gutenberg/pull/18828) to the Github repository. diff --git a/storybook/.babelrc b/storybook/.babelrc new file mode 100644 index 00000000000000..b2b3e792e2f972 --- /dev/null +++ b/storybook/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ "@wordpress/babel-preset-default" ], + "plugins": [ + "babel-plugin-emotion", + "babel-plugin-inline-json-import" + ], + "env": { + "test": { + "plugins": [ "babel-plugin-require-context-hook" ] + } + } +} diff --git a/storybook/.sassrc b/storybook/.sassrc new file mode 100644 index 00000000000000..4f704b80b310a5 --- /dev/null +++ b/storybook/.sassrc @@ -0,0 +1,5 @@ +{ + "includePaths": [ + "node_modules" + ] +} diff --git a/storybook/README.md b/storybook/README.md new file mode 100644 index 00000000000000..b397d03c607739 --- /dev/null +++ b/storybook/README.md @@ -0,0 +1,15 @@ +# Storybook + +Storybook is an open-source tool that provides a sandbox to develop and visualize components in isolation. See the [Storybook site](https://storybook.js.org/) for more information about the tool. + +The Gutenberg project uses Storybook to view and work with the UI components developed in the WordPress packages. + +View online at: https://wordpress.github.io/gutenberg/ + +Run locally in your development environment running: `npm run storybook:dev` from the top-level Gutenberg directory. + +## StoryShots Integration + +> [StoryShots](https://www.npmjs.com/package/@storybook/addon-storyshots) adds automatic Jest Snapshot Testing for [Storybook](https://storybook.js.org/). + +Please refer to [Testing Overview](/docs/contributors/testing-overview.md#storyshots) to learn how to maintain auto-generated unit tests from stories added to Storybook. diff --git a/packages/components/storybook/addons.js b/storybook/addons.js similarity index 56% rename from packages/components/storybook/addons.js rename to storybook/addons.js index 48824f721ea0c4..ccfec56c606dcc 100644 --- a/packages/components/storybook/addons.js +++ b/storybook/addons.js @@ -2,4 +2,6 @@ * External dependencies */ import '@storybook/addon-a11y/register'; +import '@storybook/addon-knobs/register'; +import '@storybook/addon-storysource/register'; import '@storybook/addon-viewport/register'; diff --git a/storybook/config.js b/storybook/config.js new file mode 100644 index 00000000000000..1da0348c5fffce --- /dev/null +++ b/storybook/config.js @@ -0,0 +1,30 @@ +/** + * External dependencies + */ +import { addDecorator, configure } from '@storybook/react'; +import { withA11y } from '@storybook/addon-a11y'; +import { withKnobs } from '@storybook/addon-knobs'; + +/** + * WordPress dependencies + */ +/* eslint-disable no-restricted-syntax */ +import '@wordpress/components/build-style/style.css'; +/* eslint-enable no-restricted-syntax */ + +/** + * Internal dependencies + */ +import './style.scss'; + +addDecorator( withA11y ); +addDecorator( withKnobs ); +configure( + [ + // StoryShots addon doesn't support MDX files at the moment. + // It should ignore the playground in the initial pass as well. + process.env.NODE_ENV !== 'test' && require.context( './stories/', true, /\/.+\.(js|mdx)$/ ), + require.context( '../packages/components/src/', true, /\/stories\/.+\.js$/ ), + ].filter( Boolean ), + module +); diff --git a/packages/components/storybook/presets.js b/storybook/presets.js similarity index 100% rename from packages/components/storybook/presets.js rename to storybook/presets.js diff --git a/storybook/preview-head.html b/storybook/preview-head.html new file mode 100644 index 00000000000000..ecf2ea599cb3e2 --- /dev/null +++ b/storybook/preview-head.html @@ -0,0 +1 @@ +<link href="https://fonts.googleapis.com/css?family=Noto+Serif:400,700" rel="stylesheet"> diff --git a/packages/components/docs/introduction.story.mdx b/storybook/stories/docs/introduction.story.mdx similarity index 70% rename from packages/components/docs/introduction.story.mdx rename to storybook/stories/docs/introduction.story.mdx index b21ec2d11c4072..9b25274688acdb 100644 --- a/packages/components/docs/introduction.story.mdx +++ b/storybook/stories/docs/introduction.story.mdx @@ -1,6 +1,6 @@ import { Meta } from '@storybook/addon-docs/blocks'; -<Meta title="Introduction" /> +<Meta title="Docs|Introduction" /> # Introduction diff --git a/playground/src/editor-styles.scss b/storybook/stories/playground/editor-styles.scss similarity index 100% rename from playground/src/editor-styles.scss rename to storybook/stories/playground/editor-styles.scss diff --git a/storybook/stories/playground/index.js b/storybook/stories/playground/index.js new file mode 100644 index 00000000000000..dc3806b4af303f --- /dev/null +++ b/storybook/stories/playground/index.js @@ -0,0 +1,69 @@ +/** + * WordPress dependencies + */ +import '@wordpress/editor'; // This shouldn't be necessary + +import { useEffect, useState } from '@wordpress/element'; +import { + BlockEditorKeyboardShortcuts, + BlockEditorProvider, + BlockList, + BlockInspector, + WritingFlow, + ObserveTyping, +} from '@wordpress/block-editor'; +import { + Popover, + SlotFillProvider, + DropZoneProvider, +} from '@wordpress/components'; +import { registerCoreBlocks } from '@wordpress/block-library'; +import '@wordpress/format-library'; + +/** + * Internal dependencies + */ +import './style.scss'; + +function App() { + const [ blocks, updateBlocks ] = useState( [] ); + + useEffect( () => { + registerCoreBlocks(); + }, [] ); + + return ( + <div className="playground"> + <SlotFillProvider> + <DropZoneProvider> + <BlockEditorProvider + value={ blocks } + onInput={ updateBlocks } + onChange={ updateBlocks } + > + <div className="playground__sidebar"> + <BlockInspector /> + </div> + <div className="editor-styles-wrapper"> + <BlockEditorKeyboardShortcuts /> + <WritingFlow> + <ObserveTyping> + <BlockList /> + </ObserveTyping> + </WritingFlow> + </div> + <Popover.Slot /> + </BlockEditorProvider> + </DropZoneProvider> + </SlotFillProvider> + </div> + ); +} + +export default { + title: 'Playground|Block Editor', +}; + +export const _default = () => { + return <App />; +}; diff --git a/playground/src/reset.scss b/storybook/stories/playground/reset.scss similarity index 100% rename from playground/src/reset.scss rename to storybook/stories/playground/reset.scss diff --git a/storybook/stories/playground/style.scss b/storybook/stories/playground/style.scss new file mode 100644 index 00000000000000..a9dc2af8c21b8c --- /dev/null +++ b/storybook/stories/playground/style.scss @@ -0,0 +1,63 @@ +@import "~@wordpress/base-styles/colors"; +@import "~@wordpress/base-styles/variables"; +@import "~@wordpress/base-styles/mixins"; +@import "~@wordpress/base-styles/breakpoints"; +@import "~@wordpress/base-styles/animations"; +@import "~@wordpress/base-styles/z-index"; + +@import "./reset"; +@import "./editor-styles"; + +.playground { + @include break-small() { + width: calc(100% - #{$sidebar-width}); + } + padding-top: 20px; + + img { + max-width: 100%; + height: auto; + } + + iframe { + width: 100%; + } + + .components-navigate-regions { + height: 100%; + } +} + +.playground__sidebar { + position: fixed; + top: 0; + right: 0; + bottom: 0; + width: $sidebar-width; + border-left: $border-width solid $light-gray-500; + height: auto; + overflow: auto; + -webkit-overflow-scrolling: touch; + + // Temporarily disable the sidebar on mobile + display: none; + @include break-small() { + display: block; + } +} + +/** + * Animations + */ + +// These keyframes should not be part of the _animations.scss mixins file. +// Because keyframe animations can't be defined as mixins properly, they are duplicated. +// Since hey are intended only for the editor, we add them here instead. +@keyframes edit-post__fade-in-animation { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/storybook/style.scss b/storybook/style.scss new file mode 100644 index 00000000000000..2133b26b3cb436 --- /dev/null +++ b/storybook/style.scss @@ -0,0 +1,16 @@ +// Loading @wordpress/base-styles as they have mixins and other dependencies +// used within @wordpress/*/src/*.scss +@import "~@wordpress/base-styles/animations"; +@import "~@wordpress/base-styles/breakpoints"; +@import "~@wordpress/base-styles/colors"; +@import "~@wordpress/base-styles/mixins"; +@import "~@wordpress/base-styles/variables"; +@import "~@wordpress/base-styles/z-index"; + +// @wordpress package styles +@import "../packages/components/src/style.scss"; +@import "../packages/block-editor/src/style.scss"; +@import "../packages/block-library/src/style.scss"; +@import "../packages/block-library/src/theme.scss"; +@import "../packages/block-library/src/editor.scss"; +@import "../packages/format-library/src/style.scss"; diff --git a/storybook/test/__snapshots__/index.js.snap b/storybook/test/__snapshots__/index.js.snap new file mode 100644 index 00000000000000..707b97a8e35f95 --- /dev/null +++ b/storybook/test/__snapshots__/index.js.snap @@ -0,0 +1,3979 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Components|Animate Appear Bottom Left 1`] = ` +<div + className="components-animate__appear is-from-left is-from-bottom components-notice is-success is-dismissible" +> + <div + className="components-notice__content" + > + <p> + Appear animation. Origin: + bottom left + . + </p> + </div> + <button + aria-label="Dismiss this notice" + className="components-button components-icon-button components-notice__dismiss" + onClick={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-no-alt" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z" + /> + </svg> + </button> +</div> +`; + +exports[`Storyshots Components|Animate Appear Bottom Right 1`] = ` +<div + className="components-animate__appear is-from-right is-from-bottom components-notice is-success is-dismissible" +> + <div + className="components-notice__content" + > + <p> + Appear animation. Origin: + bottom right + . + </p> + </div> + <button + aria-label="Dismiss this notice" + className="components-button components-icon-button components-notice__dismiss" + onClick={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-no-alt" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z" + /> + </svg> + </button> +</div> +`; + +exports[`Storyshots Components|Animate Appear Top Left 1`] = ` +<div + className="components-animate__appear is-from-left is-from-top components-notice is-success is-dismissible" +> + <div + className="components-notice__content" + > + <p> + Appear animation. Origin: + top left + . + </p> + </div> + <button + aria-label="Dismiss this notice" + className="components-button components-icon-button components-notice__dismiss" + onClick={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-no-alt" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z" + /> + </svg> + </button> +</div> +`; + +exports[`Storyshots Components|Animate Appear Top Right 1`] = ` +<div + className="components-animate__appear is-from-right is-from-top components-notice is-success is-dismissible" +> + <div + className="components-notice__content" + > + <p> + Appear animation. Origin: + top right + . + </p> + </div> + <button + aria-label="Dismiss this notice" + className="components-button components-icon-button components-notice__dismiss" + onClick={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-no-alt" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z" + /> + </svg> + </button> +</div> +`; + +exports[`Storyshots Components|Animate Default 1`] = ` +<div + className="components-notice is-success is-dismissible" +> + <div + className="components-notice__content" + > + <p> + No default animation. Use one of type = "appear", "slide-in", or "loading". + </p> + </div> + <button + aria-label="Dismiss this notice" + className="components-button components-icon-button components-notice__dismiss" + onClick={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-no-alt" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z" + /> + </svg> + </button> +</div> +`; + +exports[`Storyshots Components|Animate Loading 1`] = ` +<div + className="components-animate__loading components-notice is-success is-dismissible" +> + <div + className="components-notice__content" + > + <p> + Loading animation. + </p> + </div> + <button + aria-label="Dismiss this notice" + className="components-button components-icon-button components-notice__dismiss" + onClick={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-no-alt" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z" + /> + </svg> + </button> +</div> +`; + +exports[`Storyshots Components|Animate Slide In 1`] = ` +<div + className="components-animate__slide-in is-from-left components-notice is-success is-dismissible" +> + <div + className="components-notice__content" + > + <p> + Slide-in animation. + </p> + </div> + <button + aria-label="Dismiss this notice" + className="components-button components-icon-button components-notice__dismiss" + onClick={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-no-alt" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z" + /> + </svg> + </button> +</div> +`; + +exports[`Storyshots Components|BaseControl Default 1`] = ` +<div + className="components-base-control" +> + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="textarea-1" + > + Label text + </label> + <textarea + id="textarea-1" + /> + </div> + <p + className="components-base-control__help" + id="textarea-1__help" + > + Help text + </p> +</div> +`; + +exports[`Storyshots Components|Button Default 1`] = ` +<button + className="components-button" + type="button" +> + Default Button +</button> +`; + +exports[`Storyshots Components|Button Disabled 1`] = ` +<button + className="components-button" + disabled={true} + type="button" +> + Disabled Button +</button> +`; + +exports[`Storyshots Components|Button Disabled Link 1`] = ` +<button + className="components-button" + disabled={true} + type="button" +> + Disabled Link Button +</button> +`; + +exports[`Storyshots Components|Button Large 1`] = ` +<button + className="components-button is-button is-default is-large" + type="button" +> + Large Button +</button> +`; + +exports[`Storyshots Components|Button Large Primary 1`] = ` +<button + className="components-button is-button is-primary is-large" + type="button" +> + Large Primary Button +</button> +`; + +exports[`Storyshots Components|Button Link 1`] = ` +<a + className="components-button" + href="https://wordpress.org/" + target="_blank" +> + Link Button +</a> +`; + +exports[`Storyshots Components|Button Pressed 1`] = ` +<button + aria-pressed={true} + className="components-button is-pressed" + type="button" +> + Pressed Button +</button> +`; + +exports[`Storyshots Components|Button Primary 1`] = ` +<button + className="components-button is-button is-primary" + type="button" +> + Primary Button +</button> +`; + +exports[`Storyshots Components|Button Small 1`] = ` +<button + className="components-button is-button is-default is-small" + type="button" +> + Small Button +</button> +`; + +exports[`Storyshots Components|ButtonGroup Default 1`] = ` +<div + className="components-button-group" + role="group" +> + <button + className="components-button is-button is-primary" + style={ + Object { + "margin": "0 4px", + } + } + type="button" + > + Button 1 + </button> + <button + className="components-button is-button is-primary" + style={ + Object { + "margin": "0 4px", + } + } + type="button" + > + Button 2 + </button> +</div> +`; + +exports[`Storyshots Components|Card Default 1`] = ` +<div + className="components-card is-size-medium css-l3gor6-CardUI e1q7k77g0" + style={ + Object { + "width": "360px", + } + } +> + <div + className="components-card__header is-size-medium css-6qsnk-HeaderUI e1q7k77g1" + > + Header + </div> + <div + className="components-card__body is-size-medium css-1yyn1r2-BodyUI e1q7k77g3" + > + Code is Poetry + </div> + <div + className="components-card__footer is-size-medium css-wy9sfy-FooterUI e1q7k77g4" + > + Footer + </div> +</div> +`; + +exports[`Storyshots Components|Card/Body Default 1`] = ` +<div + className="components-card is-size-medium css-l3gor6-CardUI e1q7k77g0" + style={ + Object { + "width": "360px", + } + } +> + <div + className="components-card__body is-size-medium css-1yyn1r2-BodyUI e1q7k77g3" + > + Content + </div> +</div> +`; + +exports[`Storyshots Components|Card/Divider Default 1`] = ` +<div + className="components-card is-size-medium css-l3gor6-CardUI e1q7k77g0" + style={ + Object { + "width": "360px", + } + } +> + <div + className="components-card__body is-size-medium css-1yyn1r2-BodyUI e1q7k77g3" + > + ... + </div> + <hr + className="components-card__divider css-lh2anh-DividerUI e1q7k77g5" + role="separator" + /> + <div + className="components-card__body is-size-medium css-1yyn1r2-BodyUI e1q7k77g3" + > + ... + </div> +</div> +`; + +exports[`Storyshots Components|Card/Footer Default 1`] = ` +<div + className="components-card is-size-medium css-l3gor6-CardUI e1q7k77g0" + style={ + Object { + "width": "360px", + } + } +> + <div + className="components-card__footer is-size-medium css-wy9sfy-FooterUI e1q7k77g4" + > + Content + </div> +</div> +`; + +exports[`Storyshots Components|Card/Header Default 1`] = ` +<div + className="components-card is-size-medium css-l3gor6-CardUI e1q7k77g0" + style={ + Object { + "width": "360px", + } + } +> + <div + className="components-card__header is-size-medium css-6qsnk-HeaderUI e1q7k77g1" + > + Content + </div> +</div> +`; + +exports[`Storyshots Components|Card/Media Default 1`] = ` +<div + className="components-card is-size-medium css-l3gor6-CardUI e1q7k77g0" + style={ + Object { + "width": "360px", + } + } +> + <div + className="components-card__media css-eunfqt-MediaUI e1q7k77g2" + > + <img + alt="SMELLING MARSHMELLOW ICECREAM CONE" + src="https://images.unsplash.com/photo-1570776765652-4ce2a88cc1f9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80" + /> + </div> + <div + className="components-card__body is-size-medium css-1yyn1r2-BodyUI e1q7k77g3" + > + Content + </div> +</div> +`; + +exports[`Storyshots Components|Card/Media Header And Footer 1`] = ` +<div + className="components-card is-size-medium css-l3gor6-CardUI e1q7k77g0" + style={ + Object { + "width": "360px", + } + } +> + <div + className="components-card__header is-size-medium css-6qsnk-HeaderUI e1q7k77g1" + > + Header Content + </div> + <div + className="components-card__media css-eunfqt-MediaUI e1q7k77g2" + > + <img + alt="SMELLING MARSHMELLOW ICECREAM CONE" + src="https://images.unsplash.com/photo-1570776765652-4ce2a88cc1f9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80" + /> + </div> + <div + className="components-card__footer is-shady is-size-medium css-wy9sfy-FooterUI e1q7k77g4" + > + Caption + </div> +</div> +`; + +exports[`Storyshots Components|Card/Media Horizontally Aligned 1`] = ` +Array [ + <p> + Note: This story demonstrates how this UI may be created. It requires extra styling. + </p>, + <div + className="components-card is-size-medium is-left e1gk3lep2 css-5mtgus-CardUI-HorizontallyAlignedCard e1q7k77g0" + > + <div + className="components-card__media is-left e1gk3lep0 css-7tds6a-MediaUI-StyledCardMedia e1q7k77g2" + style={ + Object { + "maxWidth": 200, + } + } + > + <img + alt="SMELLING MARSHMELLOW ICECREAM CONE" + src="https://images.unsplash.com/photo-1570776765652-4ce2a88cc1f9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80" + /> + </div> + <div + className="components-card__body is-size-medium e1gk3lep1 css-1drywz3-BodyUI-StyledCardBody e1q7k77g3" + > + Content + </div> + </div>, +] +`; + +exports[`Storyshots Components|Card/Media Iframe Embed 1`] = ` +<div + className="components-card is-size-medium css-l3gor6-CardUI e1q7k77g0" + style={ + Object { + "width": "360px", + } + } +> + <div + className="components-card__header is-size-medium css-6qsnk-HeaderUI e1q7k77g1" + > + Header Content + </div> + <div + className="components-card__media css-eunfqt-MediaUI e1q7k77g2" + > + <iframe + allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" + allowFullScreen={true} + frameBorder="0" + height="315" + src="https://www.youtube.com/embed/zGP6zk7jcrQ" + title="Corgi" + width="560" + /> + </div> +</div> +`; + +exports[`Storyshots Components|Card/Media On Bottom 1`] = ` +<div + className="components-card is-size-medium css-l3gor6-CardUI e1q7k77g0" + style={ + Object { + "width": "360px", + } + } +> + <div + className="components-card__body is-size-medium css-1yyn1r2-BodyUI e1q7k77g3" + > + Content + </div> + <div + className="components-card__media css-eunfqt-MediaUI e1q7k77g2" + > + <img + alt="SMELLING MARSHMELLOW ICECREAM CONE" + src="https://images.unsplash.com/photo-1570776765652-4ce2a88cc1f9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80" + /> + </div> +</div> +`; + +exports[`Storyshots Components|Card/Media On Top 1`] = ` +<div + className="components-card is-size-medium css-l3gor6-CardUI e1q7k77g0" + style={ + Object { + "width": "360px", + } + } +> + <div + className="components-card__media css-eunfqt-MediaUI e1q7k77g2" + > + <img + alt="SMELLING MARSHMELLOW ICECREAM CONE" + src="https://images.unsplash.com/photo-1570776765652-4ce2a88cc1f9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80" + /> + </div> + <div + className="components-card__body is-size-medium css-1yyn1r2-BodyUI e1q7k77g3" + > + <div> + Content + </div> + </div> +</div> +`; + +exports[`Storyshots Components|CheckboxControl All 1`] = ` +<div + className="components-base-control" +> + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-checkbox-control-1" + > + User + </label> + <span + className="components-checkbox-control__input-container" + > + <input + aria-describedby="inspector-checkbox-control-1__help" + checked={true} + className="components-checkbox-control__input" + id="inspector-checkbox-control-1" + onChange={[Function]} + type="checkbox" + value="1" + /> + <svg + aria-hidden="true" + className="dashicon dashicons-yes components-checkbox-control__checked" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M14.83 4.89l1.34.94-5.81 8.38H9.02L5.78 9.67l1.34-1.25 2.57 2.4z" + /> + </svg> + </span> + <label + className="components-checkbox-control__label" + htmlFor="inspector-checkbox-control-1" + > + Is author + </label> + </div> + <p + className="components-base-control__help" + id="inspector-checkbox-control-1__help" + > + Is the user an author or not? + </p> +</div> +`; + +exports[`Storyshots Components|CheckboxControl Default 1`] = ` +<div + className="components-base-control" +> + <div + className="components-base-control__field" + > + <span + className="components-checkbox-control__input-container" + > + <input + checked={true} + className="components-checkbox-control__input" + id="inspector-checkbox-control-0" + onChange={[Function]} + type="checkbox" + value="1" + /> + <svg + aria-hidden="true" + className="dashicon dashicons-yes components-checkbox-control__checked" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M14.83 4.89l1.34.94-5.81 8.38H9.02L5.78 9.67l1.34-1.25 2.57 2.4z" + /> + </svg> + </span> + <label + className="components-checkbox-control__label" + htmlFor="inspector-checkbox-control-0" + > + Is author + </label> + </div> +</div> +`; + +exports[`Storyshots Components|ClipboardButton Default 1`] = ` +<span + onCopy={[Function]} +> + <button + className="components-button components-clipboard-button is-button is-primary" + type="button" + > + Copy "Text" + </button> +</span> +`; + +exports[`Storyshots Components|ColorIndicator Default 1`] = ` +<span + className="component-color-indicator" + style={ + Object { + "background": "#0073aa", + } + } +/> +`; + +exports[`Storyshots Components|ColorPalette Default 1`] = ` +<div + className="components-circular-option-picker" +> + <div + className="components-circular-option-picker__option-wrapper" + > + <button + aria-label="Color: red" + aria-pressed={false} + className="components-button components-circular-option-picker__option" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + style={ + Object { + "color": "#f00", + } + } + type="button" + /> + </div> + <div + className="components-circular-option-picker__option-wrapper" + > + <button + aria-label="Color: white" + aria-pressed={false} + className="components-button components-circular-option-picker__option" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + style={ + Object { + "color": "#fff", + } + } + type="button" + /> + </div> + <div + className="components-circular-option-picker__option-wrapper" + > + <button + aria-label="Color: blue" + aria-pressed={false} + className="components-button components-circular-option-picker__option" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + style={ + Object { + "color": "#00f", + } + } + type="button" + /> + </div> + <div + className="components-circular-option-picker__custom-clear-wrapper" + > + <div + className="components-dropdown components-circular-option-picker__dropdown-link-action" + > + <button + aria-expanded={false} + aria-label="Custom color picker" + className="components-button is-link" + onClick={[Function]} + type="button" + > + Custom Color + </button> + </div> + <button + className="components-button components-circular-option-picker__clear is-button is-default is-small" + onClick={[Function]} + type="button" + > + Clear + </button> + </div> +</div> +`; + +exports[`Storyshots Components|ColorPalette With Knobs 1`] = ` +<div + className="components-circular-option-picker" +> + <div + className="components-circular-option-picker__option-wrapper" + > + <button + aria-label="Color: red" + aria-pressed={false} + className="components-button components-circular-option-picker__option" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + style={ + Object { + "color": "#f00", + } + } + type="button" + /> + </div> + <div + className="components-circular-option-picker__option-wrapper" + > + <button + aria-label="Color: white" + aria-pressed={false} + className="components-button components-circular-option-picker__option" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + style={ + Object { + "color": "#fff", + } + } + type="button" + /> + </div> + <div + className="components-circular-option-picker__option-wrapper" + > + <button + aria-label="Color: blue" + aria-pressed={false} + className="components-button components-circular-option-picker__option" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + style={ + Object { + "color": "#00f", + } + } + type="button" + /> + </div> + <div + className="components-circular-option-picker__custom-clear-wrapper" + > + <div + className="components-dropdown components-circular-option-picker__dropdown-link-action" + > + <button + aria-expanded={false} + aria-label="Custom color picker" + className="components-button is-link" + onClick={[Function]} + type="button" + > + Custom Color + </button> + </div> + <button + className="components-button components-circular-option-picker__clear is-button is-default is-small" + onClick={[Function]} + type="button" + > + Clear + </button> + </div> +</div> +`; + +exports[`Storyshots Components|ColorPicker Alpha Enabled 1`] = ` +<div + className="components-color-picker is-alpha-enabled" +> + <div + className="components-color-picker__saturation" + > + <div> + <div + className="components-color-picker__saturation-color" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + role="application" + style={ + Object { + "background": "hsl(0,100%, 50%)", + } + } + > + <div + className="components-color-picker__saturation-white" + /> + <div + className="components-color-picker__saturation-black" + /> + <button + aria-describedby="color-picker-saturation-1" + aria-label="Choose a shade" + className="components-button components-color-picker__saturation-pointer" + onKeyDown={[Function]} + style={ + Object { + "left": "100%", + "top": "0%", + } + } + type="button" + /> + <div + className="components-visually-hidden" + id="color-picker-saturation-1" + > + Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. + </div> + </div> + </div> + </div> + <div + className="components-color-picker__body" + > + <div + className="components-color-picker__controls" + > + <div + className="components-color-picker__swatch" + > + <div + className="components-color-picker__active" + style={ + Object { + "backgroundColor": "rgb(255, 0, 0)", + } + } + /> + </div> + <div + className="components-color-picker__toggles" + > + <div> + <div + className="components-color-picker__hue" + > + <div + className="components-color-picker__hue-gradient" + /> + <div + className="components-color-picker__hue-bar" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + > + <div + aria-describedby="components-color-picker__hue-description-1" + aria-label="Hue value in degrees, from 0 to 359." + aria-orientation="horizontal" + aria-valuemax="1" + aria-valuemin="359" + aria-valuenow={0} + className="components-color-picker__hue-pointer" + onKeyDown={[Function]} + role="slider" + style={ + Object { + "left": "0%", + } + } + tabIndex="0" + /> + <p + className="components-visually-hidden" + id="components-color-picker__hue-description-1" + > + Move the arrow left or right to change hue. + </p> + </div> + </div> + </div> + <div> + <div + className="components-color-picker__alpha" + > + <div + className="components-color-picker__alpha-gradient" + style={ + Object { + "background": "linear-gradient(to right, rgba(255,0,0, 0) 0%, rgba(255,0,0, 1) 100%)", + } + } + /> + <div + className="components-color-picker__alpha-bar" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + > + <div + aria-label="Alpha value, from 0 (transparent) to 1 (fully opaque)." + aria-orientation="horizontal" + aria-valuemax="1" + aria-valuemin="0" + aria-valuenow={1} + className="components-color-picker__alpha-pointer" + onKeyDown={[Function]} + role="slider" + style={ + Object { + "left": "100%", + } + } + tabIndex="0" + /> + </div> + </div> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-wrapper" + > + <div + className="components-color-picker__inputs-fields" + > + <div + className="components-base-control components-color-picker__inputs-field" + > + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-text-control-1" + > + Color value in hexadecimal + </label> + <input + className="components-text-control__input" + id="inspector-text-control-1" + onBlur={[Function]} + onChange={[Function]} + onKeyDown={[Function]} + type="text" + value="#ff0000" + /> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-toggle" + > + <button + aria-label="Change color format" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-arrow-down-alt2" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 6l5 5 5-5 2 1-7 7-7-7z" + /> + </svg> + </button> + </div> + </div> + </div> +</div> +`; + +exports[`Storyshots Components|ColorPicker Default 1`] = ` +<div + className="components-color-picker is-alpha-disabled" +> + <div + className="components-color-picker__saturation" + > + <div> + <div + className="components-color-picker__saturation-color" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + role="application" + style={ + Object { + "background": "hsl(0,100%, 50%)", + } + } + > + <div + className="components-color-picker__saturation-white" + /> + <div + className="components-color-picker__saturation-black" + /> + <button + aria-describedby="color-picker-saturation-0" + aria-label="Choose a shade" + className="components-button components-color-picker__saturation-pointer" + onKeyDown={[Function]} + style={ + Object { + "left": "100%", + "top": "0%", + } + } + type="button" + /> + <div + className="components-visually-hidden" + id="color-picker-saturation-0" + > + Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation. + </div> + </div> + </div> + </div> + <div + className="components-color-picker__body" + > + <div + className="components-color-picker__controls" + > + <div + className="components-color-picker__swatch" + > + <div + className="components-color-picker__active" + style={ + Object { + "backgroundColor": "rgb(255, 0, 0)", + } + } + /> + </div> + <div + className="components-color-picker__toggles" + > + <div> + <div + className="components-color-picker__hue" + > + <div + className="components-color-picker__hue-gradient" + /> + <div + className="components-color-picker__hue-bar" + onMouseDown={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + > + <div + aria-describedby="components-color-picker__hue-description-0" + aria-label="Hue value in degrees, from 0 to 359." + aria-orientation="horizontal" + aria-valuemax="1" + aria-valuemin="359" + aria-valuenow={0} + className="components-color-picker__hue-pointer" + onKeyDown={[Function]} + role="slider" + style={ + Object { + "left": "0%", + } + } + tabIndex="0" + /> + <p + className="components-visually-hidden" + id="components-color-picker__hue-description-0" + > + Move the arrow left or right to change hue. + </p> + </div> + </div> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-wrapper" + > + <div + className="components-color-picker__inputs-fields" + > + <div + className="components-base-control components-color-picker__inputs-field" + > + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-text-control-0" + > + Color value in hexadecimal + </label> + <input + className="components-text-control__input" + id="inspector-text-control-0" + onBlur={[Function]} + onChange={[Function]} + onKeyDown={[Function]} + type="text" + value="#ff0000" + /> + </div> + </div> + </div> + <div + className="components-color-picker__inputs-toggle" + > + <button + aria-label="Change color format" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-arrow-down-alt2" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 6l5 5 5-5 2 1-7 7-7-7z" + /> + </svg> + </button> + </div> + </div> + </div> +</div> +`; + +exports[`Storyshots Components|Dashicon Default 1`] = ` +<svg + aria-hidden="true" + className="dashicon dashicons-wordpress" + color="#0079AA" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" +> + <path + d="M20 10c0-5.52-4.48-10-10-10S0 4.48 0 10s4.48 10 10 10 10-4.48 10-10zM10 1.01c4.97 0 8.99 4.02 8.99 8.99s-4.02 8.99-8.99 8.99S1.01 14.97 1.01 10 5.03 1.01 10 1.01zM8.01 14.82L4.96 6.61c.49-.03 1.05-.08 1.05-.08.43-.05.38-1.01-.06-.99 0 0-1.29.1-2.13.1-.15 0-.33 0-.52-.01 1.44-2.17 3.9-3.6 6.7-3.6 2.09 0 3.99.79 5.41 2.09-.6-.08-1.45.35-1.45 1.42 0 .66.38 1.22.79 1.88.31.54.5 1.22.5 2.21 0 1.34-1.27 4.48-1.27 4.48l-2.71-7.5c.48-.03.75-.16.75-.16.43-.05.38-1.1-.05-1.08 0 0-1.3.11-2.14.11-.78 0-2.11-.11-2.11-.11-.43-.02-.48 1.06-.05 1.08l.84.08 1.12 3.04zm6.02 2.15L16.64 10s.67-1.69.39-3.81c.63 1.14.94 2.42.94 3.81 0 2.96-1.56 5.58-3.94 6.97zM2.68 6.77L6.5 17.25c-2.67-1.3-4.47-4.08-4.47-7.25 0-1.16.2-2.23.65-3.23zm7.45 4.53l2.29 6.25c-.75.27-1.57.42-2.42.42-.72 0-1.41-.11-2.06-.3z" + /> +</svg> +`; + +exports[`Storyshots Components|Draggable Default 1`] = ` +<div> + <p> + Is Dragging? + No + </p> + <div + id="draggable-example-box" + style={ + Object { + "display": "inline-flex", + } + } + > + <div + draggable={true} + onDragEnd={[Function]} + onDragStart={[Function]} + style={ + Object { + "alignItems": "center", + "background": "#ddd", + "display": "flex", + "height": 100, + "justifyContent": "center", + "width": 100, + } + } + > + <svg + aria-hidden="true" + className="dashicon dashicons-move" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M19 10l-4 4v-3h-4v4h3l-4 4-4-4h3v-4H5v3l-4-4 4-4v3h4V5H6l4-4 4 4h-3v4h4V6z" + /> + </svg> + </div> + </div> +</div> +`; + +exports[`Storyshots Components|Dropdown Default 1`] = ` +Array [ + <div> + <p> + This is a DropdownMenu component: + </p> + <div + className="components-dropdown components-dropdown-menu" + > + <button + aria-expanded={false} + aria-haspopup="true" + aria-label="Select a direction" + className="components-button components-icon-button components-dropdown-menu__toggle" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-move" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M19 10l-4 4v-3h-4v4h3l-4 4-4-4h3v-4H5v3l-4-4 4-4v3h4V5H6l4-4 4 4h-3v4h4V6z" + /> + </svg> + </button> + </div> + </div>, + <div> + <p> + This is an assembled Dropdown component: + </p> + <div + className="components-dropdown my-container-class-name" + > + <button + aria-expanded={false} + aria-label="Select a direction" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-move" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M19 10l-4 4v-3h-4v4h3l-4 4-4-4h3v-4H5v3l-4-4 4-4v3h4V5H6l4-4 4 4h-3v4h4V6z" + /> + </svg> + </button> + </div> + </div>, +] +`; + +exports[`Storyshots Components|ExternalLink Default 1`] = ` +<a + className="components-external-link" + href="https://wordpress.org" + rel="external noreferrer noopener" + target="_blank" +> + WordPress + <span + className="components-visually-hidden" + > + (opens in a new tab) + </span> + <svg + aria-hidden="true" + className="dashicon dashicons-external components-external-link__icon" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M9 3h8v8l-2-1V6.92l-5.6 5.59-1.41-1.41L14.08 5H10zm3 12v-3l2-2v7H3V6h8L9 8H5v7h7z" + /> + </svg> +</a> +`; + +exports[`Storyshots Components|FontSizePicker Default 1`] = ` +<fieldset + className="components-font-size-picker" +> + <legend + className="screen-reader-text" + > + Font Size + </legend> + <div + className="components-font-size-picker__controls" + > + <div + className="components-custom-select-control components-font-size-picker__select" + > + <label + className="components-custom-select-control__label" + htmlFor="downshift-null-toggle-button" + id="downshift-null-label" + > + Preset Size + </label> + <button + aria-expanded={false} + aria-haspopup="listbox" + aria-label="Preset Size" + className="components-button components-custom-select-control__button" + id="downshift-null-toggle-button" + onClick={[Function]} + onKeyDown={[Function]} + type="button" + > + Normal + <svg + aria-hidden="true" + className="dashicon dashicons-arrow-down-alt2 components-custom-select-control__button-icon" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 6l5 5 5-5 2 1-7 7-7-7z" + /> + </svg> + </button> + <ul + aria-labelledby="downshift-null-label" + className="components-custom-select-control__menu" + id="downshift-null-menu" + onBlur={[Function]} + onKeyDown={[Function]} + onMouseLeave={[Function]} + role="listbox" + tabIndex={-1} + /> + </div> + <div + className="components-range-control__number-container" + > + <label + htmlFor="components-range-control__number#0" + > + Custom + </label> + <input + aria-label="Custom" + className="components-range-control__number" + id="components-range-control__number#0" + onChange={[Function]} + type="number" + value={16} + /> + </div> + <button + className="components-button components-color-palette__clear is-button is-default is-small" + disabled={false} + onClick={[Function]} + type="button" + > + Reset + </button> + </div> +</fieldset> +`; + +exports[`Storyshots Components|FontSizePicker With Slider 1`] = ` +<fieldset + className="components-font-size-picker" +> + <legend + className="screen-reader-text" + > + Font Size + </legend> + <div + className="components-font-size-picker__controls" + > + <div + className="components-custom-select-control components-font-size-picker__select" + > + <label + className="components-custom-select-control__label" + htmlFor="downshift-null-toggle-button" + id="downshift-null-label" + > + Preset Size + </label> + <button + aria-expanded={false} + aria-haspopup="listbox" + aria-label="Preset Size" + className="components-button components-custom-select-control__button" + id="downshift-null-toggle-button" + onClick={[Function]} + onKeyDown={[Function]} + type="button" + > + Normal + <svg + aria-hidden="true" + className="dashicon dashicons-arrow-down-alt2 components-custom-select-control__button-icon" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 6l5 5 5-5 2 1-7 7-7-7z" + /> + </svg> + </button> + <ul + aria-labelledby="downshift-null-label" + className="components-custom-select-control__menu" + id="downshift-null-menu" + onBlur={[Function]} + onKeyDown={[Function]} + onMouseLeave={[Function]} + role="listbox" + tabIndex={-1} + /> + </div> + <button + className="components-button components-color-palette__clear is-button is-default is-small" + disabled={false} + onClick={[Function]} + type="button" + > + Reset + </button> + </div> + <div + className="components-base-control components-range-control components-font-size-picker__custom-input" + > + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-range-control-0" + > + Custom Size + </label> + <svg + aria-hidden="true" + className="dashicon dashicons-editor-textcolor" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M13.23 15h1.9L11 4H9L5 15h1.88l1.07-3h4.18zm-1.53-4.54H8.51L10 5.6z" + /> + </svg> + <input + className="components-range-control__slider" + id="inspector-range-control-0" + max={100} + min={12} + onChange={[Function]} + type="range" + value={16} + /> + <svg + aria-hidden="true" + className="dashicon dashicons-editor-textcolor" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M13.23 15h1.9L11 4H9L5 15h1.88l1.07-3h4.18zm-1.53-4.54H8.51L10 5.6z" + /> + </svg> + <input + aria-label="Custom Size" + className="components-range-control__number" + max={100} + min={12} + onBlur={[Function]} + onChange={[Function]} + type="number" + value={16} + /> + </div> + </div> +</fieldset> +`; + +exports[`Storyshots Components|FontSizePicker Without Custom Sizes 1`] = ` +<fieldset + className="components-font-size-picker" +> + <legend + className="screen-reader-text" + > + Font Size + </legend> + <div + className="components-font-size-picker__controls" + > + <div + className="components-custom-select-control components-font-size-picker__select" + > + <label + className="components-custom-select-control__label" + htmlFor="downshift-null-toggle-button" + id="downshift-null-label" + > + Preset Size + </label> + <button + aria-expanded={false} + aria-haspopup="listbox" + aria-label="Preset Size" + className="components-button components-custom-select-control__button" + id="downshift-null-toggle-button" + onClick={[Function]} + onKeyDown={[Function]} + type="button" + > + Normal + <svg + aria-hidden="true" + className="dashicon dashicons-arrow-down-alt2 components-custom-select-control__button-icon" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 6l5 5 5-5 2 1-7 7-7-7z" + /> + </svg> + </button> + <ul + aria-labelledby="downshift-null-label" + className="components-custom-select-control__menu" + id="downshift-null-menu" + onBlur={[Function]} + onKeyDown={[Function]} + onMouseLeave={[Function]} + role="listbox" + tabIndex={-1} + /> + </div> + <button + className="components-button components-color-palette__clear is-button is-default is-small" + disabled={false} + onClick={[Function]} + type="button" + > + Reset + </button> + </div> +</fieldset> +`; + +exports[`Storyshots Components|Guide Default 1`] = ` +<button + className="components-button is-button is-default" + onClick={[Function]} + type="button" +> + Open Guide +</button> +`; + +exports[`Storyshots Components|Icon Colors 1`] = ` +Array [ + <div + style={ + Object { + "color": "blue", + "display": "inline-block", + "padding": 20, + } + } + > + <svg + aria-hidden="true" + className="dashicon dashicons-screenoptions" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M9 9V3H3v6h6zm8 0V3h-6v6h6zm-8 8v-6H3v6h6zm8 0v-6h-6v6h6z" + /> + </svg> + <div + style={ + Object { + "fontSize": 12, + } + } + > + 24 + px + </div> + </div>, + <div + style={ + Object { + "color": "purple", + "display": "inline-block", + "padding": 20, + } + } + > + <svg + aria-hidden="true" + className="dashicon dashicons-screenoptions" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M9 9V3H3v6h6zm8 0V3h-6v6h6zm-8 8v-6H3v6h6zm8 0v-6h-6v6h6z" + /> + </svg> + <div + style={ + Object { + "fontSize": 12, + } + } + > + 24 + px + </div> + </div>, + <div + style={ + Object { + "color": "green", + "display": "inline-block", + "padding": 20, + } + } + > + <svg + aria-hidden="true" + className="dashicon dashicons-screenoptions" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M9 9V3H3v6h6zm8 0V3h-6v6h6zm-8 8v-6H3v6h6zm8 0v-6h-6v6h6z" + /> + </svg> + <div + style={ + Object { + "fontSize": 12, + } + } + > + 24 + px + </div> + </div>, +] +`; + +exports[`Storyshots Components|Icon Default 1`] = ` +<div> + <svg + aria-hidden="true" + className="dashicon dashicons-screenoptions" + focusable="false" + height="24" + role="img" + viewBox="0 0 20 20" + width="24" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M9 9V3H3v6h6zm8 0V3h-6v6h6zm-8 8v-6H3v6h6zm8 0v-6h-6v6h6z" + /> + </svg> + <div + style={ + Object { + "fontSize": 12, + } + } + > + 24 + px + </div> +</div> +`; + +exports[`Storyshots Components|Icon Sizes 1`] = ` +Array [ + <div + style={ + Object { + "display": "inline-block", + "padding": 20, + } + } + > + <svg + aria-hidden="true" + className="dashicon dashicons-screenoptions" + focusable="false" + height={14} + role="img" + viewBox="0 0 20 20" + width={14} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M9 9V3H3v6h6zm8 0V3h-6v6h6zm-8 8v-6H3v6h6zm8 0v-6h-6v6h6z" + /> + </svg> + <div + style={ + Object { + "fontSize": 12, + } + } + > + 14 + px + </div> + </div>, + <div + style={ + Object { + "display": "inline-block", + "padding": 20, + } + } + > + <svg + aria-hidden="true" + className="dashicon dashicons-screenoptions" + focusable="false" + height={16} + role="img" + viewBox="0 0 20 20" + width={16} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M9 9V3H3v6h6zm8 0V3h-6v6h6zm-8 8v-6H3v6h6zm8 0v-6h-6v6h6z" + /> + </svg> + <div + style={ + Object { + "fontSize": 12, + } + } + > + 16 + px + </div> + </div>, + <div + style={ + Object { + "display": "inline-block", + "padding": 20, + } + } + > + <svg + aria-hidden="true" + className="dashicon dashicons-screenoptions" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M9 9V3H3v6h6zm8 0V3h-6v6h6zm-8 8v-6H3v6h6zm8 0v-6h-6v6h6z" + /> + </svg> + <div + style={ + Object { + "fontSize": 12, + } + } + > + 20 + px + </div> + </div>, + <div + style={ + Object { + "display": "inline-block", + "padding": 20, + } + } + > + <svg + aria-hidden="true" + className="dashicon dashicons-screenoptions" + focusable="false" + height={24} + role="img" + viewBox="0 0 20 20" + width={24} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M9 9V3H3v6h6zm8 0V3h-6v6h6zm-8 8v-6H3v6h6zm8 0v-6h-6v6h6z" + /> + </svg> + <div + style={ + Object { + "fontSize": 12, + } + } + > + 24 + px + </div> + </div>, + <div + style={ + Object { + "display": "inline-block", + "padding": 20, + } + } + > + <svg + aria-hidden="true" + className="dashicon dashicons-screenoptions" + focusable="false" + height={28} + role="img" + viewBox="0 0 20 20" + width={28} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M9 9V3H3v6h6zm8 0V3h-6v6h6zm-8 8v-6H3v6h6zm8 0v-6h-6v6h6z" + /> + </svg> + <div + style={ + Object { + "fontSize": 12, + } + } + > + 28 + px + </div> + </div>, + <div + style={ + Object { + "display": "inline-block", + "padding": 20, + } + } + > + <svg + aria-hidden="true" + className="dashicon dashicons-screenoptions" + focusable="false" + height={32} + role="img" + viewBox="0 0 20 20" + width={32} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M9 9V3H3v6h6zm8 0V3h-6v6h6zm-8 8v-6H3v6h6zm8 0v-6h-6v6h6z" + /> + </svg> + <div + style={ + Object { + "fontSize": 12, + } + } + > + 32 + px + </div> + </div>, + <div + style={ + Object { + "display": "inline-block", + "padding": 20, + } + } + > + <svg + aria-hidden="true" + className="dashicon dashicons-screenoptions" + focusable="false" + height={40} + role="img" + viewBox="0 0 20 20" + width={40} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M9 9V3H3v6h6zm8 0V3h-6v6h6zm-8 8v-6H3v6h6zm8 0v-6h-6v6h6z" + /> + </svg> + <div + style={ + Object { + "fontSize": 12, + } + } + > + 40 + px + </div> + </div>, + <div + style={ + Object { + "display": "inline-block", + "padding": 20, + } + } + > + <svg + aria-hidden="true" + className="dashicon dashicons-screenoptions" + focusable="false" + height={48} + role="img" + viewBox="0 0 20 20" + width={48} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M9 9V3H3v6h6zm8 0V3h-6v6h6zm-8 8v-6H3v6h6zm8 0v-6h-6v6h6z" + /> + </svg> + <div + style={ + Object { + "fontSize": 12, + } + } + > + 48 + px + </div> + </div>, + <div + style={ + Object { + "display": "inline-block", + "padding": 20, + } + } + > + <svg + aria-hidden="true" + className="dashicon dashicons-screenoptions" + focusable="false" + height={56} + role="img" + viewBox="0 0 20 20" + width={56} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M9 9V3H3v6h6zm8 0V3h-6v6h6zm-8 8v-6H3v6h6zm8 0v-6h-6v6h6z" + /> + </svg> + <div + style={ + Object { + "fontSize": 12, + } + } + > + 56 + px + </div> + </div>, +] +`; + +exports[`Storyshots Components|Icon With A Component 1`] = ` +<svg + aria-hidden="true" + focusable="false" + role="img" +> + <path + d="M5 4v3h5.5v12h3V7H19V4z" + /> +</svg> +`; + +exports[`Storyshots Components|Icon With A Function 1`] = ` +<svg + aria-hidden="true" + focusable="false" + role="img" +> + <path + d="M5 4v3h5.5v12h3V7H19V4z" + /> +</svg> +`; + +exports[`Storyshots Components|Icon With An SVG 1`] = ` +<svg + aria-hidden="true" + focusable="false" + height={24} + role="img" + width={24} +> + <path + d="M5 4v3h5.5v12h3V7H19V4z" + /> +</svg> +`; + +exports[`Storyshots Components|IconButton Default 1`] = ` +<button + aria-label="More" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" +> + <svg + aria-hidden="true" + className="dashicon dashicons-ellipsis" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 10c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm12-2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-7 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" + /> + </svg> +</button> +`; + +exports[`Storyshots Components|IconButton Grouped 1`] = ` +<div + style={ + Object { + "display": "inline-flex", + } + } +> + <button + aria-label="Bold" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-editor-bold" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M6 4v13h4.54c1.37 0 2.46-.33 3.26-1 .8-.66 1.2-1.58 1.2-2.77 0-.84-.17-1.51-.51-2.01s-.9-.85-1.67-1.03v-.09c.57-.1 1.02-.4 1.36-.9s.51-1.13.51-1.91c0-1.14-.39-1.98-1.17-2.5C12.75 4.26 11.5 4 9.78 4H6zm2.57 5.15V6.26h1.36c.73 0 1.27.11 1.61.32.34.22.51.58.51 1.07 0 .54-.16.92-.47 1.15s-.82.35-1.51.35h-1.5zm0 2.19h1.6c1.44 0 2.16.53 2.16 1.61 0 .6-.17 1.05-.51 1.34s-.86.43-1.57.43H8.57v-3.38z" + /> + </svg> + </button> + <button + aria-label="Italic" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-editor-italic" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M14.78 6h-2.13l-2.8 9h2.12l-.62 2H4.6l.62-2h2.14l2.8-9H8.03l.62-2h6.75z" + /> + </svg> + </button> + <button + aria-label="Underline" + className="components-button components-icon-button" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-editor-underline" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M14 5h-2v5.71c0 1.99-1.12 2.98-2.45 2.98-1.32 0-2.55-1-2.55-2.96V5H5v5.87c0 1.91 1 4.54 4.48 4.54 3.49 0 4.52-2.58 4.52-4.5V5zm0 13v-2H5v2h9z" + /> + </svg> + </button> +</div> +`; + +exports[`Storyshots Components|Modal Default 1`] = ` +<button + className="components-button is-button is-default" + onClick={[Function]} + type="button" +> + Open Modal +</button> +`; + +exports[`Storyshots Components|Popover Default 1`] = ` +<span> + <div + onKeyDown={[Function]} + tabIndex="-1" + > + <div + onBlur={[Function]} + onFocus={[Function]} + > + <div + onBlur={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseUp={[Function]} + onTouchEnd={[Function]} + onTouchStart={[Function]} + > + <div + className="components-popover" + onKeyDown={[Function]} + onMouseDown={[Function]} + > + <div + className="components-popover__content" + tabIndex="-1" + > + Popover Content + </div> + </div> + </div> + </div> + </div> +</span> +`; + +exports[`Storyshots Components|Popover Positioning 1`] = ` +<div> + <div + style={ + Object { + "color": "#555", + "position": "absolute", + } + } + > + <p> + Move the gray box around. + </p> + <p> + The + + <strong + style={ + Object { + "background": "pink", + } + } + > + pink bordered + </strong> + + element is a parent. + </p> + <p> + The + + <strong + style={ + Object { + "background": "cyan", + } + } + > + cyan bordered + </strong> + + element is a sibling to + <strong> + Popover + </strong> + . + </p> + <p> + <strong> + Popover + </strong> + aligns to the content within parent. + </p> + </div> + <div + style={ + Object { + "alignItems": "center", + "display": "flex", + "height": "100vh", + "justifyContent": "center", + "width": "100%", + } + } + > + <div + onMouseDown={[Function]} + onMouseUp={[Function]} + style={ + Object { + "background": "#ddd", + "border": "2px solid pink", + "borderRadius": 4, + "left": 0, + "padding": 10, + "position": "relative", + "top": 0, + "userSelect": "none", + } + } + > + <div + style={ + Object { + "border": "2px solid cyan", + } + } + > + Drag Me! + </div> + <span> + <div + onKeyDown={[Function]} + tabIndex="-1" + > + <div + onBlur={[Function]} + onFocus={[Function]} + > + <div + onBlur={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseUp={[Function]} + onTouchEnd={[Function]} + onTouchStart={[Function]} + > + <div + className="components-popover" + onKeyDown={[Function]} + onMouseDown={[Function]} + > + <div + className="components-popover__content" + tabIndex="-1" + > + Popover + </div> + </div> + </div> + </div> + </div> + </span> + </div> + </div> +</div> +`; + +exports[`Storyshots Components|RadioControl Default 1`] = ` +<div + className="components-base-control components-radio-control" +> + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-radio-control-0" + > + Post visibility + </label> + <div + className="components-radio-control__option" + > + <input + checked={true} + className="components-radio-control__input" + id="inspector-radio-control-0-0" + name="inspector-radio-control-0" + onChange={[Function]} + type="radio" + value="public" + /> + <label + htmlFor="inspector-radio-control-0-0" + > + Public + </label> + </div> + <div + className="components-radio-control__option" + > + <input + checked={false} + className="components-radio-control__input" + id="inspector-radio-control-0-1" + name="inspector-radio-control-0" + onChange={[Function]} + type="radio" + value="private" + /> + <label + htmlFor="inspector-radio-control-0-1" + > + Private + </label> + </div> + <div + className="components-radio-control__option" + > + <input + checked={false} + className="components-radio-control__input" + id="inspector-radio-control-0-2" + name="inspector-radio-control-0" + onChange={[Function]} + type="radio" + value="password" + /> + <label + htmlFor="inspector-radio-control-0-2" + > + Password Protected + </label> + </div> + </div> +</div> +`; + +exports[`Storyshots Components|RadioControl With Help 1`] = ` +<div + className="components-base-control components-radio-control" +> + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-radio-control-1" + > + Post visibility + </label> + <div + className="components-radio-control__option" + > + <input + aria-describedby="inspector-radio-control-1__help" + checked={true} + className="components-radio-control__input" + id="inspector-radio-control-1-0" + name="inspector-radio-control-1" + onChange={[Function]} + type="radio" + value="public" + /> + <label + htmlFor="inspector-radio-control-1-0" + > + Public + </label> + </div> + <div + className="components-radio-control__option" + > + <input + aria-describedby="inspector-radio-control-1__help" + checked={false} + className="components-radio-control__input" + id="inspector-radio-control-1-1" + name="inspector-radio-control-1" + onChange={[Function]} + type="radio" + value="private" + /> + <label + htmlFor="inspector-radio-control-1-1" + > + Private + </label> + </div> + <div + className="components-radio-control__option" + > + <input + aria-describedby="inspector-radio-control-1__help" + checked={false} + className="components-radio-control__input" + id="inspector-radio-control-1-2" + name="inspector-radio-control-1" + onChange={[Function]} + type="radio" + value="password" + /> + <label + htmlFor="inspector-radio-control-1-2" + > + Password Protected + </label> + </div> + </div> + <p + className="components-base-control__help" + id="inspector-radio-control-1__help" + > + The visibility level for the current post + </p> +</div> +`; + +exports[`Storyshots Components|RangeControl Default 1`] = ` +<div + className="components-base-control components-range-control" +> + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-range-control-1" + > + How many columns should this use? + </label> + <input + className="components-range-control__slider" + id="inspector-range-control-1" + onChange={[Function]} + type="range" + value={5} + /> + <input + aria-label="How many columns should this use?" + className="components-range-control__number" + onBlur={[Function]} + onChange={[Function]} + type="number" + value={5} + /> + </div> +</div> +`; + +exports[`Storyshots Components|RangeControl With Help 1`] = ` +<div + className="components-base-control components-range-control" +> + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-range-control-2" + > + How many columns should this use? + </label> + <input + aria-describedby="inspector-range-control-2__help" + className="components-range-control__slider" + id="inspector-range-control-2" + onChange={[Function]} + type="range" + value={5} + /> + <input + aria-label="How many columns should this use?" + className="components-range-control__number" + onBlur={[Function]} + onChange={[Function]} + type="number" + value={5} + /> + </div> + <p + className="components-base-control__help" + id="inspector-range-control-2__help" + > + Please select the number of columns you would like this to contain. + </p> +</div> +`; + +exports[`Storyshots Components|RangeControl With Icon After 1`] = ` +<div + className="components-base-control components-range-control" +> + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-range-control-5" + > + How many columns should this use? + </label> + <input + className="components-range-control__slider" + id="inspector-range-control-5" + onChange={[Function]} + type="range" + value={5} + /> + <svg + aria-hidden="true" + className="dashicon dashicons-wordpress" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M20 10c0-5.52-4.48-10-10-10S0 4.48 0 10s4.48 10 10 10 10-4.48 10-10zM10 1.01c4.97 0 8.99 4.02 8.99 8.99s-4.02 8.99-8.99 8.99S1.01 14.97 1.01 10 5.03 1.01 10 1.01zM8.01 14.82L4.96 6.61c.49-.03 1.05-.08 1.05-.08.43-.05.38-1.01-.06-.99 0 0-1.29.1-2.13.1-.15 0-.33 0-.52-.01 1.44-2.17 3.9-3.6 6.7-3.6 2.09 0 3.99.79 5.41 2.09-.6-.08-1.45.35-1.45 1.42 0 .66.38 1.22.79 1.88.31.54.5 1.22.5 2.21 0 1.34-1.27 4.48-1.27 4.48l-2.71-7.5c.48-.03.75-.16.75-.16.43-.05.38-1.1-.05-1.08 0 0-1.3.11-2.14.11-.78 0-2.11-.11-2.11-.11-.43-.02-.48 1.06-.05 1.08l.84.08 1.12 3.04zm6.02 2.15L16.64 10s.67-1.69.39-3.81c.63 1.14.94 2.42.94 3.81 0 2.96-1.56 5.58-3.94 6.97zM2.68 6.77L6.5 17.25c-2.67-1.3-4.47-4.08-4.47-7.25 0-1.16.2-2.23.65-3.23zm7.45 4.53l2.29 6.25c-.75.27-1.57.42-2.42.42-.72 0-1.41-.11-2.06-.3z" + /> + </svg> + <input + aria-label="How many columns should this use?" + className="components-range-control__number" + onBlur={[Function]} + onChange={[Function]} + type="number" + value={5} + /> + </div> +</div> +`; + +exports[`Storyshots Components|RangeControl With Icon Before 1`] = ` +<div + className="components-base-control components-range-control" +> + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-range-control-4" + > + How many columns should this use? + </label> + <svg + aria-hidden="true" + className="dashicon dashicons-wordpress" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M20 10c0-5.52-4.48-10-10-10S0 4.48 0 10s4.48 10 10 10 10-4.48 10-10zM10 1.01c4.97 0 8.99 4.02 8.99 8.99s-4.02 8.99-8.99 8.99S1.01 14.97 1.01 10 5.03 1.01 10 1.01zM8.01 14.82L4.96 6.61c.49-.03 1.05-.08 1.05-.08.43-.05.38-1.01-.06-.99 0 0-1.29.1-2.13.1-.15 0-.33 0-.52-.01 1.44-2.17 3.9-3.6 6.7-3.6 2.09 0 3.99.79 5.41 2.09-.6-.08-1.45.35-1.45 1.42 0 .66.38 1.22.79 1.88.31.54.5 1.22.5 2.21 0 1.34-1.27 4.48-1.27 4.48l-2.71-7.5c.48-.03.75-.16.75-.16.43-.05.38-1.1-.05-1.08 0 0-1.3.11-2.14.11-.78 0-2.11-.11-2.11-.11-.43-.02-.48 1.06-.05 1.08l.84.08 1.12 3.04zm6.02 2.15L16.64 10s.67-1.69.39-3.81c.63 1.14.94 2.42.94 3.81 0 2.96-1.56 5.58-3.94 6.97zM2.68 6.77L6.5 17.25c-2.67-1.3-4.47-4.08-4.47-7.25 0-1.16.2-2.23.65-3.23zm7.45 4.53l2.29 6.25c-.75.27-1.57.42-2.42.42-.72 0-1.41-.11-2.06-.3z" + /> + </svg> + <input + className="components-range-control__slider" + id="inspector-range-control-4" + onChange={[Function]} + type="range" + value={5} + /> + <input + aria-label="How many columns should this use?" + className="components-range-control__number" + onBlur={[Function]} + onChange={[Function]} + type="number" + value={5} + /> + </div> +</div> +`; + +exports[`Storyshots Components|RangeControl With Minimum And Maximum Limits 1`] = ` +<div + className="components-base-control components-range-control" +> + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-range-control-3" + > + How many columns should this use? + </label> + <input + className="components-range-control__slider" + id="inspector-range-control-3" + max={10} + min={2} + onChange={[Function]} + type="range" + value={5} + /> + <input + aria-label="How many columns should this use?" + className="components-range-control__number" + max={10} + min={2} + onBlur={[Function]} + onChange={[Function]} + type="number" + value={5} + /> + </div> +</div> +`; + +exports[`Storyshots Components|RangeControl With Reset 1`] = ` +<div + className="components-base-control components-range-control" +> + <div + className="components-base-control__field" + > + <label + className="components-base-control__label" + htmlFor="inspector-range-control-6" + > + How many columns should this use? + </label> + <input + className="components-range-control__slider" + id="inspector-range-control-6" + onChange={[Function]} + type="range" + value={5} + /> + <input + aria-label="How many columns should this use?" + className="components-range-control__number" + onBlur={[Function]} + onChange={[Function]} + type="number" + value={5} + /> + <button + className="components-button components-range-control__reset is-button is-default is-small" + disabled={false} + onClick={[Function]} + type="button" + > + Reset + </button> + </div> +</div> +`; + +exports[`Storyshots Components|ResizableBox Default 1`] = ` +<div + style={ + Object { + "margin": 30, + } + } +> + <div + className="components-resizable-box__container has-show-handle" + style={ + Object { + "boxSizing": "border-box", + "height": "200px", + "maxHeight": undefined, + "maxWidth": undefined, + "minHeight": 50, + "minWidth": 50, + "position": "relative", + "userSelect": "auto", + "width": "400px", + } + } + > + <div + style={ + Object { + "alignItems": "center", + "background": "#eee", + "display": "flex", + "height": "100%", + "justifyContent": "center", + "width": "100%", + } + } + > + <span> + Resize + </span> + </div> + <span> + <div + className="components-resizable-box__handle components-resizable-box__side-handle components-resizable-box__handle-right" + onMouseDown={[Function]} + onTouchStart={[Function]} + style={ + Object { + "bottom": null, + "cursor": "col-resize", + "height": null, + "left": null, + "position": "absolute", + "right": null, + "top": null, + "userSelect": "none", + "width": null, + } + } + /> + <div + className="components-resizable-box__handle components-resizable-box__side-handle components-resizable-box__handle-bottom" + onMouseDown={[Function]} + onTouchStart={[Function]} + style={ + Object { + "bottom": null, + "cursor": "row-resize", + "height": null, + "left": null, + "position": "absolute", + "right": null, + "top": null, + "userSelect": "none", + "width": null, + } + } + /> + <div + className="components-resizable-box__handle components-resizable-box__corner-handle components-resizable-box__handle-bottom components-resizable-box__handle-right" + onMouseDown={[Function]} + onTouchStart={[Function]} + style={ + Object { + "bottom": null, + "cursor": "se-resize", + "height": null, + "left": null, + "position": "absolute", + "right": null, + "top": null, + "userSelect": "none", + "width": null, + } + } + /> + </span> + </div> +</div> +`; + +exports[`Storyshots Components|ScrollLock Default 1`] = ` +<div + style={ + Object { + "backgroundColor": "#fff", + "backgroundImage": "linear-gradient(transparent 50%, rgba(0, 0, 0, 0.05) 50%)", + "backgroundSize": "50px 50px", + "height": 3000, + "position": "relative", + } + } +> + <div> + Start scrolling down... + </div> + <div + style={ + Object { + "display": "flex", + "justifyContent": "center", + "padding": 40, + "position": "sticky", + "textAlign": "center", + "top": 0, + } + } + > + <div> + <button + className="components-button is-button is-default" + onClick={[Function]} + type="button" + > + Toggle Scroll Lock + </button> + <p> + Scroll locked: + <strong> + No + </strong> + </p> + </div> + </div> +</div> +`; + +exports[`Storyshots Components|Snackbar Default 1`] = ` +<div + className="components-snackbar" + label="Dismiss this notice" + onClick={[Function]} + onKeyPress={[Function]} + role="button" + tabIndex="0" +> + <div + className="components-snackbar__content" + > + Use Snackbars to communicate low priority, non-interruptive messages to the user. + </div> +</div> +`; + +exports[`Storyshots Components|Snackbar With Actions 1`] = ` +<div + className="components-snackbar" + label="Dismiss this notice" + onClick={[Function]} + onKeyPress={[Function]} + role="button" + tabIndex="0" +> + <div + className="components-snackbar__content" + > + Use Snackbars with an action link to an external page. + <a + className="components-button components-snackbar__action is-tertiary" + href="https://wordpress.org" + onClick={[Function]} + > + Open WP.org + </a> + </div> +</div> +`; + +exports[`Storyshots Components|Spinner Default 1`] = ` +<span + className="components-spinner" +/> +`; + +exports[`Storyshots Components|TabPanel Default 1`] = ` +<div + className="my-tab-panel" +> + <div + aria-orientation="horizontal" + className="components-tab-panel__tabs" + role="tablist" + > + <button + aria-controls="0-tab1-view" + aria-selected={true} + className="components-button tab-one active-tab" + id="0-tab1" + onClick={[Function]} + role="tab" + tabIndex={null} + type="button" + > + Tab 1 + </button> + <button + aria-controls="0-tab2-view" + aria-selected={false} + className="components-button tab-two" + id="0-tab2" + onClick={[Function]} + role="tab" + tabIndex={-1} + type="button" + > + Tab 2 + </button> + </div> + <div + aria-labelledby="0-tab1" + className="components-tab-panel__tab-content" + id="0-tab1-view" + role="tabpanel" + tabIndex="0" + > + <p> + Selected tab: + Tab 1 + </p> + </div> +</div> +`; + +exports[`Storyshots Components|TextHighlight Default 1`] = ` +Array [ + "We call the new editor ", + <mark> + Gutenberg + </mark>, + ". The entire editing experience has been rebuilt for media rich pages and posts.", +] +`; + +exports[`Storyshots Components|Tip Default 1`] = ` +<div + className="components-tip" +> + <svg + aria-hidden="true" + focusable="false" + height="24" + role="img" + viewBox="0 0 24 24" + width="24" + > + <path + d="M20.45 4.91L19.04 3.5l-1.79 1.8 1.41 1.41 1.79-1.8zM13 4h-2V1h2v3zm10 9h-3v-2h3v2zm-12 6.95v-3.96l-1-.58c-1.24-.72-2-2.04-2-3.46 0-2.21 1.79-4 4-4s4 1.79 4 4c0 1.42-.77 2.74-2 3.46l-1 .58v3.96h-2zm-2 2h6v-4.81c1.79-1.04 3-2.97 3-5.19 0-3.31-2.69-6-6-6s-6 2.69-6 6c0 2.22 1.21 4.15 3 5.19v4.81zM4 13H1v-2h3v2zm2.76-7.71l-1.79-1.8L3.56 4.9l1.8 1.79 1.4-1.4z" + /> + </svg> + <p> + <p> + An example tip + </p> + </p> +</div> +`; + +exports[`Storyshots Components|ToggleControl Default 1`] = ` +<div + className="components-base-control components-toggle-control" +> + <div + className="components-base-control__field" + > + <span + className="components-form-toggle is-checked" + > + <input + checked={true} + className="components-form-toggle__input" + id="inspector-toggle-control-0" + onChange={[Function]} + type="checkbox" + /> + <span + className="components-form-toggle__track" + /> + <span + className="components-form-toggle__thumb" + /> + <svg + aria-hidden="true" + className="components-form-toggle__on" + focusable="false" + height="6" + role="img" + viewBox="0 0 2 6" + width="2" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M0 0h2v6H0z" + /> + </svg> + </span> + <label + className="components-toggle-control__label" + htmlFor="inspector-toggle-control-0" + > + Does this have a fixed background? + </label> + </div> +</div> +`; + +exports[`Storyshots Components|ToggleControl With Help Text 1`] = ` +<div + className="components-base-control components-toggle-control" +> + <div + className="components-base-control__field" + > + <span + className="components-form-toggle is-checked" + > + <input + aria-describedby="inspector-toggle-control-1__help" + checked={true} + className="components-form-toggle__input" + id="inspector-toggle-control-1" + onChange={[Function]} + type="checkbox" + /> + <span + className="components-form-toggle__track" + /> + <span + className="components-form-toggle__thumb" + /> + <svg + aria-hidden="true" + className="components-form-toggle__on" + focusable="false" + height="6" + role="img" + viewBox="0 0 2 6" + width="2" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M0 0h2v6H0z" + /> + </svg> + </span> + <label + className="components-toggle-control__label" + htmlFor="inspector-toggle-control-1" + > + Does this have a fixed background? + </label> + </div> + <p + className="components-base-control__help" + id="inspector-toggle-control-1__help" + > + Has fixed background. + </p> +</div> +`; + +exports[`Storyshots Components|Toolbar Default 1`] = ` +<div + aria-label="Options" + aria-orientation="horizontal" + className="components-accessible-toolbar" + id="options-toolbar" + role="toolbar" +> + <div + className="components-toolbar-group" + > + <div> + <button + aria-label="Paragraph" + className="components-button components-icon-button components-toolbar__control" + id="options-toolbar-1" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + tabIndex={-1} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-editor-paragraph" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M15 2H7.54c-.83 0-1.59.2-2.28.6-.7.41-1.25.96-1.65 1.65C3.2 4.94 3 5.7 3 6.52s.2 1.58.61 2.27c.4.69.95 1.24 1.65 1.64.69.41 1.45.61 2.28.61h.43V17c0 .27.1.51.29.71.2.19.44.29.71.29.28 0 .51-.1.71-.29.2-.2.3-.44.3-.71V5c0-.27.09-.51.29-.71.2-.19.44-.29.71-.29s.51.1.71.29c.19.2.29.44.29.71v12c0 .27.1.51.3.71.2.19.43.29.71.29.27 0 .51-.1.71-.29.19-.2.29-.44.29-.71V4H15c.27 0 .5-.1.7-.3.2-.19.3-.43.3-.7s-.1-.51-.3-.71C15.5 2.1 15.27 2 15 2z" + /> + </svg> + </button> + </div> + </div> + <div + className="components-dropdown components-dropdown-menu components-toolbar-group" + > + <button + aria-expanded={false} + aria-haspopup="true" + aria-label="Change text alignment" + className="components-button components-icon-button components-dropdown-menu__toggle has-text" + id="options-toolbar-2" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + tabIndex={-1} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-editor-alignleft" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M12 5V3H3v2h9zm5 4V7H3v2h14zm-5 4v-2H3v2h9zm5 4v-2H3v2h14z" + /> + </svg> + <span + className="components-dropdown-menu__indicator" + /> + </button> + </div> + <div + className="components-toolbar-group" + > + <div> + <button + aria-label="Bold" + className="components-button components-icon-button components-toolbar__control" + id="options-toolbar-3" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + tabIndex={-1} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-editor-bold" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M6 4v13h4.54c1.37 0 2.46-.33 3.26-1 .8-.66 1.2-1.58 1.2-2.77 0-.84-.17-1.51-.51-2.01s-.9-.85-1.67-1.03v-.09c.57-.1 1.02-.4 1.36-.9s.51-1.13.51-1.91c0-1.14-.39-1.98-1.17-2.5C12.75 4.26 11.5 4 9.78 4H6zm2.57 5.15V6.26h1.36c.73 0 1.27.11 1.61.32.34.22.51.58.51 1.07 0 .54-.16.92-.47 1.15s-.82.35-1.51.35h-1.5zm0 2.19h1.6c1.44 0 2.16.53 2.16 1.61 0 .6-.17 1.05-.51 1.34s-.86.43-1.57.43H8.57v-3.38z" + /> + </svg> + </button> + </div> + <div> + <button + aria-label="Italic" + className="components-button components-icon-button components-toolbar__control" + id="options-toolbar-4" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + tabIndex={-1} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-editor-italic" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M14.78 6h-2.13l-2.8 9h2.12l-.62 2H4.6l.62-2h2.14l2.8-9H8.03l.62-2h6.75z" + /> + </svg> + </button> + </div> + <div> + <button + aria-label="Link" + className="components-button components-icon-button components-toolbar__control" + id="options-toolbar-5" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + tabIndex={-1} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-admin-links" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M17.74 2.76c1.68 1.69 1.68 4.41 0 6.1l-1.53 1.52c-1.12 1.12-2.7 1.47-4.14 1.09l2.62-2.61.76-.77.76-.76c.84-.84.84-2.2 0-3.04-.84-.85-2.2-.85-3.04 0l-.77.76-3.38 3.38c-.37-1.44-.02-3.02 1.1-4.14l1.52-1.53c1.69-1.68 4.42-1.68 6.1 0zM8.59 13.43l5.34-5.34c.42-.42.42-1.1 0-1.52-.44-.43-1.13-.39-1.53 0l-5.33 5.34c-.42.42-.42 1.1 0 1.52.44.43 1.13.39 1.52 0zm-.76 2.29l4.14-4.15c.38 1.44.03 3.02-1.09 4.14l-1.52 1.53c-1.69 1.68-4.41 1.68-6.1 0-1.68-1.68-1.68-4.42 0-6.1l1.53-1.52c1.12-1.12 2.7-1.47 4.14-1.1l-4.14 4.15c-.85.84-.85 2.2 0 3.05.84.84 2.2.84 3.04 0z" + /> + </svg> + </button> + </div> + <div + className="components-dropdown components-dropdown-menu components-toolbar-group" + > + <button + aria-expanded={false} + aria-haspopup="true" + aria-label="More rich text controls" + className="components-button components-icon-button components-dropdown-menu__toggle has-text" + id="options-toolbar-6" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + tabIndex={-1} + type="button" + > + <span + className="components-dropdown-menu__indicator" + /> + </button> + </div> + </div> + <div + className="components-dropdown components-dropdown-menu components-toolbar-group" + > + <button + aria-expanded={false} + aria-haspopup="true" + aria-label="Change text alignment" + className="components-button components-icon-button components-dropdown-menu__toggle" + id="options-toolbar-7" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + tabIndex={-1} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-ellipsis" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 10c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm12-2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-7 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" + /> + </svg> + </button> + </div> +</div> +`; + +exports[`Storyshots Components|Toolbar Without Group 1`] = ` +<div + aria-label="Options" + aria-orientation="horizontal" + className="components-accessible-toolbar" + id="options-toolbar-without-group" + role="toolbar" +> + <div> + <button + aria-label="Bold" + className="components-button components-icon-button components-toolbar__control" + id="options-toolbar-without-group-1" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + tabIndex={-1} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-editor-bold" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M6 4v13h4.54c1.37 0 2.46-.33 3.26-1 .8-.66 1.2-1.58 1.2-2.77 0-.84-.17-1.51-.51-2.01s-.9-.85-1.67-1.03v-.09c.57-.1 1.02-.4 1.36-.9s.51-1.13.51-1.91c0-1.14-.39-1.98-1.17-2.5C12.75 4.26 11.5 4 9.78 4H6zm2.57 5.15V6.26h1.36c.73 0 1.27.11 1.61.32.34.22.51.58.51 1.07 0 .54-.16.92-.47 1.15s-.82.35-1.51.35h-1.5zm0 2.19h1.6c1.44 0 2.16.53 2.16 1.61 0 .6-.17 1.05-.51 1.34s-.86.43-1.57.43H8.57v-3.38z" + /> + </svg> + </button> + </div> + <div> + <button + aria-label="Italic" + className="components-button components-icon-button components-toolbar__control" + id="options-toolbar-without-group-2" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + tabIndex={-1} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-editor-italic" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M14.78 6h-2.13l-2.8 9h2.12l-.62 2H4.6l.62-2h2.14l2.8-9H8.03l.62-2h6.75z" + /> + </svg> + </button> + </div> + <div> + <button + aria-label="Link" + className="components-button components-icon-button components-toolbar__control" + id="options-toolbar-without-group-3" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + tabIndex={-1} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-admin-links" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M17.74 2.76c1.68 1.69 1.68 4.41 0 6.1l-1.53 1.52c-1.12 1.12-2.7 1.47-4.14 1.09l2.62-2.61.76-.77.76-.76c.84-.84.84-2.2 0-3.04-.84-.85-2.2-.85-3.04 0l-.77.76-3.38 3.38c-.37-1.44-.02-3.02 1.1-4.14l1.52-1.53c1.69-1.68 4.42-1.68 6.1 0zM8.59 13.43l5.34-5.34c.42-.42.42-1.1 0-1.52-.44-.43-1.13-.39-1.53 0l-5.33 5.34c-.42.42-.42 1.1 0 1.52.44.43 1.13.39 1.52 0zm-.76 2.29l4.14-4.15c.38 1.44.03 3.02-1.09 4.14l-1.52 1.53c-1.69 1.68-4.41 1.68-6.1 0-1.68-1.68-1.68-4.42 0-6.1l1.53-1.52c1.12-1.12 2.7-1.47 4.14-1.1l-4.14 4.15c-.85.84-.85 2.2 0 3.05.84.84 2.2.84 3.04 0z" + /> + </svg> + </button> + </div> +</div> +`; + +exports[`Storyshots Components|ToolbarGroup Default 1`] = ` +<div + className="components-toolbar" +> + <div> + <button + aria-label="Bold" + aria-pressed={true} + className="components-button components-icon-button components-toolbar__control is-pressed" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-editor-bold" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M6 4v13h4.54c1.37 0 2.46-.33 3.26-1 .8-.66 1.2-1.58 1.2-2.77 0-.84-.17-1.51-.51-2.01s-.9-.85-1.67-1.03v-.09c.57-.1 1.02-.4 1.36-.9s.51-1.13.51-1.91c0-1.14-.39-1.98-1.17-2.5C12.75 4.26 11.5 4 9.78 4H6zm2.57 5.15V6.26h1.36c.73 0 1.27.11 1.61.32.34.22.51.58.51 1.07 0 .54-.16.92-.47 1.15s-.82.35-1.51.35h-1.5zm0 2.19h1.6c1.44 0 2.16.53 2.16 1.61 0 .6-.17 1.05-.51 1.34s-.86.43-1.57.43H8.57v-3.38z" + /> + </svg> + </button> + </div> + <div> + <button + aria-label="Italic" + className="components-button components-icon-button components-toolbar__control" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-editor-italic" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M14.78 6h-2.13l-2.8 9h2.12l-.62 2H4.6l.62-2h2.14l2.8-9H8.03l.62-2h6.75z" + /> + </svg> + </button> + </div> + <div> + <button + aria-label="Link" + className="components-button components-icon-button components-toolbar__control" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-admin-links" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M17.74 2.76c1.68 1.69 1.68 4.41 0 6.1l-1.53 1.52c-1.12 1.12-2.7 1.47-4.14 1.09l2.62-2.61.76-.77.76-.76c.84-.84.84-2.2 0-3.04-.84-.85-2.2-.85-3.04 0l-.77.76-3.38 3.38c-.37-1.44-.02-3.02 1.1-4.14l1.52-1.53c1.69-1.68 4.42-1.68 6.1 0zM8.59 13.43l5.34-5.34c.42-.42.42-1.1 0-1.52-.44-.43-1.13-.39-1.53 0l-5.33 5.34c-.42.42-.42 1.1 0 1.52.44.43 1.13.39 1.52 0zm-.76 2.29l4.14-4.15c.38 1.44.03 3.02-1.09 4.14l-1.52 1.53c-1.69 1.68-4.41 1.68-6.1 0-1.68-1.68-1.68-4.42 0-6.1l1.53-1.52c1.12-1.12 2.7-1.47 4.14-1.1l-4.14 4.15c-.85.84-.85 2.2 0 3.05.84.84 2.2.84 3.04 0z" + /> + </svg> + </button> + </div> +</div> +`; + +exports[`Storyshots Components|ToolbarGroup With Controls Prop 1`] = ` +<div + className="components-toolbar" +> + <div + className={null} + > + <button + aria-label="Bold" + aria-pressed={true} + className="components-button components-icon-button components-toolbar__control is-pressed" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-editor-bold" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M6 4v13h4.54c1.37 0 2.46-.33 3.26-1 .8-.66 1.2-1.58 1.2-2.77 0-.84-.17-1.51-.51-2.01s-.9-.85-1.67-1.03v-.09c.57-.1 1.02-.4 1.36-.9s.51-1.13.51-1.91c0-1.14-.39-1.98-1.17-2.5C12.75 4.26 11.5 4 9.78 4H6zm2.57 5.15V6.26h1.36c.73 0 1.27.11 1.61.32.34.22.51.58.51 1.07 0 .54-.16.92-.47 1.15s-.82.35-1.51.35h-1.5zm0 2.19h1.6c1.44 0 2.16.53 2.16 1.61 0 .6-.17 1.05-.51 1.34s-.86.43-1.57.43H8.57v-3.38z" + /> + </svg> + </button> + </div> + <div + className={null} + > + <button + aria-label="Italic" + className="components-button components-icon-button components-toolbar__control" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-editor-italic" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M14.78 6h-2.13l-2.8 9h2.12l-.62 2H4.6l.62-2h2.14l2.8-9H8.03l.62-2h6.75z" + /> + </svg> + </button> + </div> + <div + className={null} + > + <button + aria-label="Link" + className="components-button components-icon-button components-toolbar__control" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-admin-links" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M17.74 2.76c1.68 1.69 1.68 4.41 0 6.1l-1.53 1.52c-1.12 1.12-2.7 1.47-4.14 1.09l2.62-2.61.76-.77.76-.76c.84-.84.84-2.2 0-3.04-.84-.85-2.2-.85-3.04 0l-.77.76-3.38 3.38c-.37-1.44-.02-3.02 1.1-4.14l1.52-1.53c1.69-1.68 4.42-1.68 6.1 0zM8.59 13.43l5.34-5.34c.42-.42.42-1.1 0-1.52-.44-.43-1.13-.39-1.53 0l-5.33 5.34c-.42.42-.42 1.1 0 1.52.44.43 1.13.39 1.52 0zm-.76 2.29l4.14-4.15c.38 1.44.03 3.02-1.09 4.14l-1.52 1.53c-1.69 1.68-4.41 1.68-6.1 0-1.68-1.68-1.68-4.42 0-6.1l1.53-1.52c1.12-1.12 2.7-1.47 4.14-1.1l-4.14 4.15c-.85.84-.85 2.2 0 3.05.84.84 2.2.84 3.04 0z" + /> + </svg> + </button> + </div> +</div> +`; + +exports[`Storyshots Components|VisuallyHidden Default 1`] = ` +Array [ + <div + className="components-visually-hidden" + > + This should not show. + </div>, + <div> + This text will + <span + className="components-visually-hidden" + > + but not inline + </span> + always show. + </div>, +] +`; + +exports[`Storyshots CustomSelectControl Default 1`] = ` +<div + className="components-custom-select-control" +> + <label + className="components-custom-select-control__label" + htmlFor="downshift-null-toggle-button" + id="downshift-null-label" + > + Font Size + </label> + <button + aria-expanded={false} + aria-haspopup="listbox" + aria-label="Font Size" + className="components-button components-custom-select-control__button" + id="downshift-null-toggle-button" + onClick={[Function]} + onKeyDown={[Function]} + type="button" + > + <svg + aria-hidden="true" + className="dashicon dashicons-arrow-down-alt2 components-custom-select-control__button-icon" + focusable="false" + height={20} + role="img" + viewBox="0 0 20 20" + width={20} + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M5 6l5 5 5-5 2 1-7 7-7-7z" + /> + </svg> + </button> + <ul + aria-labelledby="downshift-null-label" + className="components-custom-select-control__menu" + id="downshift-null-menu" + onBlur={[Function]} + onKeyDown={[Function]} + onMouseLeave={[Function]} + role="listbox" + tabIndex={-1} + /> +</div> +`; diff --git a/storybook/test/index.js b/storybook/test/index.js new file mode 100644 index 00000000000000..3702a12325a175 --- /dev/null +++ b/storybook/test/index.js @@ -0,0 +1,29 @@ +/** + * External dependencies + */ +import initStoryshots, { snapshotWithOptions } from '@storybook/addon-storyshots'; +import path from 'path'; + +initStoryshots( { + configPath: path.resolve( __dirname, '../' ), + test: snapshotWithOptions( ( story ) => ( { + // We need to mock refs for some stories which use them. + // @see https://reactjs.org/blog/2016/11/16/react-v15.4.0.html#mocking-refs-for-snapshot-testing + // @see https://github.com/storybookjs/storybook/tree/master/addons/storyshots/storyshots-core#using-createnodemock-to-mock-refs + createNodeMock: ( element ) => { + const currentElement = element.type && document.createElement( element.type ); + + if ( story.kind === 'Components|ClipboardButton' ) { + currentElement.appendChild( + document.createElement( 'button' ) + ); + } + if ( story.kind === 'Components|Popover' ) { + const parentElement = document.createElement( 'div' ); + parentElement.appendChild( currentElement ); + } + + return currentElement; + }, + } ) ), +} ); diff --git a/storybook/webpack.config.js b/storybook/webpack.config.js new file mode 100644 index 00000000000000..4662c393522293 --- /dev/null +++ b/storybook/webpack.config.js @@ -0,0 +1,34 @@ +const path = require( 'path' ); +const postCssConfigPlugins = require( '../bin/packages/post-css-config' ); + +module.exports = ( { config } ) => { + config.module.rules.push( + { + test: /\/stories\/.+\.js$/, + loaders: [ require.resolve( '@storybook/source-loader' ) ], + enforce: 'pre', + }, + { + test: /\.scss$/, + use: [ + 'style-loader', + 'css-loader', + /** + * Configuring PostCSS with Webpack + * https://github.com/postcss/postcss-loader#plugins + */ + { + loader: 'postcss-loader', + options: { + ident: 'postcss', + plugins: () => postCssConfigPlugins, + }, + }, + 'sass-loader', + ], + include: path.resolve( __dirname ), + } + ); + + return config; +}; diff --git a/test/integration/__snapshots__/blocks-raw-handling.test.js.snap b/test/integration/__snapshots__/blocks-raw-handling.test.js.snap index 29b4c95f5806b0..9cefe8bffb6fd9 100644 --- a/test/integration/__snapshots__/blocks-raw-handling.test.js.snap +++ b/test/integration/__snapshots__/blocks-raw-handling.test.js.snap @@ -1,5 +1,33 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Blocks raw handling pasteHandler should remove extra blank lines 1`] = ` +"<!-- wp:paragraph --> +<p>1</p> +<!-- /wp:paragraph --> + +<!-- wp:paragraph --> +<p>2</p> +<!-- /wp:paragraph -->" +`; + +exports[`Blocks raw handling pasteHandler should strip HTML formatting space from inline text 1`] = `"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent a elit eget tortor molestie egestas. Donec pretium urna vitae mattis imperdiet. Praesent et lorem iaculis, volutpat odio vitae, ornare lacus. Donec ut felis tristique, pharetra erat id, viverra justo. Integer sit amet elementum arcu, eget pharetra felis. In malesuada enim est, sed placerat nulla feugiat at. Vestibulum feugiat vitae elit sit amet tincidunt. Pellentesque finibus sed dolor non facilisis. Curabitur accumsan ante ac hendrerit vestibulum."`; + +exports[`Blocks raw handling pasteHandler should strip some text-level elements 1`] = ` +"<!-- wp:paragraph --> +<p>This is ncorect</p> +<!-- /wp:paragraph -->" +`; + +exports[`Blocks raw handling pasteHandler should strip windows data 1`] = ` +"<!-- wp:heading --> +<h2>Heading Win</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Paragraph Win</p> +<!-- /wp:paragraph -->" +`; + exports[`rawHandler should convert HTML post to blocks with minimal content changes 1`] = ` "<!-- wp:heading --> <h2>Howdy</h2> @@ -72,3 +100,19 @@ exports[`rawHandler should convert a caption shortcode with link 1`] = ` <figure class=\\"wp-block-image alignnone\\"><a href=\\"http://build.wordpress-develop.test/wp-content/uploads/2011/07/100_5478.jpg\\"><img src=\\"http://build.wordpress-develop.test/wp-content/uploads/2011/07/100_5478.jpg?w=604\\" alt=\\"Bell on Wharf\\" class=\\"wp-image-754\\"/></a><figcaption>Bell on wharf in San Francisco</figcaption></figure> <!-- /wp:image -->" `; + +exports[`rawHandler should convert a list with attributes 1`] = ` +"<!-- wp:list {\\"ordered\\":true,\\"type\\":\\"i\\",\\"start\\":2,\\"reversed\\":true} --> +<ol type=\\"i\\" reversed start=\\"2\\"><li>1 + <ol start=\\"2\\" reversed=\\"\\" type=\\"i\\"> + <li>1</li> + </ol> + </li></ol> +<!-- /wp:list -->" +`; + +exports[`rawHandler should not strip any text-level elements 1`] = ` +"<!-- wp:paragraph --> +<p>This is <u>ncorect</u></p> +<!-- /wp:paragraph -->" +`; diff --git a/test/integration/blocks-raw-handling.test.js b/test/integration/blocks-raw-handling.test.js index fda576a042cbb3..d042702d37b0d2 100644 --- a/test/integration/blocks-raw-handling.test.js +++ b/test/integration/blocks-raw-handling.test.js @@ -11,6 +11,7 @@ import { getBlockContent, pasteHandler, rawHandler, + registerBlockType, serialize, } from '@wordpress/blocks'; import { registerCoreBlocks } from '@wordpress/block-library'; @@ -24,6 +25,38 @@ describe( 'Blocks raw handling', () => { // Load all hooks that modify blocks require( '../../packages/editor/src/hooks' ); registerCoreBlocks(); + registerBlockType( 'test/gallery', { + title: 'Test Gallery', + category: 'common', + attributes: { + ids: { + type: 'array', + default: [], + }, + }, + transforms: { + from: [ + { + type: 'shortcode', + tag: 'gallery', + isMatch( { named: { ids } } ) { + return ids.indexOf( 42 ) > -1; + }, + attributes: { + ids: { + type: 'array', + shortcode: ( { named: { ids } } ) => + ids.split( ',' ).map( ( id ) => ( + parseInt( id, 10 ) + ) ), + }, + }, + priority: 9, + }, + ], + }, + save: () => null, + } ); } ); it( 'should filter inline content', () => { @@ -40,9 +73,9 @@ describe( 'Blocks raw handling', () => { const filtered = pasteHandler( { HTML: '<b id="docs-internal-guid-0"><em>test</em></b>', mode: 'AUTO', - } ).map( getBlockContent ).join( '' ); + } ); - expect( filtered ).toBe( '<p><em>test</em></p>' ); + expect( filtered ).toBe( '<em>test</em>' ); expect( console ).toHaveLogged(); } ); @@ -248,12 +281,17 @@ describe( 'Blocks raw handling', () => { 'markdown', 'wordpress', 'gutenberg', - 'caption-shortcode', + 'shortcode-matching', ].forEach( ( type ) => { it( type, () => { const HTML = readFile( path.join( __dirname, `fixtures/${ type }-in.html` ) ); const plainText = readFile( path.join( __dirname, `fixtures/${ type }-in.txt` ) ); const output = readFile( path.join( __dirname, `fixtures/${ type }-out.html` ) ); + + if ( ! ( HTML || plainText ) || ! output ) { + throw new Error( `Expected fixtures for type ${ type }` ); + } + const converted = pasteHandler( { HTML, plainText, canUserUseUnfilteredHTML: true } ); const serialized = typeof converted === 'string' ? converted : serialize( converted ); @@ -264,6 +302,29 @@ describe( 'Blocks raw handling', () => { } } ); } ); + + it( 'should strip some text-level elements', () => { + const HTML = '<p>This is <u>ncorect</u></p>'; + expect( serialize( pasteHandler( { HTML } ) ) ).toMatchSnapshot(); + expect( console ).toHaveLogged(); + } ); + + it( 'should remove extra blank lines', () => { + const HTML = readFile( path.join( __dirname, 'fixtures/google-docs-blank-lines.html' ) ); + expect( serialize( pasteHandler( { HTML } ) ) ).toMatchSnapshot(); + expect( console ).toHaveLogged(); + } ); + + it( 'should strip windows data', () => { + const HTML = readFile( path.join( __dirname, 'fixtures/windows.html' ) ); + expect( serialize( pasteHandler( { HTML } ) ) ).toMatchSnapshot(); + } ); + + it( 'should strip HTML formatting space from inline text', () => { + const HTML = readFile( path.join( __dirname, 'fixtures/inline-with-html-formatting-space.html' ) ); + expect( pasteHandler( { HTML } ) ).toMatchSnapshot(); + expect( console ).toHaveLogged(); + } ); } ); } ); @@ -287,4 +348,14 @@ describe( 'rawHandler', () => { const HTML = readFile( path.join( __dirname, 'fixtures/shortcode-caption-with-caption-link.html' ) ); expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); } ); + + it( 'should convert a list with attributes', () => { + const HTML = readFile( path.join( __dirname, 'fixtures/list-with-attributes.html' ) ); + expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); + } ); + + it( 'should not strip any text-level elements', () => { + const HTML = '<p>This is <u>ncorect</u></p>'; + expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); + } ); } ); diff --git a/test/integration/fixtures/apple-out.html b/test/integration/fixtures/apple-out.html index 40d4f1a8d133ed..f10d4d526e6b36 100644 --- a/test/integration/fixtures/apple-out.html +++ b/test/integration/fixtures/apple-out.html @@ -19,27 +19,9 @@ <!-- /wp:list --> <!-- wp:table --> -<figure class="wp-block-table"><table class=""><tbody><tr><td> -One -</td><td> -Two -</td><td> -Three -</td></tr><tr><td> -1 -</td><td> -2 -</td><td> -3 -</td></tr><tr><td> -I -</td><td> -II -</td><td> -III -</td></tr></tbody></table></figure> +<figure class="wp-block-table"><table><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table></figure> <!-- /wp:table --> <!-- wp:paragraph --> -<p>An image: </p> +<p>An image:</p> <!-- /wp:paragraph --> diff --git a/test/integration/fixtures/classic-out.html b/test/integration/fixtures/classic-out.html index dbc94528db7b22..1298053376dfa0 100644 --- a/test/integration/fixtures/classic-out.html +++ b/test/integration/fixtures/classic-out.html @@ -15,7 +15,7 @@ <!-- /wp:paragraph --> <!-- wp:paragraph --> -<p>Fourth paragraph</p> +<p>Fourth paragraph</p> <!-- /wp:paragraph --> <!-- wp:more --> diff --git a/test/integration/fixtures/evernote-out.html b/test/integration/fixtures/evernote-out.html index c984df312f0bef..71dc9ceaca2856 100644 --- a/test/integration/fixtures/evernote-out.html +++ b/test/integration/fixtures/evernote-out.html @@ -1,7 +1,5 @@ <!-- wp:paragraph --> -<p>This is a <em>paragraph</em>. -<br>This is a <a href="https://w.org">link</a>. -<br></p> +<p>This is a <em>paragraph</em>.<br>This is a <a href="https://w.org">link</a>.</p> <!-- /wp:paragraph --> <!-- wp:list --> @@ -17,13 +15,7 @@ <!-- /wp:separator --> <!-- wp:table --> -<figure class="wp-block-table"><table class=""><tbody><tr><td>One -</td><td>Two -</td><td>Three -</td></tr><tr><td>Four -</td><td>Five -</td><td>Six -</td></tr></tbody></table></figure> +<figure class="wp-block-table"><table><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>Four</td><td>Five</td><td>Six</td></tr></tbody></table></figure> <!-- /wp:table --> <!-- wp:image --> diff --git a/test/integration/fixtures/google-docs-blank-lines.html b/test/integration/fixtures/google-docs-blank-lines.html new file mode 100644 index 00000000000000..3bfbb42aa29244 --- /dev/null +++ b/test/integration/fixtures/google-docs-blank-lines.html @@ -0,0 +1 @@ +<b id="docs-internal-guid-fb0aa287-7fff-3f4b-05ee-91125971677f" style="caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-style: normal; font-variant-caps: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; text-decoration: none; font-weight: normal;"><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">1</span></p><br><p dir="ltr" style="line-height: 1.38; margin-top: 0pt; margin-bottom: 0pt;"><span style="font-size: 11pt; font-family: Arial; color: rgb(0, 0, 0); background-color: transparent; font-weight: 400; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-variant-east-asian: normal; font-variant-position: normal; text-decoration: none; vertical-align: baseline; white-space: pre-wrap;">2</span></p><br></b><br class="Apple-interchange-newline"> diff --git a/test/integration/fixtures/google-docs-out.html b/test/integration/fixtures/google-docs-out.html index 576b2cd7fda73f..46894395d9cbe3 100644 --- a/test/integration/fixtures/google-docs-out.html +++ b/test/integration/fixtures/google-docs-out.html @@ -1,5 +1,5 @@ <!-- wp:paragraph --> -<p>This is a <strong>title</strong><br></p> +<p>This is a <strong>title</strong></p> <!-- /wp:paragraph --> <!-- wp:heading --> @@ -7,7 +7,7 @@ <h2>This is a <em>heading</em></h2> <!-- /wp:heading --> <!-- wp:paragraph --> -<p>Formatting test: <strong>bold</strong>, <em>italic</em>, <a href="https://w.org/">link</a>, strikethrough, <sup>superscript</sup>, <sub>subscript</sub>, <strong><em>nested</em></strong>.<br></p> +<p>Formatting test: <strong>bold</strong>, <em>italic</em>, <a href="https://w.org/">link</a>, <s>strikethrough</s>, <sup>superscript</sup>, <sub>subscript</sub>, <strong><em>nested</em></strong>.</p> <!-- /wp:paragraph --> <!-- wp:list --> @@ -19,7 +19,7 @@ <h2>This is a <em>heading</em></h2> <!-- /wp:list --> <!-- wp:table --> -<figure class="wp-block-table"><table class=""><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table></figure> +<figure class="wp-block-table"><table><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table></figure> <!-- /wp:table --> <!-- wp:separator --> @@ -27,7 +27,7 @@ <h2>This is a <em>heading</em></h2> <!-- /wp:separator --> <!-- wp:paragraph --> -<p>An image:<br></p> +<p>An image:</p> <!-- /wp:paragraph --> <!-- wp:image --> diff --git a/test/integration/fixtures/google-docs-table-out.html b/test/integration/fixtures/google-docs-table-out.html index fbca760d25258b..07fe4106f3a6a7 100644 --- a/test/integration/fixtures/google-docs-table-out.html +++ b/test/integration/fixtures/google-docs-table-out.html @@ -1,3 +1,3 @@ <!-- wp:table --> -<figure class="wp-block-table"><table class=""><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table></figure> +<figure class="wp-block-table"><table><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table></figure> <!-- /wp:table --> diff --git a/test/integration/fixtures/google-docs-table-with-comments-out.html b/test/integration/fixtures/google-docs-table-with-comments-out.html index fbca760d25258b..07fe4106f3a6a7 100644 --- a/test/integration/fixtures/google-docs-table-with-comments-out.html +++ b/test/integration/fixtures/google-docs-table-with-comments-out.html @@ -1,3 +1,3 @@ <!-- wp:table --> -<figure class="wp-block-table"><table class=""><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table></figure> +<figure class="wp-block-table"><table><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table></figure> <!-- /wp:table --> diff --git a/test/integration/fixtures/google-docs-with-comments-out.html b/test/integration/fixtures/google-docs-with-comments-out.html index 576b2cd7fda73f..46894395d9cbe3 100644 --- a/test/integration/fixtures/google-docs-with-comments-out.html +++ b/test/integration/fixtures/google-docs-with-comments-out.html @@ -1,5 +1,5 @@ <!-- wp:paragraph --> -<p>This is a <strong>title</strong><br></p> +<p>This is a <strong>title</strong></p> <!-- /wp:paragraph --> <!-- wp:heading --> @@ -7,7 +7,7 @@ <h2>This is a <em>heading</em></h2> <!-- /wp:heading --> <!-- wp:paragraph --> -<p>Formatting test: <strong>bold</strong>, <em>italic</em>, <a href="https://w.org/">link</a>, strikethrough, <sup>superscript</sup>, <sub>subscript</sub>, <strong><em>nested</em></strong>.<br></p> +<p>Formatting test: <strong>bold</strong>, <em>italic</em>, <a href="https://w.org/">link</a>, <s>strikethrough</s>, <sup>superscript</sup>, <sub>subscript</sub>, <strong><em>nested</em></strong>.</p> <!-- /wp:paragraph --> <!-- wp:list --> @@ -19,7 +19,7 @@ <h2>This is a <em>heading</em></h2> <!-- /wp:list --> <!-- wp:table --> -<figure class="wp-block-table"><table class=""><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table></figure> +<figure class="wp-block-table"><table><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table></figure> <!-- /wp:table --> <!-- wp:separator --> @@ -27,7 +27,7 @@ <h2>This is a <em>heading</em></h2> <!-- /wp:separator --> <!-- wp:paragraph --> -<p>An image:<br></p> +<p>An image:</p> <!-- /wp:paragraph --> <!-- wp:image --> diff --git a/test/integration/fixtures/inline-with-html-formatting-space.html b/test/integration/fixtures/inline-with-html-formatting-space.html new file mode 100644 index 00000000000000..3e51738cab59ea --- /dev/null +++ b/test/integration/fixtures/inline-with-html-formatting-space.html @@ -0,0 +1,11 @@ +<html><body> + <!--StartFragment-->Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent a elit + eget tortor molestie egestas. Donec pretium urna vitae mattis + imperdiet. Praesent et lorem iaculis, volutpat odio vitae, ornare lacus. + Donec ut felis tristique, pharetra erat id, viverra justo. Integer sit + amet elementum arcu, eget pharetra felis. In malesuada enim est, sed + placerat nulla feugiat at. Vestibulum feugiat vitae elit sit amet + tincidunt. Pellentesque finibus sed dolor non facilisis. Curabitur + accumsan ante ac hendrerit vestibulum. + <!--EndFragment--> + </body> diff --git a/test/integration/fixtures/list-with-attributes.html b/test/integration/fixtures/list-with-attributes.html new file mode 100644 index 00000000000000..7114d2da756938 --- /dev/null +++ b/test/integration/fixtures/list-with-attributes.html @@ -0,0 +1,7 @@ +<ol start="2" reversed type="i"> + <li>1 + <ol start="2" reversed type="i"> + <li>1</li> + </ol> + </li> +</ol> diff --git a/test/integration/fixtures/markdown-out.html b/test/integration/fixtures/markdown-out.html index 85a105b640923b..5e4ddca850da3e 100644 --- a/test/integration/fixtures/markdown-out.html +++ b/test/integration/fixtures/markdown-out.html @@ -7,8 +7,7 @@ <h1>This is a heading with <em>italic</em></h1> <!-- /wp:paragraph --> <!-- wp:paragraph --> -<p>Preserve<br> -line breaks please.</p> +<p>Preserve<br>line breaks please.</p> <!-- /wp:paragraph --> <!-- wp:heading --> @@ -28,11 +27,11 @@ <h2>Table</h2> <!-- /wp:heading --> <!-- wp:table --> -<figure class="wp-block-table"><table class=""><thead><tr><th>First Header</th><th>Second Header</th></tr></thead><tbody><tr><td>Content from cell 1</td><td>Content from cell 2</td></tr><tr><td>Content in the first column</td><td>Content in the second column</td></tr></tbody></table></figure> +<figure class="wp-block-table"><table><thead><tr><th>First Header</th><th>Second Header</th></tr></thead><tbody><tr><td>Content from cell 1</td><td>Content from cell 2</td></tr><tr><td>Content in the first column</td><td>Content in the second column</td></tr></tbody></table></figure> <!-- /wp:table --> <!-- wp:table --> -<figure class="wp-block-table"><table class=""><thead><tr><th></th><th></th></tr></thead><tbody><tr><td></td><td>Table with empty cells.</td></tr></tbody></table></figure> +<figure class="wp-block-table"><table><thead><tr><th></th><th></th></tr></thead><tbody><tr><td></td><td>Table with empty cells.</td></tr></tbody></table></figure> <!-- /wp:table --> <!-- wp:heading --> diff --git a/test/integration/fixtures/ms-word-online-out.html b/test/integration/fixtures/ms-word-online-out.html index 798881e8c0151b..d3aaeb98e302fa 100644 --- a/test/integration/fixtures/ms-word-online-out.html +++ b/test/integration/fixtures/ms-word-online-out.html @@ -15,7 +15,7 @@ <!-- /wp:list --> <!-- wp:table --> -<figure class="wp-block-table"><table class=""><tbody><tr><td>One&nbsp;</td><td>Two&nbsp;</td><td>Three&nbsp;</td></tr><tr><td>1&nbsp;</td><td>2&nbsp;</td><td>3&nbsp;</td></tr><tr><td>I&nbsp;</td><td>II&nbsp;</td><td>III&nbsp;</td></tr></tbody></table></figure> +<figure class="wp-block-table"><table><tbody><tr><td>One&nbsp;</td><td>Two&nbsp;</td><td>Three&nbsp;</td></tr><tr><td>1&nbsp;</td><td>2&nbsp;</td><td>3&nbsp;</td></tr><tr><td>I&nbsp;</td><td>II&nbsp;</td><td>III&nbsp;</td></tr></tbody></table></figure> <!-- /wp:table --> <!-- wp:paragraph --> diff --git a/test/integration/fixtures/ms-word-out.html b/test/integration/fixtures/ms-word-out.html index 427e12686ad625..fba58fd4fb9dda 100644 --- a/test/integration/fixtures/ms-word-out.html +++ b/test/integration/fixtures/ms-word-out.html @@ -1,11 +1,9 @@ <!-- wp:paragraph --> -<p>This is a -title</p> +<p>This is a title</p> <!-- /wp:paragraph --> <!-- wp:paragraph --> -<p>This is a -subtitle</p> +<p>This is a subtitle</p> <!-- /wp:paragraph --> <!-- wp:heading {"level":1} --> @@ -24,30 +22,12 @@ <h2>This is a heading level 2</h2> <ul><li>A</li><li>Bulleted<ul><li>Indented</li></ul></li><li>List</li></ul> <!-- /wp:list --> -<!-- wp:list {"ordered":true} --> -<ol><li>One</li><li>Two</li><li>Three</li></ol> +<!-- wp:list {"ordered":true,"type":"1"} --> +<ol type="1"><li>One</li><li>Two</li><li>Three</li></ol> <!-- /wp:list --> <!-- wp:table --> -<figure class="wp-block-table"><table class=""><tbody><tr><td> - One - </td><td> - Two - </td><td> - Three - </td></tr><tr><td> - 1 - </td><td> - 2 - </td><td> - 3 - </td></tr><tr><td> - I - </td><td> - II - </td><td> - III - </td></tr></tbody></table></figure> +<figure class="wp-block-table"><table><tbody><tr><td>One</td><td>Two</td><td>Three</td></tr><tr><td>1</td><td>2</td><td>3</td></tr><tr><td>I</td><td>II</td><td>III</td></tr></tbody></table></figure> <!-- /wp:table --> <!-- wp:paragraph --> diff --git a/test/integration/fixtures/ms-word-styled-out.html b/test/integration/fixtures/ms-word-styled-out.html index 2eef7365f9668a..277d8d37bbc630 100644 --- a/test/integration/fixtures/ms-word-styled-out.html +++ b/test/integration/fixtures/ms-word-styled-out.html @@ -1,15 +1,7 @@ <!-- wp:paragraph --> -<p> -<strong>Lorem -ipsum dolor sit amet, consectetur adipiscing elit&nbsp; </strong> -</p> +<p><strong>Lorem ipsum dolor sit amet, consectetur adipiscing elit&nbsp;</strong></p> <!-- /wp:paragraph --> <!-- wp:paragraph --> -<p> -Lorem -ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque -aliquet hendrerit auctor. Nam lobortis, est vel lacinia tincidunt, -purus tellus vehicula ex, nec pharetra justo dui sed lorem. Nam -congue laoreet massa, quis varius est tincidunt ut.</p> +<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque aliquet hendrerit auctor. Nam lobortis, est vel lacinia tincidunt, purus tellus vehicula ex, nec pharetra justo dui sed lorem. Nam congue laoreet massa, quis varius est tincidunt ut.</p> <!-- /wp:paragraph --> diff --git a/test/integration/fixtures/plain-out.html b/test/integration/fixtures/plain-out.html index 2cebc072c71b02..ef9ecabfdcf318 100644 --- a/test/integration/fixtures/plain-out.html +++ b/test/integration/fixtures/plain-out.html @@ -3,5 +3,5 @@ <!-- /wp:paragraph --> <!-- wp:paragraph --> -<p>test<br></p> +<p>test</p> <!-- /wp:paragraph --> diff --git a/test/integration/fixtures/shortcode-matching-in.html b/test/integration/fixtures/shortcode-matching-in.html new file mode 100644 index 00000000000000..f5b46fa16abc9e --- /dev/null +++ b/test/integration/fixtures/shortcode-matching-in.html @@ -0,0 +1,3 @@ +<p>[gallery ids="40,41,42"]</p> +<p>[gallery ids="1000"]</p> +<p>[gallery ids="42"]</p> diff --git a/test/integration/fixtures/shortcode-matching-out.html b/test/integration/fixtures/shortcode-matching-out.html new file mode 100644 index 00000000000000..02599dea662943 --- /dev/null +++ b/test/integration/fixtures/shortcode-matching-out.html @@ -0,0 +1,7 @@ +<!-- wp:test/gallery {"ids":[40,41,42]} /--> + +<!-- wp:gallery {"ids":[1000],"columns":3,"linkTo":"attachment"} --> +<figure class="wp-block-gallery columns-3 is-cropped"><ul class="blocks-gallery-grid"><li class="blocks-gallery-item"><figure><img data-id="1000" class="wp-image-1000"/></figure></li></ul></figure> +<!-- /wp:gallery --> + +<!-- wp:test/gallery {"ids":[42]} /--> diff --git a/test/integration/fixtures/windows.html b/test/integration/fixtures/windows.html new file mode 100644 index 00000000000000..022cc8f53b0fa3 --- /dev/null +++ b/test/integration/fixtures/windows.html @@ -0,0 +1,10 @@ +<html><body> +<!--StartFragment--><!-- wp:heading --> +<h2>Heading Win</h2> +<!-- /wp:heading --> + +<!-- wp:paragraph --> +<p>Paragraph Win</p> +<!-- /wp:paragraph --><!--EndFragment--> +</body> +</html> diff --git a/test/integration/full-content/full-content.test.js b/test/integration/full-content/full-content.test.js index 1a50c84a834503..953eba86306206 100644 --- a/test/integration/full-content/full-content.test.js +++ b/test/integration/full-content/full-content.test.js @@ -49,7 +49,10 @@ function normalizeParsedBlocks( blocks ) { describe( 'full post content fixture', () => { beforeAll( () => { unstable__bootstrapServerSideBlockDefinitions( require( './server-registered.json' ) ); - const settings = { __experimentalEnableLegacyWidgetBlock: true, __experimentalEnableMenuBlock: true }; + const settings = { + __experimentalEnableLegacyWidgetBlock: true, + __experimentalEnableFullSiteEditing: true, + }; // Load all hooks that modify blocks require( '../../../packages/editor/src/hooks' ); registerCoreBlocks(); diff --git a/test/integration/full-content/server-registered.json b/test/integration/full-content/server-registered.json index e11fe0283b44a5..b224e6ef2d0951 100644 --- a/test/integration/full-content/server-registered.json +++ b/test/integration/full-content/server-registered.json @@ -1 +1 @@ -{"core\/archives":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/block":{"attributes":{"ref":{"type":"number"}}},"core\/calendar":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"month":{"type":"integer"},"year":{"type":"integer"}}},"core\/categories":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showHierarchy":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/latest-comments":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"commentsToShow":{"type":"number","default":5,"minimum":1,"maximum":100},"displayAvatar":{"type":"boolean","default":true},"displayDate":{"type":"boolean","default":true},"displayExcerpt":{"type":"boolean","default":true}}},"core\/latest-posts":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"categories":{"type":"string"},"postsToShow":{"type":"number","default":5},"displayPostContent":{"type":"boolean","default":false},"displayPostContentRadio":{"type":"string","default":"excerpt"},"excerptLength":{"type":"number","default":55},"displayPostDate":{"type":"boolean","default":false},"postLayout":{"type":"string","default":"list"},"columns":{"type":"number","default":3},"order":{"type":"string","default":"desc"},"orderBy":{"type":"string","default":"date"}}},"core\/legacy-widget":{"attributes":{"identifier":{"type":"string"},"instance":{"type":"object"},"isCallbackWidget":{"type":"boolean"}}},"core\/navigation-menu":{"category":"layout","attributes":{"automaticallyAdd":{"type":"boolean","default":false}}},"core\/rss":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"columns":{"type":"number","default":2},"blockLayout":{"type":"string","default":"list"},"feedURL":{"type":"string","default":""},"itemsToShow":{"type":"number","default":5},"displayExcerpt":{"type":"boolean","default":false},"displayAuthor":{"type":"boolean","default":false},"displayDate":{"type":"boolean","default":false},"excerptLength":{"type":"number","default":55}}},"core\/search":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"label":{"type":"string","default":"Search"},"placeholder":{"type":"string","default":""},"buttonText":{"type":"string","default":"Search"}}},"core\/shortcode":{"attributes":{"text":{"type":"string","source":"html"}}},"core\/tag-cloud":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"taxonomy":{"type":"string","default":"post_tag"},"showTagCounts":{"type":"boolean","default":false}}}} \ No newline at end of file +{"core\/archives":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/block":{"attributes":{"ref":{"type":"number"}}},"core\/calendar":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"month":{"type":"integer"},"year":{"type":"integer"}}},"core\/categories":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"displayAsDropdown":{"type":"boolean","default":false},"showHierarchy":{"type":"boolean","default":false},"showPostCounts":{"type":"boolean","default":false}}},"core\/latest-comments":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"commentsToShow":{"type":"number","default":5,"minimum":1,"maximum":100},"displayAvatar":{"type":"boolean","default":true},"displayDate":{"type":"boolean","default":true},"displayExcerpt":{"type":"boolean","default":true}}},"core\/latest-posts":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"categories":{"type":"string"},"postsToShow":{"type":"number","default":5},"displayPostContent":{"type":"boolean","default":false},"displayPostContentRadio":{"type":"string","default":"excerpt"},"excerptLength":{"type":"number","default":55},"displayPostDate":{"type":"boolean","default":false},"postLayout":{"type":"string","default":"list"},"columns":{"type":"number","default":3},"order":{"type":"string","default":"desc"},"orderBy":{"type":"string","default":"date"}}},"core\/legacy-widget":{"attributes":{"widgetClass":{"type":"string"},"id":{"type":"string"},"idBase":{"type":"string"},"number":{"type":"number"},"instance":{"type":"object"}}},"core\/navigation":{"attributes":{"className":{"type":"string"},"textColor":{"type":"string"},"customTextColor":{"type":"string"}}},"core\/rss":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"columns":{"type":"number","default":2},"blockLayout":{"type":"string","default":"list"},"feedURL":{"type":"string","default":""},"itemsToShow":{"type":"number","default":5},"displayExcerpt":{"type":"boolean","default":false},"displayAuthor":{"type":"boolean","default":false},"displayDate":{"type":"boolean","default":false},"excerptLength":{"type":"number","default":55}}},"core\/search":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"label":{"type":"string","default":"Search"},"placeholder":{"type":"string","default":""},"buttonText":{"type":"string","default":"Search"}}},"core\/shortcode":{"attributes":{"text":{"type":"string","source":"html"}}},"core\/social-link-amazon":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"amazon"}}},"core\/social-link-bandcamp":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"bandcamp"}}},"core\/social-link-behance":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"behance"}}},"core\/social-link-chain":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"chain"}}},"core\/social-link-codepen":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"codepen"}}},"core\/social-link-deviantart":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"deviantart"}}},"core\/social-link-dribbble":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"dribbble"}}},"core\/social-link-dropbox":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"dropbox"}}},"core\/social-link-etsy":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"etsy"}}},"core\/social-link-facebook":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"facebook"}}},"core\/social-link-feed":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"feed"}}},"core\/social-link-fivehundredpx":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"fivehundredpx"}}},"core\/social-link-flickr":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"flickr"}}},"core\/social-link-foursquare":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"foursquare"}}},"core\/social-link-goodreads":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"goodreads"}}},"core\/social-link-google":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"google"}}},"core\/social-link-github":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"github"}}},"core\/social-link-instagram":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"instagram"}}},"core\/social-link-lastfm":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"lastfm"}}},"core\/social-link-linkedin":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"linkedin"}}},"core\/social-link-mail":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"mail"}}},"core\/social-link-mastodon":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"mastodon"}}},"core\/social-link-meetup":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"meetup"}}},"core\/social-link-medium":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"medium"}}},"core\/social-link-pinterest":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"pinterest"}}},"core\/social-link-pocket":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"pocket"}}},"core\/social-link-reddit":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"reddit"}}},"core\/social-link-skype":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"skype"}}},"core\/social-link-snapchat":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"snapchat"}}},"core\/social-link-soundcloud":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"soundcloud"}}},"core\/social-link-spotify":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"spotify"}}},"core\/social-link-tumblr":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"tumblr"}}},"core\/social-link-twitch":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"twitch"}}},"core\/social-link-twitter":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"twitter"}}},"core\/social-link-vimeo":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"vimeo"}}},"core\/social-link-vk":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"vk"}}},"core\/social-link-wordpress":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"wordpress"}}},"core\/social-link-yelp":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"yelp"}}},"core\/social-link-youtube":{"attributes":{"url":{"type":"string"},"site":{"type":"string","default":"youtube"}}},"core\/tag-cloud":{"attributes":{"align":{"type":"string","enum":["left","center","right","wide","full"]},"className":{"type":"string"},"taxonomy":{"type":"string","default":"post_tag"},"showTagCounts":{"type":"boolean","default":false}}},"core\/template-part":{"attributes":{"postId":{"type":"number"},"slug":{"type":"string"},"theme":{"type":"string"}}}} diff --git a/test/integration/shortcode-converter.test.js b/test/integration/shortcode-converter.test.js index 2f0e5ab632fa74..dc59ee49d2fb5b 100644 --- a/test/integration/shortcode-converter.test.js +++ b/test/integration/shortcode-converter.test.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { registerCoreBlocks } from '@wordpress/block-library'; -import { createBlock } from '@wordpress/blocks'; +import { createBlock, registerBlockType } from '@wordpress/blocks'; /** * Internal dependencies @@ -12,6 +12,88 @@ import segmentHTMLToShortcodeBlock from '../../packages/blocks/src/api/raw-handl describe( 'segmentHTMLToShortcodeBlock', () => { beforeAll( () => { registerCoreBlocks(); + registerBlockType( 'test/gallery', { + title: 'Test Gallery', + category: 'common', + attributes: { + ids: { + type: 'array', + default: [], + }, + }, + transforms: { + from: [ + { + type: 'shortcode', + tag: [ 'my-gallery', 'my-bunch-of-images' ], + attributes: { + ids: { + type: 'array', + shortcode: ( { named: { ids } } ) => + ids.split( ',' ).map( ( id ) => ( + parseInt( id, 10 ) + ) ), + }, + }, + }, + ], + }, + save: () => null, + } ); + registerBlockType( 'test/broccoli', { + title: 'Test Broccoli', + category: 'common', + attributes: { + id: { + type: 'number', + }, + }, + transforms: { + from: [ + { + type: 'shortcode', + tag: [ 'my-broccoli' ], + attributes: { + id: { + type: 'number', + shortcode: ( { named: { id } } ) => parseInt( id, 10 ), + }, + }, + isMatch( { named: { id } } ) { + return id < 1000; + }, + }, + ], + }, + save: () => null, + } ); + registerBlockType( 'test/fallback-broccoli', { + title: 'Test Fallback Broccoli', + category: 'common', + attributes: { + id: { + type: 'number', + }, + }, + transforms: { + from: [ + { + type: 'shortcode', + tag: [ 'my-broccoli' ], + attributes: { + id: { + type: 'number', + shortcode: ( { named: { id } } ) => parseInt( id, 10 ), + }, + }, + isMatch( { named: { id } } ) { + return id > 1000; + }, + }, + ], + }, + save: () => null, + } ); } ); it( 'should convert a standalone shortcode between two paragraphs', () => { @@ -36,6 +118,41 @@ describe( 'segmentHTMLToShortcodeBlock', () => { <p>Bar</p>` ); } ); + it( 'should convert a shortcode to a block type with a passing `isMatch`', () => { + const original = `<p>[my-broccoli id="42"]</p>`; + + const transformed = segmentHTMLToShortcodeBlock( original, 0 ); + const expectedBlock = createBlock( 'test/broccoli', { id: 42 } ); + expectedBlock.clientId = transformed[ 1 ].clientId; + expect( transformed[ 1 ] ).toEqual( expectedBlock ); + } ); + + it( 'should not convert a shortcode to a block type with a failing `isMatch`', () => { + const original = `<p>[my-broccoli id="1000"]</p>`; + + const transformed = segmentHTMLToShortcodeBlock( original, 0 ); + const expectedBlock = createBlock( 'core/shortcode' ); + expectedBlock.clientId = transformed[ 1 ].clientId; + expect( transformed[ 1 ] ).toEqual( expectedBlock ); + } ); + + it( 'should not blindly exclude a transform in subsequent shortcodes after a failed `isMatch`', () => { + const original = `<p>[my-broccoli id="1001"]</p> + <p>[my-broccoli id="42"]</p> + <p>[my-broccoli id="1000"]</p>`; + + const transformed = segmentHTMLToShortcodeBlock( original ); + const firstExpectedBlock = createBlock( 'test/fallback-broccoli', { id: 1001 } ); + firstExpectedBlock.clientId = transformed[ 1 ].clientId; + const secondExpectedBlock = createBlock( 'test/broccoli', { id: 42 } ); + secondExpectedBlock.clientId = transformed[ 3 ].clientId; + const thirdExpectedBlock = createBlock( 'core/shortcode' ); + thirdExpectedBlock.clientId = transformed[ 5 ].clientId; + expect( transformed[ 1 ] ).toEqual( firstExpectedBlock ); + expect( transformed[ 3 ] ).toEqual( secondExpectedBlock ); + expect( transformed[ 5 ] ).toEqual( thirdExpectedBlock ); + } ); + it( 'should convert two instances of the same shortcode', () => { const original = `<p>[foo one]</p> <p>[foo two]</p>`; @@ -101,4 +218,15 @@ describe( 'segmentHTMLToShortcodeBlock', () => { expect( transformed[ 8 ] ).toEqual( '</p>' ); expect( transformed ).toHaveLength( 9 ); } ); + + it( 'should convert regardless of shortcode alias', () => { + const original = `<p>[my-gallery ids="1,2,3"]</p> +<p>[my-bunch-of-images ids="4,5,6"]</p>`; + const transformed = segmentHTMLToShortcodeBlock( original, 0 ); + expect( transformed[ 0 ] ).toBe( '<p>' ); + expect( transformed[ 1 ] ).toHaveProperty( 'name', 'test/gallery' ); + expect( transformed[ 2 ] ).toBe( '</p>\n<p>' ); + expect( transformed[ 3 ] ).toHaveProperty( 'name', 'test/gallery' ); + expect( transformed[ 4 ] ).toBe( '</p>' ); + } ); } ); diff --git a/test/native/__mocks__/styleMock.js b/test/native/__mocks__/styleMock.js index 4e63b494feff94..c982d7b4734d0b 100644 --- a/test/native/__mocks__/styleMock.js +++ b/test/native/__mocks__/styleMock.js @@ -75,4 +75,10 @@ module.exports = { unsupportedBlockIcon: { color: 'white', }, + infoIcon: { + size: 36, + }, + infoSheetIcon: { + color: 'gray', + }, }; diff --git a/test/native/jest.config.js b/test/native/jest.config.js index 6ff9370be48939..3527fa6d070f2d 100644 --- a/test/native/jest.config.js +++ b/test/native/jest.config.js @@ -38,7 +38,9 @@ module.exports = { '/__device-tests__/', ], testURL: 'http://localhost/', - moduleDirectories: [ 'node_modules' ], + // Add the `Libraries/Utilities` subfolder to the module directories, otherwise haste/jest doesn't find Platform.js on Travis, + // and add it first so https://github.com/facebook/react-native/blob/v0.60.0/Libraries/react-native/react-native-implementation.js#L324-L326 doesn't pick up the Platform npm module. + moduleDirectories: [ 'node_modules/react-native/Libraries/Utilities', 'node_modules' ], moduleNameMapper: { // Mock the CSS modules. See https://facebook.github.io/jest/docs/en/webpack.html#handling-static-assets '\\.(scss)$': '<rootDir>/' + configPath + '/__mocks__/styleMock.js', diff --git a/test/native/setup.js b/test/native/setup.js index 60fe7194c0aa60..33ee2d2b746d4a 100644 --- a/test/native/setup.js +++ b/test/native/setup.js @@ -16,9 +16,13 @@ jest.mock( 'react-native-gutenberg-bridge', () => { editorDidMount: jest.fn(), editorDidAutosave: jest.fn(), subscribeMediaUpload: jest.fn(), - requestMediaPickFromMediaLibrary: jest.fn(), - requestMediaPickFromDeviceLibrary: jest.fn(), - requestMediaPickFromDeviceCamera: jest.fn(), + getOtherMediaOptions: jest.fn(), + requestMediaPicker: jest.fn(), + mediaSources: { + deviceLibrary: 'DEVICE_MEDIA_LIBRARY', + deviceCamera: 'DEVICE_CAMERA', + siteMediaLibrary: 'SITE_MEDIA_LIBRARY', + }, }; } ); @@ -63,6 +67,8 @@ jest.mock( 'react-native-safe-area', () => { jest.mock( 'react-native-recyclerview-list' ); +jest.mock( '@react-native-community/slider', () => () => 'Slider', { virtual: true } ); + if ( ! global.window.matchMedia ) { global.window.matchMedia = () => ( { matches: false, diff --git a/test/unit/config/global-mocks.js b/test/unit/config/global-mocks.js new file mode 100644 index 00000000000000..60b430759ab287 --- /dev/null +++ b/test/unit/config/global-mocks.js @@ -0,0 +1,6 @@ +jest.mock( '@wordpress/compose', () => { + return { + ...jest.requireActual( '@wordpress/compose' ), + useViewportMatch: jest.fn(), + }; +} ); diff --git a/test/unit/config/register-context.js b/test/unit/config/register-context.js new file mode 100644 index 00000000000000..b73423550f42c6 --- /dev/null +++ b/test/unit/config/register-context.js @@ -0,0 +1,6 @@ +/** + * External dependencies + */ +import registerRequireContextHook from 'babel-plugin-require-context-hook/register'; + +registerRequireContextHook(); diff --git a/test/unit/jest.config.js b/test/unit/jest.config.js index 6ad2de76e69ef2..dfa82488822630 100644 --- a/test/unit/jest.config.js +++ b/test/unit/jest.config.js @@ -14,7 +14,9 @@ module.exports = { }, preset: '@wordpress/jest-preset-default', setupFiles: [ + '<rootDir>/test/unit/config/global-mocks.js', '<rootDir>/test/unit/config/gutenberg-phase.js', + '<rootDir>/test/unit/config/register-context.js', ], testURL: 'http://localhost', testPathIgnorePatterns: [ diff --git a/tsconfig.json b/tsconfig.json index cee321c53693eb..2f91f5d56fa077 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,7 +22,8 @@ }, "include": [ - "./packages/url/**/*.js" + "./packages/url/**/*.js", + "./packages/token-list/**/*.js" ], "exclude": [ "./packages/**/test/**", From 4a780ea60cfb8c785a551ac8b11a80981a8f47bd Mon Sep 17 00:00:00 2001 From: Luke Walczak <lukasz.walczak.pwr@gmail.com> Date: Tue, 7 Jan 2020 10:51:13 +0100 Subject: [PATCH 081/183] styling fixes after navigation feature merge (#19455) * Styling fixes to navigation feature * Add netural styles for toolbar * Fix condition for not registered component * Display 'Unsupported' in breadcrumbs for missing components * Refactor after CR * Remove leftovers --- packages/base-styles/_variables.scss | 2 +- .../src/components/block-list/block.native.js | 20 ++++++++++++++++--- .../components/block-list/block.native.scss | 13 ++++++++++-- .../block-list/breadcrumb.native.js | 6 +++++- packages/block-library/src/missing/index.js | 2 +- 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/packages/base-styles/_variables.scss b/packages/base-styles/_variables.scss index 5c034cf7fb963c..ab3cad778bce93 100644 --- a/packages/base-styles/_variables.scss +++ b/packages/base-styles/_variables.scss @@ -74,7 +74,7 @@ $block-selected-child-border-width: 1px; $block-selected-child-padding: 0; $block-selected-to-content: $block-edge-to-content - $block-selected-margin - $block-selected-border-width; $block-selected-child-to-content: $block-selected-to-content - $block-selected-child-margin - $block-selected-child-border-width; -$block-custom-appender-to-content: $block-edge-to-content - $block-selected-margin - $block-selected-child-margin + $block-selected-border-width; +$block-custom-appender-to-content: $block-selected-margin - $block-selected-border-width; $block-media-container-to-content: $block-selected-child-margin + $block-selected-border-width; // Buttons & UI Widgets diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index e5eec2effa8cce..52207023a13c54 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -14,7 +14,7 @@ import { Component } from '@wordpress/element'; import { ToolbarButton, Toolbar } from '@wordpress/components'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; -import { getBlockType } from '@wordpress/blocks'; +import { getBlockType, getUnregisteredTypeHandlerName } from '@wordpress/blocks'; import { __, sprintf } from '@wordpress/i18n'; /** @@ -188,6 +188,17 @@ class BlockListBlock extends Component { ]; } + applyToolbarStyle() { + const { + hasChildren, + isUnregisteredBlock, + } = this.props; + + if ( ! hasChildren || isUnregisteredBlock ) { + return styles.neutralToolbar; + } + } + render() { const { clientId, @@ -228,7 +239,7 @@ class BlockListBlock extends Component { style={ this.applyBlockStyle() } > { isValid ? this.getBlockForType() : <BlockInvalidWarning blockTitle={ title } icon={ icon } /> } - { isSelected && <BlockMobileToolbar clientId={ clientId } /> } + <View style={ this.applyToolbarStyle() } >{ isSelected && <BlockMobileToolbar clientId={ clientId } /> }</View> </View> </TouchableWithoutFeedback> </> @@ -261,6 +272,8 @@ export default compose( [ const isLastBlock = order === getBlocks().length - 1; const block = __unstableGetBlockWithoutInnerBlocks( clientId ); const { name, attributes, isValid } = block || {}; + + const isUnregisteredBlock = name === getUnregisteredTypeHandlerName(); const blockType = getBlockType( name || 'core/missing' ); const title = blockType.title; const icon = blockType.icon; @@ -281,7 +294,7 @@ export default compose( [ const commonAncestorIndex = parents.indexOf( commonAncestor ) - 1; const firstToSelectId = commonAncestor ? parents[ commonAncestorIndex ] : parents[ parents.length - 1 ]; - const hasChildren = !! getBlockCount( clientId ); + const hasChildren = ! isUnregisteredBlock && !! getBlockCount( clientId ); const hasParent = !! parentId; const isParentSelected = selectedBlockClientId && selectedBlockClientId === parentId; const isAncestorSelected = selectedBlockClientId && parents.includes( selectedBlockClientId ); @@ -317,6 +330,7 @@ export default compose( [ isTouchable, isDimmed, isRootListInnerBlockHolder, + isUnregisteredBlock, }; } ), withDispatch( ( dispatch, ownProps, { select } ) => { diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index ff9cea835a83c9..3867b583105979 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -75,12 +75,16 @@ .selectedLeaf { margin: $block-selected-margin; - padding: $block-selected-to-content; + padding-left: $block-selected-to-content; + padding-right: $block-selected-to-content; + padding-top: $block-selected-to-content; } .selectedRootLeaf { margin: 0; - padding: $block-edge-to-content; + padding-left: $block-edge-to-content; + padding-right: $block-edge-to-content; + padding-top: $block-edge-to-content; } .selectedParent { @@ -130,3 +134,8 @@ background-color: #e9eff3; opacity: 0.4; } + +.neutralToolbar { + margin-left: -$block-edge-to-content; + margin-right: -$block-edge-to-content; +} diff --git a/packages/block-editor/src/components/block-list/breadcrumb.native.js b/packages/block-editor/src/components/block-list/breadcrumb.native.js index 0bea56e4da6ddf..6e47499b350c8d 100644 --- a/packages/block-editor/src/components/block-list/breadcrumb.native.js +++ b/packages/block-editor/src/components/block-list/breadcrumb.native.js @@ -22,7 +22,11 @@ import styles from './breadcrumb.scss'; const BlockBreadcrumb = ( { clientId, blockIcon, rootClientId, rootBlockIcon } ) => { return ( <View style={ styles.breadcrumbContainer }> - <TouchableOpacity style={ styles.button } onPress={ () => {/* Open BottomSheet with markup */} }> + <TouchableOpacity + style={ styles.button } + onPress={ () => {/* Open BottomSheet with markup */} } + disabled={ true } /* Disable temporarily since onPress function is empty */ + > { rootClientId && rootBlockIcon && ( [ <Icon key="parent-icon" size={ 20 } icon={ rootBlockIcon.src } fill={ styles.icon.color } />, <View key="subdirectory-icon" style={ styles.arrow }><SubdirectorSVG fill={ styles.arrow.color } /></View>, diff --git a/packages/block-library/src/missing/index.js b/packages/block-library/src/missing/index.js index 301efedc3b8265..3b9f0c48c13956 100644 --- a/packages/block-library/src/missing/index.js +++ b/packages/block-library/src/missing/index.js @@ -16,7 +16,7 @@ export { metadata, name }; export const settings = { name, - title: __( 'Unrecognized Block' ), + title: __( 'Unsupported' ), description: __( 'Your site doesn’t include support for this block.' ), supports: { className: false, From 320f472dd77f942b6b16eb95d97664ece3e40083 Mon Sep 17 00:00:00 2001 From: lukewalczak <lukasz.walczak.pwr@gmail.com> Date: Tue, 7 Jan 2020 11:58:41 +0100 Subject: [PATCH 082/183] Check if icon exists before passing to cloneElement --- packages/components/src/button/index.native.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/button/index.native.js b/packages/components/src/button/index.native.js index 1c6db4c0e53c50..4b2c92b3194bb2 100644 --- a/packages/components/src/button/index.native.js +++ b/packages/components/src/button/index.native.js @@ -125,8 +125,8 @@ export function Button( props ) { ) ); - const newIcon = cloneElement( ( icon && <Icon icon={ icon } size={ iconSize } /> ), - { colorScheme: props.preferredColorScheme, isPressed } ); + const newIcon = icon ? cloneElement( ( <Icon icon={ icon } size={ iconSize } /> ), + { colorScheme: props.preferredColorScheme, isPressed } ) : null; const element = ( <TouchableOpacity From e43e0107d2f7ad4c80746e4b8336881f880062f2 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Tue, 7 Jan 2020 13:38:44 +0100 Subject: [PATCH 083/183] fix breadcrumbs missing-key warning --- .../block-editor/src/components/block-list/breadcrumb.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-list/breadcrumb.native.js b/packages/block-editor/src/components/block-list/breadcrumb.native.js index de09d82dd51d05..b6e1207d4e34b8 100644 --- a/packages/block-editor/src/components/block-list/breadcrumb.native.js +++ b/packages/block-editor/src/components/block-list/breadcrumb.native.js @@ -22,7 +22,7 @@ import styles from './breadcrumb.scss'; const BlockBreadcrumb = ( { clientId, blockIcon, rootClientId, rootBlockIcon } ) => { const renderIcon = ( icon, key ) => { if ( typeof icon.src === 'function' ) { - return <Icon icon={ icon.src( { key, size: 24, fill: styles.icon.color } ) } />; + return <Icon key={ key } icon={ icon.src( { size: 24, fill: styles.icon.color } ) } />; } return <Icon key={ key } size={ 24 } icon={ icon.src } fill={ styles.icon.color } />; }; From 42d6e2b9fed9e2ce34340b3732ac63dc3bab2c80 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Mon, 13 Jan 2020 12:48:20 +0100 Subject: [PATCH 084/183] replace RangeControl with StepperControl to set columns number --- packages/block-library/src/columns/edit.native.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 4a73aeb634be88..15e8cb55643c67 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -11,7 +11,7 @@ import { dropRight, times } from 'lodash'; import { __ } from '@wordpress/i18n'; import { PanelBody, - RangeControl, + StepperControl, Toolbar, ToolbarButton, } from '@wordpress/components'; @@ -86,13 +86,14 @@ function ColumnsEditContainer( { <> <InspectorControls> <PanelBody title={ __( 'Columns Settings' ) }> - <RangeControl + <StepperControl label={ __( 'Number of columns' ) } + icon="columns" value={ count } defaultValue={ DEFAULT_COLUMNS } - onChange={ ( value ) => updateColumns( count, value ) } - min={ 2 } - max={ 6 } + onChangeValue={ ( value ) => updateColumns( count, value ) } + minValue={ 2 } + maxValue={ 6 } /> </PanelBody> </InspectorControls> From 707383216c554d617b5414b3ee6e7654b2fca968 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Tue, 14 Jan 2020 09:49:30 +0100 Subject: [PATCH 085/183] add columns const and vieportWidth --- packages/block-library/src/columns/edit.native.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 15e8cb55643c67..4ffb47c242e3fe 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -29,6 +29,7 @@ import { } from '@wordpress/data'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { createBlock } from '@wordpress/blocks'; +import { withViewportMatch } from '@wordpress/viewport'; /** * Internal dependencies @@ -61,6 +62,8 @@ const ALLOWED_BLOCKS = [ 'core/column' ]; * @type {number} */ const DEFAULT_COLUMNS = 2; +const MIN_COLUMNS_NUMBER = 2; +const MAX_COLUMNS_NUMBER = 6; function ColumnsEditContainer( { attributes, @@ -68,6 +71,7 @@ function ColumnsEditContainer( { updateAlignment, updateColumns, clientId, + isSmallScreen, // isSelected, } ) { const { verticalAlignment } = attributes; @@ -92,8 +96,8 @@ function ColumnsEditContainer( { value={ count } defaultValue={ DEFAULT_COLUMNS } onChangeValue={ ( value ) => updateColumns( count, value ) } - minValue={ 2 } - maxValue={ 6 } + minValue={ MIN_COLUMNS_NUMBER } + maxValue={ MAX_COLUMNS_NUMBER } /> </PanelBody> </InspectorControls> @@ -272,5 +276,6 @@ const ColumnsEdit = ( props ) => { }; export default compose( [ + withViewportMatch( { isSmallScreen: '< small' } ), withPreferredColorScheme, ] )( ColumnsEdit ); From 5f6d0ab76933542ca239193425ec0f3d1877de13 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Wed, 15 Jan 2020 09:27:21 +0100 Subject: [PATCH 086/183] distribute columns --- .../src/components/block-list/index.native.js | 4 ++ .../components/inner-blocks/index.native.js | 4 ++ .../block-library/src/columns/edit.native.js | 68 ++++++------------- .../src/columns/editor.native.scss | 6 ++ packages/block-library/src/columns/icon.js | 4 +- .../keyboard-aware-flat-list/index.ios.js | 16 ++++- 6 files changed, 50 insertions(+), 52 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index a76412157f549c..9d140a3d26a4be 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -77,6 +77,8 @@ export class BlockList extends Component { withFooter = true, renderAppender, isRootList, + containerStyle, + itemStyle, } = this.props; return ( @@ -92,6 +94,8 @@ export class BlockList extends Component { extraScrollHeight={ innerToolbarHeight + 10 } keyboardShouldPersistTaps="always" scrollViewStyle={ { flex: isRootList ? 1 : 0 } } + containerStyle={ containerStyle } + itemStyle={ itemStyle } data={ blockClientIds } extraData={ [ isFullyBordered ] } keyExtractor={ identity } diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index 145c961f08233b..b096f1e984b0e6 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -106,6 +106,8 @@ class InnerBlocks extends Component { const { clientId, renderAppender, + containerStyle, + itemStyle, } = this.props; const { templateInProcess } = this.state; @@ -117,6 +119,8 @@ class InnerBlocks extends Component { renderAppender={ renderAppender } withFooter={ false } isFullyBordered={ true } + containerStyle={ containerStyle } + itemStyle={ itemStyle } /> ) } </> diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 4ffb47c242e3fe..2a3a9e84377125 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -2,7 +2,6 @@ * External dependencies */ import { View } from 'react-native'; -import classnames from 'classnames'; import { dropRight, times } from 'lodash'; /** @@ -67,12 +66,12 @@ const MAX_COLUMNS_NUMBER = 6; function ColumnsEditContainer( { attributes, - className, updateAlignment, updateColumns, clientId, isSmallScreen, - // isSelected, + isLargeScreen, + isMediumScreen, } ) { const { verticalAlignment } = attributes; @@ -82,9 +81,18 @@ function ColumnsEditContainer( { }; } ); - const classes = classnames( className, { - [ `are-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, - } ); + const getColumnsInRow = ( columnsCount ) => { + if ( isSmallScreen ) { + return 1; + } + if ( isMediumScreen && ! isLargeScreen ) { + return 2; + } + return columnsCount; + }; + + const columnsInRow = getColumnsInRow( count ); + const columnsWidth = `${ 100 / columnsInRow }%`; return ( <> @@ -114,13 +122,11 @@ function ColumnsEditContainer( { value={ verticalAlignment } /> </BlockControls> - <View className={ classes }> - <InnerBlocks - // templateLock="all" - allowedBlocks={ ALLOWED_BLOCKS } - // renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } - /> - </View> + <InnerBlocks + containerStyle={ ! isSmallScreen ? styles.columnsContainer : undefined } + itemStyle={ { width: columnsWidth } } + allowedBlocks={ ALLOWED_BLOCKS } + /> </> ); } @@ -208,16 +214,6 @@ const ColumnsEditContainerWrapper = withDispatch( ( dispatch, ownProps, registry }, } ) )( ColumnsEditContainer ); -// TODO: implement "templates" to allow select column layout - -// const createBlocksFromInnerBlocksTemplate = ( innerBlocksTemplate ) => { -// return map( -// innerBlocksTemplate, -// ( [ name, attributes, innerBlocks = [] ] ) => -// createBlock( name, attributes, createBlocksFromInnerBlocksTemplate( innerBlocks ) ) -// ); -// }; - const ColumnsEdit = ( props ) => { const { clientId, name, isSelected, getStylesFromColorScheme } = props; // const { blockType, defaultPattern, hasInnerBlocks, patterns } = useSelect( ( select ) => { @@ -236,8 +232,6 @@ const ColumnsEdit = ( props ) => { }; }, [ clientId, name ] ); - // const { replaceInnerBlocks } = useDispatch( 'core/block-editor' ); - // TODO: make sure if columns should reder placeholder if block is not selected if ( ! isSelected && ! hasInnerBlocks ) { return ( @@ -251,31 +245,9 @@ const ColumnsEdit = ( props ) => { return ( <ColumnsEditContainerWrapper { ...props } /> ); - - // TODO: implement "templates" to allow select column layout - - // return ( - // <__experimentalBlockPatternPicker - // icon={ get( blockType, [ 'icon', 'src' ] ) } - // label={ get( blockType, [ 'title' ] ) } - // patterns={ patterns } - // onSelect={ ( nextPattern = defaultPattern ) => { - // if ( nextPattern.attributes ) { - // props.setAttributes( nextPattern.attributes ); - // } - // if ( nextPattern.innerBlocks ) { - // replaceInnerBlocks( - // props.clientId, - // createBlocksFromInnerBlocksTemplate( nextPattern.innerBlocks ) - // ); - // } - // } } - // allowSkip - // /> - // ); }; export default compose( [ - withViewportMatch( { isSmallScreen: '< small' } ), + withViewportMatch( { isSmallScreen: '< small', isLargeScreen: '>= large', isMediumScreen: '>= small' } ), withPreferredColorScheme, ] )( ColumnsEdit ); diff --git a/packages/block-library/src/columns/editor.native.scss b/packages/block-library/src/columns/editor.native.scss index ff92d0885ba3dc..ab8489aeace7b3 100644 --- a/packages/block-library/src/columns/editor.native.scss +++ b/packages/block-library/src/columns/editor.native.scss @@ -20,3 +20,9 @@ margin-left: 0; margin-right: 0; } + +.columnsContainer { + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-start; +} diff --git a/packages/block-library/src/columns/icon.js b/packages/block-library/src/columns/icon.js index bcbbb79f33d867..594a80b5d78e86 100644 --- a/packages/block-library/src/columns/icon.js +++ b/packages/block-library/src/columns/icon.js @@ -3,6 +3,6 @@ */ import { G, Path, SVG } from '@wordpress/components'; -const Icon = ( props ) => <SVG {...props} viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M4,4H20a2,2,0,0,1,2,2V18a2,2,0,0,1-2,2H4a2,2,0,0,1-2-2V6A2,2,0,0,1,4,4ZM4 6V18H8V6Zm6 0V18h4V6Zm6 0V18h4V6Z" /></G></SVG>; +const Icon = ( props ) => <SVG { ...props } viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M4,4H20a2,2,0,0,1,2,2V18a2,2,0,0,1-2,2H4a2,2,0,0,1-2-2V6A2,2,0,0,1,4,4ZM4 6V18H8V6Zm6 0V18h4V6Zm6 0V18h4V6Z" /></G></SVG>; -export default Icon +export default Icon; diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js index 0d22e649d6787e..a3755f4ecd42e8 100644 --- a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js +++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js @@ -2,7 +2,7 @@ * External dependencies */ import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; -import { FlatList } from 'react-native'; +import { FlatList, View } from 'react-native'; export const KeyboardAwareFlatList = ( { extraScrollHeight, @@ -10,6 +10,8 @@ export const KeyboardAwareFlatList = ( { innerRef, autoScroll, scrollViewStyle, + containerStyle, + itemStyle, ...listProps } ) => ( <KeyboardAwareScrollView @@ -45,7 +47,17 @@ export const KeyboardAwareFlatList = ( { onScroll={ ( event ) => { this.latestContentOffsetY = event.nativeEvent.contentOffset.y; } } > - <FlatList { ...listProps } /> + <FlatList + { ...listProps } + style={ containerStyle ? containerStyle : {} } + CellRendererComponent={ containerStyle && ( ( props ) => ( + <View + { ...props } + style={ itemStyle } + /> + ) ) + } + /> </KeyboardAwareScrollView> ); From fefc237ea29efcb6a5664acd4e64e79bf9575764 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 16 Jan 2020 13:11:51 +0100 Subject: [PATCH 087/183] initial calculated version --- .../src/components/block-list/index.native.js | 2 - .../components/inner-blocks/index.native.js | 2 - .../src/components/inserter/menu.native.js | 2 +- .../block-library/src/column/edit.native.js | 60 ++++++++++++++++--- .../src/column/editor.native.scss | 8 +++ .../block-library/src/columns/edit.native.js | 18 +----- .../keyboard-aware-flat-list/index.ios.js | 12 +--- 7 files changed, 65 insertions(+), 39 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 9d140a3d26a4be..95ee98c61cd384 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -78,7 +78,6 @@ export class BlockList extends Component { renderAppender, isRootList, containerStyle, - itemStyle, } = this.props; return ( @@ -95,7 +94,6 @@ export class BlockList extends Component { keyboardShouldPersistTaps="always" scrollViewStyle={ { flex: isRootList ? 1 : 0 } } containerStyle={ containerStyle } - itemStyle={ itemStyle } data={ blockClientIds } extraData={ [ isFullyBordered ] } keyExtractor={ identity } diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index b096f1e984b0e6..a8bfd45e1d9abe 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -107,7 +107,6 @@ class InnerBlocks extends Component { clientId, renderAppender, containerStyle, - itemStyle, } = this.props; const { templateInProcess } = this.state; @@ -120,7 +119,6 @@ class InnerBlocks extends Component { withFooter={ false } isFullyBordered={ true } containerStyle={ containerStyle } - itemStyle={ itemStyle } /> ) } </> diff --git a/packages/block-editor/src/components/inserter/menu.native.js b/packages/block-editor/src/components/inserter/menu.native.js index 782017a527d88f..81e027dc40bd04 100644 --- a/packages/block-editor/src/components/inserter/menu.native.js +++ b/packages/block-editor/src/components/inserter/menu.native.js @@ -78,7 +78,7 @@ export class InserterMenu extends Component { <FlatList onLayout={ this.onLayout } scrollEnabled={ false } - key={ `InserterUI-${ this.state.numberOfColumns }` } //re-render when numberOfColumns changes + // key={ `InserterUI-${ this.state.numberOfColumns }` } //re-render when numberOfColumns changes keyboardShouldPersistTaps="always" numColumns={ this.state.numberOfColumns } data={ this.props.items } diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index b7ece7b095cc34..561e781cc35f79 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -2,7 +2,7 @@ /** * External dependencies */ -import { View } from 'react-native'; +import { View, Dimensions } from 'react-native'; /** * WordPress dependencies @@ -13,6 +13,7 @@ import { InnerBlocks, withColors, } from '@wordpress/block-editor'; +import { withViewportMatch } from '@wordpress/viewport'; /** * Internal dependencies */ @@ -22,22 +23,48 @@ function ColumnEdit( { hasInnerBlocks, isSelected, getStylesFromColorScheme, + isParentSelected, + columnsCount, + isSmallScreen, + isLargeScreen, + isMediumScreen, } ) { - // TODO: make sure if column should reder placeholder if whole parent - // columns block is not selected + const columnContainerBaseWidth = styles[ 'column-container-base' ].maxWidth; + + const screenWidth = Dimensions.get( 'window' ).width; + const containerWidth = styles[ 'columns-container' ].maxWidth; + const minWidth = Math.min( screenWidth, containerWidth ); + + const getColumnsInRow = ( columnsNumber ) => { + if ( isSmallScreen ) { + return 1; + } + if ( isMediumScreen && ( ! isLargeScreen || minWidth <= containerWidth ) ) { + return 2; + } + return columnsNumber; + }; + + const columnsInRow = getColumnsInRow( columnsCount ); + const columnBaseWidth = columnContainerBaseWidth / columnsInRow; + console.log( columnBaseWidth, columnsInRow ); + if ( ! isSelected && ! hasInnerBlocks ) { return ( <View style={ [ getStylesFromColorScheme( styles.columnPlaceholder, styles.columnPlaceholderDark ), - ! hasInnerBlocks && { ...styles.marginVerticalDense, ...styles.marginHorizontalNone }, + ! isSmallScreen ? { width: columnBaseWidth - ( isParentSelected ? 24 : 32 ) } : {}, + { ...styles.marginVerticalDense, ...styles.marginHorizontalNone }, ] } /> ); } return ( - <InnerBlocks - renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } - /> + <View style={ ! isSmallScreen ? { width: columnBaseWidth - ( isParentSelected ? 12 : 0 ) } : {} } > + <InnerBlocks + renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } + /> + </View> ); } @@ -46,13 +73,32 @@ export default compose( [ withSelect( ( select, { clientId } ) => { const { getBlock, + getBlockParents, + getSelectedBlockClientId, } = select( 'core/block-editor' ); const block = getBlock( clientId ); + const selectedBlockClientId = getSelectedBlockClientId(); + const isSelected = selectedBlockClientId === clientId; + + const parents = getBlockParents( clientId, true ); + const parentId = parents[ 0 ] || ''; + + const parentBlock = getBlock( parentId ); + const columnsCount = parentBlock && parentBlock.innerBlocks.length; + + console.log( parentBlock ); + + const isParentSelected = selectedBlockClientId && selectedBlockClientId === parentId; + return { hasInnerBlocks: !! ( block && block.innerBlocks.length ), + isParentSelected, + isSelected, + columnsCount, }; } ), + withViewportMatch( { isSmallScreen: '< small', isLargeScreen: '>= large', isMediumScreen: '>= small' } ), withPreferredColorScheme, ] )( ColumnEdit ); diff --git a/packages/block-library/src/column/editor.native.scss b/packages/block-library/src/column/editor.native.scss index fe84f0d5689214..06194505ee51c0 100644 --- a/packages/block-library/src/column/editor.native.scss +++ b/packages/block-library/src/column/editor.native.scss @@ -22,3 +22,11 @@ margin-left: 0; margin-right: 0; } + +.columns-container { + max-width: $content-width; +} + +.column-container-base { + max-width: $content-width - 2 * ( $block-selected-margin + $block-selected-border-width ); +} diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 2a3a9e84377125..a648f39e85b554 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -70,8 +70,6 @@ function ColumnsEditContainer( { updateColumns, clientId, isSmallScreen, - isLargeScreen, - isMediumScreen, } ) { const { verticalAlignment } = attributes; @@ -81,19 +79,6 @@ function ColumnsEditContainer( { }; } ); - const getColumnsInRow = ( columnsCount ) => { - if ( isSmallScreen ) { - return 1; - } - if ( isMediumScreen && ! isLargeScreen ) { - return 2; - } - return columnsCount; - }; - - const columnsInRow = getColumnsInRow( count ); - const columnsWidth = `${ 100 / columnsInRow }%`; - return ( <> <InspectorControls> @@ -124,7 +109,6 @@ function ColumnsEditContainer( { </BlockControls> <InnerBlocks containerStyle={ ! isSmallScreen ? styles.columnsContainer : undefined } - itemStyle={ { width: columnsWidth } } allowedBlocks={ ALLOWED_BLOCKS } /> </> @@ -248,6 +232,6 @@ const ColumnsEdit = ( props ) => { }; export default compose( [ - withViewportMatch( { isSmallScreen: '< small', isLargeScreen: '>= large', isMediumScreen: '>= small' } ), + withViewportMatch( { isSmallScreen: '< small' } ), withPreferredColorScheme, ] )( ColumnsEdit ); diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js index a3755f4ecd42e8..dec45eef16cd18 100644 --- a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js +++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js @@ -2,7 +2,7 @@ * External dependencies */ import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; -import { FlatList, View } from 'react-native'; +import { FlatList } from 'react-native'; export const KeyboardAwareFlatList = ( { extraScrollHeight, @@ -11,7 +11,6 @@ export const KeyboardAwareFlatList = ( { autoScroll, scrollViewStyle, containerStyle, - itemStyle, ...listProps } ) => ( <KeyboardAwareScrollView @@ -49,14 +48,7 @@ export const KeyboardAwareFlatList = ( { } } > <FlatList { ...listProps } - style={ containerStyle ? containerStyle : {} } - CellRendererComponent={ containerStyle && ( ( props ) => ( - <View - { ...props } - style={ itemStyle } - /> - ) ) - } + style={ containerStyle ? containerStyle : undefined } /> </KeyboardAwareScrollView> ); From 8a75c23f2d392a3ef3a688d90087e99acbb3733c Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 16 Jan 2020 14:29:04 +0100 Subject: [PATCH 088/183] handle size on selection --- packages/block-library/src/column/edit.native.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 561e781cc35f79..cba59130b1cd04 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -24,6 +24,7 @@ function ColumnEdit( { isSelected, getStylesFromColorScheme, isParentSelected, + isDescendantOfParentSelected, columnsCount, isSmallScreen, isLargeScreen, @@ -53,14 +54,14 @@ function ColumnEdit( { return ( <View style={ [ getStylesFromColorScheme( styles.columnPlaceholder, styles.columnPlaceholderDark ), - ! isSmallScreen ? { width: columnBaseWidth - ( isParentSelected ? 24 : 32 ) } : {}, + ! isSmallScreen ? { width: columnBaseWidth - ( isParentSelected ? 24 : isDescendantOfParentSelected ? 28 : 32 ) } : {}, { ...styles.marginVerticalDense, ...styles.marginHorizontalNone }, ] } /> ); } return ( - <View style={ ! isSmallScreen ? { width: columnBaseWidth - ( isParentSelected ? 12 : 0 ) } : {} } > + <View style={ ! isSmallScreen ? { maxWidth: columnBaseWidth, width: columnBaseWidth - ( isParentSelected ? 12 : isSelected ? ! hasInnerBlocks ? 28 : 4 : isDescendantOfParentSelected ? -4 : 0 ) } : {} } > <InnerBlocks renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } /> @@ -92,11 +93,15 @@ export default compose( [ const isParentSelected = selectedBlockClientId && selectedBlockClientId === parentId; + const selectedParents = selectedBlockClientId ? getBlockParents( selectedBlockClientId ) : []; + const isDescendantOfParentSelected = selectedParents.includes( parentId ); + return { hasInnerBlocks: !! ( block && block.innerBlocks.length ), isParentSelected, isSelected, columnsCount, + isDescendantOfParentSelected, }; } ), withViewportMatch( { isSmallScreen: '< small', isLargeScreen: '>= large', isMediumScreen: '>= small' } ), From 9e3b6c8ab7dd13a83aab379bd592140d104384d4 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 17 Jan 2020 11:45:25 +0100 Subject: [PATCH 089/183] logic adjustment --- .../block-library/src/column/edit.native.js | 23 ++++++++-------- packages/block-library/src/columns/block.json | 3 +++ .../block-library/src/columns/edit.native.js | 27 +++++++++++++------ 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index cba59130b1cd04..6aa8f0e2a2d40f 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -2,7 +2,7 @@ /** * External dependencies */ -import { View, Dimensions } from 'react-native'; +import { View } from 'react-native'; /** * WordPress dependencies @@ -26,35 +26,36 @@ function ColumnEdit( { isParentSelected, isDescendantOfParentSelected, columnsCount, + columnsContainerWidth, isSmallScreen, isLargeScreen, isMediumScreen, } ) { const columnContainerBaseWidth = styles[ 'column-container-base' ].maxWidth; - const screenWidth = Dimensions.get( 'window' ).width; - const containerWidth = styles[ 'columns-container' ].maxWidth; - const minWidth = Math.min( screenWidth, containerWidth ); + const containerWidth = columnsContainerWidth || styles[ 'columns-container' ].maxWidth; + + const containerMaxWidth = styles[ 'columns-container' ].maxWidth; + const minWidth = Math.min( containerWidth, columnContainerBaseWidth ); const getColumnsInRow = ( columnsNumber ) => { - if ( isSmallScreen ) { + if ( isSmallScreen || minWidth < 480 ) { return 1; } - if ( isMediumScreen && ( ! isLargeScreen || minWidth <= containerWidth ) ) { + if ( isMediumScreen && ( ! isLargeScreen || minWidth <= containerMaxWidth ) ) { return 2; } return columnsNumber; }; const columnsInRow = getColumnsInRow( columnsCount ); - const columnBaseWidth = columnContainerBaseWidth / columnsInRow; - console.log( columnBaseWidth, columnsInRow ); + const columnBaseWidth = minWidth / columnsInRow; if ( ! isSelected && ! hasInnerBlocks ) { return ( <View style={ [ getStylesFromColorScheme( styles.columnPlaceholder, styles.columnPlaceholderDark ), - ! isSmallScreen ? { width: columnBaseWidth - ( isParentSelected ? 24 : isDescendantOfParentSelected ? 28 : 32 ) } : {}, + ! isSmallScreen ? { width: columnBaseWidth - ( isParentSelected ? 24 : isDescendantOfParentSelected ? 28 : columnsInRow === 1 ? 12 : 32 ) } : {}, { ...styles.marginVerticalDense, ...styles.marginHorizontalNone }, ] } /> ); @@ -88,8 +89,7 @@ export default compose( [ const parentBlock = getBlock( parentId ); const columnsCount = parentBlock && parentBlock.innerBlocks.length; - - console.log( parentBlock ); + const columnsContainerWidth = parentBlock && parentBlock.attributes.width; const isParentSelected = selectedBlockClientId && selectedBlockClientId === parentId; @@ -101,6 +101,7 @@ export default compose( [ isParentSelected, isSelected, columnsCount, + columnsContainerWidth, isDescendantOfParentSelected, }; } ), diff --git a/packages/block-library/src/columns/block.json b/packages/block-library/src/columns/block.json index 3c22ca71fba621..5da84624737b0b 100644 --- a/packages/block-library/src/columns/block.json +++ b/packages/block-library/src/columns/block.json @@ -4,6 +4,9 @@ "attributes": { "verticalAlignment": { "type": "string" + }, + "width": { + "type": "number" } } } diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index a648f39e85b554..8e39a3a29e5706 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -18,14 +18,13 @@ import { InspectorControls, InnerBlocks, BlockControls, - // __experimentalBlockPatternPicker, BlockVerticalAlignmentToolbar, } from '@wordpress/block-editor'; import { withDispatch, - // useDispatch, useSelect, } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { createBlock } from '@wordpress/blocks'; import { withViewportMatch } from '@wordpress/viewport'; @@ -66,12 +65,13 @@ const MAX_COLUMNS_NUMBER = 6; function ColumnsEditContainer( { attributes, + setAttributes, updateAlignment, updateColumns, clientId, isSmallScreen, } ) { - const { verticalAlignment } = attributes; + const { verticalAlignment, width } = attributes; const { count } = useSelect( ( select ) => { return { @@ -79,6 +79,10 @@ function ColumnsEditContainer( { }; } ); + useEffect( () => { + updateColumns( count, DEFAULT_COLUMNS ); + }, [] ); + return ( <> <InspectorControls> @@ -99,7 +103,7 @@ function ColumnsEditContainer( { <ToolbarButton title={ __( 'ColumnsButton' ) } icon={ <Icon width={ 20 } height={ 20 } /> } - onClick={ () => console.log( 'click' ) } + onClick={ () => {} } /> </Toolbar> <BlockVerticalAlignmentToolbar @@ -107,10 +111,17 @@ function ColumnsEditContainer( { value={ verticalAlignment } /> </BlockControls> - <InnerBlocks - containerStyle={ ! isSmallScreen ? styles.columnsContainer : undefined } - allowedBlocks={ ALLOWED_BLOCKS } - /> + <View onLayout={ ( event ) => { + const { width: newWidth } = event.nativeEvent.layout; + if ( newWidth !== width ) { + setAttributes( { width: newWidth } ); + } + } }> + <InnerBlocks + containerStyle={ ! isSmallScreen ? styles.columnsContainer : undefined } + allowedBlocks={ ALLOWED_BLOCKS } + /> + </View> </> ); } From d57c3ef5ec1e9540ca54bdecc8abd15fd921c6b9 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Mon, 20 Jan 2020 10:12:25 +0100 Subject: [PATCH 090/183] add logic for android --- .../src/mobile/keyboard-aware-flat-list/index.android.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js index 0774c845ebcf54..717079cd613fdf 100644 --- a/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js +++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js @@ -8,9 +8,9 @@ import { FlatList } from 'react-native'; */ import KeyboardAvoidingView from '../keyboard-avoiding-view'; -export const KeyboardAwareFlatList = ( props ) => ( +export const KeyboardAwareFlatList = ( { containerStyle, ...props } ) => ( <KeyboardAvoidingView style={ { flex: 1 } }> - <FlatList { ...props } /> + <FlatList style={ containerStyle ? containerStyle : undefined } { ...props } /> </KeyboardAvoidingView> ); From 9d9f7780fed49a984e91537951bd02e7f99023d9 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Mon, 20 Jan 2020 10:14:21 +0100 Subject: [PATCH 091/183] add vertical alingment --- packages/block-library/src/columns/edit.native.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 8e39a3a29e5706..cc7202e340b4fb 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -62,6 +62,7 @@ const ALLOWED_BLOCKS = [ 'core/column' ]; const DEFAULT_COLUMNS = 2; const MIN_COLUMNS_NUMBER = 2; const MAX_COLUMNS_NUMBER = 6; +const DEFAULT_ALIGNMENT = 'top'; function ColumnsEditContainer( { attributes, @@ -71,7 +72,7 @@ function ColumnsEditContainer( { clientId, isSmallScreen, } ) { - const { verticalAlignment, width } = attributes; + const { verticalAlignment = DEFAULT_ALIGNMENT, width } = attributes; const { count } = useSelect( ( select ) => { return { @@ -80,7 +81,7 @@ function ColumnsEditContainer( { } ); useEffect( () => { - updateColumns( count, DEFAULT_COLUMNS ); + updateColumns( count, Math.min( MAX_COLUMNS_NUMBER, count || DEFAULT_COLUMNS ) ); }, [] ); return ( @@ -109,6 +110,7 @@ function ColumnsEditContainer( { <BlockVerticalAlignmentToolbar onChange={ updateAlignment } value={ verticalAlignment } + isCollapsed={ false } /> </BlockControls> <View onLayout={ ( event ) => { @@ -118,7 +120,7 @@ function ColumnsEditContainer( { } } }> <InnerBlocks - containerStyle={ ! isSmallScreen ? styles.columnsContainer : undefined } + containerStyle={ ! isSmallScreen ? { ...styles.columnsContainer, alignItems: verticalAlignment !== 'center' ? `flex-${ verticalAlignment === 'top' ? 'start' : 'end' }` : 'center' } : undefined } allowedBlocks={ ALLOWED_BLOCKS } /> </View> From b2f9ce5cfd1e7ec8d75699cfbc04f08d731cb94f Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Mon, 20 Jan 2020 10:16:04 +0100 Subject: [PATCH 092/183] show appender when column is selected --- packages/block-library/src/column/edit.native.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 6aa8f0e2a2d40f..27854f219ac2ad 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -54,10 +54,12 @@ function ColumnEdit( { if ( ! isSelected && ! hasInnerBlocks ) { return ( <View style={ [ - getStylesFromColorScheme( styles.columnPlaceholder, styles.columnPlaceholderDark ), + ! isParentSelected && getStylesFromColorScheme( styles.columnPlaceholder, styles.columnPlaceholderDark ), ! isSmallScreen ? { width: columnBaseWidth - ( isParentSelected ? 24 : isDescendantOfParentSelected ? 28 : columnsInRow === 1 ? 12 : 32 ) } : {}, { ...styles.marginVerticalDense, ...styles.marginHorizontalNone }, - ] } /> + ] } > + { isParentSelected && <InnerBlocks.ButtonBlockAppender /> } + </View> ); } From 4db7a6c2208dff6c8adf55956fd1d6cb37f00ca4 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Mon, 20 Jan 2020 13:15:05 +0100 Subject: [PATCH 093/183] initial CodeRefactor --- .../src/components/inserter/menu.native.js | 2 +- .../block-library/src/columns/edit.native.js | 26 ++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/inserter/menu.native.js b/packages/block-editor/src/components/inserter/menu.native.js index 81e027dc40bd04..782017a527d88f 100644 --- a/packages/block-editor/src/components/inserter/menu.native.js +++ b/packages/block-editor/src/components/inserter/menu.native.js @@ -78,7 +78,7 @@ export class InserterMenu extends Component { <FlatList onLayout={ this.onLayout } scrollEnabled={ false } - // key={ `InserterUI-${ this.state.numberOfColumns }` } //re-render when numberOfColumns changes + key={ `InserterUI-${ this.state.numberOfColumns }` } //re-render when numberOfColumns changes keyboardShouldPersistTaps="always" numColumns={ this.state.numberOfColumns } data={ this.props.items } diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index cc7202e340b4fb..80280aa05cf74b 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -84,6 +84,30 @@ function ColumnsEditContainer( { updateColumns( count, Math.min( MAX_COLUMNS_NUMBER, count || DEFAULT_COLUMNS ) ); }, [] ); + const getVerticalAlignmentRemap = () => { + let alingment; + + switch ( verticalAlignment ) { + case 'center': + alingment = 'center'; + break; + + case 'bottom': + alingment = 'flex-end'; + break; + + case 'top': + default: + alingment = 'flex-start'; + } + return { alignItems: alingment }; + }; + + const containerStyle = { + ...styles.columnsContainer, + ...getVerticalAlignmentRemap(), + }; + return ( <> <InspectorControls> @@ -120,7 +144,7 @@ function ColumnsEditContainer( { } } }> <InnerBlocks - containerStyle={ ! isSmallScreen ? { ...styles.columnsContainer, alignItems: verticalAlignment !== 'center' ? `flex-${ verticalAlignment === 'top' ? 'start' : 'end' }` : 'center' } : undefined } + containerStyle={ ! isSmallScreen ? containerStyle : undefined } allowedBlocks={ ALLOWED_BLOCKS } /> </View> From cc26270eac99f664440863b70ed6bbbb7486f274 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Tue, 21 Jan 2020 10:11:45 +0100 Subject: [PATCH 094/183] refactor logic --- .../block-library/src/column/edit.native.js | 24 ++++------- .../block-library/src/columns/edit.native.js | 42 ++----------------- 2 files changed, 13 insertions(+), 53 deletions(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 27854f219ac2ad..ee3b0ca70a42f1 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -9,10 +9,7 @@ import { View } from 'react-native'; */ import { withSelect } from '@wordpress/data'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; -import { - InnerBlocks, - withColors, -} from '@wordpress/block-editor'; +import { InnerBlocks } from '@wordpress/block-editor'; import { withViewportMatch } from '@wordpress/viewport'; /** * Internal dependencies @@ -27,22 +24,20 @@ function ColumnEdit( { isDescendantOfParentSelected, columnsCount, columnsContainerWidth, - isSmallScreen, - isLargeScreen, - isMediumScreen, + isMobile, } ) { const columnContainerBaseWidth = styles[ 'column-container-base' ].maxWidth; + const containerMaxWidth = styles[ 'columns-container' ].maxWidth; - const containerWidth = columnsContainerWidth || styles[ 'columns-container' ].maxWidth; + const containerWidth = columnsContainerWidth || containerMaxWidth; - const containerMaxWidth = styles[ 'columns-container' ].maxWidth; const minWidth = Math.min( containerWidth, columnContainerBaseWidth ); const getColumnsInRow = ( columnsNumber ) => { - if ( isSmallScreen || minWidth < 480 ) { + if ( minWidth < 480 ) { return 1; } - if ( isMediumScreen && ( ! isLargeScreen || minWidth <= containerMaxWidth ) ) { + if ( minWidth >= 480 && minWidth < 768 ) { return 2; } return columnsNumber; @@ -55,7 +50,7 @@ function ColumnEdit( { return ( <View style={ [ ! isParentSelected && getStylesFromColorScheme( styles.columnPlaceholder, styles.columnPlaceholderDark ), - ! isSmallScreen ? { width: columnBaseWidth - ( isParentSelected ? 24 : isDescendantOfParentSelected ? 28 : columnsInRow === 1 ? 12 : 32 ) } : {}, + ! isMobile ? { width: columnBaseWidth - ( isParentSelected ? 24 : isDescendantOfParentSelected ? 28 : columnsInRow === 1 ? 12 : 32 ) } : {}, { ...styles.marginVerticalDense, ...styles.marginHorizontalNone }, ] } > { isParentSelected && <InnerBlocks.ButtonBlockAppender /> } @@ -64,7 +59,7 @@ function ColumnEdit( { } return ( - <View style={ ! isSmallScreen ? { maxWidth: columnBaseWidth, width: columnBaseWidth - ( isParentSelected ? 12 : isSelected ? ! hasInnerBlocks ? 28 : 4 : isDescendantOfParentSelected ? -4 : 0 ) } : {} } > + <View style={ ! isMobile ? { maxWidth: columnBaseWidth, width: columnBaseWidth - ( isParentSelected ? 12 : isSelected ? ! hasInnerBlocks ? 28 : 4 : isDescendantOfParentSelected ? -4 : 0 ) } : {} } > <InnerBlocks renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } /> @@ -73,7 +68,6 @@ function ColumnEdit( { } export default compose( [ - withColors( 'backgroundColor' ), withSelect( ( select, { clientId } ) => { const { getBlock, @@ -107,6 +101,6 @@ export default compose( [ isDescendantOfParentSelected, }; } ), - withViewportMatch( { isSmallScreen: '< small', isLargeScreen: '>= large', isMediumScreen: '>= small' } ), + withViewportMatch( { isMobile: '< mobile' } ), withPreferredColorScheme, ] )( ColumnEdit ); diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 80280aa05cf74b..658ae1df11b5b3 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -32,13 +32,6 @@ import { withViewportMatch } from '@wordpress/viewport'; /** * Internal dependencies */ -import { - hasExplicitColumnWidths, - getMappedColumnWidths, - getRedistributedColumnWidths, - toWidthPrecision, -} from './utils'; - import styles from './editor.scss'; import Icon from './icon'; @@ -70,7 +63,7 @@ function ColumnsEditContainer( { updateAlignment, updateColumns, clientId, - isSmallScreen, + isMobile, } ) { const { verticalAlignment = DEFAULT_ALIGNMENT, width } = attributes; @@ -144,7 +137,7 @@ function ColumnsEditContainer( { } } }> <InnerBlocks - containerStyle={ ! isSmallScreen ? containerStyle : undefined } + containerStyle={ ! isMobile ? containerStyle : undefined } allowedBlocks={ ALLOWED_BLOCKS } /> </View> @@ -190,29 +183,11 @@ const ColumnsEditContainerWrapper = withDispatch( ( dispatch, ownProps, registry const { getBlocks } = registry.select( 'core/block-editor' ); let innerBlocks = getBlocks( clientId ); - const hasExplicitWidths = hasExplicitColumnWidths( innerBlocks ); // Redistribute available width for existing inner blocks. const isAddingColumn = newColumns > previousColumns; - if ( isAddingColumn && hasExplicitWidths ) { - // If adding a new column, assign width to the new column equal to - // as if it were `1 / columns` of the total available space. - const newColumnWidth = toWidthPrecision( 100 / newColumns ); - - // Redistribute in consideration of pending block insertion as - // constraining the available working width. - const widths = getRedistributedColumnWidths( innerBlocks, 100 - newColumnWidth ); - - innerBlocks = [ - ...getMappedColumnWidths( innerBlocks, widths ), - ...times( newColumns - previousColumns, () => { - return createBlock( 'core/column', { - width: newColumnWidth, - } ); - } ), - ]; - } else if ( isAddingColumn ) { + if ( isAddingColumn ) { innerBlocks = [ ...innerBlocks, ...times( newColumns - previousColumns, () => { @@ -222,13 +197,6 @@ const ColumnsEditContainerWrapper = withDispatch( ( dispatch, ownProps, registry } else { // The removed column will be the last of the inner blocks. innerBlocks = dropRight( innerBlocks, previousColumns - newColumns ); - - if ( hasExplicitWidths ) { - // Redistribute as if block is already removed. - const widths = getRedistributedColumnWidths( innerBlocks, 100 ); - - innerBlocks = getMappedColumnWidths( innerBlocks, widths ); - } } replaceInnerBlocks( clientId, innerBlocks, false ); @@ -237,7 +205,6 @@ const ColumnsEditContainerWrapper = withDispatch( ( dispatch, ownProps, registry const ColumnsEdit = ( props ) => { const { clientId, name, isSelected, getStylesFromColorScheme } = props; - // const { blockType, defaultPattern, hasInnerBlocks, patterns } = useSelect( ( select ) => { const { hasInnerBlocks } = useSelect( ( select ) => { const { __experimentalGetBlockPatterns, @@ -253,7 +220,6 @@ const ColumnsEdit = ( props ) => { }; }, [ clientId, name ] ); - // TODO: make sure if columns should reder placeholder if block is not selected if ( ! isSelected && ! hasInnerBlocks ) { return ( <View style={ [ @@ -269,6 +235,6 @@ const ColumnsEdit = ( props ) => { }; export default compose( [ - withViewportMatch( { isSmallScreen: '< small' } ), + withViewportMatch( { isMobile: '< mobile' } ), withPreferredColorScheme, ] )( ColumnsEdit ); From 4341ae8215ff6510f9ca192ab2c58ffb96952d35 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Tue, 21 Jan 2020 10:41:15 +0100 Subject: [PATCH 095/183] refactor style logic --- .../button-block-appender/index.native.js | 2 +- .../block-library/src/column/edit.native.js | 42 ++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/button-block-appender/index.native.js b/packages/block-editor/src/components/button-block-appender/index.native.js index 4e9de907eab9c3..2a3f6ef2ea9fb2 100644 --- a/packages/block-editor/src/components/button-block-appender/index.native.js +++ b/packages/block-editor/src/components/button-block-appender/index.native.js @@ -24,7 +24,7 @@ function ButtonBlockAppender( { rootClientId, getStylesFromColorScheme, showSepa <Inserter rootClientId={ rootClientId } renderToggle={ ( { onToggle, disabled, isOpen } ) => ( - ! isOpen && <Button + <Button onClick={ onToggle } aria-expanded={ isOpen } disabled={ disabled } diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index ee3b0ca70a42f1..8f3dfad2f7e098 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -46,11 +46,49 @@ function ColumnEdit( { const columnsInRow = getColumnsInRow( columnsCount ); const columnBaseWidth = minWidth / columnsInRow; + const applyColumnPlaceholderStyle = () => { + if ( isMobile ) { + return; + } + + let width = columnBaseWidth; + + if ( isParentSelected ) { + width -= 24; + } else if ( isDescendantOfParentSelected ) { + width -= 28; + } else { + width -= ( columnsInRow === 1 ? 12 : 32 ); + } + + return { width }; + // return { width: columnBaseWidth - ( isParentSelected ? 24 : isDescendantOfParentSelected ? 28 : columnsInRow === 1 ? 12 : 32 ) } + }; + + const applyColumnBlockStyle = () => { + if ( isMobile ) { + return; + } + + let width = columnBaseWidth; + + if ( isParentSelected ) { + width -= 12; + } else if ( isSelected ) { + width -= ( ! hasInnerBlocks ? 28 : 4 ); + } else if ( isDescendantOfParentSelected ) { + width += 4; + } + + return { width }; + // return { width: columnBaseWidth - ( isParentSelected ? 12 : isSelected ? ! hasInnerBlocks ? 28 : 4 : isDescendantOfParentSelected ? -4 : 0 ) } + }; + if ( ! isSelected && ! hasInnerBlocks ) { return ( <View style={ [ ! isParentSelected && getStylesFromColorScheme( styles.columnPlaceholder, styles.columnPlaceholderDark ), - ! isMobile ? { width: columnBaseWidth - ( isParentSelected ? 24 : isDescendantOfParentSelected ? 28 : columnsInRow === 1 ? 12 : 32 ) } : {}, + applyColumnPlaceholderStyle(), { ...styles.marginVerticalDense, ...styles.marginHorizontalNone }, ] } > { isParentSelected && <InnerBlocks.ButtonBlockAppender /> } @@ -59,7 +97,7 @@ function ColumnEdit( { } return ( - <View style={ ! isMobile ? { maxWidth: columnBaseWidth, width: columnBaseWidth - ( isParentSelected ? 12 : isSelected ? ! hasInnerBlocks ? 28 : 4 : isDescendantOfParentSelected ? -4 : 0 ) } : {} } > + <View style={ applyColumnBlockStyle() } > <InnerBlocks renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } /> From 616ad59ebe3cb2f78769685518b421dbacf89eb9 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 23 Jan 2020 13:33:36 +0100 Subject: [PATCH 096/183] add verticalALignment separately for Column --- .../src/components/block-list/index.native.js | 39 ++++++++++++++++++- .../block-library/src/column/edit.native.js | 33 +++++++++++++--- .../block-library/src/columns/edit.native.js | 29 +------------- .../src/columns/editor.native.scss | 2 + .../keyboard-aware-flat-list/index.ios.js | 4 +- .../readable-content-view/index.native.js | 4 +- 6 files changed, 73 insertions(+), 38 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index c973ebb788e651..7dfb8e2887a5b2 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -79,6 +79,7 @@ export class BlockList extends Component { isReadOnly, isRootList, containerStyle, + columnStyle, } = this.props; return ( @@ -95,6 +96,7 @@ export class BlockList extends Component { keyboardShouldPersistTaps="always" scrollViewStyle={ { flex: isRootList ? 1 : 0 } } containerStyle={ containerStyle } + columnStyle={ columnStyle } data={ blockClientIds } extraData={ [ isFullyBordered ] } keyExtractor={ identity } @@ -133,11 +135,40 @@ export class BlockList extends Component { shouldShowBlockAtIndex, shouldShowInsertionPointBefore, shouldShowInsertionPointAfter, + containerStyle, + getBlockAttributes, } = this.props; + const getVerticalAlignmentRemap = ( newAlignment ) => { + let alingment; + switch ( newAlignment ) { + case 'center': + alingment = 'center'; + break; + + case 'bottom': + alingment = 'flex-end'; + break; + + case 'top': + alingment = 'flex-start'; + break; + + default: + alingment = newAlignment; + } + return { justifyContent: alingment }; + }; + + const attributes = getBlockAttributes( clientId ); + let columnContainerStyle = {}; + if ( attributes ) { + columnContainerStyle = getVerticalAlignmentRemap( attributes.verticalAlignment ); + } + return ( - <ReadableContentView> - <View pointerEvents={ isReadOnly ? 'box-only' : 'auto' }> + <ReadableContentView style={ containerStyle ? columnContainerStyle : undefined } > + <View pointerEvents={ isReadOnly ? 'box-only' : 'auto' } > { shouldShowInsertionPointBefore( clientId ) && <BlockInsertionPoint /> } { shouldShowBlockAtIndex( index ) && ( <BlockListBlock @@ -180,6 +211,7 @@ export default compose( [ isBlockInsertionPointVisible, getSelectedBlock, getSettings, + __unstableGetBlockWithoutInnerBlocks, } = select( 'core/block-editor' ); const { @@ -232,6 +264,8 @@ export default compose( [ const isReadOnly = getSettings().readOnly; + const getBlockAttributes = ( clientId ) => ( __unstableGetBlockWithoutInnerBlocks( clientId ) || {} ).attributes; + return { blockClientIds, blockCount: getBlockCount( rootClientId ), @@ -242,6 +276,7 @@ export default compose( [ selectedBlockClientId, isReadOnly, isRootList: rootClientId === undefined, + getBlockAttributes, }; } ), withDispatch( ( dispatch ) => { diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 8f3dfad2f7e098..01393834dd646b 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -9,7 +9,11 @@ import { View } from 'react-native'; */ import { withSelect } from '@wordpress/data'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; -import { InnerBlocks } from '@wordpress/block-editor'; +import { + InnerBlocks, + BlockControls, + BlockVerticalAlignmentToolbar, +} from '@wordpress/block-editor'; import { withViewportMatch } from '@wordpress/viewport'; /** * Internal dependencies @@ -17,6 +21,8 @@ import { withViewportMatch } from '@wordpress/viewport'; import styles from './editor.scss'; function ColumnEdit( { + attributes, + setAttributes, hasInnerBlocks, isSelected, getStylesFromColorScheme, @@ -26,6 +32,8 @@ function ColumnEdit( { columnsContainerWidth, isMobile, } ) { + const { verticalAlignment } = attributes; + const columnContainerBaseWidth = styles[ 'column-container-base' ].maxWidth; const containerMaxWidth = styles[ 'columns-container' ].maxWidth; @@ -84,6 +92,10 @@ function ColumnEdit( { // return { width: columnBaseWidth - ( isParentSelected ? 12 : isSelected ? ! hasInnerBlocks ? 28 : 4 : isDescendantOfParentSelected ? -4 : 0 ) } }; + const updateAlignment = ( alignment ) => { + setAttributes( { verticalAlignment: alignment } ); + }; + if ( ! isSelected && ! hasInnerBlocks ) { return ( <View style={ [ @@ -97,11 +109,20 @@ function ColumnEdit( { } return ( - <View style={ applyColumnBlockStyle() } > - <InnerBlocks - renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } - /> - </View> + <> + <BlockControls> + <BlockVerticalAlignmentToolbar + onChange={ updateAlignment } + value={ verticalAlignment } + isCollapsed={ false } + /> + </BlockControls> + <View style={ applyColumnBlockStyle() } > + <InnerBlocks + renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } + /> + </View> + </> ); } diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 658ae1df11b5b3..ad85136b264f9e 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -55,7 +55,6 @@ const ALLOWED_BLOCKS = [ 'core/column' ]; const DEFAULT_COLUMNS = 2; const MIN_COLUMNS_NUMBER = 2; const MAX_COLUMNS_NUMBER = 6; -const DEFAULT_ALIGNMENT = 'top'; function ColumnsEditContainer( { attributes, @@ -65,7 +64,7 @@ function ColumnsEditContainer( { clientId, isMobile, } ) { - const { verticalAlignment = DEFAULT_ALIGNMENT, width } = attributes; + const { verticalAlignment, width } = attributes; const { count } = useSelect( ( select ) => { return { @@ -77,30 +76,6 @@ function ColumnsEditContainer( { updateColumns( count, Math.min( MAX_COLUMNS_NUMBER, count || DEFAULT_COLUMNS ) ); }, [] ); - const getVerticalAlignmentRemap = () => { - let alingment; - - switch ( verticalAlignment ) { - case 'center': - alingment = 'center'; - break; - - case 'bottom': - alingment = 'flex-end'; - break; - - case 'top': - default: - alingment = 'flex-start'; - } - return { alignItems: alingment }; - }; - - const containerStyle = { - ...styles.columnsContainer, - ...getVerticalAlignmentRemap(), - }; - return ( <> <InspectorControls> @@ -137,7 +112,7 @@ function ColumnsEditContainer( { } } }> <InnerBlocks - containerStyle={ ! isMobile ? containerStyle : undefined } + containerStyle={ ! isMobile ? styles.columnsContainer : undefined } allowedBlocks={ ALLOWED_BLOCKS } /> </View> diff --git a/packages/block-library/src/columns/editor.native.scss b/packages/block-library/src/columns/editor.native.scss index ab8489aeace7b3..2ec3a7242aefc8 100644 --- a/packages/block-library/src/columns/editor.native.scss +++ b/packages/block-library/src/columns/editor.native.scss @@ -25,4 +25,6 @@ flex-direction: row; flex-wrap: wrap; justify-content: flex-start; + align-items: stretch; + max-width: $content-width; } diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js index dec45eef16cd18..ef9c2eb279a9c0 100644 --- a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js +++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js @@ -43,12 +43,14 @@ export const KeyboardAwareFlatList = ( { onKeyboardWillShow={ () => { this.keyboardWillShowIndicator = true; } } + scrollEnabled={ containerStyle ? false : true } onScroll={ ( event ) => { this.latestContentOffsetY = event.nativeEvent.contentOffset.y; } } > <FlatList { ...listProps } - style={ containerStyle ? containerStyle : undefined } + horizontal={ containerStyle ? true : false } + contentContainerStyle={ containerStyle ? containerStyle : undefined } /> </KeyboardAwareScrollView> ); diff --git a/packages/components/src/mobile/readable-content-view/index.native.js b/packages/components/src/mobile/readable-content-view/index.native.js index dbeb172ab04342..de71700bc18374 100644 --- a/packages/components/src/mobile/readable-content-view/index.native.js +++ b/packages/components/src/mobile/readable-content-view/index.native.js @@ -8,8 +8,8 @@ import { View, Dimensions } from 'react-native'; */ import styles from './style.scss'; -const ReadableContentView = ( { reversed, children } ) => ( - <View style={ styles.container } > +const ReadableContentView = ( { reversed, children, style } ) => ( + <View style={ [ styles.container, style ] } > <View style={ reversed ? styles.reversedCenteredContent : styles.centeredContent } > { children } </View> From eccbfe1dfa0bf9b5ce65108a13fb0035162bc726 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 23 Jan 2020 14:11:02 +0100 Subject: [PATCH 097/183] add Column icon on Toolbar when block is selected --- packages/block-library/src/column/edit.native.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 01393834dd646b..68ae6f0e383a9c 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -7,6 +7,7 @@ import { View } from 'react-native'; /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { withSelect } from '@wordpress/data'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { @@ -14,6 +15,10 @@ import { BlockControls, BlockVerticalAlignmentToolbar, } from '@wordpress/block-editor'; +import { + Toolbar, + ToolbarButton, +} from '@wordpress/components'; import { withViewportMatch } from '@wordpress/viewport'; /** * Internal dependencies @@ -111,6 +116,13 @@ function ColumnEdit( { return ( <> <BlockControls> + <Toolbar> + <ToolbarButton + title={ __( 'ColumnButton' ) } + icon="columns" + onClick={ () => {} } + /> + </Toolbar> <BlockVerticalAlignmentToolbar onChange={ updateAlignment } value={ verticalAlignment } From 33191444dbebe493bdd82406f80adf5eb4c93b6f Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 24 Jan 2020 12:17:15 +0100 Subject: [PATCH 098/183] fix appender to duplicate separator line --- .../block-editor/src/components/block-list/index.native.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 7dfb8e2887a5b2..6814a75fde8281 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -113,6 +113,7 @@ export class BlockList extends Component { <BlockListAppender rootClientId={ this.props.rootClientId } renderAppender={ this.props.renderAppender } + showSeparator /> </View> ) @@ -137,6 +138,8 @@ export class BlockList extends Component { shouldShowInsertionPointAfter, containerStyle, getBlockAttributes, + renderAppender, + blockClientIds, } = this.props; const getVerticalAlignmentRemap = ( newAlignment ) => { @@ -179,7 +182,7 @@ export class BlockList extends Component { onCaretVerticalPositionChange={ this.onCaretVerticalPositionChange } isSmallScreen={ ! this.props.isFullyBordered } /> ) } - { shouldShowInsertionPointAfter( clientId ) && <BlockInsertionPoint /> } + { ! ( renderAppender && blockClientIds.length > 0 ) && shouldShowInsertionPointAfter( clientId ) && <BlockInsertionPoint /> } </View> </ReadableContentView> ); @@ -223,7 +226,7 @@ export default compose( [ const insertionPoint = getBlockInsertionPoint(); const blockInsertionPointIsVisible = isBlockInsertionPointVisible(); const selectedBlock = getSelectedBlock(); - const hasInnerBlocks = selectedBlock && ( selectedBlock.name === getGroupingBlockName() || selectedBlock.name === 'core/columns' ); + const hasInnerBlocks = selectedBlock && ( selectedBlock.name === getGroupingBlockName() || selectedBlock.name === 'core/columns' || selectedBlock.name === 'core/column' ); const shouldShowInsertionPointBefore = ( clientId ) => { return ( blockInsertionPointIsVisible && From 1043d31dba83c534e1a47a60aee564d72313a55f Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 24 Jan 2020 12:47:04 +0100 Subject: [PATCH 099/183] distribute changes to android KeyboardAwareFlatList --- .../src/mobile/keyboard-aware-flat-list/index.android.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js index 717079cd613fdf..687b3013bc501d 100644 --- a/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js +++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js @@ -10,7 +10,12 @@ import KeyboardAvoidingView from '../keyboard-avoiding-view'; export const KeyboardAwareFlatList = ( { containerStyle, ...props } ) => ( <KeyboardAvoidingView style={ { flex: 1 } }> - <FlatList style={ containerStyle ? containerStyle : undefined } { ...props } /> + <FlatList + scrollEnabled={ containerStyle ? false : true } + horizontal={ containerStyle ? true : false } + contentContainerStyle={ containerStyle ? containerStyle : undefined } + { ...props } + /> </KeyboardAvoidingView> ); From 933ced89e937371788580c78f16eb9f223be1a01 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 24 Jan 2020 12:54:22 +0100 Subject: [PATCH 100/183] ColumnsEdit refactor --- packages/block-library/src/columns/edit.native.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index ad85136b264f9e..bf15c68a4ee4eb 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -179,21 +179,16 @@ const ColumnsEditContainerWrapper = withDispatch( ( dispatch, ownProps, registry } ) )( ColumnsEditContainer ); const ColumnsEdit = ( props ) => { - const { clientId, name, isSelected, getStylesFromColorScheme } = props; + const { clientId, isSelected, getStylesFromColorScheme } = props; const { hasInnerBlocks } = useSelect( ( select ) => { const { - __experimentalGetBlockPatterns, - getBlockType, - __experimentalGetDefaultBlockPattern, - } = select( 'core/blocks' ); + getBlocks, + } = select( 'core/block-editor' ); return { - blockType: getBlockType( name ), - defaultPattern: __experimentalGetDefaultBlockPattern( name ), - hasInnerBlocks: select( 'core/block-editor' ).getBlocks( clientId ).length > 0, - patterns: __experimentalGetBlockPatterns( name ), + hasInnerBlocks: getBlocks( clientId ).length > 0, }; - }, [ clientId, name ] ); + }, [ clientId ] ); if ( ! isSelected && ! hasInnerBlocks ) { return ( From 690959171f3c47d6976f4590772c7fbe38ec6bdf Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 24 Jan 2020 13:07:11 +0100 Subject: [PATCH 101/183] remove columnStyle prop --- packages/block-editor/src/components/block-list/index.native.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 6814a75fde8281..e3b6adef1c2227 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -79,7 +79,6 @@ export class BlockList extends Component { isReadOnly, isRootList, containerStyle, - columnStyle, } = this.props; return ( @@ -96,7 +95,6 @@ export class BlockList extends Component { keyboardShouldPersistTaps="always" scrollViewStyle={ { flex: isRootList ? 1 : 0 } } containerStyle={ containerStyle } - columnStyle={ columnStyle } data={ blockClientIds } extraData={ [ isFullyBordered ] } keyExtractor={ identity } From 32d8bbb3be367d6c9f58e5e8acb36813edd88d59 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 30 Jan 2020 13:40:52 +0100 Subject: [PATCH 102/183] fix merge issue --- .../block-editor/src/components/block-list/index.native.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 5e8e66fddfcf20..51597180232f28 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -136,8 +136,6 @@ export class BlockList extends Component { shouldShowInsertionPointAfter, containerStyle, getBlockAttributes, - renderAppender, - blockClientIds, } = this.props; const getVerticalAlignmentRemap = ( newAlignment ) => { @@ -216,8 +214,6 @@ export default compose( [ const blockClientIds = getBlockOrder( rootClientId ); const insertionPoint = getBlockInsertionPoint(); const blockInsertionPointIsVisible = isBlockInsertionPointVisible(); - const selectedBlock = getSelectedBlock(); - const hasInnerBlocks = selectedBlock && ( selectedBlock.name === getGroupingBlockName() || selectedBlock.name === 'core/columns' || selectedBlock.name === 'core/column' ); const shouldShowInsertionPointBefore = ( clientId ) => { return ( blockInsertionPointIsVisible && From 8fa514b6827b5c77521fac937acebdc0e7ab2171 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Mon, 3 Feb 2020 14:25:19 +0100 Subject: [PATCH 103/183] refactor Column Block style and passing width --- .../block-library/src/column/edit.native.js | 61 +++++++------------ .../src/column/editor.native.scss | 20 ++++++ .../block-library/src/columns/edit.native.js | 26 +++++--- .../src/columns/editor.native.scss | 1 + 4 files changed, 61 insertions(+), 47 deletions(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 68ae6f0e383a9c..606d6187dbac36 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -28,7 +28,7 @@ import styles from './editor.scss'; function ColumnEdit( { attributes, setAttributes, - hasInnerBlocks, + hasChildren, isSelected, getStylesFromColorScheme, isParentSelected, @@ -59,53 +59,37 @@ function ColumnEdit( { const columnsInRow = getColumnsInRow( columnsCount ); const columnBaseWidth = minWidth / columnsInRow; - const applyColumnPlaceholderStyle = () => { + const applyBlockStyle = ( placeholder = false ) => { if ( isMobile ) { return; } + + const pullWidth = ( name ) => ( styles[`column-${name}-margin`] || {} ).width let width = columnBaseWidth; if ( isParentSelected ) { - width -= 24; + width -= pullWidth( placeholder ? 'placeholder-selected' : 'parent-selected' ); + } else if ( isSelected && ! placeholder ) { + width -= ( ! hasChildren ? pullWidth('selected') : pullWidth('descendant-selected') ); } else if ( isDescendantOfParentSelected ) { - width -= 28; - } else { - width -= ( columnsInRow === 1 ? 12 : 32 ); + width += pullWidth( placeholder ? 'selected' : 'descendant-selected'); + } else if ( placeholder ) { + width -= ( columnsInRow === 1 ? pullWidth('parent-selected') : pullWidth('placeholder-multicol') ); } return { width }; - // return { width: columnBaseWidth - ( isParentSelected ? 24 : isDescendantOfParentSelected ? 28 : columnsInRow === 1 ? 12 : 32 ) } - }; - - const applyColumnBlockStyle = () => { - if ( isMobile ) { - return; - } - - let width = columnBaseWidth; - - if ( isParentSelected ) { - width -= 12; - } else if ( isSelected ) { - width -= ( ! hasInnerBlocks ? 28 : 4 ); - } else if ( isDescendantOfParentSelected ) { - width += 4; - } - - return { width }; - // return { width: columnBaseWidth - ( isParentSelected ? 12 : isSelected ? ! hasInnerBlocks ? 28 : 4 : isDescendantOfParentSelected ? -4 : 0 ) } }; const updateAlignment = ( alignment ) => { setAttributes( { verticalAlignment: alignment } ); }; - if ( ! isSelected && ! hasInnerBlocks ) { + if ( ! isSelected && ! hasChildren ) { return ( <View style={ [ ! isParentSelected && getStylesFromColorScheme( styles.columnPlaceholder, styles.columnPlaceholderDark ), - applyColumnPlaceholderStyle(), + applyBlockStyle( true ), { ...styles.marginVerticalDense, ...styles.marginHorizontalNone }, ] } > { isParentSelected && <InnerBlocks.ButtonBlockAppender /> } @@ -129,7 +113,7 @@ function ColumnEdit( { isCollapsed={ false } /> </BlockControls> - <View style={ applyColumnBlockStyle() } > + <View style={ applyBlockStyle() } > <InnerBlocks renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } /> @@ -141,22 +125,23 @@ function ColumnEdit( { export default compose( [ withSelect( ( select, { clientId } ) => { const { - getBlock, getBlockParents, + getBlockCount, + getBlockRootClientId, getSelectedBlockClientId, + getBlockListSettings, } = select( 'core/block-editor' ); - const block = getBlock( clientId ); - const selectedBlockClientId = getSelectedBlockClientId(); const isSelected = selectedBlockClientId === clientId; - const parents = getBlockParents( clientId, true ); - const parentId = parents[ 0 ] || ''; + const parentId = getBlockRootClientId( clientId ); + + const columnsContainerSettings = getBlockListSettings( parentId ); - const parentBlock = getBlock( parentId ); - const columnsCount = parentBlock && parentBlock.innerBlocks.length; - const columnsContainerWidth = parentBlock && parentBlock.attributes.width; + const columnsCount = getBlockCount( parentId ); + const hasChildren = getBlockCount( clientId ); + const columnsContainerWidth = columnsContainerSettings && columnsContainerSettings.width; const isParentSelected = selectedBlockClientId && selectedBlockClientId === parentId; @@ -164,7 +149,7 @@ export default compose( [ const isDescendantOfParentSelected = selectedParents.includes( parentId ); return { - hasInnerBlocks: !! ( block && block.innerBlocks.length ), + hasChildren, isParentSelected, isSelected, columnsCount, diff --git a/packages/block-library/src/column/editor.native.scss b/packages/block-library/src/column/editor.native.scss index 06194505ee51c0..790b8bf479b27d 100644 --- a/packages/block-library/src/column/editor.native.scss +++ b/packages/block-library/src/column/editor.native.scss @@ -30,3 +30,23 @@ .column-container-base { max-width: $content-width - 2 * ( $block-selected-margin + $block-selected-border-width ); } + +.column-placeholder-selected-margin { + width: 2 * $block-selected-to-content; +} + +.column-placeholder-multicol-margin { + width: 2 * $block-edge-to-content; +} + +.column-parent-selected-margin { + width: $block-selected-to-content; +} + +.column-selected-margin { + width: 2 * $block-padding; +} + +.column-descendant-selected-margin { + width: $block-selected-margin + $block-selected-border-width; +} diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index bf15c68a4ee4eb..505cf7da970a55 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -58,13 +58,15 @@ const MAX_COLUMNS_NUMBER = 6; function ColumnsEditContainer( { attributes, - setAttributes, + updateBlockSettings, + blockListSettings, updateAlignment, updateColumns, clientId, isMobile, } ) { - const { verticalAlignment, width } = attributes; + const { verticalAlignment } = attributes; + const { width } = blockListSettings; const { count } = useSelect( ( select ) => { return { @@ -108,7 +110,7 @@ function ColumnsEditContainer( { <View onLayout={ ( event ) => { const { width: newWidth } = event.nativeEvent.layout; if ( newWidth !== width ) { - setAttributes( { width: newWidth } ); + updateBlockSettings( { ...blockListSettings, width: newWidth } ); } } }> <InnerBlocks @@ -144,7 +146,11 @@ const ColumnsEditContainerWrapper = withDispatch( ( dispatch, ownProps, registry } ); } ); }, - + updateBlockSettings( settings ) { + const { clientId } = ownProps; + const { updateBlockListSettings } = dispatch( 'core/block-editor' ); + updateBlockListSettings( clientId, settings ); + }, /** * Updates the column count, including necessary revisions to child Column * blocks to grant required or redistribute available space. @@ -180,27 +186,29 @@ const ColumnsEditContainerWrapper = withDispatch( ( dispatch, ownProps, registry const ColumnsEdit = ( props ) => { const { clientId, isSelected, getStylesFromColorScheme } = props; - const { hasInnerBlocks } = useSelect( ( select ) => { + const { hasChildren, blockListSettings } = useSelect( ( select ) => { const { getBlocks, + getBlockListSettings, } = select( 'core/block-editor' ); return { - hasInnerBlocks: getBlocks( clientId ).length > 0, + hasChildren: getBlocks( clientId ).length > 0, + blockListSettings: getBlockListSettings( clientId ) || {}, }; }, [ clientId ] ); - if ( ! isSelected && ! hasInnerBlocks ) { + if ( ! isSelected && ! hasChildren ) { return ( <View style={ [ getStylesFromColorScheme( styles.columnsPlaceholder, styles.columnsPlaceholderDark ), - ! hasInnerBlocks && { ...styles.marginVerticalDense, ...styles.marginHorizontalNone }, + ! hasChildren && { ...styles.marginVerticalDense, ...styles.marginHorizontalNone }, ] } /> ); } return ( - <ColumnsEditContainerWrapper { ...props } /> + <ColumnsEditContainerWrapper blockListSettings={ blockListSettings } { ...props } /> ); }; diff --git a/packages/block-library/src/columns/editor.native.scss b/packages/block-library/src/columns/editor.native.scss index 2ec3a7242aefc8..4b59f9833b7d29 100644 --- a/packages/block-library/src/columns/editor.native.scss +++ b/packages/block-library/src/columns/editor.native.scss @@ -27,4 +27,5 @@ justify-content: flex-start; align-items: stretch; max-width: $content-width; + overflow: hidden; } From 28b4a8861f66a9e7106c59bd79d453aa05dd167f Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Mon, 3 Feb 2020 14:51:00 +0100 Subject: [PATCH 104/183] merge master --- .eslintrc.js | 125 +- babel.config.js | 5 +- bin/api-docs/are-readmes-unstaged.js | 29 +- bin/api-docs/packages.js | 11 +- bin/api-docs/update-readmes.js | 11 +- bin/commander.js | 747 ++++++++---- bin/generate-public-grammar.js | 50 +- bin/packages/build-worker.js | 64 +- bin/packages/build.js | 84 +- bin/packages/get-babel-config.js | 8 +- bin/packages/watch.js | 63 +- bin/process-git-diff.js | 5 +- docs/tool/are-data-files-unstaged.js | 33 +- docs/tool/index.js | 5 +- docs/tool/manifest.js | 8 +- docs/tool/packages.js | 28 +- docs/tool/update-data.js | 16 +- experimental-default-global-styles.json | 9 + lib/compat.php | 3 + lib/global-styles.php | 131 +++ lib/load.php | 1 + package-lock.json | 27 + package.json | 1 + packages/a11y/src/addContainer.js | 25 +- packages/a11y/src/index.js | 8 +- packages/a11y/src/index.native.js | 2 - packages/a11y/src/test/addContainer.test.js | 12 +- packages/a11y/src/test/index.test.js | 31 +- packages/annotations/src/block/index.js | 18 +- packages/annotations/src/format/annotation.js | 50 +- packages/annotations/src/format/index.js | 4 +- packages/annotations/src/index.js | 1 - packages/annotations/src/store/actions.js | 15 +- packages/annotations/src/store/reducer.js | 66 +- packages/annotations/src/store/selectors.js | 37 +- .../annotations/src/store/test/reducer.js | 97 +- packages/api-fetch/src/index.js | 63 +- .../src/middlewares/fetch-all-middleware.js | 19 +- packages/api-fetch/src/middlewares/http-v1.js | 6 +- .../api-fetch/src/middlewares/media-upload.js | 39 +- .../src/middlewares/namespace-endpoint.js | 2 +- .../api-fetch/src/middlewares/preloading.js | 38 +- .../api-fetch/src/middlewares/root-url.js | 5 +- .../middlewares/test/fetch-all-middleware.js | 6 +- .../src/middlewares/test/preloading.js | 17 +- .../src/middlewares/test/root-url.js | 4 +- .../src/middlewares/test/user-locale.js | 16 +- .../api-fetch/src/middlewares/user-locale.js | 10 +- packages/api-fetch/src/test/index.js | 165 +-- packages/api-fetch/src/utils/response.js | 33 +- packages/autop/src/index.js | 142 ++- packages/autop/src/test/index.test.js | 43 +- .../babel-plugin-import-jsx-pragma/index.js | 19 +- .../test/index.js | 17 +- packages/babel-plugin-makepot/src/index.js | 141 ++- packages/babel-plugin-makepot/test/index.js | 28 +- packages/babel-preset-default/index.js | 22 +- packages/babel-preset-default/test/index.js | 4 +- packages/base-styles/_mixins.scss | 2 +- packages/base-styles/_variables.scss | 2 + packages/blob/src/test/index.js | 4 +- .../src/components/block-ratings/index.js | 5 +- .../src/components/block-ratings/stars.js | 28 +- .../downloadable-block-author-info/index.js | 6 +- .../downloadable-block-header/index.js | 32 +- .../test/fixtures/index.js | 8 +- .../downloadable-block-header/test/index.js | 11 +- .../downloadable-block-info/index.js | 22 +- .../downloadable-block-list-item/index.js | 5 +- .../downloadable-blocks-list/index.js | 99 +- .../downloadable-blocks-panel/index.js | 40 +- .../index.js | 36 +- packages/block-directory/src/store/actions.js | 6 +- .../block-directory/src/store/controls.js | 44 +- packages/block-directory/src/store/reducer.js | 39 +- .../block-directory/src/store/resolvers.js | 18 +- .../src/store/test/fixtures/index.js | 3 +- .../block-directory/src/store/test/reducer.js | 12 +- .../src/store/test/selectors.js | 4 +- .../src/components/alignment-toolbar/index.js | 7 +- .../alignment-toolbar/test/index.js | 8 +- .../src/components/autocomplete/index.js | 9 +- .../src/components/block-actions/index.js | 24 +- .../block-alignment-toolbar/index.js | 41 +- .../block-alignment-toolbar/test/index.js | 12 +- .../src/components/block-breadcrumb/index.js | 21 +- .../components/block-caption/index.native.js | 14 +- .../src/components/block-card/index.js | 8 +- .../components/block-compare/block-view.js | 17 +- .../src/components/block-compare/index.js | 27 +- .../block-compare/test/block-view.js | 11 +- .../src/components/block-draggable/index.js | 61 +- .../src/components/block-drop-zone/index.js | 163 ++- .../src/components/block-edit/context.js | 46 +- .../src/components/block-edit/edit.js | 12 +- .../src/components/block-edit/edit.native.js | 4 +- .../src/components/block-edit/index.js | 39 +- .../src/components/block-edit/test/edit.js | 4 +- .../src/components/block-icon/index.js | 24 +- .../src/components/block-icon/index.native.js | 22 +- .../src/components/block-icon/test/index.js | 15 +- .../src/components/block-inspector/index.js | 66 +- .../components/block-list-appender/index.js | 13 +- .../block-list-appender/index.native.js | 17 +- .../src/components/block-list-footer/index.js | 4 +- .../src/components/block-list/block-html.js | 34 +- .../block-list/block-invalid-warning.js | 71 +- .../block-mobile-floating-toolbar.native.js | 5 +- .../components/block-list/block-popover.js | 56 +- .../src/components/block-list/block.js | 93 +- .../src/components/block-list/block.native.js | 158 ++- .../components/block-list/block.native.scss | 13 + .../src/components/block-list/breadcrumb.js | 44 +- .../block-list/breadcrumb.native.js | 49 +- .../src/components/block-list/index.js | 30 +- .../src/components/block-list/index.native.js | 110 +- .../components/block-list/insertion-point.js | 149 ++- .../block-list/insertion-point.native.js | 14 +- .../components/block-list/moving-animation.js | 84 +- .../src/components/block-list/nav-up-icon.js | 17 +- .../block-list/subdirectory-icon.js | 5 +- .../block-list/use-multi-selection.js | 159 +-- .../components/block-mobile-toolbar/index.js | 5 +- .../block-mobile-toolbar/index.native.js | 24 +- .../src/components/block-mover/icons.js | 27 +- .../src/components/block-mover/index.js | 114 +- .../components/block-mover/index.native.js | 58 +- .../block-mover/mover-description.js | 108 +- .../src/components/block-mover/test/index.js | 18 +- .../block-mover/test/mover-description.js | 253 +++-- .../components/block-navigation/dropdown.js | 36 +- .../src/components/block-navigation/index.js | 27 +- .../src/components/block-navigation/list.js | 39 +- .../components/block-pattern-picker/index.js | 67 -- .../src/components/block-preview/index.js | 77 +- .../block-selection-clearer/index.js | 10 +- .../block-convert-button.js | 5 +- .../block-html-convert-button.js | 13 +- .../block-settings-menu/block-mode-toggle.js | 27 +- .../block-settings-menu-first-item.js | 4 +- .../block-settings-menu-plugins-extension.js | 5 +- .../block-unknown-convert-button.js | 20 +- .../components/block-settings-menu/index.js | 50 +- .../block-settings/button.native.js | 5 +- .../block-settings/container.native.js | 15 +- .../src/components/block-styles/index.js | 57 +- .../src/components/block-styles/test/index.js | 40 +- .../src/components/block-switcher/index.js | 141 ++- .../block-switcher/multi-blocks-switcher.js | 23 +- .../components/block-switcher/test/index.js | 74 +- .../test/multi-blocks-switcher.js | 9 +- .../src/components/block-toolbar/index.js | 54 +- .../components/block-toolbar/index.native.js | 14 +- .../src/components/block-types-list/index.js | 55 +- .../block-variation-picker/index.js | 65 ++ .../style.scss | 10 +- .../block-vertical-alignment-toolbar/icons.js | 24 +- .../block-vertical-alignment-toolbar/index.js | 47 +- .../test/index.js | 15 +- .../components/button-block-appender/index.js | 41 +- .../button-block-appender/index.native.js | 19 +- .../src/components/caption/index.native.js | 6 +- .../color-palette/with-color-context.js | 29 +- .../components/colors-gradients/control.js | 108 +- .../panel-color-gradient-settings.js | 128 ++- .../colors-gradients/test/control.js | 137 ++- .../src/components/colors/index.js | 5 +- .../src/components/colors/test/utils.js | 51 +- .../src/components/colors/test/with-colors.js | 48 +- .../src/components/colors/use-colors.js | 172 +-- .../src/components/colors/utils.js | 13 +- .../src/components/colors/with-colors.js | 175 ++- .../src/components/contrast-checker/index.js | 45 +- .../components/contrast-checker/test/index.js | 75 +- .../src/components/copy-handler/index.js | 4 +- .../default-block-appender/index.js | 31 +- .../default-block-appender/index.native.js | 39 +- .../default-block-appender/test/index.js | 16 +- .../components/default-style-picker/index.js | 33 +- .../components/font-sizes/font-size-picker.js | 21 +- .../src/components/font-sizes/utils.js | 6 +- .../components/font-sizes/with-font-sizes.js | 118 +- .../src/components/gradient-picker/control.js | 27 +- .../src/components/gradient-picker/index.js | 35 +- .../src/components/gradient-picker/panel.js | 11 +- .../src/components/gradients/index.js | 33 +- .../components/image-size-control/index.js | 39 +- packages/block-editor/src/components/index.js | 2 +- .../src/components/index.native.js | 6 +- .../inner-blocks/button-block-appender.js | 5 +- .../inner-blocks/default-block-appender.js | 4 +- .../src/components/inner-blocks/index.js | 45 +- .../components/inner-blocks/index.native.js | 57 +- .../src/components/inner-blocks/test/index.js | 16 +- .../components/inner-blocks/with-client-id.js | 5 +- .../components/inserter-list-item/index.js | 20 +- .../inserter-menu-extension/index.js | 4 +- .../src/components/inserter/child-blocks.js | 10 +- .../src/components/inserter/index.js | 52 +- .../src/components/inserter/index.native.js | 98 +- .../src/components/inserter/menu.js | 320 ++++-- .../src/components/inserter/menu.native.js | 85 +- .../src/components/inserter/search-items.js | 138 +-- .../inserter/test/fixtures/index.js | 24 +- .../src/components/inserter/test/menu.js | 30 +- .../components/inserter/test/search-items.js | 87 +- .../inspector-controls/index.native.js | 3 +- .../components/keyboard-shortcuts/index.js | 109 +- .../src/components/link-control/index.js | 155 ++- .../components/link-control/search-input.js | 10 +- .../components/link-control/search-item.js | 39 +- .../link-control/settings-drawer.js | 13 +- .../link-control/test/fixtures/index.js | 14 +- .../src/components/link-control/test/index.js | 664 +++++++---- .../src/components/media-edit/index.native.js | 7 +- .../src/components/media-placeholder/index.js | 107 +- .../media-placeholder/index.native.js | 56 +- .../media-placeholder/test/index.js | 6 +- .../components/media-replace-flow/index.js | 76 +- .../image-size.native.js | 5 +- .../media-upload-progress/index.native.js | 61 +- .../test/index.native.js | 65 +- .../src/components/media-upload/check.js | 6 +- .../components/media-upload/index.native.js | 60 +- .../media-upload/test/index.native.js | 86 +- .../multi-select-scroll-into-view/index.js | 7 +- .../multi-selection-inspector/index.js | 31 +- .../src/components/navigable-toolbar/index.js | 9 +- .../src/components/observe-typing/index.js | 33 +- .../components/page-template-picker/button.js | 6 +- .../page-template-picker/button.native.js | 17 +- .../page-template-picker/container.native.js | 2 +- .../page-template-picker/default-templates.js | 10 +- .../components/page-template-picker/picker.js | 6 +- .../page-template-picker/preview.native.js | 20 +- .../use-page-template-picker-visible.js | 18 +- .../components/panel-color-settings/index.js | 5 +- .../panel-color-settings/test/index.js | 48 +- .../src/components/plain-text/index.js | 1 - .../src/components/plain-text/index.native.js | 16 +- .../src/components/provider/index.js | 20 +- .../src/components/provider/index.native.js | 20 +- .../provider/with-registry-provider.js | 63 +- .../responsive-block-control/index.js | 49 +- .../responsive-block-control/label.js | 24 +- .../responsive-block-control/test/index.js | 162 ++- .../rich-text/format-toolbar/index.js | 28 +- .../rich-text/format-toolbar/index.native.js | 9 +- .../src/components/rich-text/index.js | 928 +++++++-------- .../rich-text/remove-browser-shortcuts.js | 8 +- .../components/rich-text/toolbar-button.js | 12 +- .../skip-to-selected-block/index.js | 17 +- .../src/components/tool-selector/index.js | 37 +- .../src/components/typewriter/index.js | 53 +- .../src/components/ungroup-button/icon.js | 20 +- .../components/ungroup-button/index.native.js | 36 +- .../src/components/url-input/button.js | 9 +- .../src/components/url-input/index.js | 256 +++-- .../src/components/url-input/test/button.js | 38 +- .../url-popover/image-url-input-ui.js | 71 +- .../src/components/url-popover/index.js | 17 +- .../src/components/url-popover/link-viewer.js | 24 +- .../src/components/url-popover/test/index.js | 16 +- .../video-player/gridicon-play.native.js | 10 +- .../components/video-player/index.native.js | 62 +- .../src/components/warning/index.js | 9 +- .../src/components/warning/index.native.js | 33 +- .../src/components/warning/test/index.js | 22 +- .../components/writing-flow/focus-capture.js | 119 +- .../src/components/writing-flow/index.js | 128 ++- .../src/components/writing-flow/test/index.js | 24 +- packages/block-editor/src/hooks/align.js | 158 +-- packages/block-editor/src/hooks/anchor.js | 91 +- .../src/hooks/custom-class-name.js | 98 +- .../src/hooks/custom-class-name.native.js | 28 +- .../src/hooks/generated-class-name.js | 10 +- packages/block-editor/src/hooks/test/align.js | 135 ++- .../block-editor/src/hooks/test/anchor.js | 49 +- .../src/hooks/test/custom-class-name.js | 41 +- .../src/hooks/test/generated-class-name.js | 31 +- packages/block-editor/src/store/actions.js | 149 ++- packages/block-editor/src/store/array.js | 2 +- packages/block-editor/src/store/controls.js | 8 +- packages/block-editor/src/store/defaults.js | 41 +- packages/block-editor/src/store/effects.js | 108 +- .../block-editor/src/store/middlewares.js | 7 +- packages/block-editor/src/store/reducer.js | 273 +++-- packages/block-editor/src/store/selectors.js | 212 ++-- .../block-editor/src/store/test/actions.js | 330 +++--- packages/block-editor/src/store/test/array.js | 37 +- .../block-editor/src/store/test/effects.js | 181 +-- .../block-editor/src/store/test/reducer.js | 1012 ++++++++++------- .../block-editor/src/store/test/selectors.js | 341 ++++-- packages/block-editor/src/style.scss | 2 +- packages/block-editor/src/utils/dom.js | 14 +- .../src/utils/get-paste-event-data.js | 4 +- packages/block-editor/src/utils/test/dom.js | 10 +- .../src/utils/transform-styles/ast/parse.js | 55 +- .../ast/stringify/compress.js | 114 +- .../ast/stringify/identity.js | 137 +-- .../transform-styles/ast/stringify/index.js | 6 +- .../utils/transform-styles/test/traverse.js | 4 +- .../transforms/url-rewrite.js | 5 +- .../utils/transform-styles/transforms/wrap.js | 8 +- packages/block-library/package.json | 2 + packages/block-library/src/archives/edit.js | 23 +- .../block-library/src/audio/deprecated.js | 15 +- packages/block-library/src/audio/edit.js | 23 +- packages/block-library/src/audio/index.js | 7 +- packages/block-library/src/audio/save.js | 20 +- .../block-library/src/audio/transforms.js | 5 +- .../src/block/edit-panel/index.js | 22 +- packages/block-library/src/block/edit.js | 47 +- packages/block-library/src/block/index.js | 4 +- .../block-library/src/button/deprecated.js | 56 +- packages/block-library/src/button/edit.js | 69 +- packages/block-library/src/button/index.js | 4 +- packages/block-library/src/button/save.js | 15 +- packages/block-library/src/button/style.scss | 4 + packages/block-library/src/buttons/icon.js | 7 +- packages/block-library/src/buttons/index.js | 4 +- packages/block-library/src/calendar/edit.js | 24 +- packages/block-library/src/calendar/icon.js | 7 +- packages/block-library/src/categories/edit.js | 97 +- packages/block-library/src/classic/edit.js | 22 +- .../block-library/src/code/edit.native.js | 18 +- packages/block-library/src/code/icon.js | 5 +- packages/block-library/src/code/index.js | 8 +- packages/block-library/src/code/save.js | 6 +- .../src/code/test/edit.native.js | 8 +- packages/block-library/src/code/transforms.js | 5 +- packages/block-library/src/code/utils.js | 5 +- packages/block-library/src/column/edit.js | 71 +- packages/block-library/src/column/index.js | 1 - .../block-library/src/columns/deprecated.js | 29 +- packages/block-library/src/columns/edit.js | 276 +++-- packages/block-library/src/columns/index.js | 27 +- .../block-library/src/columns/patterns.js | 68 -- packages/block-library/src/columns/save.js | 5 +- packages/block-library/src/columns/utils.js | 40 +- .../block-library/src/columns/variations.js | 130 +++ .../block-library/src/cover/deprecated.js | 177 +-- packages/block-library/src/cover/edit.js | 271 ++--- packages/block-library/src/cover/index.js | 4 +- packages/block-library/src/cover/save.js | 40 +- packages/block-library/src/cover/shared.js | 10 +- .../block-library/src/cover/transforms.js | 52 +- .../block-library/src/embed/core-embeds.js | 59 +- packages/block-library/src/embed/edit.js | 91 +- .../block-library/src/embed/embed-controls.js | 5 +- .../src/embed/embed-placeholder.js | 40 +- .../block-library/src/embed/embed-preview.js | 97 +- packages/block-library/src/embed/icons.js | 112 +- packages/block-library/src/embed/index.js | 36 +- packages/block-library/src/embed/settings.js | 66 +- .../embed/test/__snapshots__/index.js.snap | 2 +- .../block-library/src/embed/test/index.js | 28 +- packages/block-library/src/embed/util.js | 134 ++- packages/block-library/src/file/edit.js | 70 +- packages/block-library/src/file/inspector.js | 6 +- packages/block-library/src/file/save.js | 48 +- packages/block-library/src/file/transforms.js | 12 +- .../block-library/src/gallery/deprecated.js | 165 ++- packages/block-library/src/gallery/edit.js | 168 +-- .../src/gallery/gallery-image.js | 34 +- .../src/gallery/gallery-image.native.js | 172 ++- packages/block-library/src/gallery/gallery.js | 42 +- .../src/gallery/gallery.native.js | 45 +- packages/block-library/src/gallery/icons.js | 14 +- packages/block-library/src/gallery/index.js | 10 +- packages/block-library/src/gallery/save.js | 37 +- .../src/gallery/shared-icon.native.js | 13 +- packages/block-library/src/gallery/shared.js | 9 +- .../block-library/src/gallery/tiles.native.js | 36 +- .../block-library/src/gallery/transforms.js | 60 +- .../block-library/src/group/deprecated.js | 9 +- packages/block-library/src/group/edit.js | 22 +- .../block-library/src/group/edit.native.js | 32 +- packages/block-library/src/group/icon.js | 18 +- packages/block-library/src/group/index.js | 43 +- packages/block-library/src/group/save.js | 12 +- .../block-library/src/heading/deprecated.js | 5 +- packages/block-library/src/heading/edit.js | 32 +- .../block-library/src/heading/edit.native.js | 4 +- .../src/heading/heading-level-icon.js | 8 +- .../src/heading/heading-toolbar.js | 22 +- packages/block-library/src/heading/index.js | 32 +- packages/block-library/src/heading/save.js | 13 +- .../block-library/src/heading/transforms.js | 5 +- packages/block-library/src/html/edit.js | 29 +- packages/block-library/src/html/icon.js | 4 +- packages/block-library/src/html/index.js | 5 +- packages/block-library/src/html/transforms.js | 10 +- .../block-library/src/image/deprecated.js | 64 +- packages/block-library/src/image/edit.js | 211 ++-- .../block-library/src/image/edit.native.js | 296 +++-- .../src/image/icon-customize.native.js | 6 +- .../src/image/icon-retry.native.js | 7 +- packages/block-library/src/image/icon.js | 9 +- .../block-library/src/image/image-size.js | 5 +- packages/block-library/src/image/index.js | 14 +- packages/block-library/src/image/save.js | 18 +- .../src/image/styles.native.scss | 16 +- packages/block-library/src/image/test/edit.js | 5 +- .../src/image/test/edit.native.js | 20 +- .../src/image/test/transforms.js | 56 +- .../block-library/src/image/transforms.js | 91 +- packages/block-library/src/image/utils.js | 5 +- packages/block-library/src/index.js | 50 +- packages/block-library/src/index.native.js | 2 +- .../block-library/src/latest-comments/edit.js | 8 +- .../block-library/src/latest-posts/edit.js | 243 ++-- .../block-library/src/latest-posts/icon.js | 11 +- .../src/legacy-widget/edit/dom-manager.js | 111 +- .../src/legacy-widget/edit/handler.js | 29 +- .../src/legacy-widget/edit/index.js | 49 +- .../src/legacy-widget/edit/placeholder.js | 9 +- packages/block-library/src/list/edit.js | 145 ++- packages/block-library/src/list/index.js | 9 +- .../src/list/ordered-list-settings.js | 9 +- packages/block-library/src/list/transforms.js | 52 +- .../src/media-text/deprecated.js | 56 +- packages/block-library/src/media-text/edit.js | 205 ++-- .../src/media-text/edit.native.js | 134 ++- .../src/media-text/icon-retry.native.js | 7 +- .../block-library/src/media-text/index.js | 7 +- .../src/media-text/media-container-icon.js | 8 +- .../src/media-text/media-container.js | 36 +- .../src/media-text/media-container.native.js | 132 ++- packages/block-library/src/media-text/save.js | 42 +- .../src/media-text/transforms.js | 10 +- packages/block-library/src/missing/edit.js | 21 +- .../block-library/src/missing/edit.native.js | 86 +- packages/block-library/src/missing/index.js | 4 +- .../src/missing/test/edit.native.js | 11 +- packages/block-library/src/more/edit.js | 21 +- .../block-library/src/more/edit.native.js | 10 +- packages/block-library/src/more/icon.js | 7 +- packages/block-library/src/more/index.js | 4 +- packages/block-library/src/more/save.js | 12 +- packages/block-library/src/more/transforms.js | 3 +- .../block-library/src/navigation-link/edit.js | 185 ++- .../src/navigation-link/icons.js | 7 +- .../src/navigation-link/index.js | 1 - .../block-library/src/navigation-link/save.js | 8 +- .../src/navigation/block-colors-selector.js | 14 +- .../src/navigation/block-navigation-list.js | 38 +- packages/block-library/src/navigation/edit.js | 138 ++- .../block-library/src/navigation/icons.js | 23 +- .../block-library/src/navigation/index.js | 1 - packages/block-library/src/navigation/save.js | 4 +- .../src/navigation/use-block-navigator.js | 18 +- .../block-library/src/nextpage/edit.native.js | 35 +- packages/block-library/src/nextpage/save.js | 6 +- .../block-library/src/nextpage/transforms.js | 3 +- .../block-library/src/paragraph/deprecated.js | 81 +- packages/block-library/src/paragraph/edit.js | 146 ++- .../src/paragraph/edit.native.js | 35 +- packages/block-library/src/paragraph/index.js | 8 +- packages/block-library/src/paragraph/save.js | 5 +- .../block-library/src/post-author/edit.js | 7 +- .../block-library/src/post-excerpt/edit.js | 6 +- .../block-library/src/preformatted/edit.js | 8 +- .../src/preformatted/edit.native.js | 16 +- .../block-library/src/preformatted/index.js | 8 +- .../src/preformatted/transforms.js | 5 +- .../block-library/src/pullquote/deprecated.js | 117 +- packages/block-library/src/pullquote/edit.js | 76 +- packages/block-library/src/pullquote/index.js | 21 +- packages/block-library/src/pullquote/save.js | 35 +- .../block-library/src/quote/deprecated.js | 16 +- packages/block-library/src/quote/edit.js | 26 +- packages/block-library/src/quote/index.js | 13 +- packages/block-library/src/quote/save.js | 4 +- .../block-library/src/quote/transforms.js | 52 +- packages/block-library/src/rss/edit.js | 38 +- packages/block-library/src/search/edit.js | 8 +- packages/block-library/src/separator/edit.js | 15 +- packages/block-library/src/separator/index.js | 5 +- packages/block-library/src/separator/save.js | 14 +- .../src/separator/separator-settings.js | 8 +- packages/block-library/src/shortcode/edit.js | 5 +- .../src/shortcode/edit.native.js | 31 +- packages/block-library/src/shortcode/index.js | 4 +- .../src/shortcode/test/edit.native.js | 17 +- .../block-library/src/social-link/edit.js | 49 +- .../src/social-link/icons/amazon.js | 7 +- .../src/social-link/icons/bandcamp.js | 7 +- .../src/social-link/icons/behance.js | 7 +- .../src/social-link/icons/chain.js | 7 +- .../src/social-link/icons/codepen.js | 7 +- .../src/social-link/icons/deviantart.js | 7 +- .../src/social-link/icons/dribbble.js | 7 +- .../src/social-link/icons/dropbox.js | 7 +- .../src/social-link/icons/etsy.js | 7 +- .../src/social-link/icons/facebook.js | 7 +- .../src/social-link/icons/feed.js | 7 +- .../src/social-link/icons/fivehundredpx.js | 7 +- .../src/social-link/icons/flickr.js | 7 +- .../src/social-link/icons/foursquare.js | 7 +- .../src/social-link/icons/github.js | 7 +- .../src/social-link/icons/goodreads.js | 7 +- .../src/social-link/icons/google.js | 7 +- .../src/social-link/icons/index.js | 1 - .../src/social-link/icons/instagram.js | 7 +- .../src/social-link/icons/lastfm.js | 7 +- .../src/social-link/icons/linkedin.js | 7 +- .../src/social-link/icons/mail.js | 7 +- .../src/social-link/icons/mastodon.js | 7 +- .../src/social-link/icons/medium.js | 7 +- .../src/social-link/icons/meetup.js | 7 +- .../src/social-link/icons/pinterest.js | 7 +- .../src/social-link/icons/pocket.js | 7 +- .../src/social-link/icons/reddit.js | 7 +- .../src/social-link/icons/skype.js | 7 +- .../src/social-link/icons/snapchat.js | 7 +- .../src/social-link/icons/soundcloud.js | 7 +- .../src/social-link/icons/spotify.js | 7 +- .../src/social-link/icons/tumblr.js | 7 +- .../src/social-link/icons/twitch.js | 7 +- .../src/social-link/icons/twitter.js | 7 +- .../src/social-link/icons/vimeo.js | 7 +- .../block-library/src/social-link/icons/vk.js | 7 +- .../src/social-link/icons/wordpress.js | 15 +- .../src/social-link/icons/yelp.js | 7 +- .../src/social-link/icons/youtube.js | 7 +- .../block-library/src/social-link/index.js | 46 +- .../block-library/src/social-links/edit.js | 4 +- .../block-library/src/social-links/index.js | 19 +- packages/block-library/src/spacer/edit.js | 18 +- .../block-library/src/spacer/edit.native.js | 36 +- packages/block-library/src/spacer/index.js | 4 +- packages/block-library/src/subhead/edit.js | 6 +- packages/block-library/src/subhead/index.js | 4 +- .../block-library/src/table/deprecated.js | 23 +- packages/block-library/src/table/edit.js | 186 +-- packages/block-library/src/table/index.js | 40 +- packages/block-library/src/table/save.js | 48 +- packages/block-library/src/table/state.js | 76 +- .../block-library/src/table/test/state.js | 203 +++- packages/block-library/src/tag-cloud/edit.js | 6 +- .../src/template-part/edit/index.js | 9 +- .../src/template-part/edit/placeholder.js | 12 +- .../edit/use-template-part-post.js | 5 +- .../block-library/src/text-columns/edit.js | 23 +- .../block-library/src/text-columns/index.js | 4 +- .../block-library/src/text-columns/save.js | 9 +- .../src/text-columns/transforms.js | 20 +- packages/block-library/src/verse/edit.js | 7 +- packages/block-library/src/verse/index.js | 8 +- .../src/video/edit-common-settings.js | 11 +- packages/block-library/src/video/edit.js | 76 +- .../block-library/src/video/edit.native.js | 146 ++- .../src/video/icon-retry.native.js | 8 +- packages/block-library/src/video/index.js | 4 +- packages/block-library/src/video/save.js | 12 +- .../block-library/src/video/transforms.js | 9 +- .../src/index.js | 52 +- .../test/index.js | 10 +- .../bin/create-php-parser.js | 29 +- .../shared-tests.js | 405 +++++-- .../test/index.js | 5 +- packages/blocks/src/api/children.js | 5 +- packages/blocks/src/api/factory.js | 189 ++- packages/blocks/src/api/index.js | 16 +- packages/blocks/src/api/node.js | 2 +- packages/blocks/src/api/parser.js | 161 ++- .../raw-handling/html-formatting-remover.js | 6 +- .../raw-handling/image-corrector.native.js | 1 - packages/blocks/src/api/raw-handling/index.js | 99 +- .../src/api/raw-handling/is-inline-content.js | 16 +- .../src/api/raw-handling/ms-list-converter.js | 4 +- .../src/api/raw-handling/normalise-blocks.js | 2 +- .../src/api/raw-handling/paste-handler.js | 164 +-- .../raw-handling/phrasing-content-reducer.js | 5 +- .../src/api/raw-handling/phrasing-content.js | 95 +- .../api/raw-handling/shortcode-converter.js | 42 +- .../test/blockquote-normaliser.js | 4 +- .../api/raw-handling/test/comment-remover.js | 44 +- .../test/figure-content-reducer.js | 46 +- .../raw-handling/test/is-inline-content.js | 4 +- .../src/api/raw-handling/test/list-reducer.js | 9 +- .../raw-handling/test/markdown-converter.js | 2 +- .../raw-handling/test/ms-list-converter.js | 51 +- .../api/raw-handling/test/normalise-blocks.js | 28 +- .../test/phrasing-content-reducer.js | 62 +- .../test/special-comment-converter.js | 36 +- .../blocks/src/api/raw-handling/test/utils.js | 154 +-- packages/blocks/src/api/raw-handling/utils.js | 71 +- packages/blocks/src/api/registration.js | 188 +-- packages/blocks/src/api/serializer.js | 148 ++- packages/blocks/src/api/templates.js | 91 +- packages/blocks/src/api/test/children.js | 23 +- packages/blocks/src/api/test/factory.js | 969 ++++++++++------ packages/blocks/src/api/test/matchers.js | 6 +- packages/blocks/src/api/test/node.js | 15 +- packages/blocks/src/api/test/parser.js | 188 +-- packages/blocks/src/api/test/registration.js | 475 +++++--- packages/blocks/src/api/test/serializer.js | 73 +- packages/blocks/src/api/test/templates.js | 81 +- packages/blocks/src/api/test/utils.js | 71 +- packages/blocks/src/api/test/validation.js | 93 +- packages/blocks/src/api/utils.js | 45 +- packages/blocks/src/api/validation/index.js | 149 ++- packages/blocks/src/api/validation/logger.js | 8 +- .../src/block-content-provider/index.js | 30 +- packages/blocks/src/store/actions.js | 29 +- packages/blocks/src/store/reducer.js | 93 +- packages/blocks/src/store/selectors.js | 95 +- packages/blocks/src/store/test/actions.js | 40 +- packages/blocks/src/store/test/reducer.js | 234 ++-- packages/blocks/src/store/test/selectors.js | 142 ++- packages/components/src/angle-picker/index.js | 29 +- .../src/angle-picker/stories/index.js | 5 +- packages/components/src/animate/index.js | 17 +- packages/components/src/autocomplete/index.js | 223 ++-- packages/components/src/base-control/index.js | 60 +- .../src/base-control/index.native.js | 5 +- .../src/base-control/stories/index.js | 4 +- packages/components/src/button-group/index.js | 4 +- .../src/button-group/stories/index.js | 8 +- packages/components/src/button/deprecated.js | 8 +- packages/components/src/button/index.js | 45 +- .../components/src/button/index.native.js | 71 +- .../components/src/button/stories/index.js | 56 +- packages/components/src/button/test/index.js | 78 +- packages/components/src/card/stories/media.js | 4 +- .../components/src/card/styles/card-styles.js | 52 +- .../components/src/checkbox-control/index.js | 30 +- .../src/checkbox-control/stories/index.js | 12 +- .../src/circular-option-picker/index.js | 29 +- .../components/src/clipboard-button/index.js | 17 +- .../src/clipboard-button/stories/index.js | 4 +- .../src/color-indicator/stories/index.js | 4 +- .../src/color-indicator/test/index.js | 4 +- .../components/src/color-palette/index.js | 63 +- .../src/color-palette/test/index.js | 37 +- packages/components/src/color-picker/alpha.js | 12 +- packages/components/src/color-picker/hue.js | 25 +- packages/components/src/color-picker/index.js | 33 +- .../components/src/color-picker/saturation.js | 22 +- .../src/color-picker/stories/index.js | 13 +- .../components/src/color-picker/test/index.js | 32 +- .../components/src/color-picker/test/input.js | 24 +- packages/components/src/color-picker/utils.js | 4 +- .../src/custom-gradient-picker/constants.js | 6 +- .../custom-gradient-picker/control-points.js | 107 +- .../src/custom-gradient-picker/index.js | 66 +- .../src/custom-gradient-picker/serializer.js | 21 +- .../custom-gradient-picker/test/serializer.js | 148 ++- .../src/custom-gradient-picker/utils.js | 113 +- .../src/custom-select-control/index.js | 30 +- .../custom-select-control/stories/index.js | 5 +- packages/components/src/dashicon/index.js | 727 ++++++++---- .../components/src/dashicon/test/index.js | 12 +- packages/components/src/date-time/date.js | 28 +- packages/components/src/date-time/index.js | 102 +- .../components/src/date-time/test/date.js | 57 +- .../components/src/date-time/test/time.js | 128 ++- packages/components/src/date-time/time.js | 39 +- .../components/src/dimension-control/index.js | 39 +- .../components/src/dimension-control/sizes.js | 6 +- .../src/dimension-control/test/index.test.js | 39 +- packages/components/src/disabled/index.js | 4 +- .../components/src/disabled/test/index.js | 76 +- packages/components/src/draggable/index.js | 29 +- .../components/src/draggable/stories/index.js | 5 +- packages/components/src/drop-zone/index.js | 23 +- packages/components/src/drop-zone/provider.js | 81 +- .../components/src/dropdown-menu/index.js | 87 +- .../src/dropdown-menu/index.native.js | 1 - .../src/dropdown-menu/test/index.js | 19 +- packages/components/src/dropdown/index.js | 5 +- .../components/src/dropdown/index.native.js | 5 +- .../components/src/dropdown/test/index.js | 70 +- .../components/src/external-link/index.js | 36 +- .../src/focal-point-picker/index.js | 96 +- .../components/src/font-size-picker/index.js | 42 +- .../src/font-size-picker/stories/index.js | 11 +- .../components/src/form-file-upload/index.js | 16 +- packages/components/src/form-toggle/index.js | 36 +- .../components/src/form-toggle/test/index.js | 34 +- .../components/src/form-token-field/index.js | 155 ++- .../src/form-token-field/suggestions-list.js | 95 +- .../src/form-token-field/test/index.js | 114 +- .../src/form-token-field/test/lib/fixtures.js | 70 +- .../test/lib/token-field-wrapper.js | 8 +- .../src/form-token-field/token-input.js | 20 +- .../components/src/form-token-field/token.js | 4 +- .../components/src/gradient-picker/index.js | 81 +- packages/components/src/guide/icons.js | 7 +- packages/components/src/guide/index.js | 23 +- packages/components/src/guide/page-control.js | 23 +- packages/components/src/guide/test/index.js | 24 +- .../components/src/guide/test/page-control.js | 13 +- .../higher-order/navigate-regions/index.js | 95 +- .../with-constrained-tabbing/index.js | 98 +- .../with-fallback-styles/index.js | 26 +- .../higher-order/with-filters/test/index.js | 56 +- .../higher-order/with-focus-outside/index.js | 188 ++- .../with-focus-outside/index.native.js | 170 ++- .../with-focus-outside/test/index.js | 11 +- .../higher-order/with-focus-return/index.js | 13 +- .../with-focus-return/test/index.js | 38 +- .../src/higher-order/with-notices/index.js | 16 +- .../with-spoken-messages/index.js | 3 +- .../with-spoken-messages/test/index.js | 12 +- packages/components/src/icon/index.js | 20 +- packages/components/src/icon/stories/index.js | 9 +- packages/components/src/icon/test/index.js | 39 +- packages/components/src/index.js | 28 +- packages/components/src/index.native.js | 18 +- .../src/isolated-event-container/index.js | 6 +- .../isolated-event-container/test/index.js | 4 +- .../src/keyboard-shortcuts/index.js | 8 +- .../src/keyboard-shortcuts/test/index.js | 9 +- packages/components/src/menu-group/index.js | 15 +- .../components/src/menu-group/test/index.js | 5 +- packages/components/src/menu-item/index.js | 16 +- .../components/src/menu-item/test/index.js | 26 +- .../components/src/menu-items-choice/index.js | 6 +- .../src/mobile/bottom-sheet/button.native.js | 11 +- .../src/mobile/bottom-sheet/cell.native.js | 191 +++- .../src/mobile/bottom-sheet/index.native.js | 65 +- .../keyboard-avoiding-view.native.js | 18 +- .../mobile/bottom-sheet/picker-cell.native.js | 5 +- .../mobile/bottom-sheet/range-cell.native.js | 54 +- .../bottom-sheet/stepper-cell/index.native.js | 30 +- .../stepper-cell/stepper.android.js | 38 +- .../bottom-sheet/stepper-cell/stepper.ios.js | 23 +- .../mobile/bottom-sheet/switch-cell.native.js | 34 +- .../html-text-input/container.android.js | 9 +- .../mobile/html-text-input/index.native.js | 29 +- .../html-text-input/test/index.native.js | 14 +- .../keyboard-avoiding-view/index.ios.js | 11 +- .../keyboard-aware-flat-list/index.ios.js | 30 +- .../mobile/modal-header-bar/button.native.js | 43 +- .../src/mobile/modal-header-bar/close-icon.js | 5 +- .../mobile/modal-header-bar/index.native.js | 40 +- .../src/mobile/picker/index.android.js | 14 +- .../components/src/mobile/picker/index.ios.js | 2 +- .../mobile/stepper-control/index.native.js | 22 +- packages/components/src/modal/aria-helper.js | 4 +- packages/components/src/modal/frame.js | 10 +- packages/components/src/modal/header.js | 42 +- packages/components/src/modal/index.js | 12 +- .../src/navigable-container/container.js | 14 +- .../src/navigable-container/menu.js | 9 +- .../src/navigable-container/test/menu.js | 132 ++- .../src/navigable-container/test/tabbable.js | 75 +- packages/components/src/notice/index.js | 12 +- packages/components/src/notice/test/index.js | 8 +- packages/components/src/notice/test/list.js | 4 +- .../components/src/panel/actions.native.js | 4 +- packages/components/src/panel/body.js | 26 +- packages/components/src/panel/body.native.js | 6 +- packages/components/src/panel/row.js | 6 +- .../components/src/panel/stories/index.js | 37 +- packages/components/src/panel/test/body.js | 31 +- packages/components/src/panel/test/header.js | 29 +- packages/components/src/panel/test/index.js | 32 +- packages/components/src/panel/test/row.js | 6 +- packages/components/src/placeholder/index.js | 48 +- .../src/placeholder/stories/index.js | 5 +- .../components/src/placeholder/test/index.js | 79 +- packages/components/src/popover/index.js | 127 ++- .../components/src/popover/stories/index.js | 4 +- packages/components/src/popover/test/index.js | 18 +- packages/components/src/popover/test/utils.js | 32 +- packages/components/src/popover/utils.js | 122 +- .../src/query-controls/category-select.js | 8 +- .../components/src/query-controls/index.js | 5 +- .../components/src/query-controls/terms.js | 7 +- .../src/query-controls/test/terms.js | 20 +- .../components/src/radio-control/index.js | 67 +- .../src/radio-control/stories/index.js | 8 +- .../components/src/range-control/index.js | 18 +- .../src/range-control/index.native.js | 4 +- .../src/range-control/stories/index.js | 55 +- .../src/range-control/test/index.js | 362 +++--- .../components/src/resizable-box/index.js | 16 +- .../src/resizable-box/stories/index.js | 5 +- .../src/responsive-wrapper/index.js | 7 +- packages/components/src/sandbox/index.js | 46 +- packages/components/src/scroll-lock/index.js | 3 +- .../src/scroll-lock/stories/index.js | 3 +- .../components/src/scroll-lock/test/index.js | 12 +- .../components/src/select-control/index.js | 50 +- .../components/src/shortcut/test/index.js | 6 +- packages/components/src/slot-fill/context.js | 18 +- packages/components/src/slot-fill/slot.js | 26 +- .../components/src/slot-fill/test/slot.js | 60 +- packages/components/src/snackbar/index.js | 61 +- packages/components/src/snackbar/list.js | 26 +- .../components/src/spinner/index.native.js | 6 +- packages/components/src/tab-panel/index.js | 18 +- .../components/src/tab-panel/stories/index.js | 10 +- .../components/src/tab-panel/test/index.js | 43 +- packages/components/src/text-control/index.js | 22 +- .../components/src/text-highlight/index.js | 4 +- .../src/text-highlight/test/index.js | 76 +- packages/components/src/text/mixins.js | 32 +- packages/components/src/text/stories/index.js | 34 +- packages/components/src/text/text.styles.js | 5 +- .../components/src/text/text.styles.native.js | 4 +- .../components/src/textarea-control/index.js | 19 +- .../src/textarea-control/index.native.js | 2 +- .../components/src/toggle-control/index.js | 5 +- .../src/toggle-control/stories/index.js | 22 +- .../src/toggle-control/test/index.js | 8 +- .../components/src/toolbar-button/index.js | 9 +- .../toolbar-button-container.js | 6 +- .../toolbar-button-container.native.js | 6 +- .../components/src/toolbar-group/index.js | 8 +- .../src/toolbar-group/test/index.js | 22 +- .../toolbar-group-collapsed.native.js | 4 +- .../toolbar-group-container.native.js | 13 +- packages/components/src/toolbar-item/index.js | 13 +- .../src/toolbar-item/index.native.js | 4 +- packages/components/src/toolbar/index.js | 5 +- packages/components/src/toolbar/test/index.js | 4 +- .../src/toolbar/toolbar-container.native.js | 4 +- packages/components/src/tooltip/index.js | 20 +- packages/components/src/tooltip/test/index.js | 47 +- packages/components/src/tree-select/index.js | 3 +- .../components/src/visually-hidden/index.js | 7 +- .../src/higher-order/if-condition/index.js | 19 +- .../compose/src/higher-order/pure/index.js | 5 +- .../src/higher-order/pure/test/index.js | 18 +- .../higher-order/with-global-events/index.js | 7 +- .../with-global-events/listener.js | 5 +- .../with-global-events/test/index.js | 20 +- .../with-global-events/test/listener.js | 10 +- .../higher-order/with-safe-timeout/index.js | 79 +- .../src/higher-order/with-state/test/index.js | 17 +- .../compose/src/hooks/use-dragging/index.js | 55 +- .../src/hooks/use-keyboard-shortcut/index.js | 37 +- .../src/hooks/use-media-query/index.js | 3 +- .../src/hooks/use-media-query/test/index.js | 36 +- .../src/hooks/use-reduced-motion/index.js | 6 +- .../src/hooks/use-viewport-match/index.js | 16 +- .../hooks/use-viewport-match/test/index.js | 20 +- packages/compose/src/index.native.js | 1 - .../create-higher-order-component/index.js | 17 +- .../test/index.js | 12 +- packages/core-data/src/actions.js | 104 +- packages/core-data/src/controls.js | 12 +- packages/core-data/src/entities.js | 42 +- packages/core-data/src/entity-provider.js | 38 +- packages/core-data/src/index.js | 23 +- .../src/queried-data/get-query-parts.js | 5 +- .../core-data/src/queried-data/reducer.js | 16 +- .../core-data/src/queried-data/selectors.js | 8 +- .../src/queried-data/test/reducer.js | 58 +- .../src/queried-data/test/selectors.js | 5 +- packages/core-data/src/reducer.js | 137 ++- packages/core-data/src/resolvers.js | 25 +- packages/core-data/src/selectors.js | 81 +- packages/core-data/src/test/actions.js | 47 +- packages/core-data/src/test/entities.js | 12 +- packages/core-data/src/test/reducer.js | 166 +-- packages/core-data/src/test/resolvers.js | 143 ++- packages/core-data/src/test/selectors.js | 144 ++- packages/core-data/src/utils/on-sub-key.js | 5 +- .../core-data/src/utils/test/on-sub-key.js | 4 +- .../src/utils/test/replace-action.js | 8 +- packages/create-block/CHANGELOG.md | 36 +- packages/create-block/lib/init-wp-scripts.js | 8 +- packages/create-block/lib/prompts.js | 9 +- packages/create-block/lib/scaffold.js | 26 +- packages/create-block/lib/templates.js | 15 +- .../index.js | 36 +- packages/data-controls/src/index.js | 6 +- packages/data-controls/src/test/index.js | 51 +- .../use-dispatch/test/use-dispatch.js | 7 +- .../use-dispatch/use-dispatch-with-map.js | 30 +- .../data/src/components/use-select/index.js | 9 +- .../src/components/use-select/test/index.js | 122 +- .../src/components/with-dispatch/index.js | 22 +- .../components/with-dispatch/test/index.js | 148 +-- .../src/components/with-registry/index.js | 5 +- .../data/src/components/with-select/index.js | 26 +- .../src/components/with-select/test/index.js | 241 ++-- packages/data/src/factory.js | 3 +- packages/data/src/index.js | 3 +- packages/data/src/namespace-store/index.js | 110 +- .../src/namespace-store/metadata/reducer.js | 40 +- .../namespace-store/metadata/test/reducer.js | 70 +- .../namespace-store/metadata/test/utils.js | 4 +- .../src/namespace-store/metadata/utils.js | 5 +- .../data/src/namespace-store/test/index.js | 131 ++- .../data/src/plugins/persistence/index.js | 60 +- .../src/plugins/persistence/test/index.js | 81 +- packages/data/src/registry.js | 37 +- .../data/src/resolvers-cache-middleware.js | 47 +- packages/data/src/store/index.js | 23 +- packages/data/src/test/registry.js | 175 ++- packages/date/src/index.js | 77 +- packages/date/src/test/index.js | 15 +- .../index.js | 45 +- .../test/build.js | 18 +- .../fixtures/no-default/webpack.config.js | 4 +- .../output-format-json/webpack.config.js | 4 +- .../test/util.js | 23 +- .../util.js | 9 +- packages/deprecated/src/index.js | 16 +- packages/deprecated/src/test/index.js | 9 +- packages/docgen/bin/cli.js | 81 +- packages/docgen/src/engine.js | 50 +- packages/docgen/src/get-export-entries.js | 48 +- .../src/get-intermediate-representation.js | 45 +- packages/docgen/src/get-type-as-string.js | 9 +- packages/docgen/src/index.js | 32 +- packages/docgen/src/is-symbol-private.js | 3 +- packages/docgen/src/markdown/embed.js | 11 +- packages/docgen/src/markdown/formatter.js | 63 +- packages/docgen/src/markdown/index.js | 32 +- .../default-undocumented-oneliner/code.js | 2 +- .../named-default-exported/module-code.js | 2 +- .../fixtures/named-default/module-code.js | 2 +- .../named-identifier-destructuring/code.js | 2 +- .../named-identifiers-and-inline/code.js | 1 - .../src/test/fixtures/named-variable/code.js | 1 - .../src/test/fixtures/named-variables/code.js | 1 - .../docgen/src/test/formatter-markdown.js | 75 +- .../docgen/src/test/get-export-entries.js | 156 ++- .../test/get-intermediate-representation.js | 656 ++++++++--- .../docgen/src/test/get-jsdoc-from-token.js | 155 +-- packages/dom-ready/src/test/index.test.js | 5 +- packages/dom/src/dom.js | 67 +- packages/dom/src/test/dom.js | 26 +- packages/dom/src/test/tabbable.js | 23 +- packages/dom/src/test/utils/create-element.js | 49 +- .../e2e-test-utils/src/activate-plugin.js | 4 +- .../src/click-block-toolbar-button.js | 2 +- packages/e2e-test-utils/src/click-button.js | 4 +- .../src/click-on-close-modal-button.js | 3 +- .../src/click-on-more-menu-item.js | 20 +- .../e2e-test-utils/src/create-new-post.js | 6 +- .../e2e-test-utils/src/drag-and-resize.js | 4 +- .../src/ensure-sidebar-opened.js | 4 +- ...-sidebar-panel-toggle-button-with-title.js | 6 +- .../src/find-sidebar-panel-with-title.js | 9 +- .../src/get-all-block-inserter-item-titles.js | 8 +- .../src/get-available-block-transforms.js | 12 +- .../e2e-test-utils/src/get-block-setting.js | 14 +- packages/e2e-test-utils/src/index.js | 5 +- packages/e2e-test-utils/src/insert-block.js | 12 +- packages/e2e-test-utils/src/install-plugin.js | 7 +- .../e2e-test-utils/src/is-in-default-block.js | 8 +- packages/e2e-test-utils/src/login-user.js | 14 +- .../src/mocks/create-embedding-matcher.js | 4 +- .../src/mocks/create-json-response.js | 3 +- .../src/mocks/mock-or-transform.js | 4 +- .../e2e-test-utils/src/observe-focus-loss.js | 10 +- .../src/open-all-block-inserter-categories.js | 3 +- .../src/press-key-with-modifier.js | 26 +- .../src/set-browser-viewport.js | 7 +- packages/e2e-test-utils/src/shared/config.js | 7 +- .../src/toggle-screen-option.js | 4 +- .../e2e-test-utils/src/transform-block-to.js | 8 +- .../e2e-test-utils/src/visit-admin-page.js | 4 +- packages/e2e-test-utils/src/wp-data-select.js | 4 +- packages/e2e-tests/config/gutenberg-phase.js | 5 +- .../e2e-tests/config/setup-test-framework.js | 17 +- packages/e2e-tests/experimental-features.js | 18 +- .../e2e-tests/fixtures/block-transforms.js | 456 ++------ packages/e2e-tests/fixtures/utils.js | 13 +- packages/e2e-tests/jest.config.js | 4 +- packages/e2e-tests/jest.performance.config.js | 13 +- .../specs/editor/blocks/buttons.test.js | 4 +- .../specs/editor/blocks/classic.test.js | 17 +- .../specs/editor/blocks/columns.test.js | 6 +- .../specs/editor/blocks/heading.test.js | 22 +- .../specs/editor/blocks/html.test.js | 4 +- .../specs/editor/blocks/navigation.test.js | 85 +- .../specs/editor/blocks/spacer.test.js | 8 +- .../specs/editor/blocks/table.test.js | 36 +- .../specs/editor/plugins/align-hook.test.js | 185 +-- .../editor/plugins/allowed-blocks.test.js | 33 +- .../specs/editor/plugins/annotations.test.js | 30 +- .../specs/editor/plugins/block-icons.test.js | 54 +- .../editor/plugins/container-blocks.test.js | 47 +- .../specs/editor/plugins/cpt-locking.test.js | 60 +- .../editor/plugins/custom-post-types.test.js | 21 +- .../editor/plugins/custom-taxonomies.test.js | 21 +- .../specs/editor/plugins/hooks-api.test.js | 9 +- .../inner-blocks-allowed-blocks.test.js | 21 +- .../plugins/innerblocks-locking-all-embed.js | 7 +- .../plugins/meta-attribute-block.test.js | 28 +- .../specs/editor/plugins/meta-boxes.test.js | 37 +- .../specs/editor/plugins/plugins-api.test.js | 30 +- .../specs/editor/plugins/templates.test.js | 8 +- .../specs/editor/various/a11y.test.js | 16 +- .../editor/various/adding-blocks.test.js | 73 +- .../various/adding-inline-tokens.test.js | 17 +- .../specs/editor/various/autosave.test.js | 186 ++- .../editor/various/block-deletion.test.js | 25 +- .../editor/various/block-grouping.test.js | 35 +- .../block-hierarchy-navigation.test.js | 22 +- .../editor/various/block-switcher.test.js | 20 +- .../editor/various/change-detection.test.js | 53 +- .../compatibility-classic-editor.test.js | 14 +- .../specs/editor/various/datepicker.test.js | 8 +- .../specs/editor/various/editor-modes.test.js | 66 +- .../specs/editor/various/embedding.test.js | 104 +- .../editor/various/font-size-picker.test.js | 48 +- .../editor/various/fullscreen-mode.test.js | 4 +- .../editor/various/invalid-block.test.js | 17 +- .../various/keyboard-navigable-blocks.test.js | 46 +- .../specs/editor/various/links.test.js | 53 +- .../various/manage-reusable-blocks.test.js | 30 +- .../various/multi-block-selection.test.js | 71 +- .../editor/various/navigable-toolbar.test.js | 105 +- .../various/new-post-default-content.test.js | 4 +- .../specs/editor/various/new-post.test.js | 20 +- .../specs/editor/various/nux.test.js | 44 +- .../specs/editor/various/popovers.test.js | 3 +- .../editor/various/post-visibility.test.js | 22 +- .../specs/editor/various/preferences.test.js | 4 +- .../specs/editor/various/preview.test.js | 81 +- .../editor/various/publish-button.test.js | 26 +- .../editor/various/publish-panel.test.js | 46 +- .../specs/editor/various/publishing.test.js | 75 +- .../editor/various/reusable-blocks.test.js | 36 +- .../specs/editor/various/rich-text.test.js | 55 +- .../specs/editor/various/rtl.test.js | 12 +- .../specs/editor/various/scheduling.test.js | 16 +- .../editor/various/shortcut-help.test.js | 16 +- .../various/sidebar-permalink-panel.test.js | 16 +- .../specs/editor/various/sidebar.test.js | 63 +- .../editor/various/splitting-merging.test.js | 4 +- .../editor/various/style-variation.test.js | 4 +- .../specs/editor/various/taxonomies.test.js | 84 +- .../specs/editor/various/typewriter.test.js | 75 +- .../specs/editor/various/undo.test.js | 28 +- .../specs/editor/various/writing-flow.test.js | 67 +- .../specs/performance/performance.test.js | 21 +- .../inner-blocks-render-appender.test.js | 30 +- .../src/components/admin-notices/index.js | 8 +- .../components/admin-notices/test/index.js | 4 +- .../plugin-block-settings-menu-group.js | 30 +- .../plugin-block-settings-menu-item.js | 33 +- .../src/components/browser-url/index.js | 5 +- .../editor-initialization/listener-hooks.js | 21 +- .../test/listener-hooks.js | 297 ++--- .../src/components/editor-regions/index.js | 9 +- .../components/fullscreen-mode/test/index.js | 16 +- .../components/header/feature-toggle/index.js | 10 +- .../header/fullscreen-mode-close/index.js | 4 +- .../components/header/header-toolbar/index.js | 36 +- .../header/header-toolbar/index.native.js | 43 +- .../edit-post/src/components/header/index.js | 46 +- .../src/components/header/index.native.js | 4 +- .../components/header/mode-switcher/index.js | 23 +- .../components/header/more-menu/test/index.js | 4 +- .../components/header/pinned-plugins/index.js | 10 +- .../header/plugin-more-menu-item/index.js | 2 +- .../plugin-more-menu-item/test/index.js | 9 +- .../plugin-sidebar-more-menu-item/index.js | 26 +- .../header/plugins-more-menu-group/index.js | 14 +- .../header/post-publish-button-or-toggle.js | 10 +- .../src/components/header/test/index.js | 32 +- .../header/tools-more-menu-group/index.js | 14 +- .../components/header/writing-menu/index.js | 8 +- .../keyboard-shortcut-help-modal/index.js | 35 +- .../keyboard-shortcut-help-modal/shortcut.js | 33 +- .../components/keyboard-shortcuts/index.js | 69 +- .../edit-post/src/components/layout/index.js | 130 ++- .../src/components/layout/index.native.js | 79 +- .../manage-blocks-modal/category.js | 27 +- .../manage-blocks-modal/checklist.js | 4 +- .../components/manage-blocks-modal/manager.js | 39 +- .../manage-blocks-modal/show-all.js | 6 +- .../src/components/meta-boxes/index.js | 4 +- .../meta-boxes/meta-box-visibility.js | 4 +- .../meta-boxes/meta-boxes-area/index.js | 9 +- .../src/components/options-modal/index.js | 42 +- .../options-modal/meta-boxes-section.js | 24 +- .../options/enable-custom-fields.js | 19 +- .../options-modal/options/enable-panel.js | 7 +- .../enable-plugin-document-setting-panel.js | 9 +- .../options/enable-publish-sidebar.js | 9 +- .../options/test/enable-custom-fields.js | 31 +- .../options-modal/test/meta-boxes-section.js | 8 +- .../sidebar/discussion-panel/index.js | 25 +- .../sidebar/featured-image/index.js | 9 +- .../edit-post/src/components/sidebar/index.js | 15 +- .../sidebar/page-attributes/index.js | 29 +- .../plugin-document-setting-panel/index.js | 29 +- .../plugin-post-publish-panel/index.js | 10 +- .../plugin-post-publish-panel/test/index.js | 2 +- .../sidebar/plugin-post-status-info/index.js | 4 +- .../plugin-post-status-info/test/index.js | 4 +- .../sidebar/plugin-pre-publish-panel/index.js | 10 +- .../sidebar/plugin-sidebar/index.js | 39 +- .../components/sidebar/post-author/index.js | 5 +- .../components/sidebar/post-excerpt/index.js | 24 +- .../components/sidebar/post-format/index.js | 5 +- .../src/components/sidebar/post-link/index.js | 46 +- .../sidebar/post-pending-status/index.js | 5 +- .../components/sidebar/post-schedule/index.js | 10 +- .../components/sidebar/post-status/index.js | 16 +- .../components/sidebar/post-sticky/index.js | 5 +- .../sidebar/post-taxonomies/index.js | 5 +- .../sidebar/post-taxonomies/taxonomy-panel.js | 26 +- .../sidebar/post-visibility/index.js | 58 +- .../sidebar/settings-header/index.js | 28 +- .../sidebar/settings-sidebar/index.js | 13 +- .../sidebar/sidebar-header/index.js | 20 +- .../src/components/text-editor/index.js | 5 +- .../visual-editor/block-inspector-button.js | 29 +- .../src/components/visual-editor/index.js | 10 +- .../components/visual-editor/index.native.js | 24 +- .../src/components/welcome-guide/index.js | 42 +- packages/edit-post/src/editor.js | 33 +- packages/edit-post/src/editor.native.js | 33 +- .../src/hooks/validate-multiple-use/index.js | 47 +- packages/edit-post/src/index.js | 53 +- .../plugins/copy-content-menu-item/index.js | 31 +- packages/edit-post/src/plugins/index.js | 12 +- .../index.js | 4 +- .../plugins/manage-blocks-menu-item/index.js | 4 +- .../edit-post/src/prevent-event-discovery.js | 7 +- packages/edit-post/src/store/effects.js | 53 +- packages/edit-post/src/store/middlewares.js | 6 +- packages/edit-post/src/store/reducer.js | 29 +- packages/edit-post/src/store/selectors.js | 30 +- packages/edit-post/src/store/test/reducer.js | 43 +- .../edit-post/src/store/test/selectors.js | 27 +- packages/edit-post/src/test/editor.native.js | 8 +- packages/edit-post/src/utils/meta-boxes.js | 4 +- .../src/components/block-editor/index.js | 5 +- .../src/components/save-button/index.js | 16 +- .../sync-customizer.js | 69 +- .../edit-widgets-initializer/index.js | 6 +- .../src/components/inserter/index.js | 8 +- .../src/components/save-button/index.js | 39 +- .../src/components/sidebar/index.js | 5 +- .../src/components/widget-area/index.js | 54 +- .../src/components/widget-areas/index.js | 7 +- packages/edit-widgets/src/index.js | 13 +- .../src/components/autocompleters/block.js | 9 +- .../components/autocompleters/test/block.js | 59 +- .../components/autocompleters/test/user.js | 37 +- .../src/components/autocompleters/user.js | 22 +- .../src/components/autosave-monitor/index.js | 26 +- .../components/autosave-monitor/test/index.js | 28 +- .../convert-button.js | 123 +- .../convert-to-group-buttons/icons.js | 41 +- .../convert-to-group-buttons/index.native.js | 1 - packages/editor/src/components/deprecated.js | 202 +++- .../src/components/document-outline/check.js | 5 +- .../src/components/document-outline/index.js | 52 +- .../src/components/document-outline/item.js | 29 +- .../components/document-outline/test/index.js | 75 +- .../components/entities-saved-states/index.js | 34 +- .../src/components/error-boundary/index.js | 12 +- .../save-shortcut.js | 33 +- .../text-editor-shortcuts.js | 4 +- .../visual-editor-shortcuts.js | 67 +- .../editor/src/components/index.native.js | 1 - .../local-autosave-monitor/index.js | 95 +- .../src/components/page-attributes/check.js | 16 +- .../src/components/page-attributes/order.js | 63 +- .../src/components/page-attributes/parent.js | 37 +- .../components/page-attributes/template.js | 25 +- .../components/page-attributes/test/check.js | 42 +- .../src/components/post-author/check.js | 18 +- .../src/components/post-author/index.js | 8 +- .../src/components/post-author/test/check.js | 11 +- .../src/components/post-author/test/index.js | 3 +- .../src/components/post-comments/index.js | 9 +- .../src/components/post-excerpt/index.js | 8 +- .../components/post-featured-image/index.js | 152 ++- .../src/components/post-format/check.js | 22 +- .../src/components/post-format/index.js | 37 +- .../components/post-last-revision/check.js | 31 +- .../components/post-last-revision/index.js | 37 +- .../src/components/post-locked-modal/index.js | 53 +- .../components/post-pending-status/check.js | 18 +- .../components/post-pending-status/index.js | 2 +- .../post-pending-status/test/check.js | 6 +- .../src/components/post-permalink/editor.js | 7 +- .../src/components/post-permalink/index.js | 52 +- .../src/components/post-pingbacks/index.js | 9 +- .../components/post-preview-button/index.js | 30 +- .../post-preview-button/test/index.js | 25 +- .../components/post-publish-button/index.js | 35 +- .../components/post-publish-button/label.js | 12 +- .../post-publish-button/test/index.js | 69 +- .../post-publish-button/test/label.js | 33 +- .../components/post-publish-panel/index.js | 42 +- .../maybe-post-format-panel.js | 44 +- .../post-publish-panel/maybe-tags-panel.js | 27 +- .../post-publish-panel/postpublish.js | 42 +- .../post-publish-panel/prepublish.js | 73 +- .../post-publish-panel/test/index.js | 22 +- .../src/components/post-saved-state/index.js | 27 +- .../components/post-saved-state/test/index.js | 9 +- .../src/components/post-schedule/check.js | 6 +- .../src/components/post-schedule/index.js | 4 +- .../src/components/post-schedule/label.js | 9 +- .../components/post-schedule/test/check.js | 10 +- .../components/post-schedule/test/label.js | 8 +- .../editor/src/components/post-slug/check.js | 4 +- .../editor/src/components/post-slug/index.js | 16 +- .../src/components/post-slug/test/check.js | 6 +- .../src/components/post-slug/test/index.js | 9 +- .../src/components/post-sticky/check.js | 11 +- .../src/components/post-sticky/index.js | 4 +- .../post-switch-to-draft-button/index.js | 15 +- .../src/components/post-taxonomies/check.js | 5 +- .../post-taxonomies/flat-term-selector.js | 118 +- .../hierarchical-term-selector.js | 245 ++-- .../src/components/post-taxonomies/index.js | 30 +- .../components/post-taxonomies/test/index.js | 13 +- .../src/components/post-text-editor/index.js | 5 +- .../components/post-text-editor/test/index.js | 29 +- .../editor/src/components/post-title/index.js | 31 +- .../src/components/post-title/index.native.js | 69 +- .../post-type-support-check/test/index.js | 18 +- .../src/components/post-visibility/check.js | 6 +- .../src/components/post-visibility/index.js | 43 +- .../src/components/post-visibility/label.js | 3 +- .../components/post-visibility/test/check.js | 8 +- .../src/components/post-visibility/utils.js | 4 +- .../editor/src/components/provider/index.js | 34 +- .../src/components/provider/index.native.js | 99 +- .../provider/with-registry-provider.js | 65 +- .../reusable-blocks-buttons/index.native.js | 1 - .../reusable-block-convert-button.js | 55 +- .../reusable-block-delete-button.js | 31 +- .../src/components/table-of-contents/index.js | 7 +- .../src/components/table-of-contents/panel.js | 24 +- .../template-validation-notice/index.js | 34 +- .../components/theme-support-check/index.js | 37 +- .../theme-support-check/test/index.js | 65 +- .../unsaved-changes-warning/index.js | 4 +- .../editor/src/components/word-count/index.js | 4 +- .../custom-sources-backwards-compatibility.js | 90 +- .../src/hooks/test/default-autocompleters.js | 24 +- packages/editor/src/store/actions.js | 149 ++- packages/editor/src/store/constants.js | 4 +- packages/editor/src/store/controls.js | 17 +- packages/editor/src/store/defaults.js | 1 - .../src/store/effects/reusable-blocks.js | 80 +- .../src/store/effects/test/reusable-blocks.js | 286 +++-- packages/editor/src/store/middlewares.js | 2 +- packages/editor/src/store/reducer.js | 35 +- packages/editor/src/store/reducer.native.js | 28 +- packages/editor/src/store/selectors.js | 526 ++++++--- packages/editor/src/store/selectors.native.js | 37 +- packages/editor/src/store/test/actions.js | 207 ++-- .../editor/src/store/test/actions.native.js | 1 - packages/editor/src/store/test/reducer.js | 49 +- .../editor/src/store/test/reducer.native.js | 12 +- packages/editor/src/store/test/selectors.js | 248 ++-- .../editor/src/store/utils/notice-builder.js | 24 +- .../src/store/utils/test/notice-builder.js | 124 +- .../editor/src/utils/media-upload/index.js | 3 +- .../src/utils/media-upload/index.native.js | 2 +- packages/editor/src/utils/terms.js | 7 +- packages/editor/src/utils/test/terms.js | 20 +- packages/editor/src/utils/url.js | 4 +- .../element/src/create-interpolate-element.js | 60 +- packages/element/src/react.js | 21 +- packages/element/src/serialize.js | 74 +- .../src/test/create-interpolate-element.js | 248 ++-- packages/element/src/test/index.js | 57 +- packages/element/src/test/raw-html.js | 12 +- packages/element/src/test/serialize.js | 115 +- packages/element/src/utils.js | 6 +- packages/env/lib/cli.js | 6 +- packages/env/lib/detect-context.js | 21 +- packages/env/lib/env.js | 17 +- packages/env/test/cli.js | 4 +- packages/escape-html/src/index.js | 4 +- packages/escape-html/src/test/index.js | 16 +- packages/eslint-plugin/CHANGELOG.md | 2 +- packages/eslint-plugin/README.md | 2 +- packages/eslint-plugin/configs/custom.js | 22 +- packages/eslint-plugin/configs/es5.js | 37 +- packages/eslint-plugin/configs/esnext.js | 23 +- packages/eslint-plugin/configs/jsdoc.js | 38 +- packages/eslint-plugin/configs/jsx-a11y.js | 17 +- packages/eslint-plugin/configs/react.js | 30 +- .../configs/recommended-with-formatting.js | 18 +- packages/eslint-plugin/configs/recommended.js | 6 +- packages/eslint-plugin/configs/test-e2e.js | 4 +- packages/eslint-plugin/configs/test-unit.js | 4 +- packages/eslint-plugin/package.json | 1 + .../rules/__tests__/dependency-group.js | 15 +- .../rules/__tests__/gutenberg-phase.js | 9 +- .../no-base-control-with-label-without-id.js | 14 +- .../__tests__/no-unused-vars-before-return.js | 21 +- .../__tests__/react-no-unsafe-timeout.js | 21 +- .../rules/__tests__/valid-sprintf.js | 53 +- .../eslint-plugin/rules/dependency-group.js | 13 +- .../eslint-plugin/rules/gutenberg-phase.js | 35 +- .../no-base-control-with-label-without-id.js | 9 +- .../rules/no-unguarded-get-range-at.js | 4 +- .../rules/no-unused-vars-before-return.js | 24 +- .../rules/react-no-unsafe-timeout.js | 16 +- packages/format-library/src/bold/index.js | 6 +- .../format-library/src/default-formats.js | 10 +- .../src/default-formats.native.js | 7 +- packages/format-library/src/image/index.js | 98 +- packages/format-library/src/index.js | 8 +- packages/format-library/src/italic/index.js | 6 +- packages/format-library/src/link/index.js | 177 +-- .../format-library/src/link/index.native.js | 201 ++-- packages/format-library/src/link/inline.js | 68 +- .../format-library/src/link/modal.native.js | 56 +- .../format-library/src/link/test/inline.js | 9 +- .../src/link/test/modal.native.js | 29 +- .../format-library/src/link/test/utils.js | 50 +- packages/format-library/src/link/utils.js | 5 +- .../format-library/src/underline/index.js | 8 +- packages/hooks/benchmark/index.js | 2 +- packages/hooks/src/createAddHook.js | 9 +- packages/hooks/src/createDidHook.js | 6 +- packages/hooks/src/createDoingHook.js | 6 +- packages/hooks/src/createHasHook.js | 8 +- packages/hooks/src/createRemoveHook.js | 9 +- packages/hooks/src/createRunHook.js | 4 +- packages/hooks/src/test/index.test.js | 164 ++- packages/hooks/src/validateHookName.js | 4 +- packages/hooks/src/validateNamespace.js | 4 +- packages/html-entities/src/index.js | 9 +- packages/html-entities/src/test/entities.js | 2 +- packages/i18n/benchmark/index.js | 2 +- packages/i18n/src/test/index.js | 53 +- packages/i18n/tools/pot-to-php.js | 63 +- packages/icons/src/library/align-center.js | 1 - packages/icons/src/library/align-left.js | 1 - packages/icons/src/library/align-right.js | 1 - packages/icons/src/library/check.js | 1 - packages/icons/src/library/group.js | 12 +- packages/icons/src/library/heading.js | 1 - packages/icons/src/library/menu.js | 1 - packages/icons/src/library/position-center.js | 1 - packages/icons/src/library/position-left.js | 1 - packages/icons/src/library/position-right.js | 1 - packages/icons/src/library/rss.js | 1 - packages/icons/src/library/search.js | 1 - .../icons/src/library/stretch-full-width.js | 1 - packages/icons/src/library/stretch-wide.js | 1 - packages/icons/src/library/tag.js | 1 - packages/icons/src/library/trash.js | 1 - packages/icons/src/library/video.js | 1 - packages/is-shallow-equal/benchmark/index.js | 70 +- packages/jest-console/src/index.js | 4 +- packages/jest-console/src/matchers.js | 91 +- packages/jest-console/src/test/index.test.js | 140 +-- packages/jest-preset-default/jest-preset.js | 20 +- .../scripts/setup-globals.js | 4 +- .../scripts/travis-fold-passes-reporter.js | 4 +- packages/jest-puppeteer-axe/src/index.js | 104 +- .../src/hooks/use-shortcut.js | 11 +- .../keyboard-shortcuts/src/store/actions.js | 8 +- .../keyboard-shortcuts/src/store/selectors.js | 33 +- packages/keycodes/src/index.js | 42 +- packages/keycodes/src/platform.js | 6 +- packages/keycodes/src/test/index.js | 124 +- packages/keycodes/src/test/platform.js | 40 +- .../index.js | 38 +- .../test/fixtures/webpack.config.js | 4 +- .../src/components/import-dropdown/index.js | 6 +- .../src/components/import-form/index.js | 6 +- packages/list-reusable-blocks/src/index.js | 8 +- .../list-reusable-blocks/src/utils/export.js | 18 +- .../src/components/media-upload/index.js | 62 +- .../src/utils/test/upload-media.test.js | 80 +- .../media-utils/src/utils/upload-media.js | 42 +- packages/notices/src/store/actions.js | 6 +- packages/notices/src/store/test/reducer.js | 51 +- packages/notices/src/store/test/selectors.js | 4 +- .../notices/src/store/utils/on-sub-key.js | 5 +- .../npm-package-json-lint-config/index.js | 4 +- packages/nux/src/components/dot-tip/index.js | 2 +- .../nux/src/components/dot-tip/test/index.js | 10 +- packages/nux/src/store/reducer.js | 5 +- packages/nux/src/store/selectors.js | 15 +- packages/nux/src/store/test/reducer.js | 15 +- packages/nux/src/store/test/selectors.js | 4 +- packages/plugins/src/api/index.js | 16 +- packages/plugins/src/api/test/index.js | 28 +- .../src/components/plugin-area/index.js | 22 +- .../src/components/plugin-context/index.js | 25 +- packages/postcss-themes/index.js | 29 +- packages/postcss-themes/test/index.js | 8 +- .../src/block-quotation/index.native.js | 10 +- .../src/horizontal-rule/index.native.js | 4 +- packages/primitives/src/svg/index.js | 3 +- packages/primitives/src/svg/index.native.js | 25 +- packages/priority-queue/src/index.js | 15 +- packages/priority-queue/src/test/index.js | 10 +- .../lib/add-first-time-contributor-label.js | 16 +- .../lib/add-milestone.js | 31 +- .../lib/assign-fixed-issues.js | 8 +- .../lib/index.js | 15 +- .../test/add-first-time-contributor-label.js | 24 +- .../lib/test/add-milestone.js | 108 +- .../lib/test/assign-fixed-issues.js | 6 +- packages/redux-routine/src/runtime.js | 54 +- packages/redux-routine/src/test/index.js | 57 +- packages/redux-routine/src/test/is-action.js | 9 +- .../redux-routine/src/test/is-generator.js | 4 +- packages/rich-text/src/apply-format.js | 33 +- packages/rich-text/src/change-list-type.js | 8 +- .../rich-text/src/component/format-edit.js | 9 +- packages/rich-text/src/component/index.js | 267 +++-- .../rich-text/src/component/index.native.js | 274 +++-- .../rich-text/src/component/inline-warning.js | 8 +- .../src/component/test/index.native.js | 3 +- .../src/component/with-format-types.js | 138 ++- packages/rich-text/src/create.js | 63 +- packages/rich-text/src/get-active-formats.js | 10 +- packages/rich-text/src/indent-list-items.js | 3 +- .../rich-text/src/insert-line-separator.js | 5 +- packages/rich-text/src/insert-object.js | 7 +- packages/rich-text/src/insert.js | 16 +- packages/rich-text/src/is-empty.js | 5 +- packages/rich-text/src/join.js | 15 +- packages/rich-text/src/outdent-list-items.js | 3 +- .../rich-text/src/register-format-type.js | 34 +- packages/rich-text/src/remove-format.js | 4 +- .../rich-text/src/remove-line-separator.js | 11 +- packages/rich-text/src/replace.js | 17 +- packages/rich-text/src/slice.js | 6 +- packages/rich-text/src/split.js | 5 +- packages/rich-text/src/store/selectors.js | 4 +- .../rich-text/src/store/test/selectors.js | 10 +- packages/rich-text/src/test/apply-format.js | 46 +- .../rich-text/src/test/change-list-type.js | 36 +- packages/rich-text/src/test/create.js | 102 +- .../rich-text/src/test/get-format-types.js | 5 +- .../src/test/get-last-child-index.js | 65 +- .../src/test/get-parent-line-index.js | 52 +- packages/rich-text/src/test/helpers/index.js | 123 +- .../rich-text/src/test/is-format-equal.js | 6 +- packages/rich-text/src/test/join.js | 5 +- .../rich-text/src/test/normalise-formats.js | 9 +- .../src/test/register-format-type.js | 78 +- packages/rich-text/src/test/remove-format.js | 32 +- packages/rich-text/src/test/replace.js | 10 +- packages/rich-text/src/test/split.js | 56 +- packages/rich-text/src/test/to-dom.js | 30 +- packages/rich-text/src/test/to-html-string.js | 89 +- packages/rich-text/src/test/toggle-format.js | 34 +- .../src/test/unregister-format-type.js | 4 +- packages/rich-text/src/to-dom.js | 21 +- packages/rich-text/src/to-html-string.js | 18 +- packages/rich-text/src/to-tree.js | 89 +- .../rich-text/src/unregister-format-type.js | 4 +- packages/scripts/CHANGELOG.md | 3 + packages/scripts/config/.eslintrc-md.js | 14 +- packages/scripts/config/.prettierrc.js | 4 +- packages/scripts/config/jest-e2e.config.js | 10 +- packages/scripts/config/webpack.config.js | 14 +- packages/scripts/scripts/build.js | 8 +- packages/scripts/scripts/check-engines.js | 19 +- packages/scripts/scripts/check-licenses.js | 87 +- packages/scripts/scripts/env.js | 55 +- packages/scripts/scripts/env/cli.js | 8 +- packages/scripts/scripts/env/connect.js | 39 +- packages/scripts/scripts/env/docker-run.js | 6 +- packages/scripts/scripts/env/install.js | 27 +- packages/scripts/scripts/env/lint-php.js | 6 +- packages/scripts/scripts/env/start.js | 8 +- packages/scripts/scripts/env/test-php.js | 6 +- packages/scripts/scripts/env/update.js | 16 +- packages/scripts/scripts/format-js.js | 30 +- packages/scripts/scripts/lint-js.js | 29 +- packages/scripts/scripts/lint-md-docs.js | 30 +- packages/scripts/scripts/lint-md-js.js | 23 +- packages/scripts/scripts/lint-pkg-json.js | 31 +- packages/scripts/scripts/lint-style.js | 32 +- packages/scripts/scripts/packages-update.js | 42 +- packages/scripts/scripts/start.js | 4 +- packages/scripts/scripts/test-e2e.js | 21 +- packages/scripts/scripts/test-unit-jest.js | 17 +- packages/scripts/utils/cli.js | 33 +- packages/scripts/utils/config.js | 23 +- packages/scripts/utils/env.js | 66 +- packages/scripts/utils/index.js | 20 +- packages/scripts/utils/process.js | 4 +- packages/scripts/utils/string.js | 5 +- packages/scripts/utils/test/index.js | 44 +- packages/scripts/utils/test/string.js | 4 +- packages/server-side-render/src/index.js | 52 +- .../src/server-side-render.js | 81 +- packages/server-side-render/src/test/index.js | 19 +- packages/shortcode/src/index.js | 94 +- packages/shortcode/src/test/index.js | 132 ++- packages/token-list/src/index.js | 2 +- packages/token-list/src/test/index.js | 4 +- packages/url/src/clean-for-slug.js | 4 +- packages/url/src/get-authority.js | 4 +- packages/url/src/get-path.js | 4 +- packages/url/src/get-query-arg.js | 5 +- packages/url/src/remove-query-args.js | 8 +- packages/url/src/test/index.test.js | 247 +++- packages/viewport/src/if-viewport-matches.js | 25 +- packages/viewport/src/listener.js | 40 +- packages/viewport/src/listener.native.js | 20 +- packages/viewport/src/store/test/selectors.js | 22 +- .../viewport/src/test/if-viewport-matches.js | 16 +- .../viewport/src/test/with-viewport-match.js | 7 +- packages/viewport/src/with-viewport-match.js | 51 +- .../src/with-viewport-match.native.js | 17 +- packages/warning/babel-plugin.js | 19 +- packages/warning/test/babel-plugin.js | 11 +- packages/wordcount/src/defaultSettings.js | 79 +- packages/wordcount/src/index.js | 21 +- packages/wordcount/src/test/index.test.js | 11 +- storybook/test/__snapshots__/index.js.snap | 27 +- storybook/test/index.js | 7 +- test/integration/blocks-raw-handling.test.js | 179 ++- .../full-content/full-content.test.js | 138 ++- test/integration/is-valid-block.test.js | 17 +- .../non-matched-tags-handling.test.js | 9 +- test/integration/shortcode-converter.test.js | 34 +- .../__mocks__/react-native-aztec/index.js | 10 +- test/native/babel.config.js | 20 +- test/native/jest.config.js | 34 +- test/native/setup.js | 8 +- test/unit/config/gutenberg-phase.js | 5 +- test/unit/jest.config.js | 13 +- test/unit/scripts/babel-transformer.js | 10 +- webpack.config.js | 115 +- 1532 files changed, 39908 insertions(+), 22817 deletions(-) create mode 100644 experimental-default-global-styles.json create mode 100644 lib/global-styles.php delete mode 100644 packages/block-editor/src/components/block-pattern-picker/index.js create mode 100644 packages/block-editor/src/components/block-variation-picker/index.js rename packages/block-editor/src/components/{block-pattern-picker => block-variation-picker}/style.scss (81%) delete mode 100644 packages/block-library/src/columns/patterns.js create mode 100644 packages/block-library/src/columns/variations.js diff --git a/.eslintrc.js b/.eslintrc.js index c6404f79817f42..9f7da03674a7df 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,7 +14,8 @@ const { version } = require( './package' ); * * @type {string} */ -const majorMinorRegExp = escapeRegExp( version.replace( /\.\d+$/, '' ) ) + '(\\.\\d+)?'; +const majorMinorRegExp = + escapeRegExp( version.replace( /\.\d+$/, '' ) ) + '(\\.\\d+)?'; /** * The list of patterns matching files used only for development purposes. @@ -33,9 +34,7 @@ module.exports = { 'plugin:@wordpress/eslint-plugin/recommended', 'plugin:eslint-comments/recommended', ], - plugins: [ - 'import', - ], + plugins: [ 'import' ], globals: { wp: 'off', }, @@ -49,63 +48,88 @@ module.exports = { // we'll get a `SyntaxError` (Invalid regular expression: \ at end of pattern) // here. That's why we use \\u002F in the regexes below. { - selector: 'ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]', - message: 'Path access on WordPress dependencies is not allowed.', + selector: + 'ImportDeclaration[source.value=/^@wordpress\\u002F.+\\u002F/]', + message: + 'Path access on WordPress dependencies is not allowed.', }, { - selector: 'ImportDeclaration[source.value=/^react-spring(?!\\u002Fweb\.cjs)/]', - message: 'The react-spring dependency must specify CommonJS bundle: react-spring/web.cjs', + selector: + 'ImportDeclaration[source.value=/^react-spring(?!\\u002Fweb.cjs)/]', + message: + 'The react-spring dependency must specify CommonJS bundle: react-spring/web.cjs', }, { - selector: 'CallExpression[callee.name="deprecated"] Property[key.name="version"][value.value=/' + majorMinorRegExp + '/]', - message: 'Deprecated functions must be removed before releasing this version.', + selector: + 'CallExpression[callee.name="deprecated"] Property[key.name="version"][value.value=/' + + majorMinorRegExp + + '/]', + message: + 'Deprecated functions must be removed before releasing this version.', }, { - selector: 'CallExpression[callee.name=/^(__|_n|_nx|_x)$/]:not([arguments.0.type=/^Literal|BinaryExpression$/])', - message: 'Translate function arguments must be string literals.', + selector: + 'CallExpression[callee.name=/^(__|_n|_nx|_x)$/]:not([arguments.0.type=/^Literal|BinaryExpression$/])', + message: + 'Translate function arguments must be string literals.', }, { - selector: 'CallExpression[callee.name=/^(_n|_nx|_x)$/]:not([arguments.1.type=/^Literal|BinaryExpression$/])', - message: 'Translate function arguments must be string literals.', + selector: + 'CallExpression[callee.name=/^(_n|_nx|_x)$/]:not([arguments.1.type=/^Literal|BinaryExpression$/])', + message: + 'Translate function arguments must be string literals.', }, { - selector: 'CallExpression[callee.name=_nx]:not([arguments.3.type=/^Literal|BinaryExpression$/])', - message: 'Translate function arguments must be string literals.', + selector: + 'CallExpression[callee.name=_nx]:not([arguments.3.type=/^Literal|BinaryExpression$/])', + message: + 'Translate function arguments must be string literals.', }, { - selector: 'CallExpression[callee.name=/^(__|_x|_n|_nx)$/] Literal[value=/\\.{3}/]', + selector: + 'CallExpression[callee.name=/^(__|_x|_n|_nx)$/] Literal[value=/\\.{3}/]', message: 'Use ellipsis character (…) in place of three dots', }, { - selector: 'ImportDeclaration[source.value="redux"] Identifier.imported[name="combineReducers"]', + selector: + 'ImportDeclaration[source.value="redux"] Identifier.imported[name="combineReducers"]', message: 'Use `combineReducers` from `@wordpress/data`', }, { - selector: 'ImportDeclaration[source.value="lodash"] Identifier.imported[name="memoize"]', + selector: + 'ImportDeclaration[source.value="lodash"] Identifier.imported[name="memoize"]', message: 'Use memize instead of Lodash’s memoize', }, { - selector: 'CallExpression[callee.object.name="page"][callee.property.name="waitFor"]', + selector: + 'CallExpression[callee.object.name="page"][callee.property.name="waitFor"]', message: 'Prefer page.waitForSelector instead.', }, { selector: 'JSXAttribute[name.name="id"][value.type="Literal"]', - message: 'Do not use string literals for IDs; use withInstanceId instead.', + message: + 'Do not use string literals for IDs; use withInstanceId instead.', }, { // Discourage the usage of `Math.random()` as it's a code smell // for UUID generation, for which we already have a higher-order // component: `withInstanceId`. - selector: 'CallExpression[callee.object.name="Math"][callee.property.name="random"]', - message: 'Do not use Math.random() to generate unique IDs; use withInstanceId instead. (If you’re not generating unique IDs: ignore this message.)', + selector: + 'CallExpression[callee.object.name="Math"][callee.property.name="random"]', + message: + 'Do not use Math.random() to generate unique IDs; use withInstanceId instead. (If you’re not generating unique IDs: ignore this message.)', }, { - selector: 'CallExpression[callee.name="withDispatch"] > :function > BlockStatement > :not(VariableDeclaration,ReturnStatement)', - message: 'withDispatch must return an object with consistent keys. Avoid performing logic in `mapDispatchToProps`.', + selector: + 'CallExpression[callee.name="withDispatch"] > :function > BlockStatement > :not(VariableDeclaration,ReturnStatement)', + message: + 'withDispatch must return an object with consistent keys. Avoid performing logic in `mapDispatchToProps`.', }, { - selector: 'LogicalExpression[operator="&&"][left.property.name="length"][right.type="JSXElement"]', - message: 'Avoid truthy checks on length property rendering, as zero length is rendered verbatim.', + selector: + 'LogicalExpression[operator="&&"][left.property.name="length"][right.type="JSXElement"]', + message: + 'Avoid truthy checks on length property rendering, as zero length is rendered verbatim.', }, ], }, @@ -127,37 +151,34 @@ module.exports = { ...developmentFiles, ], rules: { - 'react/forbid-elements': [ 'error', { - forbid: [ - [ 'button', 'Button' ], - [ 'circle', 'Circle' ], - [ 'g', 'G' ], - [ 'path', 'Path' ], - [ 'polygon', 'Polygon' ], - [ 'rect', 'Rect' ], - [ 'svg', 'SVG' ], - ].map( ( [ element, componentName ] ) => { - return { - element, - message: `use cross-platform <${ componentName } /> component instead.`, - }; - } ), - } ], + 'react/forbid-elements': [ + 'error', + { + forbid: [ + [ 'button', 'Button' ], + [ 'circle', 'Circle' ], + [ 'g', 'G' ], + [ 'path', 'Path' ], + [ 'polygon', 'Polygon' ], + [ 'rect', 'Rect' ], + [ 'svg', 'SVG' ], + ].map( ( [ element, componentName ] ) => { + return { + element, + message: `use cross-platform <${ componentName } /> component instead.`, + }; + } ), + }, + ], }, }, { - files: [ - 'packages/jest*/**/*.js', - ], - extends: [ - 'plugin:@wordpress/eslint-plugin/test-unit', - ], + files: [ 'packages/jest*/**/*.js' ], + extends: [ 'plugin:@wordpress/eslint-plugin/test-unit' ], }, { files: [ 'packages/e2e-test*/**/*.js' ], - extends: [ - 'plugin:@wordpress/eslint-plugin/test-e2e', - ], + extends: [ 'plugin:@wordpress/eslint-plugin/test-e2e' ], }, ], }; diff --git a/babel.config.js b/babel.config.js index 83d78a0eeadb6d..f950b6603a5127 100644 --- a/babel.config.js +++ b/babel.config.js @@ -3,9 +3,6 @@ module.exports = function( api ) { return { presets: [ '@wordpress/babel-preset-default' ], - plugins: [ - 'babel-plugin-emotion', - 'babel-plugin-inline-json-import', - ], + plugins: [ 'babel-plugin-emotion', 'babel-plugin-inline-json-import' ], }; }; diff --git a/bin/api-docs/are-readmes-unstaged.js b/bin/api-docs/are-readmes-unstaged.js index 19c7eeba8d162d..c466d55bfb2e03 100644 --- a/bin/api-docs/are-readmes-unstaged.js +++ b/bin/api-docs/are-readmes-unstaged.js @@ -12,17 +12,26 @@ const execSync = require( 'child_process' ).execSync; */ const getPackages = require( './packages' ); -const getUnstagedFiles = () => execSync( 'git diff --name-only', { encoding: 'utf8' } ).split( '\n' ).filter( ( element ) => '' !== element ); -const readmeFiles = getPackages().map( ( [ packageName ] ) => join( 'packages', packageName, 'README.md' ) ); -const unstagedReadmes = getUnstagedFiles().filter( ( element ) => readmeFiles.includes( element ) ); +const getUnstagedFiles = () => + execSync( 'git diff --name-only', { encoding: 'utf8' } ) + .split( '\n' ) + .filter( ( element ) => '' !== element ); +const readmeFiles = getPackages().map( ( [ packageName ] ) => + join( 'packages', packageName, 'README.md' ) +); +const unstagedReadmes = getUnstagedFiles().filter( ( element ) => + readmeFiles.includes( element ) +); if ( unstagedReadmes.length > 0 ) { process.exitCode = 1; - process.stdout.write( chalk.red( - '\n', - 'Some API docs may be out of date:', - unstagedReadmes.toString(), - 'Either stage them or continue with --no-verify.', - '\n' - ) ); + process.stdout.write( + chalk.red( + '\n', + 'Some API docs may be out of date:', + unstagedReadmes.toString(), + 'Either stage them or continue with --no-verify.', + '\n' + ) + ); } diff --git a/bin/api-docs/packages.js b/bin/api-docs/packages.js index dcc583fe65d0e2..71c2bed2bb14b1 100644 --- a/bin/api-docs/packages.js +++ b/bin/api-docs/packages.js @@ -7,10 +7,13 @@ const packages = [ 'block-serialization-default-parser', 'blocks', 'compose', - [ 'core-data', { - 'Autogenerated actions': 'src/actions.js', - 'Autogenerated selectors': 'src/selectors.js', - } ], + [ + 'core-data', + { + 'Autogenerated actions': 'src/actions.js', + 'Autogenerated selectors': 'src/selectors.js', + }, + ], 'data', 'data-controls', 'date', diff --git a/bin/api-docs/update-readmes.js b/bin/api-docs/update-readmes.js index fd66e6cbf39d01..ab1805c3fdeccd 100755 --- a/bin/api-docs/update-readmes.js +++ b/bin/api-docs/update-readmes.js @@ -16,7 +16,14 @@ getPackages().forEach( ( entry ) => { // Each target operates over the same file, so it needs to be processed synchronously, // as to make sure the processes don't overwrite each other. const { status, stderr } = spawnSync( - join( __dirname, '..', '..', 'node_modules', '.bin', 'docgen' ).replace( / /g, '\\ ' ), + join( + __dirname, + '..', + '..', + 'node_modules', + '.bin', + 'docgen' + ).replace( / /g, '\\ ' ), [ join( 'packages', packageName, path ), `--output packages/${ packageName }/README.md`, @@ -24,7 +31,7 @@ getPackages().forEach( ( entry ) => { `--use-token "${ token }"`, '--ignore "/unstable|experimental/i"', ], - { shell: true }, + { shell: true } ); if ( status !== 0 ) { diff --git a/bin/commander.js b/bin/commander.js index 7662f65ae8c01a..788019da2148b8 100755 --- a/bin/commander.js +++ b/bin/commander.js @@ -22,7 +22,8 @@ const uuid = require( 'uuid/v4' ); const gitRepoOwner = 'WordPress'; const gitRepoURL = 'https://github.com/' + gitRepoOwner + '/gutenberg.git'; const svnRepoURL = 'https://plugins.svn.wordpress.org/gutenberg'; -const releasePageURLPrefix = 'https://github.com/WordPress/gutenberg/releases/tag/'; +const releasePageURLPrefix = + 'https://github.com/WordPress/gutenberg/releases/tag/'; // Working Directories const gitWorkingDirectoryPath = path.join( os.tmpdir(), uuid() ); @@ -54,13 +55,19 @@ function readJSONFile( fileName ) { * @param {boolean} isDefault Default reply. * @param {string} abortMessage Abort message. */ -async function askForConfirmationToContinue( message, isDefault = true, abortMessage = 'Aborting.' ) { - const { isReady } = await inquirer.prompt( [ { - type: 'confirm', - name: 'isReady', - default: isDefault, - message, - } ] ); +async function askForConfirmationToContinue( + message, + isDefault = true, + abortMessage = 'Aborting.' +) { + const { isReady } = await inquirer.prompt( [ + { + type: 'confirm', + name: 'isReady', + default: isDefault, + message, + }, + ] ); if ( ! isReady ) { console.log( error( '\n' + abortMessage ) ); @@ -80,7 +87,11 @@ async function runStep( name, abortMessage, handler ) { await handler(); } catch ( exception ) { console.log( - error( 'The following error happened during the "' + warning( name ) + '" step:' ) + '\n\n', + error( + 'The following error happened during the "' + + warning( name ) + + '" step:' + ) + '\n\n', exception, error( '\n\n' + abortMessage ) ); @@ -120,7 +131,10 @@ async function runGitRepositoryCloneStep( abortMessage ) { console.log( '>> Cloning the Git repository' ); const simpleGit = SimpleGit(); await simpleGit.clone( gitRepoURL, gitWorkingDirectoryPath ); - console.log( '>> The Gutenberg Git repository has been successfully cloned in the following temporary folder: ' + success( gitWorkingDirectoryPath ) ); + console.log( + '>> The Gutenberg Git repository has been successfully cloned in the following temporary folder: ' + + success( gitWorkingDirectoryPath ) + ); } ); } @@ -133,8 +147,13 @@ async function runSvnRepositoryCloneStep( abortMessage ) { // Cloning the repository await runStep( 'Fetching the SVN repository', abortMessage, async () => { console.log( '>> Fetching the SVN repository' ); - runShellScript( 'svn checkout ' + svnRepoURL + '/trunk ' + svnWorkingDirectoryPath ); - console.log( '>> The Gutenberg SVN repository has been successfully fetched in the following temporary folder: ' + success( svnWorkingDirectoryPath ) ); + runShellScript( + 'svn checkout ' + svnRepoURL + '/trunk ' + svnWorkingDirectoryPath + ); + console.log( + '>> The Gutenberg SVN repository has been successfully fetched in the following temporary folder: ' + + success( svnWorkingDirectoryPath ) + ); } ); } @@ -153,14 +172,21 @@ async function runUpdateTrunkContentStep( version, changelog, abortMessage ) { const readmePath = svnWorkingDirectoryPath + '/readme.txt'; const previousReadmeFileContent = fs.readFileSync( readmePath, 'utf8' ); - const stableTag = previousReadmeFileContent.match( STABLE_TAG_REGEX )[ 0 ]; + const stableTag = previousReadmeFileContent.match( + STABLE_TAG_REGEX + )[ 0 ]; // Delete everything - runShellScript( 'find . -maxdepth 1 -not -name ".svn" -not -name "." -not -name ".." -exec rm -rf {} +', svnWorkingDirectoryPath ); + runShellScript( + 'find . -maxdepth 1 -not -name ".svn" -not -name "." -not -name ".." -exec rm -rf {} +', + svnWorkingDirectoryPath + ); // Update the content using the plugin ZIP const gutenbergZipPath = gitWorkingDirectoryPath + '/gutenberg.zip'; - runShellScript( 'unzip ' + gutenbergZipPath + ' -d ' + svnWorkingDirectoryPath ); + runShellScript( + 'unzip ' + gutenbergZipPath + ' -d ' + svnWorkingDirectoryPath + ); // Replace the stable tag placeholder with the existing stable tag on the SVN repository. const newReadmeFileContent = fs.readFileSync( readmePath, 'utf8' ); @@ -170,15 +196,24 @@ async function runUpdateTrunkContentStep( version, changelog, abortMessage ) { ); // Commit the content changes - runShellScript( "svn st | grep '^\?' | awk '{print $2}' | xargs svn add", svnWorkingDirectoryPath ); - runShellScript( "svn st | grep '^!' | awk '{print $2}' | xargs svn rm", svnWorkingDirectoryPath ); + runShellScript( + "svn st | grep '^?' | awk '{print $2}' | xargs svn add", + svnWorkingDirectoryPath + ); + runShellScript( + "svn st | grep '^!' | awk '{print $2}' | xargs svn rm", + svnWorkingDirectoryPath + ); await askForConfirmationToContinue( 'Trunk content has been updated, please check the SVN diff. Commit the changes?', true, abortMessage ); - runShellScript( 'svn commit -m "Committing Gutenberg version ' + version + '"', svnWorkingDirectoryPath ); + runShellScript( + 'svn commit -m "Committing Gutenberg version ' + version + '"', + svnWorkingDirectoryPath + ); console.log( '>> Trunk has been successfully updated' ); } ); @@ -197,9 +232,23 @@ async function runSvnTagStep( version, abortMessage ) { true, abortMessage ); - runShellScript( 'svn cp ' + svnRepoURL + '/trunk ' + svnRepoURL + '/tags/' + version + ' -m "Tagging Gutenberg version ' + version + '"' ); + runShellScript( + 'svn cp ' + + svnRepoURL + + '/trunk ' + + svnRepoURL + + '/tags/' + + version + + ' -m "Tagging Gutenberg version ' + + version + + '"' + ); - console.log( '>> The SVN ' + success( version ) + ' tag has been successfully created' ); + console.log( + '>> The SVN ' + + success( version ) + + ' tag has been successfully created' + ); } ); } @@ -211,26 +260,33 @@ async function runSvnTagStep( version, abortMessage ) { */ async function updateThePluginStableVersion( version, abortMessage ) { // Updating the content of the svn - await runStep( 'Updating the plugin\'s stable version', abortMessage, async () => { - const readmePath = svnWorkingDirectoryPath + '/readme.txt'; - const readmeFileContent = fs.readFileSync( readmePath, 'utf8' ); - const newReadmeContent = readmeFileContent.replace( - STABLE_TAG_REGEX, - 'Stable tag: ' + version + '\n' - ); - fs.writeFileSync( readmePath, newReadmeContent ); - - // Commit the content changes - await askForConfirmationToContinue( - 'The stable version is updated in the readme.txt file. Commit the changes?', - true, - abortMessage - ); - - runShellScript( 'svn commit -m "Releasing Gutenberg version ' + version + '"', svnWorkingDirectoryPath ); - - console.log( '>> Stable version updated successfully' ); - } ); + await runStep( + "Updating the plugin's stable version", + abortMessage, + async () => { + const readmePath = svnWorkingDirectoryPath + '/readme.txt'; + const readmeFileContent = fs.readFileSync( readmePath, 'utf8' ); + const newReadmeContent = readmeFileContent.replace( + STABLE_TAG_REGEX, + 'Stable tag: ' + version + '\n' + ); + fs.writeFileSync( readmePath, newReadmeContent ); + + // Commit the content changes + await askForConfirmationToContinue( + 'The stable version is updated in the readme.txt file. Commit the changes?', + true, + abortMessage + ); + + runShellScript( + 'svn commit -m "Releasing Gutenberg version ' + version + '"', + svnWorkingDirectoryPath + ); + + console.log( '>> Stable version updated successfully' ); + } + ); } /** @@ -240,18 +296,19 @@ async function updateThePluginStableVersion( version, abortMessage ) { */ async function runCleanLocalCloneStep( abortMessage ) { await runStep( 'Cleaning the temporary folder', abortMessage, async () => { - await Promise.all( [ - gitWorkingDirectoryPath, - svnWorkingDirectoryPath, - ].map( async ( directoryPath ) => { - if ( fs.existsSync( directoryPath ) ) { - await rimraf( directoryPath, ( err ) => { - if ( err ) { - throw err; + await Promise.all( + [ gitWorkingDirectoryPath, svnWorkingDirectoryPath ].map( + async ( directoryPath ) => { + if ( fs.existsSync( directoryPath ) ) { + await rimraf( directoryPath, ( err ) => { + if ( err ) { + throw err; + } + } ); } - } ); - } - } ) ); + } + ) + ); } ); } @@ -274,23 +331,41 @@ async function runReleaseBranchCreationStep( abortMessage ) { // Follow the WordPress version guidelines to compute the version to be used // By default, increase the "minor" number but if we reach 9, bump to the next major. if ( parsedVersion.minor === 9 ) { - version = ( parsedVersion.major + 1 ) + '.0.0-rc.1'; + version = parsedVersion.major + 1 + '.0.0-rc.1'; releaseBranch = 'release/' + ( parsedVersion.major + 1 ) + '.0'; - versionLabel = ( parsedVersion.major + 1 ) + '.0.0 RC1'; + versionLabel = parsedVersion.major + 1 + '.0.0 RC1'; } else { - version = parsedVersion.major + '.' + ( parsedVersion.minor + 1 ) + '.0-rc.1'; - releaseBranch = 'release/' + parsedVersion.major + '.' + ( parsedVersion.minor + 1 ); - versionLabel = parsedVersion.major + '.' + ( parsedVersion.minor + 1 ) + '.0 RC1'; + version = + parsedVersion.major + + '.' + + ( parsedVersion.minor + 1 ) + + '.0-rc.1'; + releaseBranch = + 'release/' + + parsedVersion.major + + '.' + + ( parsedVersion.minor + 1 ); + versionLabel = + parsedVersion.major + + '.' + + ( parsedVersion.minor + 1 ) + + '.0 RC1'; } await askForConfirmationToContinue( - 'The Plugin version to be used is ' + success( version ) + '. Proceed with the creation of the release branch?', + 'The Plugin version to be used is ' + + success( version ) + + '. Proceed with the creation of the release branch?', true, abortMessage ); // Creating the release branch await simpleGit.checkoutLocalBranch( releaseBranch ); - console.log( '>> The local release branch ' + success( releaseBranch ) + ' has been successfully created.' ); + console.log( + '>> The local release branch ' + + success( releaseBranch ) + + ' has been successfully created.' + ); } ); return { @@ -312,7 +387,9 @@ const findReleaseBranchName = ( packageJsonPath ) => { const masterPackageJson = readJSONFile( packageJsonPath ); const masterParsedVersion = semver.parse( masterPackageJson.version ); - return 'release/' + masterParsedVersion.major + '.' + masterParsedVersion.minor; + return ( + 'release/' + masterParsedVersion.major + '.' + masterParsedVersion.minor + ); }; /** @@ -324,30 +401,55 @@ const findReleaseBranchName = ( packageJsonPath ) => { */ async function runReleaseBranchCheckoutStep( abortMessage ) { let releaseBranch, version; - await runStep( 'Getting into the release branch', abortMessage, async () => { - const simpleGit = SimpleGit( gitWorkingDirectoryPath ); - const packageJsonPath = gitWorkingDirectoryPath + '/package.json'; - releaseBranch = findReleaseBranchName( packageJsonPath ); - - // Creating the release branch - await simpleGit.checkout( releaseBranch ); - console.log( '>> The local release branch ' + success( releaseBranch ) + ' has been successfully checked out.' ); - - const releaseBranchPackageJson = readJSONFile( packageJsonPath ); - const releaseBranchParsedVersion = semver.parse( releaseBranchPackageJson.version ); + await runStep( + 'Getting into the release branch', + abortMessage, + async () => { + const simpleGit = SimpleGit( gitWorkingDirectoryPath ); + const packageJsonPath = gitWorkingDirectoryPath + '/package.json'; + releaseBranch = findReleaseBranchName( packageJsonPath ); + + // Creating the release branch + await simpleGit.checkout( releaseBranch ); + console.log( + '>> The local release branch ' + + success( releaseBranch ) + + ' has been successfully checked out.' + ); + + const releaseBranchPackageJson = readJSONFile( packageJsonPath ); + const releaseBranchParsedVersion = semver.parse( + releaseBranchPackageJson.version + ); + + if ( + releaseBranchParsedVersion.prerelease && + releaseBranchParsedVersion.prerelease.length + ) { + version = + releaseBranchParsedVersion.major + + '.' + + releaseBranchParsedVersion.minor + + '.' + + releaseBranchParsedVersion.patch; + } else { + version = + releaseBranchParsedVersion.major + + '.' + + releaseBranchParsedVersion.minor + + '.' + + ( releaseBranchParsedVersion.patch + 1 ); + } - if ( releaseBranchParsedVersion.prerelease && releaseBranchParsedVersion.prerelease.length ) { - version = releaseBranchParsedVersion.major + '.' + releaseBranchParsedVersion.minor + '.' + releaseBranchParsedVersion.patch; - } else { - version = releaseBranchParsedVersion.major + '.' + releaseBranchParsedVersion.minor + '.' + ( releaseBranchParsedVersion.patch + 1 ); + await askForConfirmationToContinue( + 'The Version to release is ' + + success( version ) + + '. Proceed?', + true, + abortMessage + ); } - - await askForConfirmationToContinue( - 'The Version to release is ' + success( version ) + '. Proceed?', - true, - abortMessage - ); - } ); + ); return { version, @@ -366,7 +468,11 @@ async function runReleaseBranchCheckoutStep( abortMessage ) { * * @return {string} hash of the version bump commit. */ -async function runBumpPluginVersionAndCommitStep( version, changelog, abortMessage ) { +async function runBumpPluginVersionAndCommitStep( + version, + changelog, + abortMessage +) { let commitHash; await runStep( 'Updating the plugin version', abortMessage, async () => { const simpleGit = SimpleGit( gitWorkingDirectoryPath ); @@ -379,23 +485,39 @@ async function runBumpPluginVersionAndCommitStep( version, changelog, abortMessa ...packageJson, version, }; - fs.writeFileSync( packageJsonPath, JSON.stringify( newPackageJson, null, '\t' ) + '\n' ); + fs.writeFileSync( + packageJsonPath, + JSON.stringify( newPackageJson, null, '\t' ) + '\n' + ); const newPackageLock = { ...packageLock, version, }; - fs.writeFileSync( packageLockPath, JSON.stringify( newPackageLock, null, '\t' ) + '\n' ); + fs.writeFileSync( + packageLockPath, + JSON.stringify( newPackageLock, null, '\t' ) + '\n' + ); const content = fs.readFileSync( pluginFilePath, 'utf8' ); - fs.writeFileSync( pluginFilePath, content.replace( ' * Version: ' + packageJson.version, ' * Version: ' + version ) ); + fs.writeFileSync( + pluginFilePath, + content.replace( + ' * Version: ' + packageJson.version, + ' * Version: ' + version + ) + ); console.log( '>> The plugin version has been updated successfully.' ); // Update the content of the readme.txt file const readmePath = gitWorkingDirectoryPath + '/readme.txt'; const readmeFileContent = fs.readFileSync( readmePath, 'utf8' ); const newReadmeContent = - readmeFileContent.substr( 0, readmeFileContent.indexOf( '== Changelog ==' ) ) + + readmeFileContent.substr( + 0, + readmeFileContent.indexOf( '== Changelog ==' ) + ) + '== Changelog ==\n\n' + - `To read the changelog for Gutenberg ${ version }, please navigate to the <a href="${ releasePageURLPrefix }v${ version }">release page</a>.` + '\n'; + `To read the changelog for Gutenberg ${ version }, please navigate to the <a href="${ releasePageURLPrefix }v${ version }">release page</a>.` + + '\n'; fs.writeFileSync( readmePath, newReadmeContent ); // Update the content of the changelog.txt file @@ -404,14 +526,19 @@ async function runBumpPluginVersionAndCommitStep( version, changelog, abortMessa const changelogFileContent = fs.readFileSync( changelogPath, 'utf8' ); const versionHeader = '= ' + version + ' =\n\n'; const regexToSearch = /=\s([0-9]+\.[0-9]+\.[0-9]+)(-rc\.[0-9]+)?\s=\n\n/g; - let lastDifferentVersionMatch = regexToSearch.exec( changelogFileContent ); + let lastDifferentVersionMatch = regexToSearch.exec( + changelogFileContent + ); if ( lastDifferentVersionMatch[ 1 ] === stableVersion ) { - lastDifferentVersionMatch = regexToSearch.exec( changelogFileContent ); + lastDifferentVersionMatch = regexToSearch.exec( + changelogFileContent + ); } const newChangelogContent = '== Changelog ==\n\n' + versionHeader + - changelog + '\n\n' + + changelog + + '\n\n' + changelogFileContent.substr( lastDifferentVersionMatch.index ); fs.writeFileSync( changelogPath, newChangelogContent ); @@ -428,9 +555,13 @@ async function runBumpPluginVersionAndCommitStep( version, changelog, abortMessa readmePath, changelogPath, ] ); - const commitData = await simpleGit.commit( 'Bump plugin version to ' + version ); + const commitData = await simpleGit.commit( + 'Bump plugin version to ' + version + ); commitHash = commitData.commit; - console.log( '>> The plugin version bump has been committed successfully.' ); + console.log( + '>> The plugin version bump has been committed successfully.' + ); } ); return commitHash; @@ -449,9 +580,15 @@ async function runPluginZIPCreationStep( abortMessage ) { true, abortMessage ); - runShellScript( '/bin/bash bin/build-plugin-zip.sh', gitWorkingDirectoryPath ); + runShellScript( + '/bin/bash bin/build-plugin-zip.sh', + gitWorkingDirectoryPath + ); - console.log( '>> The plugin ZIP has been built successfully. Path: ' + success( gutenbergZipPath ) ); + console.log( + '>> The plugin ZIP has been built successfully. Path: ' + + success( gutenbergZipPath ) + ); } ); } @@ -470,7 +607,11 @@ async function runCreateGitTagStep( version, abortMessage ) { abortMessage ); await simpleGit.addTag( 'v' + version ); - console.log( '>> The ' + success( 'v' + version ) + ' tag has been created successfully.' ); + console.log( + '>> The ' + + success( 'v' + version ) + + ' tag has been created successfully.' + ); } ); } @@ -481,16 +622,20 @@ async function runCreateGitTagStep( version, abortMessage ) { * @param {string} abortMessage Abort message. */ async function runPushGitChangesStep( releaseBranch, abortMessage ) { - await runStep( 'Pushing the release branch and the tag', abortMessage, async () => { - const simpleGit = SimpleGit( gitWorkingDirectoryPath ); - await askForConfirmationToContinue( - 'The release branch and the tag are going to be pushed to the remote repository. Continue?', - true, - abortMessage - ); - await simpleGit.push( 'origin', releaseBranch ); - await simpleGit.pushTags( 'origin' ); - } ); + await runStep( + 'Pushing the release branch and the tag', + abortMessage, + async () => { + const simpleGit = SimpleGit( gitWorkingDirectoryPath ); + await askForConfirmationToContinue( + 'The release branch and the tag are going to be pushed to the remote repository. Continue?', + true, + abortMessage + ); + await simpleGit.push( 'origin', releaseBranch ); + await simpleGit.pushTags( 'origin' ); + } + ); } /** @@ -504,7 +649,13 @@ async function runPushGitChangesStep( releaseBranch, abortMessage ) { * * @return {Object} Github release object. */ -async function runGithubReleaseStep( version, versionLabel, changelog, isPrerelease, abortMessage ) { +async function runGithubReleaseStep( + version, + versionLabel, + changelog, + isPrerelease, + abortMessage +) { let octokit; let release; await runStep( 'Creating the GitHub release', abortMessage, async () => { @@ -514,11 +665,18 @@ async function runGithubReleaseStep( version, versionLabel, changelog, isPrerele abortMessage ); - const { token } = await inquirer.prompt( [ { - type: 'input', - name: 'token', - message: 'Please provide a GitHub personal authentication token. Navigate to ' + success( 'https://github.com/settings/tokens/new?scopes=repo,admin:org,write:packages' ) + ' to create one.', - } ] ); + const { token } = await inquirer.prompt( [ + { + type: 'input', + name: 'token', + message: + 'Please provide a GitHub personal authentication token. Navigate to ' + + success( + 'https://github.com/settings/tokens/new?scopes=repo,admin:org,write:packages' + ) + + ' to create one.', + }, + ] ); octokit = new Octokit( { auth: token, @@ -536,7 +694,8 @@ async function runGithubReleaseStep( version, versionLabel, changelog, isPrerele console.log( '>> The GitHub release has been created.' ); } ); - abortMessage = abortMessage + ' Make sure to remove the the GitHub release as well.'; + abortMessage = + abortMessage + ' Make sure to remove the the GitHub release as well.'; // Uploading the Gutenberg Zip to the release await runStep( 'Uploading the plugin ZIP', abortMessage, async () => { @@ -554,7 +713,10 @@ async function runGithubReleaseStep( version, versionLabel, changelog, isPrerele console.log( '>> The plugin ZIP has been successfully uploaded.' ); } ); - console.log( '>> The GitHub release is available here: ' + success( release.html_url ) ); + console.log( + '>> The GitHub release is available here: ' + + success( release.html_url ) + ); return release; } @@ -565,21 +727,28 @@ async function runGithubReleaseStep( version, versionLabel, changelog, isPrerele * @param {string} commitHash Commit to cherry-pick. * @param {string} abortMessage Abort message. */ -async function runCherrypickBumpCommitIntoMasterStep( commitHash, abortMessage ) { - await runStep( 'Cherry-picking the bump commit into master', abortMessage, async () => { - const simpleGit = SimpleGit( gitWorkingDirectoryPath ); - await askForConfirmationToContinue( - 'The plugin is now released. Proceed with the version bump in the master branch?', - true, - abortMessage - ); - await simpleGit.fetch(); - await simpleGit.reset( 'hard' ); - await simpleGit.checkout( 'master' ); - await simpleGit.pull( 'origin', 'master' ); - await simpleGit.raw( [ 'cherry-pick', commitHash ] ); - await simpleGit.push( 'origin', 'master' ); - } ); +async function runCherrypickBumpCommitIntoMasterStep( + commitHash, + abortMessage +) { + await runStep( + 'Cherry-picking the bump commit into master', + abortMessage, + async () => { + const simpleGit = SimpleGit( gitWorkingDirectoryPath ); + await askForConfirmationToContinue( + 'The plugin is now released. Proceed with the version bump in the master branch?', + true, + abortMessage + ); + await simpleGit.fetch(); + await simpleGit.reset( 'hard' ); + await simpleGit.checkout( 'master' ); + await simpleGit.pull( 'origin', 'master' ); + await simpleGit.raw( [ 'cherry-pick', commitHash ] ); + await simpleGit.push( 'origin', 'master' ); + } + ); } /** @@ -594,22 +763,28 @@ async function releasePlugin( isRC = true ) { let abortMessage = 'Aborting!'; await askForConfirmationToContinue( 'Ready to go? ' ); - const { changelog } = await inquirer.prompt( [ { - type: 'editor', - name: 'changelog', - message: 'Please provide the CHANGELOG of the release (markdown)', - } ] ); + const { changelog } = await inquirer.prompt( [ + { + type: 'editor', + name: 'changelog', + message: 'Please provide the CHANGELOG of the release (markdown)', + }, + ] ); // Cloning the Git repository await runGitRepositoryCloneStep( abortMessage ); // Creating the release branch - const { version, versionLabel, releaseBranch } = isRC ? - await runReleaseBranchCreationStep( abortMessage ) : - await runReleaseBranchCheckoutStep( abortMessage ); + const { version, versionLabel, releaseBranch } = isRC + ? await runReleaseBranchCreationStep( abortMessage ) + : await runReleaseBranchCheckoutStep( abortMessage ); // Bumping the version and commit. - const commitHash = await runBumpPluginVersionAndCommitStep( version, changelog, abortMessage ); + const commitHash = await runBumpPluginVersionAndCommitStep( + version, + changelog, + abortMessage + ); // Plugin ZIP creation await runPluginZIPCreationStep(); @@ -619,22 +794,39 @@ async function releasePlugin( isRC = true ) { // Push the local changes await runPushGitChangesStep( releaseBranch, abortMessage ); - abortMessage = 'Aborting! Make sure to ' + isRC ? 'remove' : 'reset' + ' the remote release branch and remove the git tag.'; + abortMessage = + 'Aborting! Make sure to ' + isRC + ? 'remove' + : 'reset' + ' the remote release branch and remove the git tag.'; // Creating the GitHub Release - const release = await runGithubReleaseStep( version, versionLabel, changelog, isRC, abortMessage ); - abortMessage = 'Aborting! Make sure to manually cherry-pick the ' + success( commitHash ) + ' commit to the master branch.'; + const release = await runGithubReleaseStep( + version, + versionLabel, + changelog, + isRC, + abortMessage + ); + abortMessage = + 'Aborting! Make sure to manually cherry-pick the ' + + success( commitHash ) + + ' commit to the master branch.'; if ( ! isRC ) { - abortMessage += ' Make sure to perform the SVN release manually as well.'; + abortMessage += + ' Make sure to perform the SVN release manually as well.'; } // Cherry-picking the bump commit into master await runCherrypickBumpCommitIntoMasterStep( commitHash, abortMessage ); if ( ! isRC ) { - abortMessage = 'Aborting! The GitHub release is done. Make sure to perform the SVN release manually.'; + abortMessage = + 'Aborting! The GitHub release is done. Make sure to perform the SVN release manually.'; - await askForConfirmationToContinue( 'The GitHub release is complete. Proceed with the SVN release? ', abortMessage ); + await askForConfirmationToContinue( + 'The GitHub release is complete. Proceed with the SVN release? ', + abortMessage + ); // Fetching the SVN repository await runSvnRepositoryCloneStep( abortMessage ); @@ -642,10 +834,12 @@ async function releasePlugin( isRC = true ) { // Updating the SVN trunk content await runUpdateTrunkContentStep( version, release.body, abortMessage ); - abortMessage = 'Aborting! The GitHub release is done, SVN trunk updated. Make sure to create the SVN tag and update the stable version manually.'; + abortMessage = + 'Aborting! The GitHub release is done, SVN trunk updated. Make sure to create the SVN tag and update the stable version manually.'; await runSvnTagStep( version, abortMessage ); - abortMessage = 'Aborting! The GitHub release is done, SVN tagged. Make sure to update the stable version manually.'; + abortMessage = + 'Aborting! The GitHub release is done, SVN tagged. Make sure to update the stable version manually.'; await updateThePluginStableVersion( version, abortMessage ); } @@ -658,20 +852,26 @@ async function releasePlugin( isRC = true ) { program .command( 'release-plugin-rc' ) .alias( 'rc' ) - .description( 'Release an RC version of the plugin (supports only rc.1 for now)' ) + .description( + 'Release an RC version of the plugin (supports only rc.1 for now)' + ) .action( async () => { console.log( chalk.bold( '💃 Time to release Gutenberg 🕺\n\n' ), 'Welcome! This tool is going to help you release a new RC version of the Gutenberg Plugin.\n', 'It goes through different steps : creating the release branch, bumping the plugin version, tagging and creating the GitHub release, building the ZIP...\n', - 'To perform a release you\'ll have to be a member of the Gutenberg Core Team.\n' + "To perform a release you'll have to be a member of the Gutenberg Core Team.\n" ); const release = await releasePlugin( true ); console.log( - '\n>> 🎉 The Gutenberg version ' + success( release.name ) + ' has been successfully released.\n', - 'You can access the GitHub release here: ' + success( release.html_url ) + '\n', + '\n>> 🎉 The Gutenberg version ' + + success( release.name ) + + ' has been successfully released.\n', + 'You can access the GitHub release here: ' + + success( release.html_url ) + + '\n', 'Thanks for performing the release!' ); } ); @@ -685,16 +885,20 @@ program chalk.bold( '💃 Time to release Gutenberg 🕺\n\n' ), 'Welcome! This tool is going to help you release a new stable version of the Gutenberg Plugin.\n', 'It goes through different steps : bumping the plugin version, tagging and creating the GitHub release, building the ZIP, pushing the release to the SVN repository...\n', - 'To perform a release you\'ll have to be a member of the Gutenberg Core Team.\n' + "To perform a release you'll have to be a member of the Gutenberg Core Team.\n" ); const release = await releasePlugin( false ); console.log( - '\n>> 🎉 The Gutenberg ' + success( release.name ) + ' has been successfully released.\n', - 'You can access the GitHub release here: ' + success( release.html_url ) + '\n', - 'In a few minutes, you\'ll be able to update the plugin from the WordPress repository.\n', - 'Thanks for performing the release! and don\'t forget to publish the release post.' + '\n>> 🎉 The Gutenberg ' + + success( release.name ) + + ' has been successfully released.\n', + 'You can access the GitHub release here: ' + + success( release.html_url ) + + '\n', + "In a few minutes, you'll be able to update the plugin from the WordPress repository.\n", + "Thanks for performing the release! and don't forget to publish the release post." ); } ); @@ -706,26 +910,47 @@ program */ async function runWordPressReleaseBranchSyncStep( abortMessage ) { const wordpressReleaseBranch = 'wp/trunk'; - await runStep( 'Getting into the WordPress release branch', abortMessage, async () => { - const simpleGit = SimpleGit( gitWorkingDirectoryPath ); - const packageJsonPath = gitWorkingDirectoryPath + '/package.json'; - const pluginReleaseBranch = findReleaseBranchName( packageJsonPath ); - - // Creating the release branch - await simpleGit.checkout( wordpressReleaseBranch ); - console.log( '>> The local release branch ' + success( wordpressReleaseBranch ) + ' has been successfully checked out.' ); - - await askForConfirmationToContinue( - `The branch is ready for sync with the latest plugin release changes applied to "${ pluginReleaseBranch }". Proceed?`, - true, - abortMessage - ); - - await simpleGit.raw( [ 'rm', '-r', '.' ] ); - await simpleGit.raw( [ 'checkout', `origin/${ pluginReleaseBranch }`, '--', '.' ] ); - await simpleGit.commit( `Merge changes published in the Gutenberg plugin "${ pluginReleaseBranch }" branch` ); - console.log( '>> The local WordPress release branch ' + success( wordpressReleaseBranch ) + ' has been successfully synced.' ); - } ); + await runStep( + 'Getting into the WordPress release branch', + abortMessage, + async () => { + const simpleGit = SimpleGit( gitWorkingDirectoryPath ); + const packageJsonPath = gitWorkingDirectoryPath + '/package.json'; + const pluginReleaseBranch = findReleaseBranchName( + packageJsonPath + ); + + // Creating the release branch + await simpleGit.checkout( wordpressReleaseBranch ); + console.log( + '>> The local release branch ' + + success( wordpressReleaseBranch ) + + ' has been successfully checked out.' + ); + + await askForConfirmationToContinue( + `The branch is ready for sync with the latest plugin release changes applied to "${ pluginReleaseBranch }". Proceed?`, + true, + abortMessage + ); + + await simpleGit.raw( [ 'rm', '-r', '.' ] ); + await simpleGit.raw( [ + 'checkout', + `origin/${ pluginReleaseBranch }`, + '--', + '.', + ] ); + await simpleGit.commit( + `Merge changes published in the Gutenberg plugin "${ pluginReleaseBranch }" branch` + ); + console.log( + '>> The local WordPress release branch ' + + success( wordpressReleaseBranch ) + + ' has been successfully synced.' + ); + } + ); return { releaseBranch: wordpressReleaseBranch, @@ -740,85 +965,109 @@ async function runWordPressReleaseBranchSyncStep( abortMessage ) { * @param {string} abortMessage Abort Message. */ async function updatePackageChangelogs( minimumVersionBump, abortMessage ) { - const changelogFiles = await glob( path.resolve( gitWorkingDirectoryPath, 'packages/*/CHANGELOG.md' ) ); - const processedPackages = await Promise.all( changelogFiles.map( async ( changelogFile ) => { - const fileStream = fs.createReadStream( changelogFile ); - - const lines = readline.createInterface( { - input: fileStream, - } ); - - let changesDetected = false; - let versionBump = null; - for await ( const line of lines ) { - // Detect unpublished changes first. - if ( line.startsWith( '## Master' ) ) { - changesDetected = true; - continue; - } - - // Skip all lines until unpublished changes found. - if ( ! changesDetected ) { - continue; - } - - // A previous published version detected. Stop processing. - if ( line.startsWith( '## ' ) ) { - break; - } - - // A major version bump required. Stop processing. - if ( line.startsWith( '### Breaking Change' ) ) { - versionBump = 'major'; - break; - } - - // A minor version bump required. Proceed to the next line. - if ( line.startsWith( '### New Feature' ) || line.startsWith( '### Deprecation' ) ) { - versionBump = 'minor'; - continue; - } - - // A version bump required. Found new changelog section. - if ( versionBump !== 'minor' && line.startsWith( '### ' ) ) { - versionBump = minimumVersionBump; + const changelogFiles = await glob( + path.resolve( gitWorkingDirectoryPath, 'packages/*/CHANGELOG.md' ) + ); + const processedPackages = await Promise.all( + changelogFiles.map( async ( changelogFile ) => { + const fileStream = fs.createReadStream( changelogFile ); + + const lines = readline.createInterface( { + input: fileStream, + } ); + + let changesDetected = false; + let versionBump = null; + for await ( const line of lines ) { + // Detect unpublished changes first. + if ( line.startsWith( '## Master' ) ) { + changesDetected = true; + continue; + } + + // Skip all lines until unpublished changes found. + if ( ! changesDetected ) { + continue; + } + + // A previous published version detected. Stop processing. + if ( line.startsWith( '## ' ) ) { + break; + } + + // A major version bump required. Stop processing. + if ( line.startsWith( '### Breaking Change' ) ) { + versionBump = 'major'; + break; + } + + // A minor version bump required. Proceed to the next line. + if ( + line.startsWith( '### New Feature' ) || + line.startsWith( '### Deprecation' ) + ) { + versionBump = 'minor'; + continue; + } + + // A version bump required. Found new changelog section. + if ( versionBump !== 'minor' && line.startsWith( '### ' ) ) { + versionBump = minimumVersionBump; + } } - } - const packageName = `@wordpress/${ changelogFile.split( '/' ).reverse()[ 1 ] }`; - const { version } = readJSONFile( changelogFile.replace( 'CHANGELOG.md', 'package.json' ) ); - const nextVersion = ( versionBump !== null ) ? - semver.inc( version, versionBump ) : - null; - - return { - changelogFile, - packageName, - version, - nextVersion, - }; - } ) ); + const packageName = `@wordpress/${ + changelogFile.split( '/' ).reverse()[ 1 ] + }`; + const { version } = readJSONFile( + changelogFile.replace( 'CHANGELOG.md', 'package.json' ) + ); + const nextVersion = + versionBump !== null + ? semver.inc( version, versionBump ) + : null; + + return { + changelogFile, + packageName, + version, + nextVersion, + }; + } ) + ); - const changelogsToUpdate = processedPackages. - filter( ( { nextVersion } ) => nextVersion ); + const changelogsToUpdate = processedPackages.filter( + ( { nextVersion } ) => nextVersion + ); if ( changelogsToUpdate.length === 0 ) { console.log( '>> No changes in CHANGELOG files detected.' ); return; } - console.log( '>> Recommended version bumps based on the changes detected in CHANGELOG files:' ); + console.log( + '>> Recommended version bumps based on the changes detected in CHANGELOG files:' + ); const publishDate = new Date().toISOString().split( 'T' )[ 0 ]; await Promise.all( - changelogsToUpdate - .map( async ( { changelogFile, packageName, nextVersion, version } ) => { - const content = await fs.promises.readFile( changelogFile, 'utf8' ); - await fs.promises.writeFile( changelogFile, content.replace( - '## Master', - `## Master\n\n## ${ nextVersion } (${ publishDate })` - ) ); - console.log( ` - ${ packageName }: ${ version } -> ${ nextVersion }` ); - } ) + changelogsToUpdate.map( + async ( { changelogFile, packageName, nextVersion, version } ) => { + const content = await fs.promises.readFile( + changelogFile, + 'utf8' + ); + await fs.promises.writeFile( + changelogFile, + content.replace( + '## Master', + `## Master\n\n## ${ nextVersion } (${ publishDate })` + ) + ); + console.log( + ` - ${ packageName }: ${ version } -> ${ nextVersion }` + ); + } + ) ); await askForConfirmationToContinue( @@ -829,7 +1078,9 @@ async function updatePackageChangelogs( minimumVersionBump, abortMessage ) { const simpleGit = SimpleGit( gitWorkingDirectoryPath ); await simpleGit.add( './*' ); await simpleGit.commit( 'Update changelog files' ); - console.log( '>> Changelog files changes have been committed successfully.' ); + console.log( + '>> Changelog files changes have been committed successfully.' + ); } /** @@ -848,7 +1099,9 @@ async function prepublishPackages( minimumVersionBump ) { await runGitRepositoryCloneStep( abortMessage ); // Checking out the WordPress release branch and doing sync with the last plugin release. - const { releaseBranch } = await runWordPressReleaseBranchSyncStep( abortMessage ); + const { releaseBranch } = await runWordPressReleaseBranchSyncStep( + abortMessage + ); await updatePackageChangelogs( minimumVersionBump, abortMessage ); @@ -863,12 +1116,14 @@ async function prepublishPackages( minimumVersionBump ) { program .command( 'prepublish-packages-stable' ) .alias( 'npm-stable' ) - .description( 'Prepublish to npm steps for the next stable version of WordPress packages' ) + .description( + 'Prepublish to npm steps for the next stable version of WordPress packages' + ) .action( async () => { console.log( chalk.bold( '💃 Time to publish WordPress packages to npm 🕺\n\n' ), 'Welcome! This tool is going to help you with prepublish to npm steps for the next stable version of WordPress packages.\n', - 'To perform a release you\'ll have to be a member of the WordPress Team on npm.\n' + "To perform a release you'll have to be a member of the WordPress Team on npm.\n" ); await prepublishPackages( 'minor' ); diff --git a/bin/generate-public-grammar.js b/bin/generate-public-grammar.js index c56ec4398a8941..0c2bc74fc42241 100755 --- a/bin/generate-public-grammar.js +++ b/bin/generate-public-grammar.js @@ -2,7 +2,10 @@ const parser = require( '../node_modules/pegjs/lib/parser.js' ); const fs = require( 'fs' ); const path = require( 'path' ); -const grammarSource = fs.readFileSync( './packages/block-serialization-spec-parser/grammar.pegjs', 'utf8' ); +const grammarSource = fs.readFileSync( + './packages/block-serialization-spec-parser/grammar.pegjs', + 'utf8' +); const grammar = parser.parse( grammarSource ); function escape( text ) { @@ -15,12 +18,11 @@ function escape( text ) { } function isGroup( expression ) { - return [ - 'choice', - 'action', - 'labeled', - 'sequence', - ].indexOf( expression.type ) >= 0; + return ( + [ 'choice', 'action', 'labeled', 'sequence' ].indexOf( + expression.type + ) >= 0 + ); } function flattenUnary( expression ) { @@ -40,11 +42,17 @@ function flatten( expression ) { return '"' + escape( expression.value ) + '"'; case 'class': return ( - '[' + ( expression.inverted ? '^' : '' ) + - expression.parts.map( ( part ) => - escape( Array.isArray( part ) ? part.join( '-' ) : part ) - ).join( '' ) + - ']' + ( expression.ignoreCase ? 'i' : '' ) + '[' + + ( expression.inverted ? '^' : '' ) + + expression.parts + .map( ( part ) => + escape( + Array.isArray( part ) ? part.join( '-' ) : part + ) + ) + .join( '' ) + + ']' + + ( expression.ignoreCase ? 'i' : '' ) ); // Unary @@ -80,11 +88,15 @@ function flatten( expression ) { return `<dl>${ expression.rules.map( flatten ).join( '' ) }</dl>`; case 'rule': expression.expression.isRuleTop = true; - const displayName = expression.expression.type === 'named' ? - expression.expression.name : ''; - return `<dt>${ displayName }</dt>` + + const displayName = + expression.expression.type === 'named' + ? expression.expression.name + : ''; + return ( + `<dt>${ displayName }</dt>` + `<dd><pre><header>${ expression.name }</header> = ` + - `${ flatten( expression.expression ) }</pre></dd>`; + `${ flatten( expression.expression ) }</pre></dd>` + ); default: throw new Error( JSON.stringify( expression ) ); @@ -92,8 +104,10 @@ function flatten( expression ) { } fs.writeFileSync( - path.join( __dirname, '..', 'docs', 'grammar.md' ), ` + path.join( __dirname, '..', 'docs', 'grammar.md' ), + ` # Block Grammar ${ flatten( grammar ) } -` ); +` +); diff --git a/bin/packages/build-worker.js b/bin/packages/build-worker.js index d0f1d1c809984a..e7f2b6f058fbda 100644 --- a/bin/packages/build-worker.js +++ b/bin/packages/build-worker.js @@ -84,8 +84,14 @@ function getBuildPath( file, buildFolder ) { */ const BUILD_TASK_BY_EXTENSION = { async '.scss'( file ) { - const outputFile = getBuildPath( file.replace( '.scss', '.css' ), 'build-style' ); - const outputFileRTL = getBuildPath( file.replace( '.scss', '-rtl.css' ), 'build-style' ); + const outputFile = getBuildPath( + file.replace( '.scss', '.css' ), + 'build-style' + ); + const outputFileRTL = getBuildPath( + file.replace( '.scss', '-rtl.css' ), + 'build-style' + ); const [ , contents ] = await Promise.all( [ makeDir( path.dirname( outputFile ) ), @@ -95,7 +101,7 @@ const BUILD_TASK_BY_EXTENSION = { const builtSass = await renderSass( { file, includePaths: [ path.join( PACKAGES_DIR, 'base-styles' ) ], - data: ( + data: [ 'colors', 'breakpoints', @@ -103,20 +109,26 @@ const BUILD_TASK_BY_EXTENSION = { 'mixins', 'animations', 'z-index', - ].map( ( imported ) => `@import "${ imported }";` ).join( ' ' ) + - contents - ), + ] + .map( ( imported ) => `@import "${ imported }";` ) + .join( ' ' ) + contents, } ); - const result = await postcss( require( './post-css-config' ) ).process( builtSass.css, { - from: 'src/app.css', - to: 'dest/app.css', - } ); - - const resultRTL = await postcss( [ require( 'rtlcss' )() ] ).process( result.css, { - from: 'src/app.css', - to: 'dest/app.css', - } ); + const result = await postcss( require( './post-css-config' ) ).process( + builtSass.css, + { + from: 'src/app.css', + to: 'dest/app.css', + } + ); + + const resultRTL = await postcss( [ require( 'rtlcss' )() ] ).process( + result.css, + { + from: 'src/app.css', + to: 'dest/app.css', + } + ); await Promise.all( [ writeFile( outputFile, result.css ), @@ -125,9 +137,14 @@ const BUILD_TASK_BY_EXTENSION = { }, async '.js'( file ) { - for ( const [ environment, buildDir ] of Object.entries( JS_ENVIRONMENTS ) ) { + for ( const [ environment, buildDir ] of Object.entries( + JS_ENVIRONMENTS + ) ) { const destPath = getBuildPath( file, buildDir ); - const babelOptions = getBabelConfig( environment, file.replace( PACKAGES_DIR, '@wordpress' ) ); + const babelOptions = getBabelConfig( + environment, + file.replace( PACKAGES_DIR, '@wordpress' ) + ); const [ , transformed ] = await Promise.all( [ makeDir( path.dirname( destPath ) ), @@ -135,8 +152,17 @@ const BUILD_TASK_BY_EXTENSION = { ] ); await Promise.all( [ - writeFile( destPath + '.map', JSON.stringify( transformed.map ) ), - writeFile( destPath, transformed.code + '\n//# sourceMappingURL=' + path.basename( destPath ) + '.map' ), + writeFile( + destPath + '.map', + JSON.stringify( transformed.map ) + ), + writeFile( + destPath, + transformed.code + + '\n//# sourceMappingURL=' + + path.basename( destPath ) + + '.map' + ), ] ); } }, diff --git a/bin/packages/build.js b/bin/packages/build.js index 17006dbdc8638a..8d1a0c9cc17875 100755 --- a/bin/packages/build.js +++ b/bin/packages/build.js @@ -37,7 +37,7 @@ function getPackageName( file ) { * @return {Transform} Stream transform instance. */ function createStyleEntryTransform() { - const packages = new Set; + const packages = new Set(); return new Transform( { objectMode: true, @@ -57,7 +57,9 @@ function createStyleEntryTransform() { } packages.add( packageName ); - const entries = await glob( path.resolve( PACKAGES_DIR, packageName, 'src/*.scss' ) ); + const entries = await glob( + path.resolve( PACKAGES_DIR, packageName, 'src/*.scss' ) + ); entries.forEach( ( entry ) => this.push( entry ) ); callback(); }, @@ -73,12 +75,14 @@ function createStyleEntryTransform() { * @return {Transform} Stream transform instance. */ function createBlockJsonEntryTransform() { - const blocks = new Set; + const blocks = new Set(); return new Transform( { objectMode: true, async transform( file, encoding, callback ) { - const matches = /block-library[\/\\]src[\/\\](.*)[\/\\]block.json$/.exec( file ); + const matches = /block-library[\/\\]src[\/\\](.*)[\/\\]block.json$/.exec( + file + ); const blockName = matches ? matches[ 1 ] : undefined; // Only block.json files in the block-library folder are subject to this transform. @@ -121,27 +125,25 @@ if ( files.length ) { bar.tick( 0 ); - stream = glob.stream( [ - `${ PACKAGES_DIR }/*/src/**/*.js`, - `${ PACKAGES_DIR }/*/src/*.scss`, - ], { - ignore: [ - `**/benchmark/**`, - `**/{__mocks__,__tests__,test}/**`, - `**/{storybook,stories}/**`, - ], - onlyFiles: true, - } ); + stream = glob.stream( + [ `${ PACKAGES_DIR }/*/src/**/*.js`, `${ PACKAGES_DIR }/*/src/*.scss` ], + { + ignore: [ + `**/benchmark/**`, + `**/{__mocks__,__tests__,test}/**`, + `**/{storybook,stories}/**`, + ], + onlyFiles: true, + } + ); // Pause to avoid data flow which would begin on the `data` event binding, // but should wait until worker processing below. // // See: https://nodejs.org/api/stream.html#stream_two_reading_modes - stream - .pause() - .on( 'data', ( file ) => { - bar.total = files.push( file ); - } ); + stream.pause().on( 'data', ( file ) => { + bar.total = files.push( file ); + } ); onFileComplete = () => { bar.tick(); @@ -154,27 +156,29 @@ let ended = false, complete = 0; stream - .on( 'data', ( file ) => worker( file, ( error ) => { - onFileComplete(); - - if ( error ) { - // If an error occurs, the process can't be ended immediately since - // other workers are likely pending. Optimally, it would end at the - // earliest opportunity (after the current round of workers has had - // the chance to complete), but this is not made directly possible - // through `worker-farm`. Instead, ensure at least that when the - // process does exit, it exits with a non-zero code to reflect the - // fact that an error had occurred. - process.exitCode = 1; - - console.error( error ); - } + .on( 'data', ( file ) => + worker( file, ( error ) => { + onFileComplete(); + + if ( error ) { + // If an error occurs, the process can't be ended immediately since + // other workers are likely pending. Optimally, it would end at the + // earliest opportunity (after the current round of workers has had + // the chance to complete), but this is not made directly possible + // through `worker-farm`. Instead, ensure at least that when the + // process does exit, it exits with a non-zero code to reflect the + // fact that an error had occurred. + process.exitCode = 1; + + console.error( error ); + } - if ( ended && ++complete === files.length ) { - workerFarm.end( worker ); - } - } ) ) - .on( 'end', () => ended = true ) + if ( ended && ++complete === files.length ) { + workerFarm.end( worker ); + } + } ) + ) + .on( 'end', () => ( ended = true ) ) .resume(); /* eslint-enable no-console */ diff --git a/bin/packages/get-babel-config.js b/bin/packages/get-babel-config.js index d76e171d46b214..3df510f1ac1066 100644 --- a/bin/packages/get-babel-config.js +++ b/bin/packages/get-babel-config.js @@ -6,9 +6,11 @@ module.exports = function( environment = '', file ) { * The caller options can only be 'boolean', 'string', or 'number' by design: * https://github.com/babel/babel/blob/bd0c62dc0c30cf16a4d4ef0ddf21d386f673815c/packages/babel-core/src/config/validation/option-assertions.js#L122 */ - const callerOpts = { caller: { - name: `WP_BUILD_${ environment.toUpperCase() }`, - } }; + const callerOpts = { + caller: { + name: `WP_BUILD_${ environment.toUpperCase() }`, + }, + }; switch ( environment ) { case 'main': // to be merged as a presetEnv option diff --git a/bin/packages/watch.js b/bin/packages/watch.js index b307029dcd568f..8d5e650f6bac3c 100644 --- a/bin/packages/watch.js +++ b/bin/packages/watch.js @@ -27,7 +27,13 @@ const exists = ( filename ) => { // and files with a suffix of .test or .spec (e.g. blocks.test.js), // and deceitful source-like files, such as editor swap files. const isSourceFile = ( filename ) => { - return ! [ /\/(benchmark|__mocks__|__tests__|test|storybook|stories)\/.+.js$/, /.\.(spec|test)\.js$/ ].some( ( regex ) => regex.test( filename ) ) && /.\.(js|json|scss)$/.test( filename ); + return ( + ! [ + /\/(benchmark|__mocks__|__tests__|test|storybook|stories)\/.+.js$/, + /.\.(spec|test)\.js$/, + ].some( ( regex ) => regex.test( filename ) ) && + /.\.(js|json|scss)$/.test( filename ) + ); }; const rebuild = ( filename ) => filesToBuild.set( filename, true ); @@ -36,29 +42,44 @@ getPackages().forEach( ( p ) => { const srcDir = path.resolve( p, 'src' ); try { fs.accessSync( srcDir, fs.F_OK ); - watch( path.resolve( p, 'src' ), { recursive: true }, ( event, filename ) => { - if ( ! isSourceFile( filename ) ) { - return; - } + watch( + path.resolve( p, 'src' ), + { recursive: true }, + ( event, filename ) => { + if ( ! isSourceFile( filename ) ) { + return; + } - const filePath = path.resolve( srcDir, filename ); - if ( ( event === 'update' ) && exists( filePath ) ) { - // eslint-disable-next-line no-console - console.log( chalk.green( '->' ), `${ event }: ${ filename }` ); - rebuild( filePath ); - } else { - const buildFile = path.resolve( srcDir, '..', 'build', filename ); - try { - fs.unlinkSync( buildFile ); - process.stdout.write( - chalk.red( ' \u2022 ' ) + - path.relative( path.resolve( srcDir, '..', '..' ), buildFile ) + - ' (deleted)' + - '\n' + const filePath = path.resolve( srcDir, filename ); + if ( event === 'update' && exists( filePath ) ) { + // eslint-disable-next-line no-console + console.log( + chalk.green( '->' ), + `${ event }: ${ filename }` + ); + rebuild( filePath ); + } else { + const buildFile = path.resolve( + srcDir, + '..', + 'build', + filename ); - } catch ( e ) {} + try { + fs.unlinkSync( buildFile ); + process.stdout.write( + chalk.red( ' \u2022 ' ) + + path.relative( + path.resolve( srcDir, '..', '..' ), + buildFile + ) + + ' (deleted)' + + '\n' + ); + } catch ( e ) {} + } } - } ); + ); } catch ( e ) { // doesn't exist } diff --git a/bin/process-git-diff.js b/bin/process-git-diff.js index b80b117daa59e4..6505954143e332 100644 --- a/bin/process-git-diff.js +++ b/bin/process-git-diff.js @@ -28,7 +28,10 @@ const hasNonOptionalDiff = !! ( process.argv[ 2 ] || '' ) // Strip individual diffs of optional-only. .replace( /@@ .+ @@\n(-.+\n\+.+,\n)?\+.+\"optional\": true,?\n/gm, '' ) // If no more line diffs remain after above, remove diff heading for file. - .replace( /diff --git a\/package-lock.json b\/package-lock.json\nindex \w+..\w+ \d+\n--- a\/package-lock.json\n\+\+\+ b\/package-lock.json\n(?!@@)/, '' ); + .replace( + /diff --git a\/package-lock.json b\/package-lock.json\nindex \w+..\w+ \d+\n--- a\/package-lock.json\n\+\+\+ b\/package-lock.json\n(?!@@)/, + '' + ); // Exit with error code if, after replace, changes still exist. process.exit( hasNonOptionalDiff ? 1 : 0 ); diff --git a/docs/tool/are-data-files-unstaged.js b/docs/tool/are-data-files-unstaged.js index 5591de0ae6d62f..38f659ec9bb0c7 100644 --- a/docs/tool/are-data-files-unstaged.js +++ b/docs/tool/are-data-files-unstaged.js @@ -11,17 +11,30 @@ const execSync = require( 'child_process' ).execSync; */ const getPackages = require( './packages' ); -const getUnstagedFiles = () => execSync( 'git diff --name-only', { encoding: 'utf8' } ).split( '\n' ).filter( ( element ) => '' !== element ); -const readmeFiles = getPackages().map( ( [ packageName ] ) => `docs/designers-developers/developers/data/data-${ packageName.replace( '/', '-' ) }.md` ); -const unstagedReadmes = getUnstagedFiles().filter( ( element ) => readmeFiles.includes( element ) ); +const getUnstagedFiles = () => + execSync( 'git diff --name-only', { encoding: 'utf8' } ) + .split( '\n' ) + .filter( ( element ) => '' !== element ); +const readmeFiles = getPackages().map( + ( [ packageName ] ) => + `docs/designers-developers/developers/data/data-${ packageName.replace( + '/', + '-' + ) }.md` +); +const unstagedReadmes = getUnstagedFiles().filter( ( element ) => + readmeFiles.includes( element ) +); if ( unstagedReadmes.length > 0 ) { process.exitCode = 1; - process.stdout.write( chalk.red( - '\n', - 'Some API docs may be out of date:', - unstagedReadmes.toString(), - 'Either stage them or continue with --no-verify.', - '\n' - ) ); + process.stdout.write( + chalk.red( + '\n', + 'Some API docs may be out of date:', + unstagedReadmes.toString(), + 'Either stage them or continue with --no-verify.', + '\n' + ) + ); } diff --git a/docs/tool/index.js b/docs/tool/index.js index cd0e8223f9a6b7..074162266c364c 100644 --- a/docs/tool/index.js +++ b/docs/tool/index.js @@ -18,4 +18,7 @@ const manifestOutput = path.resolve( __dirname, '../manifest-devhub.json' ); execFileSync( 'node', [ join( __dirname, 'update-data.js' ) ] ); // Process TOC file and generate manifest handbook -fs.writeFileSync( manifestOutput, JSON.stringify( getRootManifest( tocFileInput ), undefined, '\t' ) ); +fs.writeFileSync( + manifestOutput, + JSON.stringify( getRootManifest( tocFileInput ), undefined, '\t' ) +); diff --git a/docs/tool/manifest.js b/docs/tool/manifest.js index adf9ff6488ff51..90c050388a0c50 100644 --- a/docs/tool/manifest.js +++ b/docs/tool/manifest.js @@ -85,9 +85,13 @@ function generateRootManifestFromTOCItems( items, parent = null ) { parent, } ); if ( Array.isArray( children ) && children.length ) { - pageItems = pageItems.concat( generateRootManifestFromTOCItems( children, slug ) ); + pageItems = pageItems.concat( + generateRootManifestFromTOCItems( children, slug ) + ); } else if ( children === '{{components}}' ) { - pageItems = pageItems.concat( getComponentManifest( componentPaths ) ); + pageItems = pageItems.concat( + getComponentManifest( componentPaths ) + ); } else if ( children === '{{packages}}' ) { pageItems = pageItems.concat( getPackageManifest( packagePaths ) ); } diff --git a/docs/tool/packages.js b/docs/tool/packages.js index 57d3d42b104b16..558a3e62c43c23 100644 --- a/docs/tool/packages.js +++ b/docs/tool/packages.js @@ -1,8 +1,11 @@ const packages = [ - [ 'core', { - 'Autogenerated actions': 'packages/core-data/src/actions.js', - 'Autogenerated selectors': 'packages/core-data/src/selectors.js', - } ], + [ + 'core', + { + 'Autogenerated actions': 'packages/core-data/src/actions.js', + 'Autogenerated selectors': 'packages/core-data/src/selectors.js', + }, + ], 'core/annotations', 'core/blocks', 'core/block-editor', @@ -17,10 +20,19 @@ const packages = [ module.exports = function() { return packages.map( ( entry ) => { if ( ! Array.isArray( entry ) ) { - entry = [ entry, { - 'Autogenerated actions': `packages/${ entry.replace( 'core/', '' ) }/src/store/actions.js`, - 'Autogenerated selectors': `packages/${ entry.replace( 'core/', '' ) }/src/store/selectors.js`, - } ]; + entry = [ + entry, + { + 'Autogenerated actions': `packages/${ entry.replace( + 'core/', + '' + ) }/src/store/actions.js`, + 'Autogenerated selectors': `packages/${ entry.replace( + 'core/', + '' + ) }/src/store/selectors.js`, + }, + ]; } return entry; } ); diff --git a/docs/tool/update-data.js b/docs/tool/update-data.js index 4a5d231e3c601f..89f0fc614f1c50 100644 --- a/docs/tool/update-data.js +++ b/docs/tool/update-data.js @@ -17,15 +17,25 @@ getPackages().forEach( ( entry ) => { // until docgen provides a way to update many tokens at once, we need to make sure // the output file is updated before starting the second pass for the next token. const { status, stderr } = spawnSync( - join( __dirname, '..', '..', 'node_modules', '.bin', 'docgen' ).replace( / /g, '\\ ' ), + join( + __dirname, + '..', + '..', + 'node_modules', + '.bin', + 'docgen' + ).replace( / /g, '\\ ' ), [ target, - `--output docs/designers-developers/developers/data/data-${ packageName.replace( '/', '-' ) }.md`, + `--output docs/designers-developers/developers/data/data-${ packageName.replace( + '/', + '-' + ) }.md`, '--to-token', `--use-token "${ token }"`, '--ignore "/unstable|experimental/i"', ], - { shell: true }, + { shell: true } ); if ( status !== 0 ) { diff --git a/experimental-default-global-styles.json b/experimental-default-global-styles.json new file mode 100644 index 00000000000000..9a9259b9f60979 --- /dev/null +++ b/experimental-default-global-styles.json @@ -0,0 +1,9 @@ +{ + "global": { + "color": { + "primary": "#52accc", + "background": "white", + "text": "black" + } + } +} diff --git a/lib/compat.php b/lib/compat.php index 3ab82002b2f925..cb85b3e3c72d70 100644 --- a/lib/compat.php +++ b/lib/compat.php @@ -44,6 +44,9 @@ function gutenberg_safe_style_css_column_flex_basis( $attr ) { */ function gutenberg_provide_render_callback_with_block_object( $pre_render, $block ) { global $post; + if ( 'core/navigation' !== $block['blockName'] ) { + return $pre_render; + } $source_block = $block; diff --git a/lib/global-styles.php b/lib/global-styles.php new file mode 100644 index 00000000000000..8074e454aeb8d0 --- /dev/null +++ b/lib/global-styles.php @@ -0,0 +1,131 @@ +<?php +/** + * Global styles functions, filters and actions, etc. + * + * @package gutenberg + */ + +/** + * Function that given a branch of the global styles object recursively generates + * an array defining all the css vars that the global styles object represents. + * + * @param array $global_styles_branch Array representing a brach of the global styles object. + * @param string $prefix Prefix to append to each variable. + * @param bool $is_start Indicates if we are on the first call to gutenberg_get_css_vars (outside the recursion). + * @return array An array whose keys are css variable names and whose values are the css variables value. + */ +function gutenberg_get_css_vars( $global_styles_branch, $prefix = '', $is_start = true ) { + $result = array(); + foreach ( $global_styles_branch as $key => $value ) { + $processed_key = str_replace( '/', '-', $key ); + $separator = $is_start ? '' : '--'; + $new_key = ( $prefix ? $prefix . $separator : '' ) . $processed_key; + + if ( is_array( $value ) ) { + $result = array_merge( + $result, + gutenberg_get_css_vars( $value, $new_key, false ) + ); + } else { + $result[ $new_key ] = $value; + } + } + return $result; +} + +/** + * Function responsible for enqueuing the style that define the global styles css variables. + */ +function gutenberg_enqueue_global_styles_assets() { + if ( ! locate_template( 'experimental-theme.json' ) ) { + return; + } + $default_global_styles_path = dirname( dirname( __FILE__ ) ) . '/experimental-default-global-styles.json'; + $default_global_styles = null; + + if ( file_exists( $default_global_styles_path ) ) { + $default_global_styles = json_decode( + file_get_contents( $default_global_styles_path ), + true + ); + } + + $theme_json_path = locate_template( 'experimental-theme.json' ); + $theme_json_global_styles = null; + if ( $theme_json_path ) { + $theme_json_global_styles = json_decode( + file_get_contents( $theme_json_path ), + true + ); + } + + // To-do: Load user customizations from a CPT. + $css_vars = array(); + foreach ( + array( + $default_global_styles, + $theme_json_global_styles, + ) as $global_styles_definition + ) { + if ( ! $global_styles_definition ) { + continue; + } + if ( isset( $global_styles_definition['global'] ) ) { + $css_vars = array_merge( + $css_vars, + gutenberg_get_css_vars( $global_styles_definition['global'], '--wp-' ) + ); + } + if ( isset( $global_styles_definition['blocks'] ) ) { + $css_vars = array_merge( + $css_vars, + gutenberg_get_css_vars( $global_styles_definition['blocks'], '--wp-block-' ) + ); + } + } + + if ( empty( $css_vars ) ) { + return; + } + + $inline_style = ":root {\n"; + foreach ( $css_vars as $var => $value ) { + $inline_style .= "\t" . $var . ': ' . $value . ";\n"; + } + $inline_style .= '}'; + + wp_register_style( 'global-styles', false, array(), true, true ); + wp_add_inline_style( 'global-styles', $inline_style ); + wp_enqueue_style( 'global-styles' ); +} +add_action( 'enqueue_block_assets', 'gutenberg_enqueue_global_styles_assets' ); + +/** + * Adds class wp-gs to the frontend body class if the theme defines a experimental-theme.json. + * + * @param array $classes Existing body classes. + * @return array The filtered array of body classes. + */ +function gutenberg_add_wp_gs_class_front_end( $classes ) { + if ( locate_template( 'experimental-theme.json' ) ) { + return array_merge( $classes, array( 'wp-gs' ) ); + } + return $classes; +} +add_filter( 'body_class', 'gutenberg_add_wp_gs_class_front_end' ); + + +/** + * Adds class wp-gs to the block-editor body class if the theme defines a experimental-theme.json. + * + * @param string $classes Existing body classes separated by space. + * @return string The filtered string of body classes. + */ +function gutenberg_add_wp_gs_class_editor( $classes ) { + global $current_screen; + if ( $current_screen->is_block_editor() && locate_template( 'experimental-theme.json' ) ) { + return $classes . ' wp-gs'; + } + return $classes; +} +add_filter( 'admin_body_class', 'gutenberg_add_wp_gs_class_editor' ); diff --git a/lib/load.php b/lib/load.php index 7076be29f63127..5b641a13278d1e 100644 --- a/lib/load.php +++ b/lib/load.php @@ -64,3 +64,4 @@ function gutenberg_is_experiment_enabled( $name ) { require dirname( __FILE__ ) . '/experiments-page.php'; require dirname( __FILE__ ) . '/customizer.php'; require dirname( __FILE__ ) . '/edit-site-page.php'; +require dirname( __FILE__ ) . '/global-styles.php'; diff --git a/package-lock.json b/package-lock.json index df74159a429cc8..1d5a493bf8e4e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10115,6 +10115,8 @@ "@wordpress/data": "file:packages/data", "@wordpress/date": "file:packages/date", "@wordpress/deprecated": "file:packages/deprecated", + "@wordpress/dom": "file:packages/dom", + "@wordpress/editor": "file:packages/editor", "@wordpress/element": "file:packages/element", "@wordpress/escape-html": "file:packages/escape-html", "@wordpress/i18n": "file:packages/i18n", @@ -10514,6 +10516,7 @@ "eslint-plugin-jest": "^22.15.1", "eslint-plugin-jsdoc": "^15.8.0", "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-react": "^7.14.3", "eslint-plugin-react-hooks": "^1.6.1", "globals": "^12.0.0", @@ -18025,6 +18028,15 @@ } } }, + "eslint-plugin-prettier": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz", + "integrity": "sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, "eslint-plugin-react": { "version": "7.14.3", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.14.3.tgz", @@ -18796,6 +18808,12 @@ "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", "dev": true }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "fast-glob": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", @@ -31478,6 +31496,15 @@ "integrity": "sha512-mqAC2r1NDmRjG+z3KCJ/i61tycKlmADIjxnDhQab+KBxSAGbF/W7/zwB2guy/ypIeKrrftNsIYkNZZQKf3vJcg==", "dev": true }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "pretty-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", diff --git a/package.json b/package.json index 2124fac1debc7e..38f2036891596d 100644 --- a/package.json +++ b/package.json @@ -241,6 +241,7 @@ "wp-scripts lint-style" ], "*.js": [ + "wp-scripts format-js", "wp-scripts lint-js" ], "{docs/{toc.json,tool/*.js},packages/{*/README.md,*/src/{actions,selectors}.js,components/src/*/**/README.md}}": [ diff --git a/packages/a11y/src/addContainer.js b/packages/a11y/src/addContainer.js index 3db5637d139422..edd733a6626a60 100644 --- a/packages/a11y/src/addContainer.js +++ b/packages/a11y/src/addContainer.js @@ -12,19 +12,20 @@ const addContainer = function( ariaLive ) { container.id = 'a11y-speak-' + ariaLive; container.className = 'a11y-speak-region'; - container.setAttribute( 'style', ( + container.setAttribute( + 'style', 'position: absolute;' + - 'margin: -1px;' + - 'padding: 0;' + - 'height: 1px;' + - 'width: 1px;' + - 'overflow: hidden;' + - 'clip: rect(1px, 1px, 1px, 1px);' + - '-webkit-clip-path: inset(50%);' + - 'clip-path: inset(50%);' + - 'border: 0;' + - 'word-wrap: normal !important;' - ) ); + 'margin: -1px;' + + 'padding: 0;' + + 'height: 1px;' + + 'width: 1px;' + + 'overflow: hidden;' + + 'clip: rect(1px, 1px, 1px, 1px);' + + '-webkit-clip-path: inset(50%);' + + 'clip-path: inset(50%);' + + 'border: 0;' + + 'word-wrap: normal !important;' + ); container.setAttribute( 'aria-live', ariaLive ); container.setAttribute( 'aria-relevant', 'additions text' ); container.setAttribute( 'aria-atomic', 'true' ); diff --git a/packages/a11y/src/index.js b/packages/a11y/src/index.js index cb79e767bb9c36..97d10c65920202 100644 --- a/packages/a11y/src/index.js +++ b/packages/a11y/src/index.js @@ -15,7 +15,9 @@ import filterMessage from './filterMessage'; */ export const setup = function() { const containerPolite = document.getElementById( 'a11y-speak-polite' ); - const containerAssertive = document.getElementById( 'a11y-speak-assertive' ); + const containerAssertive = document.getElementById( + 'a11y-speak-assertive' + ); if ( containerPolite === null ) { addContainer( 'polite' ); @@ -56,7 +58,9 @@ export const speak = function( message, ariaLive ) { message = filterMessage( message ); const containerPolite = document.getElementById( 'a11y-speak-polite' ); - const containerAssertive = document.getElementById( 'a11y-speak-assertive' ); + const containerAssertive = document.getElementById( + 'a11y-speak-assertive' + ); if ( containerAssertive && 'assertive' === ariaLive ) { containerAssertive.textContent = message; diff --git a/packages/a11y/src/index.native.js b/packages/a11y/src/index.native.js index b972687dbafce3..4e5e9841118b17 100644 --- a/packages/a11y/src/index.native.js +++ b/packages/a11y/src/index.native.js @@ -14,8 +14,6 @@ export const speak = function( message, ariaLive ) { message = filterMessage( message ); //TODO: Use native module to speak message if ( 'assertive' === ariaLive ) { - } else { - } }; diff --git a/packages/a11y/src/test/addContainer.test.js b/packages/a11y/src/test/addContainer.test.js index e6c722078f74f3..6ebf062237c9c0 100644 --- a/packages/a11y/src/test/addContainer.test.js +++ b/packages/a11y/src/test/addContainer.test.js @@ -13,7 +13,9 @@ describe( 'addContainer', () => { expect( container.id ).toBe( 'a11y-speak-polite' ); expect( container.getAttribute( 'style' ) ).not.toBeNull(); expect( container.getAttribute( 'aria-live' ) ).toBe( 'polite' ); - expect( container.getAttribute( 'aria-relevant' ) ).toBe( 'additions text' ); + expect( container.getAttribute( 'aria-relevant' ) ).toBe( + 'additions text' + ); expect( container.getAttribute( 'aria-atomic' ) ).toBe( 'true' ); } ); } ); @@ -27,7 +29,9 @@ describe( 'addContainer', () => { expect( container.id ).toBe( 'a11y-speak-assertive' ); expect( container.getAttribute( 'style' ) ).not.toBeNull(); expect( container.getAttribute( 'aria-live' ) ).toBe( 'assertive' ); - expect( container.getAttribute( 'aria-relevant' ) ).toBe( 'additions text' ); + expect( container.getAttribute( 'aria-relevant' ) ).toBe( + 'additions text' + ); expect( container.getAttribute( 'aria-atomic' ) ).toBe( 'true' ); } ); } ); @@ -41,7 +45,9 @@ describe( 'addContainer', () => { expect( container.id ).toBe( 'a11y-speak-polite' ); expect( container.getAttribute( 'style' ) ).not.toBeNull(); expect( container.getAttribute( 'aria-live' ) ).toBe( 'polite' ); - expect( container.getAttribute( 'aria-relevant' ) ).toBe( 'additions text' ); + expect( container.getAttribute( 'aria-relevant' ) ).toBe( + 'additions text' + ); expect( container.getAttribute( 'aria-atomic' ) ).toBe( 'true' ); } ); } ); diff --git a/packages/a11y/src/test/index.test.js b/packages/a11y/src/test/index.test.js index 08c9489bed2ae3..9888f9adb3a145 100644 --- a/packages/a11y/src/test/index.test.js +++ b/packages/a11y/src/test/index.test.js @@ -53,7 +53,9 @@ describe( 'speak', () => { it( 'should set the textcontent of the assertive aria-live region', () => { speak( 'assertive message', 'assertive' ); expect( containerPolite.textContent ).toBe( '' ); - expect( containerAssertive.textContent ).toBe( 'assertive message' ); + expect( containerAssertive.textContent ).toBe( + 'assertive message' + ); } ); } ); @@ -72,13 +74,17 @@ describe( 'speak', () => { afterEach( () => { setup(); - containerAssertive = document.getElementById( 'a11y-speak-assertive' ); + containerAssertive = document.getElementById( + 'a11y-speak-assertive' + ); } ); it( 'should set the textcontent of the polite aria-live region', () => { speak( 'message', 'assertive' ); expect( containerPolite.textContent ).toBe( 'message' ); - expect( document.getElementById( 'a11y-speak-assertive' ) ).toBe( null ); + expect( document.getElementById( 'a11y-speak-assertive' ) ).toBe( + null + ); } ); } ); @@ -91,20 +97,29 @@ describe( 'speak', () => { afterEach( () => { setup(); containerPolite = document.getElementById( 'a11y-speak-polite' ); - containerAssertive = document.getElementById( 'a11y-speak-assertive' ); + containerAssertive = document.getElementById( + 'a11y-speak-assertive' + ); } ); it( 'should set the textcontent of the polite aria-live region', () => { - expect( document.getElementById( 'a11y-speak-polite' ) ).toBe( null ); - expect( document.getElementById( 'a11y-speak-assertive' ) ).toBe( null ); + expect( document.getElementById( 'a11y-speak-polite' ) ).toBe( + null + ); + expect( document.getElementById( 'a11y-speak-assertive' ) ).toBe( + null + ); } ); } ); describe( 'setup when the elements already exist', () => { it( 'should not create the aria live regions again', () => { - const before = document.getElementsByClassName( 'a11y-speak-region' ).length; + const before = document.getElementsByClassName( + 'a11y-speak-region' + ).length; setup(); - const after = document.getElementsByClassName( 'a11y-speak-region' ).length; + const after = document.getElementsByClassName( 'a11y-speak-region' ) + .length; expect( before ).toBe( after ); } ); diff --git a/packages/annotations/src/block/index.js b/packages/annotations/src/block/index.js index e2677bbf6a9b3a..c27c170b2d64a1 100644 --- a/packages/annotations/src/block/index.js +++ b/packages/annotations/src/block/index.js @@ -12,14 +12,22 @@ import { withSelect } from '@wordpress/data'; */ const addAnnotationClassName = ( OriginalComponent ) => { return withSelect( ( select, { clientId } ) => { - const annotations = select( 'core/annotations' ).__experimentalGetAnnotationsForBlock( clientId ); + const annotations = select( + 'core/annotations' + ).__experimentalGetAnnotationsForBlock( clientId ); return { - className: annotations.map( ( annotation ) => { - return 'is-annotated-by-' + annotation.source; - } ).join( ' ' ), + className: annotations + .map( ( annotation ) => { + return 'is-annotated-by-' + annotation.source; + } ) + .join( ' ' ), }; } )( OriginalComponent ); }; -addFilter( 'editor.BlockListBlock', 'core/annotations', addAnnotationClassName ); +addFilter( + 'editor.BlockListBlock', + 'core/annotations', + addAnnotationClassName +); diff --git a/packages/annotations/src/format/annotation.js b/packages/annotations/src/format/annotation.js index 46dd6e279fa3ba..9ce2a2bd6d456f 100644 --- a/packages/annotations/src/format/annotation.js +++ b/packages/annotations/src/format/annotation.js @@ -34,7 +34,8 @@ export function applyAnnotations( record, annotations = [] ) { record = applyFormat( record, { - type: FORMAT_NAME, attributes: { + type: FORMAT_NAME, + attributes: { className, id, }, @@ -68,7 +69,9 @@ function retrieveAnnotationPositions( formats ) { formats.forEach( ( characterFormats, i ) => { characterFormats = characterFormats || []; - characterFormats = characterFormats.filter( ( format ) => format.type === FORMAT_NAME ); + characterFormats = characterFormats.filter( + ( format ) => format.type === FORMAT_NAME + ); characterFormats.forEach( ( format ) => { let { id } = format.attributes; id = id.replace( ANNOTATION_ATTRIBUTE_PREFIX, '' ); @@ -98,7 +101,11 @@ function retrieveAnnotationPositions( formats ) { * @param {Function} actions.removeAnnotation Function to remove an annotation from the state. * @param {Function} actions.updateAnnotationRange Function to update an annotation range in the state. */ -function updateAnnotationsWithPositions( annotations, positions, { removeAnnotation, updateAnnotationRange } ) { +function updateAnnotationsWithPositions( + annotations, + positions, + { removeAnnotation, updateAnnotationRange } +) { annotations.forEach( ( currentAnnotation ) => { const position = positions[ currentAnnotation.id ]; // If we cannot find an annotation, delete it. @@ -111,7 +118,11 @@ function updateAnnotationsWithPositions( annotations, positions, { removeAnnotat const { start, end } = currentAnnotation; if ( start !== position.start || end !== position.end ) { - updateAnnotationRange( currentAnnotation.id, position.start, position.end ); + updateAnnotationRange( + currentAnnotation.id, + position.start, + position.end + ); } } ); } @@ -128,9 +139,17 @@ export const annotation = { edit() { return null; }, - __experimentalGetPropsForEditableTreePreparation( select, { richTextIdentifier, blockClientId } ) { + __experimentalGetPropsForEditableTreePreparation( + select, + { richTextIdentifier, blockClientId } + ) { return { - annotations: select( STORE_KEY ).__experimentalGetAnnotationsForRichText( blockClientId, richTextIdentifier ), + annotations: select( + STORE_KEY + ).__experimentalGetAnnotationsForRichText( + blockClientId, + richTextIdentifier + ), }; }, __experimentalCreatePrepareEditableTree( { annotations } ) { @@ -146,16 +165,25 @@ export const annotation = { }, __experimentalGetPropsForEditableTreeChangeHandler( dispatch ) { return { - removeAnnotation: dispatch( STORE_KEY ).__experimentalRemoveAnnotation, - updateAnnotationRange: dispatch( STORE_KEY ).__experimentalUpdateAnnotationRange, + removeAnnotation: dispatch( STORE_KEY ) + .__experimentalRemoveAnnotation, + updateAnnotationRange: dispatch( STORE_KEY ) + .__experimentalUpdateAnnotationRange, }; }, __experimentalCreateOnChangeEditableValue( props ) { return ( formats ) => { const positions = retrieveAnnotationPositions( formats ); - const { removeAnnotation, updateAnnotationRange, annotations } = props; - - updateAnnotationsWithPositions( annotations, positions, { removeAnnotation, updateAnnotationRange } ); + const { + removeAnnotation, + updateAnnotationRange, + annotations, + } = props; + + updateAnnotationsWithPositions( annotations, positions, { + removeAnnotation, + updateAnnotationRange, + } ); }; }, }; diff --git a/packages/annotations/src/format/index.js b/packages/annotations/src/format/index.js index 1dccbbd5012a0c..e41c5640cc8c83 100644 --- a/packages/annotations/src/format/index.js +++ b/packages/annotations/src/format/index.js @@ -1,9 +1,7 @@ /** * WordPress dependencies */ -import { - registerFormatType, -} from '@wordpress/rich-text'; +import { registerFormatType } from '@wordpress/rich-text'; /** * Internal dependencies diff --git a/packages/annotations/src/index.js b/packages/annotations/src/index.js index ce64106bf903cf..9435b1d8616616 100644 --- a/packages/annotations/src/index.js +++ b/packages/annotations/src/index.js @@ -4,4 +4,3 @@ import './store'; import './format'; import './block'; - diff --git a/packages/annotations/src/store/actions.js b/packages/annotations/src/store/actions.js index 8135ca3e32066b..f5d5b8cf208e55 100644 --- a/packages/annotations/src/store/actions.js +++ b/packages/annotations/src/store/actions.js @@ -25,7 +25,14 @@ import uuid from 'uuid/v4'; * * @return {Object} Action object. */ -export function __experimentalAddAnnotation( { blockClientId, richTextIdentifier = null, range = null, selector = 'range', source = 'default', id = uuid() } ) { +export function __experimentalAddAnnotation( { + blockClientId, + richTextIdentifier = null, + range = null, + selector = 'range', + source = 'default', + id = uuid(), +} ) { const action = { type: 'ANNOTATION_ADD', id, @@ -65,7 +72,11 @@ export function __experimentalRemoveAnnotation( annotationId ) { * * @return {Object} Action object. */ -export function __experimentalUpdateAnnotationRange( annotationId, start, end ) { +export function __experimentalUpdateAnnotationRange( + annotationId, + start, + end +) { return { type: 'ANNOTATION_UPDATE_RANGE', annotationId, diff --git a/packages/annotations/src/store/reducer.js b/packages/annotations/src/store/reducer.js index 1f768a78ad2157..0756c3f3d8eac5 100644 --- a/packages/annotations/src/store/reducer.js +++ b/packages/annotations/src/store/reducer.js @@ -15,7 +15,9 @@ import { get, isNumber, mapValues } from 'lodash'; function filterWithReference( collection, predicate ) { const filteredCollection = collection.filter( predicate ); - return collection.length === filteredCollection.length ? collection : filteredCollection; + return collection.length === filteredCollection.length + ? collection + : filteredCollection; } /** @@ -25,9 +27,11 @@ function filterWithReference( collection, predicate ) { * @return {boolean} Whether the given annotation is valid. */ function isValidAnnotationRange( annotation ) { - return isNumber( annotation.start ) && + return ( + isNumber( annotation.start ) && isNumber( annotation.end ) && - annotation.start <= annotation.end; + annotation.start <= annotation.end + ); } /** @@ -51,7 +55,10 @@ export function annotations( state = {}, action ) { range: action.range, }; - if ( newAnnotation.selector === 'range' && ! isValidAnnotationRange( newAnnotation.range ) ) { + if ( + newAnnotation.selector === 'range' && + ! isValidAnnotationRange( newAnnotation.range ) + ) { return state; } @@ -59,43 +66,54 @@ export function annotations( state = {}, action ) { return { ...state, - [ blockClientId ]: [ ...previousAnnotationsForBlock, newAnnotation ], + [ blockClientId ]: [ + ...previousAnnotationsForBlock, + newAnnotation, + ], }; case 'ANNOTATION_REMOVE': return mapValues( state, ( annotationsForBlock ) => { - return filterWithReference( annotationsForBlock, ( annotation ) => { - return annotation.id !== action.annotationId; - } ); + return filterWithReference( + annotationsForBlock, + ( annotation ) => { + return annotation.id !== action.annotationId; + } + ); } ); case 'ANNOTATION_UPDATE_RANGE': return mapValues( state, ( annotationsForBlock ) => { let hasChangedRange = false; - const newAnnotations = annotationsForBlock.map( ( annotation ) => { - if ( annotation.id === action.annotationId ) { - hasChangedRange = true; - return { - ...annotation, - range: { - start: action.start, - end: action.end, - }, - }; - } + const newAnnotations = annotationsForBlock.map( + ( annotation ) => { + if ( annotation.id === action.annotationId ) { + hasChangedRange = true; + return { + ...annotation, + range: { + start: action.start, + end: action.end, + }, + }; + } - return annotation; - } ); + return annotation; + } + ); return hasChangedRange ? newAnnotations : annotationsForBlock; } ); case 'ANNOTATION_REMOVE_SOURCE': return mapValues( state, ( annotationsForBlock ) => { - return filterWithReference( annotationsForBlock, ( annotation ) => { - return annotation.source !== action.source; - } ); + return filterWithReference( + annotationsForBlock, + ( annotation ) => { + return annotation.source !== action.source; + } + ); } ); } diff --git a/packages/annotations/src/store/selectors.js b/packages/annotations/src/store/selectors.js index a39a315c92f907..fa7b97e259ab77 100644 --- a/packages/annotations/src/store/selectors.js +++ b/packages/annotations/src/store/selectors.js @@ -29,12 +29,13 @@ export const __experimentalGetAnnotationsForBlock = createSelector( return annotation.selector === 'block'; } ); }, - ( state, blockClientId ) => [ - get( state, blockClientId, EMPTY_ARRAY ), - ] + ( state, blockClientId ) => [ get( state, blockClientId, EMPTY_ARRAY ) ] ); -export const __experimentalGetAllAnnotationsForBlock = function( state, blockClientId ) { +export const __experimentalGetAllAnnotationsForBlock = function( + state, + blockClientId +) { return get( state, blockClientId, EMPTY_ARRAY ); }; @@ -52,21 +53,23 @@ export const __experimentalGetAllAnnotationsForBlock = function( state, blockCli */ export const __experimentalGetAnnotationsForRichText = createSelector( ( state, blockClientId, richTextIdentifier ) => { - return get( state, blockClientId, [] ).filter( ( annotation ) => { - return annotation.selector === 'range' && - richTextIdentifier === annotation.richTextIdentifier; - } ).map( ( annotation ) => { - const { range, ...other } = annotation; + return get( state, blockClientId, [] ) + .filter( ( annotation ) => { + return ( + annotation.selector === 'range' && + richTextIdentifier === annotation.richTextIdentifier + ); + } ) + .map( ( annotation ) => { + const { range, ...other } = annotation; - return { - ...range, - ...other, - }; - } ); + return { + ...range, + ...other, + }; + } ); }, - ( state, blockClientId ) => [ - get( state, blockClientId, EMPTY_ARRAY ), - ] + ( state, blockClientId ) => [ get( state, blockClientId, EMPTY_ARRAY ) ] ); /** diff --git a/packages/annotations/src/store/test/reducer.js b/packages/annotations/src/store/test/reducer.js index 190795bd8a98cf..0e9c62a93adc4d 100644 --- a/packages/annotations/src/store/test/reducer.js +++ b/packages/annotations/src/store/test/reducer.js @@ -23,18 +23,6 @@ describe( 'annotations', () => { } ); expect( state ).toEqual( { - blockClientId: [ { - id: 'annotationId', - blockClientId: 'blockClientId', - richTextIdentifier: 'identifier', - source: 'default', - selector: 'block', - } ], - } ); - } ); - - it( 'allows an annotation to be removed', () => { - const state = annotations( { blockClientId: [ { id: 'annotationId', @@ -44,10 +32,27 @@ describe( 'annotations', () => { selector: 'block', }, ], - }, { - type: 'ANNOTATION_REMOVE', - annotationId: 'annotationId', } ); + } ); + + it( 'allows an annotation to be removed', () => { + const state = annotations( + { + blockClientId: [ + { + id: 'annotationId', + blockClientId: 'blockClientId', + richTextIdentifier: 'identifier', + source: 'default', + selector: 'block', + }, + ], + }, + { + type: 'ANNOTATION_REMOVE', + annotationId: 'annotationId', + } + ); expect( state ).toEqual( { blockClientId: [] } ); } ); @@ -67,17 +72,16 @@ describe( 'annotations', () => { source: 'other-source', selector: 'block', }; - const state = annotations( { - blockClientId: [ - annotation1, - ], - blockClientId2: [ - annotation2, - ], - }, { - type: 'ANNOTATION_REMOVE_SOURCE', - source: 'default', - } ); + const state = annotations( + { + blockClientId: [ annotation1 ], + blockClientId2: [ annotation2 ], + }, + { + type: 'ANNOTATION_REMOVE_SOURCE', + source: 'default', + } + ); expect( state ).toEqual( { blockClientId: [], @@ -117,26 +121,29 @@ describe( 'annotations', () => { } ); it( 'moves annotations when said action is dispatched', () => { - const state = annotations( { - blockClientId: [ - { - id: 'annotationId', - blockClientId: 'blockClientId', - richTextIdentifier: 'identifier', - source: 'default', - selector: 'range', - range: { - start: 0, - end: 100, + const state = annotations( + { + blockClientId: [ + { + id: 'annotationId', + blockClientId: 'blockClientId', + richTextIdentifier: 'identifier', + source: 'default', + selector: 'range', + range: { + start: 0, + end: 100, + }, }, - }, - ], - }, { - type: 'ANNOTATION_UPDATE_RANGE', - annotationId: 'annotationId', - start: 50, - end: 75, - } ); + ], + }, + { + type: 'ANNOTATION_UPDATE_RANGE', + annotationId: 'annotationId', + start: 50, + end: 75, + } + ); expect( state ).toEqual( { blockClientId: [ diff --git a/packages/api-fetch/src/index.js b/packages/api-fetch/src/index.js index 535adb8d88a76a..9e7edc985709ed 100644 --- a/packages/api-fetch/src/index.js +++ b/packages/api-fetch/src/index.js @@ -14,7 +14,10 @@ import namespaceEndpointMiddleware from './middlewares/namespace-endpoint'; import httpV1Middleware from './middlewares/http-v1'; import userLocaleMiddleware from './middlewares/user-locale'; import mediaUploadMiddleware from './middlewares/media-upload'; -import { parseResponseAndNormalizeError, parseAndThrowError } from './utils/response'; +import { + parseResponseAndNormalizeError, + parseAndThrowError, +} from './utils/response'; /** * Default set of header values which should be sent with every request unless @@ -72,33 +75,36 @@ const defaultFetchHandler = ( nextOptions ) => { headers[ 'Content-Type' ] = 'application/json'; } - const responsePromise = window.fetch( - url || path, - { - ...DEFAULT_OPTIONS, - ...remainingOptions, - body, - headers, - } - ); + const responsePromise = window.fetch( url || path, { + ...DEFAULT_OPTIONS, + ...remainingOptions, + body, + headers, + } ); - return responsePromise - // Return early if fetch errors. If fetch error, there is most likely no - // network connection. Unfortunately fetch just throws a TypeError and - // the message might depend on the browser. - .then( - ( value ) => - Promise.resolve( value ) - .then( checkStatus ) - .catch( ( response ) => parseAndThrowError( response, parse ) ) - .then( ( response ) => parseResponseAndNormalizeError( response, parse ) ), - () => { - throw { - code: 'fetch_error', - message: __( 'You are probably offline.' ), - }; - } - ); + return ( + responsePromise + // Return early if fetch errors. If fetch error, there is most likely no + // network connection. Unfortunately fetch just throws a TypeError and + // the message might depend on the browser. + .then( + ( value ) => + Promise.resolve( value ) + .then( checkStatus ) + .catch( ( response ) => + parseAndThrowError( response, parse ) + ) + .then( ( response ) => + parseResponseAndNormalizeError( response, parse ) + ), + () => { + throw { + code: 'fetch_error', + message: __( 'You are probably offline.' ), + }; + } + ) + ); }; let fetchHandler = defaultFetchHandler; @@ -135,7 +141,8 @@ function apiFetch( options ) { } // If the nonce is invalid, refresh it and try again. - window.fetch( apiFetch.nonceEndpoint ) + window + .fetch( apiFetch.nonceEndpoint ) .then( checkStatus ) .then( ( data ) => data.text() ) .then( ( text ) => { diff --git a/packages/api-fetch/src/middlewares/fetch-all-middleware.js b/packages/api-fetch/src/middlewares/fetch-all-middleware.js index 5bc17b5413d326..2fa1b3b3629aab 100644 --- a/packages/api-fetch/src/middlewares/fetch-all-middleware.js +++ b/packages/api-fetch/src/middlewares/fetch-all-middleware.js @@ -11,18 +11,19 @@ const modifyQuery = ( { path, url, ...options }, queryArgs ) => ( { } ); // Duplicates parsing functionality from apiFetch. -const parseResponse = ( response ) => response.json ? - response.json() : - Promise.reject( response ); +const parseResponse = ( response ) => + response.json ? response.json() : Promise.reject( response ); const parseLinkHeader = ( linkHeader ) => { if ( ! linkHeader ) { return {}; } const match = linkHeader.match( /<([^>]+)>; rel="next"/ ); - return match ? { - next: match[ 1 ], - } : {}; + return match + ? { + next: match[ 1 ], + } + : {}; }; const getNextPageUrl = ( response ) => { @@ -31,8 +32,10 @@ const getNextPageUrl = ( response ) => { }; const requestContainsUnboundedQuery = ( options ) => { - const pathIsUnbounded = options.path && options.path.indexOf( 'per_page=-1' ) !== -1; - const urlIsUnbounded = options.url && options.url.indexOf( 'per_page=-1' ) !== -1; + const pathIsUnbounded = + options.path && options.path.indexOf( 'per_page=-1' ) !== -1; + const urlIsUnbounded = + options.url && options.url.indexOf( 'per_page=-1' ) !== -1; return pathIsUnbounded || urlIsUnbounded; }; diff --git a/packages/api-fetch/src/middlewares/http-v1.js b/packages/api-fetch/src/middlewares/http-v1.js index d7771bf6f9b1c2..87304712e4b218 100644 --- a/packages/api-fetch/src/middlewares/http-v1.js +++ b/packages/api-fetch/src/middlewares/http-v1.js @@ -3,11 +3,7 @@ * * @type {Set} */ -const OVERRIDE_METHODS = new Set( [ - 'PATCH', - 'PUT', - 'DELETE', -] ); +const OVERRIDE_METHODS = new Set( [ 'PATCH', 'PUT', 'DELETE' ] ); /** * Default request method. diff --git a/packages/api-fetch/src/middlewares/media-upload.js b/packages/api-fetch/src/middlewares/media-upload.js index 6772aaf3ded733..5668c18cfbd792 100644 --- a/packages/api-fetch/src/middlewares/media-upload.js +++ b/packages/api-fetch/src/middlewares/media-upload.js @@ -37,29 +37,36 @@ function mediaUploadMiddleware( options, next ) { method: 'POST', data: { action: 'create-image-subsizes' }, parse: false, - } ) - .catch( () => { - if ( retries < maxRetries ) { - return postProcess( attachmentId ); - } - next( { - path: `/wp/v2/media/${ attachmentId }?force=true`, - method: 'DELETE', - } ); - - return Promise.reject(); + } ).catch( () => { + if ( retries < maxRetries ) { + return postProcess( attachmentId ); + } + next( { + path: `/wp/v2/media/${ attachmentId }?force=true`, + method: 'DELETE', } ); + + return Promise.reject(); + } ); }; return next( { ...options, parse: false } ) .catch( ( response ) => { - const attachmentId = response.headers.get( 'x-wp-upload-attachment-id' ); - if ( response.status >= 500 && response.status < 600 && attachmentId ) { + const attachmentId = response.headers.get( + 'x-wp-upload-attachment-id' + ); + if ( + response.status >= 500 && + response.status < 600 && + attachmentId + ) { return postProcess( attachmentId ).catch( () => { if ( options.parse !== false ) { return Promise.reject( { code: 'post_process', - message: __( 'Media upload failed. If this is a photo or a large image, please scale it down and try again.' ), + message: __( + 'Media upload failed. If this is a photo or a large image, please scale it down and try again.' + ), } ); } @@ -68,7 +75,9 @@ function mediaUploadMiddleware( options, next ) { } return parseAndThrowError( response, options.parse ); } ) - .then( ( response ) => parseResponseAndNormalizeError( response, options.parse ) ); + .then( ( response ) => + parseResponseAndNormalizeError( response, options.parse ) + ); } export default mediaUploadMiddleware; diff --git a/packages/api-fetch/src/middlewares/namespace-endpoint.js b/packages/api-fetch/src/middlewares/namespace-endpoint.js index c6ee6c67b4afbb..406c12926dd976 100644 --- a/packages/api-fetch/src/middlewares/namespace-endpoint.js +++ b/packages/api-fetch/src/middlewares/namespace-endpoint.js @@ -4,7 +4,7 @@ const namespaceAndEndpointMiddleware = ( options, next ) => { if ( typeof options.namespace === 'string' && - typeof options.endpoint === 'string' + typeof options.endpoint === 'string' ) { namespaceTrimmed = options.namespace.replace( /^\/|\/$/g, '' ); endpointTrimmed = options.endpoint.replace( /^\//, '' ); diff --git a/packages/api-fetch/src/middlewares/preloading.js b/packages/api-fetch/src/middlewares/preloading.js index bb02e3dfe64af9..573a6c80b384ea 100644 --- a/packages/api-fetch/src/middlewares/preloading.js +++ b/packages/api-fetch/src/middlewares/preloading.js @@ -16,23 +16,27 @@ export function getStablePath( path ) { } // 'b=1&c=2&a=5' - return base + '?' + query - // [ 'b=1', 'c=2', 'a=5' ] - .split( '&' ) - // [ [ 'b, '1' ], [ 'c', '2' ], [ 'a', '5' ] ] - .map( function( entry ) { - return entry.split( '=' ); - } ) - // [ [ 'a', '5' ], [ 'b, '1' ], [ 'c', '2' ] ] - .sort( function( a, b ) { - return a[ 0 ].localeCompare( b[ 0 ] ); - } ) - // [ 'a=5', 'b=1', 'c=2' ] - .map( function( pair ) { - return pair.join( '=' ); - } ) - // 'a=5&b=1&c=2' - .join( '&' ); + return ( + base + + '?' + + query + // [ 'b=1', 'c=2', 'a=5' ] + .split( '&' ) + // [ [ 'b, '1' ], [ 'c', '2' ], [ 'a', '5' ] ] + .map( function( entry ) { + return entry.split( '=' ); + } ) + // [ [ 'a', '5' ], [ 'b, '1' ], [ 'c', '2' ] ] + .sort( function( a, b ) { + return a[ 0 ].localeCompare( b[ 0 ] ); + } ) + // [ 'a=5', 'b=1', 'c=2' ] + .map( function( pair ) { + return pair.join( '=' ); + } ) + // 'a=5&b=1&c=2' + .join( '&' ) + ); } function createPreloadingMiddleware( preloadedData ) { diff --git a/packages/api-fetch/src/middlewares/root-url.js b/packages/api-fetch/src/middlewares/root-url.js index 92b50523642fcc..8d2ad636285476 100644 --- a/packages/api-fetch/src/middlewares/root-url.js +++ b/packages/api-fetch/src/middlewares/root-url.js @@ -20,7 +20,10 @@ const createRootURLMiddleware = ( rootURL ) => ( options, next ) => { // API root may already include query parameter prefix if site is // configured to use plain permalinks. - if ( 'string' === typeof apiRoot && -1 !== apiRoot.indexOf( '?' ) ) { + if ( + 'string' === typeof apiRoot && + -1 !== apiRoot.indexOf( '?' ) + ) { path = path.replace( '?', '&' ); } diff --git a/packages/api-fetch/src/middlewares/test/fetch-all-middleware.js b/packages/api-fetch/src/middlewares/test/fetch-all-middleware.js index b73eeba15e14b9..f0b2c035a646ab 100644 --- a/packages/api-fetch/src/middlewares/test/fetch-all-middleware.js +++ b/packages/api-fetch/src/middlewares/test/fetch-all-middleware.js @@ -29,9 +29,9 @@ describe( 'Fetch All Middleware', () => { status: 200, headers: { get() { - return options.url === '/posts?per_page=100' ? - '</posts?per_page=100&page=2>; rel="next"' : - ''; + return options.url === '/posts?per_page=100' + ? '</posts?per_page=100&page=2>; rel="next"' + : ''; }, }, json() { diff --git a/packages/api-fetch/src/middlewares/test/preloading.js b/packages/api-fetch/src/middlewares/test/preloading.js index 06a63c8d5f1493..167e2e4eb32d65 100644 --- a/packages/api-fetch/src/middlewares/test/preloading.js +++ b/packages/api-fetch/src/middlewares/test/preloading.js @@ -36,7 +36,9 @@ describe( 'Preloading Middleware', () => { body, }, }; - const preloadingMiddleware = createPreloadingMiddleware( preloadedData ); + const preloadingMiddleware = createPreloadingMiddleware( + preloadedData + ); const requestOptions = { method: 'GET', path: 'wp/v2/posts', @@ -54,7 +56,9 @@ describe( 'Preloading Middleware', () => { 'wp/v2/demo-reverse-alphabetical?foo=bar&baz=quux': { body }, 'wp/v2/demo-alphabetical?baz=quux&foo=bar': { body }, }; - const preloadingMiddleware = createPreloadingMiddleware( preloadedData ); + const preloadingMiddleware = createPreloadingMiddleware( + preloadedData + ); let requestOptions = { method: 'GET', @@ -73,16 +77,15 @@ describe( 'Preloading Middleware', () => { expect( value ).toEqual( body ); } ); - describe.each( [ - [ 'GET' ], - [ 'OPTIONS' ], - ] )( '%s', ( method ) => { + describe.each( [ [ 'GET' ], [ 'OPTIONS' ] ] )( '%s', ( method ) => { describe.each( [ [ 'all empty', {} ], [ 'method empty', { [ method ]: {} } ], ] )( '%s', ( label, preloadedData ) => { it( 'should move to the next middleware if no preloaded data', () => { - const prelooadingMiddleware = createPreloadingMiddleware( preloadedData ); + const prelooadingMiddleware = createPreloadingMiddleware( + preloadedData + ); const requestOptions = { method, path: 'wp/v2/posts', diff --git a/packages/api-fetch/src/middlewares/test/root-url.js b/packages/api-fetch/src/middlewares/test/root-url.js index 3480cddc532b06..921e6af8bb01e9 100644 --- a/packages/api-fetch/src/middlewares/test/root-url.js +++ b/packages/api-fetch/src/middlewares/test/root-url.js @@ -14,7 +14,9 @@ describe( 'Root URL middleware', () => { path: '/wp/v2/posts', }; const callback = ( options ) => { - expect( options.url ).toBe( 'http://wp.org/wp-admin/rest/wp/v2/posts' ); + expect( options.url ).toBe( + 'http://wp.org/wp-admin/rest/wp/v2/posts' + ); }; rootURLMiddleware( requestOptions, callback ); diff --git a/packages/api-fetch/src/middlewares/test/user-locale.js b/packages/api-fetch/src/middlewares/test/user-locale.js index 03dc9136ee20e0..9626819f3e0860 100644 --- a/packages/api-fetch/src/middlewares/test/user-locale.js +++ b/packages/api-fetch/src/middlewares/test/user-locale.js @@ -73,7 +73,9 @@ describe( 'User locale middleware', () => { }; const callback = ( options ) => { - expect( options.url ).toBe( 'http://wp.org/wp-json/wp/v2/posts?_locale=user' ); + expect( options.url ).toBe( + 'http://wp.org/wp-json/wp/v2/posts?_locale=user' + ); }; userLocaleMiddleware( requestOptions, callback ); @@ -88,7 +90,9 @@ describe( 'User locale middleware', () => { }; const callback = ( options ) => { - expect( options.url ).toBe( 'http://wp.org/wp-json/wp/v2/posts?foo=bar&_locale=user' ); + expect( options.url ).toBe( + 'http://wp.org/wp-json/wp/v2/posts?foo=bar&_locale=user' + ); }; userLocaleMiddleware( requestOptions, callback ); @@ -103,7 +107,9 @@ describe( 'User locale middleware', () => { }; const callback = ( options ) => { - expect( options.url ).toBe( 'http://wp.org/wp-json/wp/v2/posts?_locale=foo' ); + expect( options.url ).toBe( + 'http://wp.org/wp-json/wp/v2/posts?_locale=foo' + ); }; userLocaleMiddleware( requestOptions, callback ); @@ -118,7 +124,9 @@ describe( 'User locale middleware', () => { }; const callback = ( options ) => { - expect( options.url ).toBe( 'http://wp.org/wp-json/wp/v2/posts?foo=bar&_locale=foo' ); + expect( options.url ).toBe( + 'http://wp.org/wp-json/wp/v2/posts?foo=bar&_locale=foo' + ); }; userLocaleMiddleware( requestOptions, callback ); diff --git a/packages/api-fetch/src/middlewares/user-locale.js b/packages/api-fetch/src/middlewares/user-locale.js index 22fd232caf91b1..8debbde8d994a7 100644 --- a/packages/api-fetch/src/middlewares/user-locale.js +++ b/packages/api-fetch/src/middlewares/user-locale.js @@ -4,11 +4,17 @@ import { addQueryArgs, hasQueryArg } from '@wordpress/url'; function userLocaleMiddleware( options, next ) { - if ( typeof options.url === 'string' && ! hasQueryArg( options.url, '_locale' ) ) { + if ( + typeof options.url === 'string' && + ! hasQueryArg( options.url, '_locale' ) + ) { options.url = addQueryArgs( options.url, { _locale: 'user' } ); } - if ( typeof options.path === 'string' && ! hasQueryArg( options.path, '_locale' ) ) { + if ( + typeof options.path === 'string' && + ! hasQueryArg( options.path, '_locale' ) + ) { options.path = addQueryArgs( options.path, { _locale: 'user' } ); } diff --git a/packages/api-fetch/src/test/index.js b/packages/api-fetch/src/test/index.js index 001483b8b23614..26431fb69a5937 100644 --- a/packages/api-fetch/src/test/index.js +++ b/packages/api-fetch/src/test/index.js @@ -25,12 +25,14 @@ describe( 'apiFetch', () => { } ); it( 'should call the API properly', () => { - window.fetch.mockReturnValue( Promise.resolve( { - status: 200, - json() { - return Promise.resolve( { message: 'ok' } ); - }, - } ) ); + window.fetch.mockReturnValue( + Promise.resolve( { + status: 200, + json() { + return Promise.resolve( { message: 'ok' } ); + }, + } ) + ); return apiFetch( { path: '/random' } ).then( ( body ) => { expect( body ).toEqual( { message: 'ok' } ); @@ -48,14 +50,17 @@ describe( 'apiFetch', () => { body, } ); - expect( window.fetch ).toHaveBeenCalledWith( '/wp/v2/media?_locale=user', { - credentials: 'include', - headers: { - Accept: 'application/json, */*;q=0.1', - }, - method: 'POST', - body, - } ); + expect( window.fetch ).toHaveBeenCalledWith( + '/wp/v2/media?_locale=user', + { + credentials: 'include', + headers: { + Accept: 'application/json, */*;q=0.1', + }, + method: 'POST', + body, + } + ); } ); it( 'should fetch with a JSON body', () => { @@ -70,15 +75,18 @@ describe( 'apiFetch', () => { data: {}, } ); - expect( window.fetch ).toHaveBeenCalledWith( '/wp/v2/posts?_locale=user', { - body: '{}', - credentials: 'include', - headers: { - Accept: 'application/json, */*;q=0.1', - 'Content-Type': 'application/json', - }, - method: 'POST', - } ); + expect( window.fetch ).toHaveBeenCalledWith( + '/wp/v2/posts?_locale=user', + { + body: '{}', + credentials: 'include', + headers: { + Accept: 'application/json, */*;q=0.1', + 'Content-Type': 'application/json', + }, + method: 'POST', + } + ); } ); it( 'should respect developer-provided options', () => { @@ -91,27 +99,32 @@ describe( 'apiFetch', () => { credentials: 'omit', } ); - expect( window.fetch ).toHaveBeenCalledWith( '/wp/v2/posts?_locale=user', { - body: '{}', - credentials: 'omit', - headers: { - Accept: 'application/json, */*;q=0.1', - 'Content-Type': 'application/json', - }, - method: 'POST', - } ); + expect( window.fetch ).toHaveBeenCalledWith( + '/wp/v2/posts?_locale=user', + { + body: '{}', + credentials: 'omit', + headers: { + Accept: 'application/json, */*;q=0.1', + 'Content-Type': 'application/json', + }, + method: 'POST', + } + ); } ); it( 'should return the error message properly', () => { - window.fetch.mockReturnValue( Promise.resolve( { - status: 400, - json() { - return Promise.resolve( { - code: 'bad_request', - message: 'Bad Request', - } ); - }, - } ) ); + window.fetch.mockReturnValue( + Promise.resolve( { + status: 400, + json() { + return Promise.resolve( { + code: 'bad_request', + message: 'Bad Request', + } ); + }, + } ) + ); return apiFetch( { path: '/random' } ).catch( ( body ) => { expect( body ).toEqual( { @@ -122,9 +135,11 @@ describe( 'apiFetch', () => { } ); it( 'should return invalid JSON error if no json response', () => { - window.fetch.mockReturnValue( Promise.resolve( { - status: 200, - } ) ); + window.fetch.mockReturnValue( + Promise.resolve( { + status: 200, + } ) + ); return apiFetch( { path: '/random' } ).catch( ( body ) => { expect( body ).toEqual( { @@ -135,12 +150,14 @@ describe( 'apiFetch', () => { } ); it( 'should return invalid JSON error if response is not valid', () => { - window.fetch.mockReturnValue( Promise.resolve( { - status: 200, - json() { - return Promise.reject(); - }, - } ) ); + window.fetch.mockReturnValue( + Promise.resolve( { + status: 200, + json() { + return Promise.reject(); + }, + } ) + ); return apiFetch( { path: '/random' } ).catch( ( body ) => { expect( body ).toEqual( { @@ -162,9 +179,11 @@ describe( 'apiFetch', () => { } ); it( 'should return null if response has no content status code', () => { - window.fetch.mockReturnValue( Promise.resolve( { - status: 204, - } ) ); + window.fetch.mockReturnValue( + Promise.resolve( { + status: 204, + } ) + ); return apiFetch( { path: '/random' } ).catch( ( body ) => { expect( body ).toEqual( null ); @@ -172,27 +191,35 @@ describe( 'apiFetch', () => { } ); it( 'should not try to parse the response', () => { - window.fetch.mockReturnValue( Promise.resolve( { - status: 200, - } ) ); - - return apiFetch( { path: '/random', parse: false } ).then( ( response ) => { - expect( response ).toEqual( { + window.fetch.mockReturnValue( + Promise.resolve( { status: 200, - } ); - } ); + } ) + ); + + return apiFetch( { path: '/random', parse: false } ).then( + ( response ) => { + expect( response ).toEqual( { + status: 200, + } ); + } + ); } ); it( 'should not try to parse the error', () => { - window.fetch.mockReturnValue( Promise.resolve( { - status: 400, - } ) ); - - return apiFetch( { path: '/random', parse: false } ).catch( ( response ) => { - expect( response ).toEqual( { + window.fetch.mockReturnValue( + Promise.resolve( { status: 400, - } ); - } ); + } ) + ); + + return apiFetch( { path: '/random', parse: false } ).catch( + ( response ) => { + expect( response ).toEqual( { + status: 400, + } ); + } + ); } ); it( 'should not use the default fetch handler when using a custom fetch handler', () => { diff --git a/packages/api-fetch/src/utils/response.js b/packages/api-fetch/src/utils/response.js index bb7b79ce2f8254..d28413862b6c63 100644 --- a/packages/api-fetch/src/utils/response.js +++ b/packages/api-fetch/src/utils/response.js @@ -33,10 +33,9 @@ const parseJsonAndNormalizeError = ( response ) => { throw invalidJsonError; } - return response.json() - .catch( () => { - throw invalidJsonError; - } ); + return response.json().catch( () => { + throw invalidJsonError; + } ); }; /** @@ -47,9 +46,13 @@ const parseJsonAndNormalizeError = ( response ) => { * * @return {Promise} Parsed response. */ -export const parseResponseAndNormalizeError = ( response, shouldParseResponse = true ) => { - return Promise.resolve( parseResponse( response, shouldParseResponse ) ) - .catch( ( res ) => parseAndThrowError( res, shouldParseResponse ) ); +export const parseResponseAndNormalizeError = ( + response, + shouldParseResponse = true +) => { + return Promise.resolve( + parseResponse( response, shouldParseResponse ) + ).catch( ( res ) => parseAndThrowError( res, shouldParseResponse ) ); }; export function parseAndThrowError( response, shouldParseResponse = true ) { @@ -57,14 +60,12 @@ export function parseAndThrowError( response, shouldParseResponse = true ) { throw response; } - return parseJsonAndNormalizeError( response ) - .then( ( error ) => { - const unknownError = { - code: 'unknown_error', - message: __( 'An unknown error occurred.' ), - }; + return parseJsonAndNormalizeError( response ).then( ( error ) => { + const unknownError = { + code: 'unknown_error', + message: __( 'An unknown error occurred.' ), + }; - throw error || unknownError; - } ); + throw error || unknownError; + } ); } - diff --git a/packages/autop/src/index.js b/packages/autop/src/index.js index 1225d5c6637d8c..3a0fc7fa73600e 100644 --- a/packages/autop/src/index.js +++ b/packages/autop/src/index.js @@ -6,42 +6,42 @@ const htmlSplitRegex = ( () => { /* eslint-disable no-multi-spaces */ const comments = - '!' + // Start of comment, after the <. - '(?:' + // Unroll the loop: Consume everything until --> is found. - '-(?!->)' + // Dash not followed by end of comment. - '[^\\-]*' + // Consume non-dashes. - ')*' + // Loop possessively. - '(?:-->)?'; // End of comment. If not found, match all input. + '!' + // Start of comment, after the <. + '(?:' + // Unroll the loop: Consume everything until --> is found. + '-(?!->)' + // Dash not followed by end of comment. + '[^\\-]*' + // Consume non-dashes. + ')*' + // Loop possessively. + '(?:-->)?'; // End of comment. If not found, match all input. const cdata = '!\\[CDATA\\[' + // Start of comment, after the <. - '[^\\]]*' + // Consume non-]. - '(?:' + // Unroll the loop: Consume everything until ]]> is found. - '](?!]>)' + // One ] not followed by end of comment. - '[^\\]]*' + // Consume non-]. - ')*?' + // Loop possessively. - '(?:]]>)?'; // End of comment. If not found, match all input. + '[^\\]]*' + // Consume non-]. + '(?:' + // Unroll the loop: Consume everything until ]]> is found. + '](?!]>)' + // One ] not followed by end of comment. + '[^\\]]*' + // Consume non-]. + ')*?' + // Loop possessively. + '(?:]]>)?'; // End of comment. If not found, match all input. const escaped = - '(?=' + // Is the element escaped? - '!--' + + '(?=' + // Is the element escaped? + '!--' + '|' + - '!\\[CDATA\\[' + + '!\\[CDATA\\[' + ')' + - '((?=!-)' + // If yes, which type? - comments + + '((?=!-)' + // If yes, which type? + comments + '|' + - cdata + + cdata + ')'; const regex = - '(' + // Capture the entire match. - '<' + // Find start of element. - '(' + // Conditional expression follows. - escaped + // Find end of escaped element. - '|' + // ... else ... - '[^>]*>?' + // Find end of normal element. - ')' + + '(' + // Capture the entire match. + '<' + // Find start of element. + '(' + // Conditional expression follows. + escaped + // Find end of escaped element. + '|' + // ... else ... + '[^>]*>?' + // Find end of normal element. + ')' + ')'; return new RegExp( regex ); @@ -92,7 +92,10 @@ function replaceInHtmlTags( haystack, replacePairs ) { for ( let j = 0; j < needles.length; j++ ) { const needle = needles[ j ]; if ( -1 !== textArr[ i ].indexOf( needle ) ) { - textArr[ i ] = textArr[ i ].replace( new RegExp( needle, 'g' ), replacePairs[ needle ] ); + textArr[ i ] = textArr[ i ].replace( + new RegExp( needle, 'g' ), + replacePairs[ needle ] + ); changed = true; // After one strtr() break out of the foreach loop and look at next element. break; @@ -166,13 +169,20 @@ export function autop( text, br = true ) { // Change multiple <br>s into two line breaks, which will turn into paragraphs. text = text.replace( /<br\s*\/?>\s*<br\s*\/?>/g, '\n\n' ); - const allBlocks = '(?:table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary)'; + const allBlocks = + '(?:table|thead|tfoot|caption|col|colgroup|tbody|tr|td|th|div|dl|dd|dt|ul|ol|li|pre|form|map|area|blockquote|address|math|style|p|h[1-6]|hr|fieldset|legend|section|article|aside|hgroup|header|footer|nav|figure|figcaption|details|menu|summary)'; // Add a double line break above block-level opening tags. - text = text.replace( new RegExp( '(<' + allBlocks + '[\\s\/>])', 'g' ), '\n\n$1' ); + text = text.replace( + new RegExp( '(<' + allBlocks + '[\\s/>])', 'g' ), + '\n\n$1' + ); // Add a double line break below block-level closing tags. - text = text.replace( new RegExp( '(<\/' + allBlocks + '>)', 'g' ), '$1\n\n' ); + text = text.replace( + new RegExp( '(</' + allBlocks + '>)', 'g' ), + '$1\n\n' + ); // Standardize newline characters to "\n". text = text.replace( /\r\n|\r/g, '\n' ); @@ -230,10 +240,16 @@ export function autop( text, br = true ) { text = text.replace( /<p>\s*<\/p>/g, '' ); // Add a closing <p> inside <div>, <address>, or <form> tag if missing. - text = text.replace( /<p>([^<]+)<\/(div|address|form)>/g, '<p>$1</p></$2>' ); + text = text.replace( + /<p>([^<]+)<\/(div|address|form)>/g, + '<p>$1</p></$2>' + ); // If an opening or closing block element tag is wrapped in a <p>, unwrap it. - text = text.replace( new RegExp( '<p>\\s*(<\/?' + allBlocks + '[^>]*>)\\s*<\/p>', 'g' ), '$1' ); + text = text.replace( + new RegExp( '<p>\\s*(</?' + allBlocks + '[^>]*>)\\s*</p>', 'g' ), + '$1' + ); // In some cases <li> may get wrapped in <p>, fix them. text = text.replace( /<p>(<li.+?)<\/p>/g, '$1' ); @@ -243,31 +259,47 @@ export function autop( text, br = true ) { text = text.replace( /<\/blockquote><\/p>/g, '</p></blockquote>' ); // If an opening or closing block element tag is preceded by an opening <p> tag, remove it. - text = text.replace( new RegExp( '<p>\\s*(<\/?' + allBlocks + '[^>]*>)', 'g' ), '$1' ); + text = text.replace( + new RegExp( '<p>\\s*(</?' + allBlocks + '[^>]*>)', 'g' ), + '$1' + ); // If an opening or closing block element tag is followed by a closing <p> tag, remove it. - text = text.replace( new RegExp( '(<\/?' + allBlocks + '[^>]*>)\\s*<\/p>', 'g' ), '$1' ); + text = text.replace( + new RegExp( '(</?' + allBlocks + '[^>]*>)\\s*</p>', 'g' ), + '$1' + ); // Optionally insert line breaks. if ( br ) { // Replace newlines that shouldn't be touched with a placeholder. - text = text.replace( /<(script|style).*?<\/\\1>/g, ( match ) => match[ 0 ].replace( /\n/g, '<WPPreserveNewline />' ) ); + text = text.replace( /<(script|style).*?<\/\\1>/g, ( match ) => + match[ 0 ].replace( /\n/g, '<WPPreserveNewline />' ) + ); // Normalize <br> text = text.replace( /<br>|<br\/>/g, '<br />' ); // Replace any new line characters that aren't preceded by a <br /> with a <br />. - text = text.replace( /(<br \/>)?\s*\n/g, ( a, b ) => b ? a : '<br />\n' ); + text = text.replace( /(<br \/>)?\s*\n/g, ( a, b ) => + b ? a : '<br />\n' + ); // Replace newline placeholders with newlines. text = text.replace( /<WPPreserveNewline \/>/g, '\n' ); } // If a <br /> tag is after an opening or closing block tag, remove it. - text = text.replace( new RegExp( '(<\/?' + allBlocks + '[^>]*>)\\s*<br \/>', 'g' ), '$1' ); + text = text.replace( + new RegExp( '(</?' + allBlocks + '[^>]*>)\\s*<br />', 'g' ), + '$1' + ); // If a <br /> tag is before a subset of opening or closing block tags, remove it. - text = text.replace( /<br \/>(\s*<\/?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)/g, '$1' ); + text = text.replace( + /<br \/>(\s*<\/?(?:p|li|div|dl|dd|dt|th|pre|td|ul|ol)[^>]*>)/g, + '$1' + ); text = text.replace( /\n<\/p>$/g, '</p>' ); // Replace placeholder <pre> tags with their original content. @@ -301,7 +333,8 @@ export function autop( text, br = true ) { * @return {string} The content with stripped paragraph tags. */ export function removep( html ) { - const blocklist = 'blockquote|ul|ol|li|dl|dt|dd|table|thead|tbody|tfoot|tr|th|td|h[1-6]|fieldset|figure'; + const blocklist = + 'blockquote|ul|ol|li|dl|dt|dd|table|thead|tbody|tfoot|tr|th|td|h[1-6]|fieldset|figure'; const blocklist1 = blocklist + '|div|p'; const blocklist2 = blocklist + '|pre'; const preserve = []; @@ -314,7 +347,9 @@ export function removep( html ) { // Protect script and style tags. if ( html.indexOf( '<script' ) !== -1 || html.indexOf( '<style' ) !== -1 ) { - html = html.replace( /<(script|style)[^>]*>[\s\S]*?<\/\1>/g, function( match ) { + html = html.replace( /<(script|style)[^>]*>[\s\S]*?<\/\1>/g, function( + match + ) { preserve.push( match ); return '<wp-preserve>'; } ); @@ -334,13 +369,21 @@ export function removep( html ) { if ( html.indexOf( '[caption' ) !== -1 ) { preserveBr = true; html = html.replace( /\[caption[\s\S]+?\[\/caption\]/g, function( a ) { - return a.replace( /<br([^>]*)>/g, '<wp-temp-br$1>' ).replace( /[\r\n\t]+/, '' ); + return a + .replace( /<br([^>]*)>/g, '<wp-temp-br$1>' ) + .replace( /[\r\n\t]+/, '' ); } ); } // Normalize white space characters before and after block tags. - html = html.replace( new RegExp( '\\s*</(' + blocklist1 + ')>\\s*', 'g' ), '</$1>\n' ); - html = html.replace( new RegExp( '\\s*<((?:' + blocklist1 + ')(?: [^>]*)?)>', 'g' ), '\n<$1>' ); + html = html.replace( + new RegExp( '\\s*</(' + blocklist1 + ')>\\s*', 'g' ), + '</$1>\n' + ); + html = html.replace( + new RegExp( '\\s*<((?:' + blocklist1 + ')(?: [^>]*)?)>', 'g' ), + '\n<$1>' + ); // Mark </p> if it has any attributes. html = html.replace( /(<p [^>]+>[\s\S]*?)<\/p>/g, '$1</p#>' ); @@ -369,12 +412,21 @@ export function removep( html ) { html = html.replace( /<\/div>\s*/g, '</div>\n' ); // Fix line breaks around caption shortcodes. - html = html.replace( /\s*\[caption([^\[]+)\[\/caption\]\s*/gi, '\n\n[caption$1[/caption]\n\n' ); + html = html.replace( + /\s*\[caption([^\[]+)\[\/caption\]\s*/gi, + '\n\n[caption$1[/caption]\n\n' + ); html = html.replace( /caption\]\n\n+\[caption/g, 'caption]\n\n[caption' ); // Pad block elements tags with a line break. - html = html.replace( new RegExp( '\\s*<((?:' + blocklist2 + ')(?: [^>]*)?)\\s*>', 'g' ), '\n<$1>' ); - html = html.replace( new RegExp( '\\s*</(' + blocklist2 + ')>\\s*', 'g' ), '</$1>\n' ); + html = html.replace( + new RegExp( '\\s*<((?:' + blocklist2 + ')(?: [^>]*)?)\\s*>', 'g' ), + '\n<$1>' + ); + html = html.replace( + new RegExp( '\\s*</(' + blocklist2 + ')>\\s*', 'g' ), + '</$1>\n' + ); // Indent <li>, <dt> and <dd> tags. html = html.replace( /<((li|dt|dd)[^>]*)>/g, ' \t<$1>' ); diff --git a/packages/autop/src/test/index.test.js b/packages/autop/src/test/index.test.js index 8fc73f4e039651..0c5d86d6d0a2b3 100644 --- a/packages/autop/src/test/index.test.js +++ b/packages/autop/src/test/index.test.js @@ -1,10 +1,7 @@ /** * Internal dependencies */ -import { - autop, - removep, -} from '../'; +import { autop, removep } from '../'; test( 'empty string', () => { expect( autop( '' ) ).toBe( '' ); @@ -81,13 +78,16 @@ done = 0; expect( autop( str ).trim() ).toBe( expected ); // Make sure HTML breaks are maintained if manually inserted - str = 'Look at this code\n\n<pre>Line1<br />Line2<br>Line3<br/>Line4\nActual Line 2\nActual Line 3</pre>\n\nCool, huh?'; - expected = '<p>Look at this code</p>\n<pre>Line1<br />Line2<br>Line3<br/>Line4\nActual Line 2\nActual Line 3</pre>\n<p>Cool, huh?</p>'; + str = + 'Look at this code\n\n<pre>Line1<br />Line2<br>Line3<br/>Line4\nActual Line 2\nActual Line 3</pre>\n\nCool, huh?'; + expected = + '<p>Look at this code</p>\n<pre>Line1<br />Line2<br>Line3<br/>Line4\nActual Line 2\nActual Line 3</pre>\n<p>Cool, huh?</p>'; expect( autop( str ).trim() ).toBe( expected ); } ); test( 'skip input elements', () => { - const str = 'Username: <input type="text" id="username" name="username" /><br />Password: <input type="password" id="password1" name="password1" />'; + const str = + 'Username: <input type="text" id="username" name="username" /><br />Password: <input type="password" id="password1" name="password1" />'; expect( autop( str ).trim() ).toBe( '<p>' + str + '</p>' ); } ); @@ -133,7 +133,8 @@ Paragraph two.`; Paragraph two.`; - const expected = '<p>Paragraph one.</p>\n' + // line breaks only after <p> + const expected = + '<p>Paragraph one.</p>\n' + // line breaks only after <p> '<p><video class="wp-video-shortcode" id="video-0-1" width="640" height="360" preload="metadata" controls="controls">' + '<source type="video/mp4" src="http://domain.tld/wp-content/uploads/2013/12/xyz.mp4" />' + '<!-- WebM/VP8 for Firefox4, Opera, and Chrome -->' + @@ -164,7 +165,8 @@ Paragraph two.`; Paragraph two.`; - const shortcodeExpected = '<p>Paragraph one.</p>\n' + // line breaks only after <p> + const shortcodeExpected = + '<p>Paragraph one.</p>\n' + // line breaks only after <p> '<p>[video width="720" height="480" mp4="http://domain.tld/wp-content/uploads/2013/12/xyz.mp4"]' + '<!-- WebM/VP8 for Firefox4, Opera, and Chrome --><source type="video/webm" src="myvideo.webm" />' + '<!-- Ogg/Vorbis for older Firefox and Opera versions --><source type="video/ogg" src="myvideo.ogv" />' + @@ -193,7 +195,8 @@ test( 'param embed elements', () => { Paragraph two.`; - const expected1 = '<p>Paragraph one.</p>\n' + // line breaks only after <p> + const expected1 = + '<p>Paragraph one.</p>\n' + // line breaks only after <p> '<p><object width="400" height="224" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0">' + '<param name="src" value="http://domain.tld/wp-content/uploads/2013/12/xyz.swf" />' + '<param name="allowfullscreen" value="true" />' + @@ -228,7 +231,8 @@ Paragraph two.`; Paragraph two.`; - const expected2 = '<p>Paragraph one.</p>\n' + // line breaks only after block tags + const expected2 = + '<p>Paragraph one.</p>\n' + // line breaks only after block tags '<div class="video-player" id="x-video-0">\n' + '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="640" height="360" id="video-0" standby="Standby text">' + '<param name="movie" value="http://domain.tld/wp-content/uploads/2013/12/xyz.swf" />' + @@ -251,7 +255,8 @@ Paragraph two.`; } ); test( 'skip select option elements', () => { - const str = 'Country: <select id="state" name="state"><option value="1">Alabama</option><option value="2">Alaska</option><option value="3">Arizona</option><option value="4">Arkansas</option><option value="5">California</option></select>'; + const str = + 'Country: <select id="state" name="state"><option value="1">Alabama</option><option value="2">Alaska</option><option value="3">Arizona</option><option value="4">Arkansas</option><option value="5">California</option></select>'; expect( autop( str ).trim() ).toBe( '<p>' + str + '</p>' ); } ); @@ -399,10 +404,7 @@ test( 'that autop treats inline elements as inline', () => { test( 'element sanity', () => { [ - [ - 'Hello <a\nhref="world">', - '<p>Hello <a\nhref="world"></p>\n', - ], + [ 'Hello <a\nhref="world">', '<p>Hello <a\nhref="world"></p>\n' ], [ 'Hello <!-- a\nhref="world" -->', '<p>Hello <!-- a\nhref="world" --></p>\n', @@ -482,7 +484,8 @@ test( 'that text before blocks is peed', () => { } ); test( 'that autop doses not add extra closing p in figure', () => { - const content1 = '<figure><img src="example.jpg" /><figcaption>Caption</figcaption></figure>'; + const content1 = + '<figure><img src="example.jpg" /><figcaption>Caption</figcaption></figure>'; const expected1 = content1; const content2 = `<figure> @@ -498,8 +501,10 @@ test( 'that autop doses not add extra closing p in figure', () => { } ); test( 'that autop correctly adds a start and end tag when followed by a div', () => { - const content = 'Testing autop with a div\n<div class="wp-some-class">content</div>'; - const expected = '<p>Testing autop with a div</p>\n<div class="wp-some-class">content</div>'; + const content = + 'Testing autop with a div\n<div class="wp-some-class">content</div>'; + const expected = + '<p>Testing autop with a div</p>\n<div class="wp-some-class">content</div>'; expect( autop( content ).trim() ).toBe( expected ); } ); diff --git a/packages/babel-plugin-import-jsx-pragma/index.js b/packages/babel-plugin-import-jsx-pragma/index.js index 273df752611c15..a7dd3a2d381556 100644 --- a/packages/babel-plugin-import-jsx-pragma/index.js +++ b/packages/babel-plugin-import-jsx-pragma/index.js @@ -48,7 +48,9 @@ module.exports = function( babel ) { } const { scopeVariable } = getOptions( state ); - state.hasUndeclaredScopeVariable = ! path.scope.hasBinding( scopeVariable ); + state.hasUndeclaredScopeVariable = ! path.scope.hasBinding( + scopeVariable + ); }, JSXFragment( path, state ) { if ( state.hasUndeclaredScopeVariableFrag ) { @@ -60,18 +62,27 @@ module.exports = function( babel ) { return; } - state.hasUndeclaredScopeVariableFrag = ! path.scope.hasBinding( scopeVariableFrag ); + state.hasUndeclaredScopeVariableFrag = ! path.scope.hasBinding( + scopeVariableFrag + ); }, Program: { exit( path, state ) { - const { scopeVariable, scopeVariableFrag, source, isDefault } = getOptions( state ); + const { + scopeVariable, + scopeVariableFrag, + source, + isDefault, + } = getOptions( state ); let scopeVariableSpecifier; let scopeVariableFragSpecifier; if ( state.hasUndeclaredScopeVariable ) { if ( isDefault ) { - scopeVariableSpecifier = t.importDefaultSpecifier( t.identifier( scopeVariable ) ); + scopeVariableSpecifier = t.importDefaultSpecifier( + t.identifier( scopeVariable ) + ); } else { scopeVariableSpecifier = t.importSpecifier( t.identifier( scopeVariable ), diff --git a/packages/babel-plugin-import-jsx-pragma/test/index.js b/packages/babel-plugin-import-jsx-pragma/test/index.js index c3e098476db3d2..bcd915f1b5dad1 100644 --- a/packages/babel-plugin-import-jsx-pragma/test/index.js +++ b/packages/babel-plugin-import-jsx-pragma/test/index.js @@ -24,7 +24,8 @@ describe( 'babel-plugin-import-jsx-pragma', () => { } ); it( 'does nothing if the scope variable is already defined', () => { - const original = 'const React = require("react");\n\nlet foo = <bar />;'; + const original = + 'const React = require("react");\n\nlet foo = <bar />;'; const string = getTransformedCode( original ); expect( string ).toBe( original ); @@ -52,7 +53,9 @@ describe( 'babel-plugin-import-jsx-pragma', () => { isDefault: false, } ); - expect( string ).toBe( 'import { createElement } from "@wordpress/element";\n' + original ); + expect( string ).toBe( + 'import { createElement } from "@wordpress/element";\n' + original + ); } ); it( 'adds import for scope variable even when defined inside the local scope', () => { @@ -64,7 +67,9 @@ describe( 'babel-plugin-import-jsx-pragma', () => { isDefault: false, } ); - expect( string ).toBe( 'import { createElement } from "@wordpress/element";\n' + original ); + expect( string ).toBe( + 'import { createElement } from "@wordpress/element";\n' + original + ); } ); it( 'does nothing if the outer scope variable is already defined when using custom options', () => { @@ -81,7 +86,8 @@ describe( 'babel-plugin-import-jsx-pragma', () => { } ); it( 'adds only Fragment when required', () => { - const original = 'const {\n createElement\n} = wp.element;\nlet foo = <><bar /></>;'; + const original = + 'const {\n createElement\n} = wp.element;\nlet foo = <><bar /></>;'; const string = getTransformedCode( original, { scopeVariable: 'createElement', scopeVariableFrag: 'Fragment', @@ -95,7 +101,8 @@ describe( 'babel-plugin-import-jsx-pragma', () => { } ); it( 'adds only createElement when required', () => { - const original = 'const {\n Fragment\n} = wp.element;\nlet foo = <><bar /></>;'; + const original = + 'const {\n Fragment\n} = wp.element;\nlet foo = <><bar /></>;'; const string = getTransformedCode( original, { scopeVariable: 'createElement', scopeVariableFrag: 'Fragment', diff --git a/packages/babel-plugin-makepot/src/index.js b/packages/babel-plugin-makepot/src/index.js index ad0cde941aecb8..507c988be91bb8 100644 --- a/packages/babel-plugin-makepot/src/index.js +++ b/packages/babel-plugin-makepot/src/index.js @@ -33,7 +33,16 @@ */ const { po } = require( 'gettext-parser' ); -const { pick, reduce, uniq, forEach, sortBy, isEqual, merge, isEmpty } = require( 'lodash' ); +const { + pick, + reduce, + uniq, + forEach, + sortBy, + isEqual, + merge, + isEmpty, +} = require( 'lodash' ); const { relative, sep } = require( 'path' ); const { writeFileSync } = require( 'fs' ); @@ -93,10 +102,7 @@ const REGEXP_TRANSLATOR_COMMENT = /^\s*translators:\s*([\s\S]+)/im; function getNodeAsString( node ) { switch ( node.type ) { case 'BinaryExpression': - return ( - getNodeAsString( node.left ) + - getNodeAsString( node.right ) - ); + return getNodeAsString( node.left ) + getNodeAsString( node.right ); case 'StringLiteral': return node.value; @@ -134,7 +140,10 @@ function getExtractedComment( path, _originalNodeLine ) { const match = commentNode.value.match( REGEXP_TRANSLATOR_COMMENT ); if ( match ) { // Extract text from matched translator prefix - comment = match[ 1 ].split( '\n' ).map( ( text ) => text.trim() ).join( ' ' ); + comment = match[ 1 ] + .split( '\n' ) + .map( ( text ) => text.trim() ) + .join( ' ' ); // False return indicates to Lodash to break iteration return false; @@ -203,20 +212,24 @@ module.exports = function() { } // Skip unhandled functions - const functionKeys = ( state.opts.functions || DEFAULT_FUNCTIONS )[ name ]; + const functionKeys = ( state.opts.functions || + DEFAULT_FUNCTIONS )[ name ]; if ( ! functionKeys ) { return; } // Assign translation keys by argument position - const translation = path.node.arguments.reduce( ( memo, arg, i ) => { - const key = functionKeys[ i ]; - if ( isValidTranslationKey( key ) ) { - memo[ key ] = getNodeAsString( arg ); - } + const translation = path.node.arguments.reduce( + ( memo, arg, i ) => { + const key = functionKeys[ i ]; + if ( isValidTranslationKey( key ) ) { + memo[ key ] = getNodeAsString( arg ); + } - return memo; - }, {} ); + return memo; + }, + {} + ); // Can only assign translation with usable msgid if ( ! translation.msgid ) { @@ -240,11 +253,15 @@ module.exports = function() { }; for ( const key in baseData.headers ) { - baseData.translations[ '' ][ '' ].msgstr.push( `${ key }: ${ baseData.headers[ key ] };\n` ); + baseData.translations[ '' ][ '' ].msgstr.push( + `${ key }: ${ baseData.headers[ key ] };\n` + ); } // Attempt to exract nplurals from header - const pluralsMatch = ( baseData.headers[ 'plural-forms' ] || '' ).match( /nplurals\s*=\s*(\d+);/ ); + const pluralsMatch = ( + baseData.headers[ 'plural-forms' ] || '' + ).match( /nplurals\s*=\s*(\d+);/ ); if ( pluralsMatch ) { nplurals = parseInt( pluralsMatch[ 1 ], 10 ); } @@ -252,7 +269,9 @@ module.exports = function() { // Create empty msgstr or array of empty msgstr by nplurals if ( translation.msgid_plural ) { - translation.msgstr = Array.from( Array( nplurals ) ).map( () => '' ); + translation.msgstr = Array.from( Array( nplurals ) ).map( + () => '' + ); } else { translation.msgstr = ''; } @@ -260,7 +279,9 @@ module.exports = function() { // Assign file reference comment, ensuring consistent pathname // reference between Win32 and POSIX const { filename } = this.file.opts; - const pathname = relative( '.', filename ).split( sep ).join( '/' ); + const pathname = relative( '.', filename ) + .split( sep ) + .join( '/' ); translation.comments = { reference: pathname + ':' + path.node.loc.start.line, }; @@ -294,34 +315,57 @@ module.exports = function() { const files = Object.keys( strings ).sort(); // Combine translations from each file grouped by context - const translations = reduce( files, ( memo, file ) => { - for ( const context in strings[ file ] ) { - // Within the same file, sort translations by line - const sortedTranslations = sortBy( - strings[ file ][ context ], - 'comments.reference' - ); - - forEach( sortedTranslations, ( translation ) => { - const { msgctxt = '', msgid } = translation; - if ( ! memo.hasOwnProperty( msgctxt ) ) { - memo[ msgctxt ] = {}; - } - - // Merge references if translation already exists - if ( isSameTranslation( translation, memo[ msgctxt ][ msgid ] ) ) { - translation.comments.reference = uniq( [ - memo[ msgctxt ][ msgid ].comments.reference, - translation.comments.reference, - ].join( '\n' ).split( '\n' ) ).join( '\n' ); - } - - memo[ msgctxt ][ msgid ] = translation; - } ); - } - - return memo; - }, {} ); + const translations = reduce( + files, + ( memo, file ) => { + for ( const context in strings[ file ] ) { + // Within the same file, sort translations by line + const sortedTranslations = sortBy( + strings[ file ][ context ], + 'comments.reference' + ); + + forEach( + sortedTranslations, + ( translation ) => { + const { + msgctxt = '', + msgid, + } = translation; + if ( + ! memo.hasOwnProperty( msgctxt ) + ) { + memo[ msgctxt ] = {}; + } + + // Merge references if translation already exists + if ( + isSameTranslation( + translation, + memo[ msgctxt ][ msgid ] + ) + ) { + translation.comments.reference = uniq( + [ + memo[ msgctxt ][ msgid ] + .comments.reference, + translation.comments + .reference, + ] + .join( '\n' ) + .split( '\n' ) + ).join( '\n' ); + } + + memo[ msgctxt ][ msgid ] = translation; + } + ); + } + + return memo; + }, + {} + ); // Merge translations from individual files into headers const data = merge( {}, baseData, { translations } ); @@ -331,7 +375,10 @@ module.exports = function() { // Babel loader doesn't expose these entry points and async // write may hit file lock (need queue). const compiled = po.compile( data ); - writeFileSync( state.opts.output || DEFAULT_OUTPUT, compiled ); + writeFileSync( + state.opts.output || DEFAULT_OUTPUT, + compiled + ); this.hasPendingWrite = false; }, }, diff --git a/packages/babel-plugin-makepot/test/index.js b/packages/babel-plugin-makepot/test/index.js index 52ae1dcc5b9134..7a5e46d3a2212e 100644 --- a/packages/babel-plugin-makepot/test/index.js +++ b/packages/babel-plugin-makepot/test/index.js @@ -56,37 +56,49 @@ describe( 'babel-plugin', () => { } it( 'should not return translator comment on same line but after call expression', () => { - const comment = getCommentFromString( "__( 'Hello world' ); // translators: Greeting" ); + const comment = getCommentFromString( + "__( 'Hello world' ); // translators: Greeting" + ); expect( comment ).toBeUndefined(); } ); it( 'should return translator comment on leading comments', () => { - const comment = getCommentFromString( "// translators: Greeting\n__( 'Hello world' );" ); + const comment = getCommentFromString( + "// translators: Greeting\n__( 'Hello world' );" + ); expect( comment ).toBe( 'Greeting' ); } ); it( 'should be case insensitive to translator prefix', () => { - const comment = getCommentFromString( "// TrANslAtORs: Greeting\n__( 'Hello world' );" ); + const comment = getCommentFromString( + "// TrANslAtORs: Greeting\n__( 'Hello world' );" + ); expect( comment ).toBe( 'Greeting' ); } ); it( 'should traverse up parents until it encounters comment', () => { - const comment = getCommentFromString( "// translators: Greeting\nconst string = __( 'Hello world' );" ); + const comment = getCommentFromString( + "// translators: Greeting\nconst string = __( 'Hello world' );" + ); expect( comment ).toBe( 'Greeting' ); } ); it( 'should not consider comment if it does not end on same or previous line', () => { - const comment = getCommentFromString( "// translators: Greeting\n\n__( 'Hello world' );" ); + const comment = getCommentFromString( + "// translators: Greeting\n\n__( 'Hello world' );" + ); expect( comment ).toBeUndefined(); } ); it( 'should use multi-line comment starting many lines previous', () => { - const comment = getCommentFromString( "/* translators: Long comment\nspanning multiple \nlines */\nconst string = __( 'Hello world' );" ); + const comment = getCommentFromString( + "/* translators: Long comment\nspanning multiple \nlines */\nconst string = __( 'Hello world' );" + ); expect( comment ).toBe( 'Long comment spanning multiple lines' ); } ); @@ -117,7 +129,9 @@ describe( 'babel-plugin', () => { } ); it( 'should be a concatenated binary expression string value', () => { - const string = getNodeAsStringFromArgument( '__( "hello" + " world" );' ); + const string = getNodeAsStringFromArgument( + '__( "hello" + " world" );' + ); expect( string ).toBe( 'hello world' ); } ); diff --git a/packages/babel-preset-default/index.js b/packages/babel-preset-default/index.js index c6ec1e3c9a71a3..15454ecb36fd65 100644 --- a/packages/babel-preset-default/index.js +++ b/packages/babel-preset-default/index.js @@ -1,8 +1,9 @@ module.exports = function( api ) { let wpBuildOpts = {}; - const isWPBuild = ( name ) => [ 'WP_BUILD_MAIN', 'WP_BUILD_MODULE' ].some( - ( buildName ) => name === buildName - ); + const isWPBuild = ( name ) => + [ 'WP_BUILD_MAIN', 'WP_BUILD_MODULE' ].some( + ( buildName ) => name === buildName + ); const isTestEnv = api.env() === 'test'; @@ -68,11 +69,16 @@ module.exports = function( api ) { isDefault: false, }, ], - [ require.resolve( '@babel/plugin-transform-react-jsx' ), { - pragma: 'createElement', - pragmaFrag: 'Fragment', - } ], - require.resolve( '@babel/plugin-proposal-async-generator-functions' ), + [ + require.resolve( '@babel/plugin-transform-react-jsx' ), + { + pragma: 'createElement', + pragmaFrag: 'Fragment', + }, + ], + require.resolve( + '@babel/plugin-proposal-async-generator-functions' + ), maybeGetPluginTransformRuntime(), ].filter( Boolean ), }; diff --git a/packages/babel-preset-default/test/index.js b/packages/babel-preset-default/test/index.js index 0be3197b9541cd..2e37f8d0f5ea25 100644 --- a/packages/babel-preset-default/test/index.js +++ b/packages/babel-preset-default/test/index.js @@ -12,7 +12,9 @@ import babelPresetDefault from '../'; describe( 'Babel preset default', () => { test( 'transpilation works properly', () => { - const input = readFileSync( path.join( __dirname, '/fixtures/input.js' ) ); + const input = readFileSync( + path.join( __dirname, '/fixtures/input.js' ) + ); const output = transform( input, { configFile: false, diff --git a/packages/base-styles/_mixins.scss b/packages/base-styles/_mixins.scss index 29639668163b3d..427b36201aef5c 100644 --- a/packages/base-styles/_mixins.scss +++ b/packages/base-styles/_mixins.scss @@ -279,7 +279,7 @@ left: $admin-sidebar-width-collapsed; } - @include break-large() { + @media (min-width: #{ ($break-large + 1) }) { left: $admin-sidebar-width; } } diff --git a/packages/base-styles/_variables.scss b/packages/base-styles/_variables.scss index ab3cad778bce93..2f6776824c45b0 100644 --- a/packages/base-styles/_variables.scss +++ b/packages/base-styles/_variables.scss @@ -76,6 +76,8 @@ $block-selected-to-content: $block-edge-to-content - $block-selected-margin - $b $block-selected-child-to-content: $block-selected-to-content - $block-selected-child-margin - $block-selected-child-border-width; $block-custom-appender-to-content: $block-selected-margin - $block-selected-border-width; $block-media-container-to-content: $block-selected-child-margin + $block-selected-border-width; +$block-selected-vertical-margin-descendant: 2 * $block-selected-to-content; +$block-selected-vertical-margin-child: $block-edge-to-content; // Buttons & UI Widgets $radius-round-rectangle: 4px; diff --git a/packages/blob/src/test/index.js b/packages/blob/src/test/index.js index 7604e3956b6d26..110340e13639e2 100644 --- a/packages/blob/src/test/index.js +++ b/packages/blob/src/test/index.js @@ -1,9 +1,7 @@ /** * Internal dependencies */ -import { - isBlobURL, -} from '../'; +import { isBlobURL } from '../'; describe( 'isBlobURL', () => { it( 'returns true if the url starts with "blob:"', () => { diff --git a/packages/block-directory/src/components/block-ratings/index.js b/packages/block-directory/src/components/block-ratings/index.js index ff4b8111fc7d84..c55cace300028c 100644 --- a/packages/block-directory/src/components/block-ratings/index.js +++ b/packages/block-directory/src/components/block-ratings/index.js @@ -15,7 +15,10 @@ export const BlockRatings = ( { rating, ratingCount } ) => ( className="block-directory-block-ratings__rating-count" aria-label={ // translators: %d: number of ratings (number). - sprintf( _n( '%d total rating', '%d total ratings', ratingCount ), ratingCount ) + sprintf( + _n( '%d total rating', '%d total ratings', ratingCount ), + ratingCount + ) } > ({ ratingCount }) diff --git a/packages/block-directory/src/components/block-ratings/stars.js b/packages/block-directory/src/components/block-ratings/stars.js index 577c3cb15e0dc2..63ec7126fea00e 100644 --- a/packages/block-directory/src/components/block-ratings/stars.js +++ b/packages/block-directory/src/components/block-ratings/stars.js @@ -9,9 +9,7 @@ import { times } from 'lodash'; import { __, sprintf } from '@wordpress/i18n'; import { Icon } from '@wordpress/components'; -function Stars( { - rating, -} ) { +function Stars( { rating } ) { const stars = Math.round( rating / 0.5 ) * 0.5; const fullStarCount = Math.floor( rating ); @@ -20,9 +18,27 @@ function Stars( { return ( <div aria-label={ sprintf( __( '%s out of 5 stars' ), stars ) }> - { times( fullStarCount, ( i ) => <Icon key={ `full_stars_${ i }` } icon="star-filled" size={ 16 } /> ) } - { times( halfStarCount, ( i ) => <Icon key={ `half_stars_${ i }` } icon="star-half" size={ 16 } /> ) } - { times( emptyStarCount, ( i ) => <Icon key={ `empty_stars_${ i }` } icon="star-empty" size={ 16 } /> ) } + { times( fullStarCount, ( i ) => ( + <Icon + key={ `full_stars_${ i }` } + icon="star-filled" + size={ 16 } + /> + ) ) } + { times( halfStarCount, ( i ) => ( + <Icon + key={ `half_stars_${ i }` } + icon="star-half" + size={ 16 } + /> + ) ) } + { times( emptyStarCount, ( i ) => ( + <Icon + key={ `empty_stars_${ i }` } + icon="star-empty" + size={ 16 } + /> + ) ) } </div> ); } diff --git a/packages/block-directory/src/components/downloadable-block-author-info/index.js b/packages/block-directory/src/components/downloadable-block-author-info/index.js index b422f1360416c1..5c27144430e109 100644 --- a/packages/block-directory/src/components/downloadable-block-author-info/index.js +++ b/packages/block-directory/src/components/downloadable-block-author-info/index.js @@ -4,7 +4,11 @@ import { Fragment } from '@wordpress/element'; import { __, _n, sprintf } from '@wordpress/i18n'; -function DownloadableBlockAuthorInfo( { author, authorBlockCount, authorBlockRating } ) { +function DownloadableBlockAuthorInfo( { + author, + authorBlockCount, + authorBlockRating, +} ) { return ( <Fragment> <span className="block-directory-downloadable-block-author-info__content-author"> diff --git a/packages/block-directory/src/components/downloadable-block-header/index.js b/packages/block-directory/src/components/downloadable-block-header/index.js index 942c4fda0c7c7c..78ab2962e60ce8 100644 --- a/packages/block-directory/src/components/downloadable-block-header/index.js +++ b/packages/block-directory/src/components/downloadable-block-header/index.js @@ -10,20 +10,32 @@ import { __, sprintf } from '@wordpress/i18n'; import { BlockIcon } from '@wordpress/block-editor'; import BlockRatings from '../block-ratings'; -function DownloadableBlockHeader( { icon, title, rating, ratingCount, onClick } ) { +function DownloadableBlockHeader( { + icon, + title, + rating, + ratingCount, + onClick, +} ) { return ( <div className="block-directory-downloadable-block-header__row"> - { - icon.match( /\.(jpeg|jpg|gif|png)(?:\?.*)?$/ ) !== null ? - // translators: %s: Name of the plugin e.g: "Akismet". - <img src={ icon } alt={ sprintf( __( '%s block icon' ), title ) } /> : - <span > - <BlockIcon icon={ icon } showColors /> - </span> - } + { icon.match( /\.(jpeg|jpg|gif|png)(?:\?.*)?$/ ) !== null ? ( + // translators: %s: Name of the plugin e.g: "Akismet". + <img + src={ icon } + alt={ sprintf( __( '%s block icon' ), title ) } + /> + ) : ( + <span> + <BlockIcon icon={ icon } showColors /> + </span> + ) } <div className="block-directory-downloadable-block-header__column"> - <span role="heading" className="block-directory-downloadable-block-header__title" > + <span + role="heading" + className="block-directory-downloadable-block-header__title" + > { title } </span> <BlockRatings rating={ rating } ratingCount={ ratingCount } /> diff --git a/packages/block-directory/src/components/downloadable-block-header/test/fixtures/index.js b/packages/block-directory/src/components/downloadable-block-header/test/fixtures/index.js index 469105aea680fe..db09e7df68d30a 100644 --- a/packages/block-directory/src/components/downloadable-block-header/test/fixtures/index.js +++ b/packages/block-directory/src/components/downloadable-block-header/test/fixtures/index.js @@ -1,7 +1,8 @@ const pluginBase = { name: 'boxer/boxer', title: 'Boxer', - description: 'Boxer is a Block that puts your WordPress posts into boxes on a page.', + description: + 'Boxer is a Block that puts your WordPress posts into boxes on a page.', id: 'boxer-block', rating: 5, rating_count: 1, @@ -17,4 +18,7 @@ const pluginBase = { }; export const pluginWithIcon = { ...pluginBase, icon: 'block-default' }; -export const pluginWithImg = { ...pluginBase, icon: 'https://ps.w.org/listicles/assets/icon-128x128.png' }; +export const pluginWithImg = { + ...pluginBase, + icon: 'https://ps.w.org/listicles/assets/icon-128x128.png', +}; diff --git a/packages/block-directory/src/components/downloadable-block-header/test/index.js b/packages/block-directory/src/components/downloadable-block-header/test/index.js index 6c8d044cd8f87b..96ba6247170cb9 100644 --- a/packages/block-directory/src/components/downloadable-block-header/test/index.js +++ b/packages/block-directory/src/components/downloadable-block-header/test/index.js @@ -30,14 +30,19 @@ describe( 'DownloadableBlockHeader', () => { describe( 'icon rendering', () => { test( 'should render an <img> tag', () => { const wrapper = getContainer( pluginWithImg ); - expect( wrapper.find( 'img' ).prop( 'src' ) ).toEqual( pluginWithImg.icon ); + expect( wrapper.find( 'img' ).prop( 'src' ) ).toEqual( + pluginWithImg.icon + ); } ); test( 'should render an <img> tag if icon URL has query string', () => { - const iconURLwithQueryString = pluginWithImg.icon + '?rev=2011672&test=234234'; + const iconURLwithQueryString = + pluginWithImg.icon + '?rev=2011672&test=234234'; const plugin = { ...pluginWithImg, icon: iconURLwithQueryString }; const wrapper = getContainer( plugin ); - expect( wrapper.find( 'img' ).prop( 'src' ) ).toEqual( plugin.icon ); + expect( wrapper.find( 'img' ).prop( 'src' ) ).toEqual( + plugin.icon + ); } ); test( 'should render a <BlockIcon/> component', () => { diff --git a/packages/block-directory/src/components/downloadable-block-info/index.js b/packages/block-directory/src/components/downloadable-block-info/index.js index 5119d1316267a8..8d647313db6a19 100644 --- a/packages/block-directory/src/components/downloadable-block-info/index.js +++ b/packages/block-directory/src/components/downloadable-block-info/index.js @@ -5,7 +5,11 @@ import { Fragment } from '@wordpress/element'; import { Icon } from '@wordpress/components'; import { __, _n, sprintf } from '@wordpress/i18n'; -function DownloadableBlockInfo( { description, activeInstalls, humanizedUpdated } ) { +function DownloadableBlockInfo( { + description, + activeInstalls, + humanizedUpdated, +} ) { return ( <Fragment> <p className="block-directory-downloadable-block-info__content"> @@ -13,14 +17,20 @@ function DownloadableBlockInfo( { description, activeInstalls, humanizedUpdated </p> <div className="block-directory-downloadable-block-info__row"> <div className="block-directory-downloadable-block-info__column"> - <Icon icon="chart-line"></Icon>{ sprintf( _n( '%d active installation', '%d active installations', activeInstalls ), activeInstalls ) } + <Icon icon="chart-line"></Icon> + { sprintf( + _n( + '%d active installation', + '%d active installations', + activeInstalls + ), + activeInstalls + ) } </div> <div className="block-directory-downloadable-block-info__column"> <Icon icon="update"></Icon> - { - // translators: %s: Humanized date of last update e.g: "2 months ago". - sprintf( __( 'Updated %s' ), humanizedUpdated ) - } + { // translators: %s: Humanized date of last update e.g: "2 months ago". + sprintf( __( 'Updated %s' ), humanizedUpdated ) } </div> </div> </Fragment> diff --git a/packages/block-directory/src/components/downloadable-block-list-item/index.js b/packages/block-directory/src/components/downloadable-block-list-item/index.js index c5bd8b8f0244e0..3eb69eba7d2d70 100644 --- a/packages/block-directory/src/components/downloadable-block-list-item/index.js +++ b/packages/block-directory/src/components/downloadable-block-list-item/index.js @@ -5,10 +5,7 @@ import DownloadableBlockHeader from '../downloadable-block-header'; import DownloadableBlockAuthorInfo from '../downloadable-block-author-info'; import DownloadableBlockInfo from '../downloadable-block-info'; -function DownloadableBlockListItem( { - item, - onClick, -} ) { +function DownloadableBlockListItem( { item, onClick } ) { const { icon, title, diff --git a/packages/block-directory/src/components/downloadable-blocks-list/index.js b/packages/block-directory/src/components/downloadable-blocks-list/index.js index 315403267a56c3..b63c40d34871f3 100644 --- a/packages/block-directory/src/components/downloadable-blocks-list/index.js +++ b/packages/block-directory/src/components/downloadable-blocks-list/index.js @@ -6,7 +6,10 @@ import { noop } from 'lodash'; /** * WordPress dependencies */ -import { getBlockMenuDefaultClassName, unregisterBlockType } from '@wordpress/blocks'; +import { + getBlockMenuDefaultClassName, + unregisterBlockType, +} from '@wordpress/blocks'; import { withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; @@ -19,7 +22,12 @@ import DownloadableBlockListItem from '../downloadable-block-list-item'; const DOWNLOAD_ERROR_NOTICE_ID = 'block-download-error'; const INSTALL_ERROR_NOTICE_ID = 'block-install-error'; -function DownloadableBlocksList( { items, onHover = noop, children, downloadAndInstallBlock } ) { +function DownloadableBlocksList( { + items, + onHover = noop, + children, + downloadAndInstallBlock, +} ) { return ( /* * Disable reason: The `list` ARIA role is redundant but @@ -27,22 +35,23 @@ function DownloadableBlocksList( { items, onHover = noop, children, downloadAndI */ /* eslint-disable jsx-a11y/no-redundant-roles */ <ul role="list" className="block-directory-downloadable-blocks-list"> - { items && items.map( ( item ) => - <DownloadableBlockListItem - key={ item.id } - className={ getBlockMenuDefaultClassName( item.id ) } - icons={ item.icons } - onClick={ () => { - downloadAndInstallBlock( item ); - onHover( null ); - } } - onFocus={ () => onHover( item ) } - onMouseEnter={ () => onHover( item ) } - onMouseLeave={ () => onHover( null ) } - onBlur={ () => onHover( null ) } - item={ item } - /> - ) } + { items && + items.map( ( item ) => ( + <DownloadableBlockListItem + key={ item.id } + className={ getBlockMenuDefaultClassName( item.id ) } + icons={ item.icons } + onClick={ () => { + downloadAndInstallBlock( item ); + onHover( null ); + } } + onFocus={ () => onHover( item ) } + onMouseEnter={ () => onHover( item ) } + onMouseLeave={ () => onHover( null ) } + onBlur={ () => onHover( null ) } + item={ item } + /> + ) ) } { children } </ul> /* eslint-enable jsx-a11y/no-redundant-roles */ @@ -51,7 +60,9 @@ function DownloadableBlocksList( { items, onHover = noop, children, downloadAndI export default compose( withDispatch( ( dispatch, props ) => { - const { installBlock, downloadBlock } = dispatch( 'core/block-directory' ); + const { installBlock, downloadBlock } = dispatch( + 'core/block-directory' + ); const { createErrorNotice, removeNotice } = dispatch( 'core/notices' ); const { removeBlocks } = dispatch( 'core/block-editor' ); const { onSelect } = props; @@ -59,20 +70,22 @@ export default compose( return { downloadAndInstallBlock: ( item ) => { const onDownloadError = () => { - createErrorNotice( - __( 'Block previews can’t load.' ), - { - id: DOWNLOAD_ERROR_NOTICE_ID, - actions: [ - { - label: __( 'Retry' ), - onClick: () => { - removeNotice( DOWNLOAD_ERROR_NOTICE_ID ); - downloadBlock( item, onSuccess, onDownloadError ); - }, + createErrorNotice( __( 'Block previews can’t load.' ), { + id: DOWNLOAD_ERROR_NOTICE_ID, + actions: [ + { + label: __( 'Retry' ), + onClick: () => { + removeNotice( DOWNLOAD_ERROR_NOTICE_ID ); + downloadBlock( + item, + onSuccess, + onDownloadError + ); }, - ], - } ); + }, + ], + } ); }; const onSuccess = () => { @@ -80,22 +93,32 @@ export default compose( const onInstallBlockError = () => { createErrorNotice( - __( 'Block previews can\'t install.' ), + __( "Block previews can't install." ), { id: INSTALL_ERROR_NOTICE_ID, actions: [ { label: __( 'Retry' ), onClick: () => { - removeNotice( INSTALL_ERROR_NOTICE_ID ); - installBlock( item, noop, onInstallBlockError ); + removeNotice( + INSTALL_ERROR_NOTICE_ID + ); + installBlock( + item, + noop, + onInstallBlockError + ); }, }, { label: __( 'Remove' ), onClick: () => { - removeNotice( INSTALL_ERROR_NOTICE_ID ); - removeBlocks( createdBlock.clientId ); + removeNotice( + INSTALL_ERROR_NOTICE_ID + ); + removeBlocks( + createdBlock.clientId + ); unregisterBlockType( item.name ); }, }, @@ -110,5 +133,5 @@ export default compose( downloadBlock( item, onSuccess, onDownloadError ); }, }; - } ), + } ) )( DownloadableBlocksList ); diff --git a/packages/block-directory/src/components/downloadable-blocks-panel/index.js b/packages/block-directory/src/components/downloadable-blocks-panel/index.js index 0936ee3eea6f23..29d7cbb29ecac0 100644 --- a/packages/block-directory/src/components/downloadable-blocks-panel/index.js +++ b/packages/block-directory/src/components/downloadable-blocks-panel/index.js @@ -12,14 +12,28 @@ import { Spinner, withSpokenMessages } from '@wordpress/components'; */ import DownloadableBlocksList from '../downloadable-blocks-list'; -function DownloadableBlocksPanel( { downloadableItems, onSelect, onHover, hasPermission, isLoading, isWaiting, debouncedSpeak } ) { +function DownloadableBlocksPanel( { + downloadableItems, + onSelect, + onHover, + hasPermission, + isLoading, + isWaiting, + debouncedSpeak, +} ) { if ( ! hasPermission ) { - debouncedSpeak( __( 'No blocks found in your library. Please contact your site administrator to install new blocks.' ) ); + debouncedSpeak( + __( + 'No blocks found in your library. Please contact your site administrator to install new blocks.' + ) + ); return ( <p className="block-directory-downloadable-blocks-panel__description has-no-results"> { __( 'No blocks found in your library.' ) } <br /> - { __( 'Please contact your site administrator to install new blocks.' ) } + { __( + 'Please contact your site administrator to install new blocks.' + ) } </p> ); } @@ -41,7 +55,11 @@ function DownloadableBlocksPanel( { downloadableItems, onSelect, onHover, hasPer } const resultsFoundMessage = sprintf( - _n( 'No blocks found in your library. We did find %d block available for download.', 'No blocks found in your library. We did find %d blocks available for download.', downloadableItems.length ), + _n( + 'No blocks found in your library. We did find %d block available for download.', + 'No blocks found in your library. We did find %d blocks available for download.', + downloadableItems.length + ), downloadableItems.length ); @@ -49,9 +67,15 @@ function DownloadableBlocksPanel( { downloadableItems, onSelect, onHover, hasPer return ( <Fragment> <p className="block-directory-downloadable-blocks-panel__description"> - { __( 'No blocks found in your library. These blocks can be downloaded and installed:' ) } + { __( + 'No blocks found in your library. These blocks can be downloaded and installed:' + ) } </p> - <DownloadableBlocksList items={ downloadableItems } onSelect={ onSelect } onHover={ onHover } /> + <DownloadableBlocksList + items={ downloadableItems } + onSelect={ onSelect } + onHover={ onHover } + /> </Fragment> ); } @@ -66,7 +90,9 @@ export default compose( [ } = select( 'core/block-directory' ); const hasPermission = hasInstallBlocksPermission(); - const downloadableItems = hasPermission ? getDownloadableBlocks( filterValue ) : []; + const downloadableItems = hasPermission + ? getDownloadableBlocks( filterValue ) + : []; const isLoading = isRequestingDownloadableBlocks(); return { diff --git a/packages/block-directory/src/plugins/inserter-menu-downloadable-blocks-panel/index.js b/packages/block-directory/src/plugins/inserter-menu-downloadable-blocks-panel/index.js index 9fb2cc8105dc0b..b91a3692b6d295 100644 --- a/packages/block-directory/src/plugins/inserter-menu-downloadable-blocks-panel/index.js +++ b/packages/block-directory/src/plugins/inserter-menu-downloadable-blocks-panel/index.js @@ -21,26 +21,24 @@ function InserterMenuDownloadableBlocksPanel() { return ( <__experimentalInserterMenuExtension> - { - ( { onSelect, onHover, filterValue, hasItems } ) => { - if ( hasItems || ! filterValue ) { - return null; - } - - if ( debouncedFilterValue !== filterValue ) { - debouncedSetFilterValue( filterValue ); - } - - return ( - <DownloadableBlocksPanel - onSelect={ onSelect } - onHover={ onHover } - filterValue={ debouncedFilterValue } - isWaiting={ filterValue !== debouncedFilterValue } - /> - ); + { ( { onSelect, onHover, filterValue, hasItems } ) => { + if ( hasItems || ! filterValue ) { + return null; } - } + + if ( debouncedFilterValue !== filterValue ) { + debouncedSetFilterValue( filterValue ); + } + + return ( + <DownloadableBlocksPanel + onSelect={ onSelect } + onHover={ onHover } + filterValue={ debouncedFilterValue } + isWaiting={ filterValue !== debouncedFilterValue } + /> + ); + } } </__experimentalInserterMenuExtension> ); } diff --git a/packages/block-directory/src/store/actions.js b/packages/block-directory/src/store/actions.js index 46bce90d2f7cb8..09ba6381b59ae0 100644 --- a/packages/block-directory/src/store/actions.js +++ b/packages/block-directory/src/store/actions.js @@ -26,7 +26,11 @@ export function fetchDownloadableBlocks() { * @return {Object} Action object. */ export function receiveDownloadableBlocks( downloadableBlocks, filterValue ) { - return { type: 'RECEIVE_DOWNLOADABLE_BLOCKS', downloadableBlocks, filterValue }; + return { + type: 'RECEIVE_DOWNLOADABLE_BLOCKS', + downloadableBlocks, + filterValue, + }; } /** diff --git a/packages/block-directory/src/store/controls.js b/packages/block-directory/src/store/controls.js index fbd187b7bbf417..6696a7071b08f5 100644 --- a/packages/block-directory/src/store/controls.js +++ b/packages/block-directory/src/store/controls.js @@ -111,14 +111,18 @@ export function* loadAssets( assets ) { } const controls = { - SELECT: createRegistryControl( ( registry ) => ( { storeName, selectorName, args } ) => { - return registry.select( storeName )[ selectorName ]( ...args ); - } ), - DISPATCH: createRegistryControl( ( registry ) => ( { storeName, dispatcherName, args } ) => { - return registry.dispatch( storeName )[ dispatcherName ]( ...args ); - } ), + SELECT: createRegistryControl( + ( registry ) => ( { storeName, selectorName, args } ) => { + return registry.select( storeName )[ selectorName ]( ...args ); + } + ), + DISPATCH: createRegistryControl( + ( registry ) => ( { storeName, dispatcherName, args } ) => { + return registry.dispatch( storeName )[ dispatcherName ]( ...args ); + } + ), API_FETCH( { request } ) { - return wpApiFetch( { ... request } ); + return wpApiFetch( { ...request } ); }, LOAD_ASSETS( { assets } ) { return new Promise( ( resolve, reject ) => { @@ -128,20 +132,28 @@ const controls = { forEach( assets, ( asset ) => { if ( asset.match( /\.js$/ ) !== null ) { scriptsCount++; - loadScript( asset, () => { - scriptsCount--; - if ( scriptsCount === 0 ) { - return resolve( scriptsCount ); - } - }, reject ); + loadScript( + asset, + () => { + scriptsCount--; + if ( scriptsCount === 0 ) { + return resolve( scriptsCount ); + } + }, + reject + ); } else { loadStyle( asset ); } } ); } else { - loadScript( assets.editor_script, () => { - return resolve( 0 ); - }, reject ); + loadScript( + assets.editor_script, + () => { + return resolve( 0 ); + }, + reject + ); loadStyle( assets.style ); } } ); diff --git a/packages/block-directory/src/store/reducer.js b/packages/block-directory/src/store/reducer.js index a8644e8fc1a601..c0993c56dfaf64 100644 --- a/packages/block-directory/src/store/reducer.js +++ b/packages/block-directory/src/store/reducer.js @@ -11,18 +11,21 @@ import { combineReducers } from '@wordpress/data'; * * @return {Object} Updated state. */ -export const downloadableBlocks = ( state = { - results: {}, - filterValue: undefined, - isRequestingDownloadableBlocks: true, -}, action ) => { +export const downloadableBlocks = ( + state = { + results: {}, + filterValue: undefined, + isRequestingDownloadableBlocks: true, + }, + action +) => { switch ( action.type ) { - case 'FETCH_DOWNLOADABLE_BLOCKS' : + case 'FETCH_DOWNLOADABLE_BLOCKS': return { ...state, isRequestingDownloadableBlocks: true, }; - case 'RECEIVE_DOWNLOADABLE_BLOCKS' : + case 'RECEIVE_DOWNLOADABLE_BLOCKS': return { ...state, results: Object.assign( {}, state.results, { @@ -42,19 +45,27 @@ export const downloadableBlocks = ( state = { * * @return {Object} Updated state. */ -export const blockManagement = ( state = { - installedBlockTypes: [], -}, action ) => { +export const blockManagement = ( + state = { + installedBlockTypes: [], + }, + action +) => { switch ( action.type ) { - case 'ADD_INSTALLED_BLOCK_TYPE' : + case 'ADD_INSTALLED_BLOCK_TYPE': return { ...state, - installedBlockTypes: [ ...state.installedBlockTypes, action.item ], + installedBlockTypes: [ + ...state.installedBlockTypes, + action.item, + ], }; - case 'REMOVE_INSTALLED_BLOCK_TYPE' : + case 'REMOVE_INSTALLED_BLOCK_TYPE': return { ...state, - installedBlockTypes: state.installedBlockTypes.filter( ( blockType ) => blockType.name !== action.item.name ), + installedBlockTypes: state.installedBlockTypes.filter( + ( blockType ) => blockType.name !== action.item.name + ), }; } return state; diff --git a/packages/block-directory/src/store/resolvers.js b/packages/block-directory/src/store/resolvers.js index 885257ea72ee8b..a1b0d7dfd05bea 100644 --- a/packages/block-directory/src/store/resolvers.js +++ b/packages/block-directory/src/store/resolvers.js @@ -7,10 +7,14 @@ import { camelCase, mapKeys } from 'lodash'; * Internal dependencies */ import { apiFetch } from './controls'; -import { fetchDownloadableBlocks, receiveDownloadableBlocks, setInstallBlocksPermission } from './actions'; +import { + fetchDownloadableBlocks, + receiveDownloadableBlocks, + setInstallBlocksPermission, +} from './actions'; export default { - * getDownloadableBlocks( filterValue ) { + *getDownloadableBlocks( filterValue ) { if ( ! filterValue ) { return; } @@ -20,9 +24,11 @@ export default { const results = yield apiFetch( { path: `__experimental/block-directory/search?term=${ filterValue }`, } ); - const blocks = results.map( ( result ) => mapKeys( result, ( value, key ) => { - return camelCase( key ); - } ) ); + const blocks = results.map( ( result ) => + mapKeys( result, ( value, key ) => { + return camelCase( key ); + } ) + ); yield receiveDownloadableBlocks( blocks, filterValue ); } catch ( error ) { @@ -31,7 +37,7 @@ export default { } } }, - * hasInstallBlocksPermission() { + *hasInstallBlocksPermission() { try { yield apiFetch( { path: `__experimental/block-directory/search?term=`, diff --git a/packages/block-directory/src/store/test/fixtures/index.js b/packages/block-directory/src/store/test/fixtures/index.js index bbbfbdaa6f9ed0..0f7cc95dc8a6b4 100644 --- a/packages/block-directory/src/store/test/fixtures/index.js +++ b/packages/block-directory/src/store/test/fixtures/index.js @@ -1,7 +1,8 @@ export const downloadableBlock = { name: 'boxer/boxer', title: 'Boxer', - description: 'Boxer is a Block that puts your WordPress posts into boxes on a page.', + description: + 'Boxer is a Block that puts your WordPress posts into boxes on a page.', id: 'boxer-block', rating: 5, ratingCount: 1, diff --git a/packages/block-directory/src/store/test/reducer.js b/packages/block-directory/src/store/test/reducer.js index f221a3bfa023d7..b10edec5845a85 100644 --- a/packages/block-directory/src/store/test/reducer.js +++ b/packages/block-directory/src/store/test/reducer.js @@ -6,11 +6,7 @@ import deepFreeze from 'deep-freeze'; /** * Internal dependencies */ -import { - downloadableBlocks, - blockManagement, - hasPermission, -} from '../reducer'; +import { downloadableBlocks, blockManagement, hasPermission } from '../reducer'; import { installedItem, downloadableBlock } from './fixtures'; describe( 'state', () => { @@ -36,14 +32,16 @@ describe( 'state', () => { expect( state.isRequestingDownloadableBlocks ).toBe( false ); } ); - it( 'should set user\'s search term and save results', () => { + it( "should set user's search term and save results", () => { const state = downloadableBlocks( undefined, { type: 'RECEIVE_DOWNLOADABLE_BLOCKS', filterValue: downloadableBlock.title, downloadableBlocks: [ downloadableBlock ], } ); expect( state.results ).toHaveProperty( downloadableBlock.title ); - expect( state.results[ downloadableBlock.title ] ).toHaveLength( 1 ); + expect( state.results[ downloadableBlock.title ] ).toHaveLength( + 1 + ); // It should append to the results const updatedState = downloadableBlocks( state, { diff --git a/packages/block-directory/src/store/test/selectors.js b/packages/block-directory/src/store/test/selectors.js index b907328e3a4203..11a019d011f7e1 100644 --- a/packages/block-directory/src/store/test/selectors.js +++ b/packages/block-directory/src/store/test/selectors.js @@ -1,9 +1,7 @@ /** * Internal dependencies */ -import { - getInstalledBlockTypes, -} from '../selectors'; +import { getInstalledBlockTypes } from '../selectors'; describe( 'selectors', () => { describe( 'getInstalledBlockTypes', () => { diff --git a/packages/block-editor/src/components/alignment-toolbar/index.js b/packages/block-editor/src/components/alignment-toolbar/index.js index 40dfa28c0ce882..9a880f4e67dd35 100644 --- a/packages/block-editor/src/components/alignment-toolbar/index.js +++ b/packages/block-editor/src/components/alignment-toolbar/index.js @@ -41,7 +41,10 @@ export function AlignmentToolbar( props ) { return () => onChange( value === align ? undefined : align ); } - const activeAlignment = find( alignmentControls, ( control ) => control.align === value ); + const activeAlignment = find( + alignmentControls, + ( control ) => control.align === value + ); return ( <Toolbar @@ -50,7 +53,7 @@ export function AlignmentToolbar( props ) { label={ label } controls={ alignmentControls.map( ( control ) => { const { align } = control; - const isActive = ( value === align ); + const isActive = value === align; return { ...control, diff --git a/packages/block-editor/src/components/alignment-toolbar/test/index.js b/packages/block-editor/src/components/alignment-toolbar/test/index.js index 03662cfdabe812..3ad4397b84da0f 100644 --- a/packages/block-editor/src/components/alignment-toolbar/test/index.js +++ b/packages/block-editor/src/components/alignment-toolbar/test/index.js @@ -12,7 +12,9 @@ describe( 'AlignmentToolbar', () => { const alignment = 'left'; const onChangeSpy = jest.fn(); - const wrapper = shallow( <AlignmentToolbar value={ alignment } onChange={ onChangeSpy } /> ); + const wrapper = shallow( + <AlignmentToolbar value={ alignment } onChange={ onChangeSpy } /> + ); const controls = wrapper.props().controls; @@ -34,7 +36,9 @@ describe( 'AlignmentToolbar', () => { } ); test( 'should call on change a new value when the control is not active', () => { - const inactiveControl = controls.find( ( { align } ) => align === 'center' ); + const inactiveControl = controls.find( + ( { align } ) => align === 'center' + ); inactiveControl.onClick(); expect( inactiveControl.isActive ).toBe( false ); diff --git a/packages/block-editor/src/components/autocomplete/index.js b/packages/block-editor/src/components/autocomplete/index.js index c95279a104db24..2deea118054500 100644 --- a/packages/block-editor/src/components/autocomplete/index.js +++ b/packages/block-editor/src/components/autocomplete/index.js @@ -33,16 +33,11 @@ export function withFilteredAutocompleters( Autocomplete ) { 'editor.Autocomplete.completers', // Provide copies so filters may directly modify them. completers.map( clone ), - props.blockName, + props.blockName ); } - return ( - <Autocomplete - { ...props } - completers={ completers } - /> - ); + return <Autocomplete { ...props } completers={ completers } />; }; } diff --git a/packages/block-editor/src/components/block-actions/index.js b/packages/block-editor/src/components/block-actions/index.js index afc69d7e5273ac..b26ca15f2590b3 100644 --- a/packages/block-editor/src/components/block-actions/index.js +++ b/packages/block-editor/src/components/block-actions/index.js @@ -70,10 +70,7 @@ export default compose( [ }; } ), withDispatch( ( dispatch, props, { select } ) => { - const { - clientIds, - blocks, - } = props; + const { clientIds, blocks } = props; const { removeBlocks, @@ -101,22 +98,20 @@ export default compose( [ return; } - const { - getGroupingBlockName, - } = select( 'core/blocks' ); + const { getGroupingBlockName } = select( 'core/blocks' ); const groupingBlockName = getGroupingBlockName(); // Activate the `transform` on `core/group` which does the conversion - const newBlocks = switchToBlockType( blocks, groupingBlockName ); + const newBlocks = switchToBlockType( + blocks, + groupingBlockName + ); if ( ! newBlocks ) { return; } - replaceBlocks( - clientIds, - newBlocks - ); + replaceBlocks( clientIds, newBlocks ); }, onUngroup() { @@ -130,10 +125,7 @@ export default compose( [ return; } - replaceBlocks( - clientIds, - innerBlocks - ); + replaceBlocks( clientIds, innerBlocks ); }, }; } ), diff --git a/packages/block-editor/src/components/block-alignment-toolbar/index.js b/packages/block-editor/src/components/block-alignment-toolbar/index.js index b3ac1df33142ca..84a7c6160d34d5 100644 --- a/packages/block-editor/src/components/block-alignment-toolbar/index.js +++ b/packages/block-editor/src/components/block-alignment-toolbar/index.js @@ -45,33 +45,44 @@ const DEFAULT_CONTROLS = [ 'left', 'center', 'right', 'wide', 'full' ]; const DEFAULT_CONTROL = 'center'; const WIDE_CONTROLS = [ 'wide', 'full' ]; -export function BlockAlignmentToolbar( { value, onChange, controls = DEFAULT_CONTROLS, isCollapsed = true, wideControlsEnabled = false } ) { +export function BlockAlignmentToolbar( { + value, + onChange, + controls = DEFAULT_CONTROLS, + isCollapsed = true, + wideControlsEnabled = false, +} ) { function applyOrUnset( align ) { return () => onChange( value === align ? undefined : align ); } const enabledControls = wideControlsEnabled ? controls - : controls.filter( ( control ) => WIDE_CONTROLS.indexOf( control ) === -1 ); + : controls.filter( + ( control ) => WIDE_CONTROLS.indexOf( control ) === -1 + ); const activeAlignmentControl = BLOCK_ALIGNMENTS_CONTROLS[ value ]; - const defaultAlignmentControl = BLOCK_ALIGNMENTS_CONTROLS[ DEFAULT_CONTROL ]; + const defaultAlignmentControl = + BLOCK_ALIGNMENTS_CONTROLS[ DEFAULT_CONTROL ]; return ( <Toolbar isCollapsed={ isCollapsed } - icon={ activeAlignmentControl ? activeAlignmentControl.icon : defaultAlignmentControl.icon } - label={ __( 'Change alignment' ) } - controls={ - enabledControls.map( ( control ) => { - return { - ...BLOCK_ALIGNMENTS_CONTROLS[ control ], - isActive: value === control, - role: isCollapsed ? 'menuitemradio' : undefined, - onClick: applyOrUnset( control ), - }; - } ) + icon={ + activeAlignmentControl + ? activeAlignmentControl.icon + : defaultAlignmentControl.icon } + label={ __( 'Change alignment' ) } + controls={ enabledControls.map( ( control ) => { + return { + ...BLOCK_ALIGNMENTS_CONTROLS[ control ], + isActive: value === control, + role: isCollapsed ? 'menuitemradio' : undefined, + onClick: applyOrUnset( control ), + }; + } ) } /> ); } @@ -88,5 +99,5 @@ export default compose( return { wideControlsEnabled: settings.alignWide, }; - } ), + } ) )( BlockAlignmentToolbar ); diff --git a/packages/block-editor/src/components/block-alignment-toolbar/test/index.js b/packages/block-editor/src/components/block-alignment-toolbar/test/index.js index b92ab806746ecf..976aa7d4abd896 100644 --- a/packages/block-editor/src/components/block-alignment-toolbar/test/index.js +++ b/packages/block-editor/src/components/block-alignment-toolbar/test/index.js @@ -12,7 +12,9 @@ describe( 'BlockAlignmentToolbar', () => { const alignment = 'left'; const onChange = jest.fn(); - const wrapper = shallow( <BlockAlignmentToolbar value={ alignment } onChange={ onChange } /> ); + const wrapper = shallow( + <BlockAlignmentToolbar value={ alignment } onChange={ onChange } /> + ); const controls = wrapper.props().controls; @@ -25,7 +27,9 @@ describe( 'BlockAlignmentToolbar', () => { } ); test( 'should call onChange with undefined, when the control is already active', () => { - const activeControl = controls.find( ( { title } ) => title === 'Align left' ); + const activeControl = controls.find( + ( { title } ) => title === 'Align left' + ); activeControl.onClick(); expect( activeControl.isActive ).toBe( true ); @@ -34,7 +38,9 @@ describe( 'BlockAlignmentToolbar', () => { } ); test( 'should call onChange with alignment value when the control is inactive', () => { - const inactiveCenterControl = controls.find( ( { title } ) => title === 'Align center' ); + const inactiveCenterControl = controls.find( + ( { title } ) => title === 'Align center' + ); inactiveCenterControl.onClick(); expect( inactiveCenterControl.isActive ).toBe( false ); diff --git a/packages/block-editor/src/components/block-breadcrumb/index.js b/packages/block-editor/src/components/block-breadcrumb/index.js index adc96ba4eb2d51..e1c7b7436cdf35 100644 --- a/packages/block-editor/src/components/block-breadcrumb/index.js +++ b/packages/block-editor/src/components/block-breadcrumb/index.js @@ -16,7 +16,9 @@ import BlockTitle from '../block-title'; * @return {WPElement} Block Breadcrumb. */ const BlockBreadcrumb = function() { - const { selectBlock, clearSelectedBlock } = useDispatch( 'core/block-editor' ); + const { selectBlock, clearSelectedBlock } = useDispatch( + 'core/block-editor' + ); const { clientId, parents, hasSelection } = useSelect( ( select ) => { const { getSelectionStart, @@ -37,9 +39,17 @@ const BlockBreadcrumb = function() { */ /* eslint-disable jsx-a11y/no-redundant-roles */ return ( - <ul className="block-editor-block-breadcrumb" role="list" aria-label={ __( 'Block breadcrumb' ) }> + <ul + className="block-editor-block-breadcrumb" + role="list" + aria-label={ __( 'Block breadcrumb' ) } + > <li - className={ ! hasSelection ? 'block-editor-block-breadcrumb__current' : undefined } + className={ + ! hasSelection + ? 'block-editor-block-breadcrumb__current' + : undefined + } aria-current={ ! hasSelection ? 'true' : undefined } > { hasSelection && ( @@ -65,7 +75,10 @@ const BlockBreadcrumb = function() { </li> ) ) } { !! clientId && ( - <li className="block-editor-block-breadcrumb__current" aria-current="true"> + <li + className="block-editor-block-breadcrumb__current" + aria-current="true" + > <BlockTitle clientId={ clientId } /> </li> ) } diff --git a/packages/block-editor/src/components/block-caption/index.native.js b/packages/block-editor/src/components/block-caption/index.native.js index 08dabe5b4a68b6..818bc3b41c0ab0 100644 --- a/packages/block-editor/src/components/block-caption/index.native.js +++ b/packages/block-editor/src/components/block-caption/index.native.js @@ -20,9 +20,7 @@ const BlockCaption = ( { shouldDisplay, text, } ) => ( - <View - style={ { flex: 1, padding: 12 } } - > + <View style={ { flex: 1, padding: 12 } }> <Caption accessibilityLabelCreator={ accessibilityLabelCreator } accessible={ accessible } @@ -38,16 +36,16 @@ const BlockCaption = ( { export default compose( [ withSelect( ( select, { clientId } ) => { - const { - getBlockAttributes, - getSelectedBlockClientId, - } = select( 'core/block-editor' ); + const { getBlockAttributes, getSelectedBlockClientId } = select( + 'core/block-editor' + ); const { caption } = getBlockAttributes( clientId ); const isBlockSelected = getSelectedBlockClientId() === clientId; // We'll render the caption so that the soft keyboard is not forced to close on Android // but still hide it by setting its display style to none. See wordpress-mobile/gutenberg-mobile#1221 - const shouldDisplay = ! RichText.isEmpty( caption ) > 0 || isBlockSelected; + const shouldDisplay = + ! RichText.isEmpty( caption ) > 0 || isBlockSelected; return { shouldDisplay, diff --git a/packages/block-editor/src/components/block-card/index.js b/packages/block-editor/src/components/block-card/index.js index f060612622804e..674d437d9c7a3e 100644 --- a/packages/block-editor/src/components/block-card/index.js +++ b/packages/block-editor/src/components/block-card/index.js @@ -8,8 +8,12 @@ function BlockCard( { blockType } ) { <div className="block-editor-block-card"> <BlockIcon icon={ blockType.icon } showColors /> <div className="block-editor-block-card__content"> - <div className="block-editor-block-card__title">{ blockType.title }</div> - <div className="block-editor-block-card__description">{ blockType.description }</div> + <div className="block-editor-block-card__title"> + { blockType.title } + </div> + <div className="block-editor-block-card__description"> + { blockType.description } + </div> </div> </div> ); diff --git a/packages/block-editor/src/components/block-compare/block-view.js b/packages/block-editor/src/components/block-compare/block-view.js index ed5e030c7fd4fe..7bb45a4402f241 100644 --- a/packages/block-editor/src/components/block-compare/block-view.js +++ b/packages/block-editor/src/components/block-compare/block-view.js @@ -3,11 +3,20 @@ */ import { Button } from '@wordpress/components'; -const BlockView = ( { title, rawContent, renderedContent, action, actionText, className } ) => { +const BlockView = ( { + title, + rawContent, + renderedContent, + action, + actionText, + className, +} ) => { return ( <div className={ className }> <div className="block-editor-block-compare__content"> - <h2 className="block-editor-block-compare__heading">{ title }</h2> + <h2 className="block-editor-block-compare__heading"> + { title } + </h2> <div className="block-editor-block-compare__html"> { rawContent } @@ -19,7 +28,9 @@ const BlockView = ( { title, rawContent, renderedContent, action, actionText, cl </div> <div className="block-editor-block-compare__action"> - <Button isSecondary tabIndex="0" onClick={ action }>{ actionText }</Button> + <Button isSecondary tabIndex="0" onClick={ action }> + { actionText } + </Button> </div> </div> ); diff --git a/packages/block-editor/src/components/block-compare/index.js b/packages/block-editor/src/components/block-compare/index.js index 18638255508ecf..f80ca412ce5114 100644 --- a/packages/block-editor/src/components/block-compare/index.js +++ b/packages/block-editor/src/components/block-compare/index.js @@ -27,7 +27,11 @@ class BlockCompare extends Component { 'block-editor-block-compare__removed': item.removed, } ); - return <span key={ pos } className={ classes }>{ item.value }</span>; + return ( + <span key={ pos } className={ classes }> + { item.value } + </span> + ); } ); } @@ -43,8 +47,12 @@ class BlockCompare extends Component { const newBlocks = castArray( block ); // Get converted block details - const newContent = newBlocks.map( ( item ) => getSaveContent( item.name, item.attributes, item.innerBlocks ) ); - const renderedContent = newBlocks.map( ( item ) => getSaveElement( item.name, item.attributes, item.innerBlocks ) ); + const newContent = newBlocks.map( ( item ) => + getSaveContent( item.name, item.attributes, item.innerBlocks ) + ); + const renderedContent = newBlocks.map( ( item ) => + getSaveElement( item.name, item.attributes, item.innerBlocks ) + ); return { rawContent: newContent.join( '' ), @@ -53,10 +61,19 @@ class BlockCompare extends Component { } render() { - const { block, onKeep, onConvert, convertor, convertButtonText } = this.props; + const { + block, + onKeep, + onConvert, + convertor, + convertButtonText, + } = this.props; const original = this.getOriginalContent( block ); const converted = this.getConvertedContent( convertor( block ) ); - const difference = this.getDifference( original.rawContent, converted.rawContent ); + const difference = this.getDifference( + original.rawContent, + converted.rawContent + ); return ( <div className="block-editor-block-compare__wrapper"> diff --git a/packages/block-editor/src/components/block-compare/test/block-view.js b/packages/block-editor/src/components/block-compare/test/block-view.js index 657f326f44ce9b..755722983351b4 100644 --- a/packages/block-editor/src/components/block-compare/test/block-view.js +++ b/packages/block-editor/src/components/block-compare/test/block-view.js @@ -11,7 +11,16 @@ import BlockView from '../block-view'; describe( 'BlockView', () => { test( 'should match snapshot', () => { - const wrapper = shallow( <BlockView title="title" rawContent="raw" renderedContent="render" action={ noop } actionText="action" className="class" /> ); + const wrapper = shallow( + <BlockView + title="title" + rawContent="raw" + renderedContent="render" + action={ noop } + actionText="action" + className="class" + /> + ); expect( wrapper ).toMatchSnapshot(); } ); diff --git a/packages/block-editor/src/components/block-draggable/index.js b/packages/block-editor/src/components/block-draggable/index.js index b5851d57a51fbc..dd87e2c5a08fd6 100644 --- a/packages/block-editor/src/components/block-draggable/index.js +++ b/packages/block-editor/src/components/block-draggable/index.js @@ -11,28 +11,35 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { useEffect, useRef } from '@wordpress/element'; const BlockDraggable = ( { children, clientIds } ) => { - const { - srcRootClientId, - index, - isDraggable, - } = useSelect( ( select ) => { - const { - getBlockIndex, - getBlockRootClientId, - getTemplateLock, - } = select( 'core/block-editor' ); - const normalizedClientIds = castArray( clientIds ); - const rootClientId = normalizedClientIds.length === 1 ? getBlockRootClientId( normalizedClientIds[ 0 ] ) : null; - const templateLock = rootClientId ? getTemplateLock( rootClientId ) : null; + const { srcRootClientId, index, isDraggable } = useSelect( + ( select ) => { + const { + getBlockIndex, + getBlockRootClientId, + getTemplateLock, + } = select( 'core/block-editor' ); + const normalizedClientIds = castArray( clientIds ); + const rootClientId = + normalizedClientIds.length === 1 + ? getBlockRootClientId( normalizedClientIds[ 0 ] ) + : null; + const templateLock = rootClientId + ? getTemplateLock( rootClientId ) + : null; - return { - index: getBlockIndex( normalizedClientIds[ 0 ], rootClientId ), - srcRootClientId: rootClientId, - isDraggable: normalizedClientIds.length === 1 && 'all' !== templateLock, - }; - }, [ clientIds ] ); + return { + index: getBlockIndex( normalizedClientIds[ 0 ], rootClientId ), + srcRootClientId: rootClientId, + isDraggable: + normalizedClientIds.length === 1 && 'all' !== templateLock, + }; + }, + [ clientIds ] + ); const isDragging = useRef( false ); - const { startDraggingBlocks, stopDraggingBlocks } = useDispatch( 'core/block-editor' ); + const { startDraggingBlocks, stopDraggingBlocks } = useDispatch( + 'core/block-editor' + ); // Stop dragging blocks if the block draggable is unmounted useEffect( () => { @@ -69,14 +76,12 @@ const BlockDraggable = ( { children, clientIds } ) => { isDragging.current = false; } } > - { - ( { onDraggableStart, onDraggableEnd } ) => { - return children( { - onDraggableStart, - onDraggableEnd, - } ); - } - } + { ( { onDraggableStart, onDraggableEnd } ) => { + return children( { + onDraggableStart, + onDraggableEnd, + } ); + } } </Draggable> ); }; diff --git a/packages/block-editor/src/components/block-drop-zone/index.js b/packages/block-editor/src/components/block-drop-zone/index.js index d78cb7521a4bc6..6f15cb0284af04 100644 --- a/packages/block-editor/src/components/block-drop-zone/index.js +++ b/packages/block-editor/src/components/block-drop-zone/index.js @@ -23,7 +23,10 @@ const parseDropEvent = ( event ) => { } try { - result = Object.assign( result, JSON.parse( event.dataTransfer.getData( 'text' ) ) ); + result = Object.assign( + result, + JSON.parse( event.dataTransfer.getData( 'text' ) ) + ); } catch ( err ) { return result; } @@ -63,55 +66,109 @@ export default function useBlockDropZone( { element, rootClientId } ) { moveBlockToPosition, } = useDispatch( 'core/block-editor' ); - const onFilesDrop = useCallback( ( files ) => { - if ( ! hasUploadPermissions ) { - return; - } - - const transformation = findTransform( - getBlockTransforms( 'from' ), - ( transform ) => transform.type === 'files' && transform.isMatch( files ) - ); - - if ( transformation ) { - const blocks = transformation.transform( files, updateBlockAttributes ); - insertBlocks( blocks, blockIndex, rootClientId ); - } - }, [ hasUploadPermissions, updateBlockAttributes, insertBlocks, blockIndex, rootClientId ] ); - - const onHTMLDrop = useCallback( ( HTML ) => { - const blocks = pasteHandler( { HTML, mode: 'BLOCKS' } ); - - if ( blocks.length ) { - insertBlocks( blocks, blockIndex, rootClientId ); - } - }, [ insertBlocks, blockIndex, rootClientId ] ); - - const onDrop = useCallback( ( event ) => { - const { srcRootClientId, srcClientId, srcIndex, type } = parseDropEvent( event ); - - const isBlockDropType = ( dropType ) => dropType === 'block'; - const isSameLevel = ( srcRoot, dstRoot ) => { - // Note that rootClientId of top-level blocks will be undefined OR a void string, - // so we also need to account for that case separately. - return ( srcRoot === dstRoot ) || ( ! srcRoot === true && ! dstRoot === true ); - }; - const isSameBlock = ( src, dst ) => src === dst; - const isSrcBlockAnAncestorOfDstBlock = ( src, dst ) => getClientIdsOfDescendants( [ src ] ).some( ( id ) => id === dst ); + const onFilesDrop = useCallback( + ( files ) => { + if ( ! hasUploadPermissions ) { + return; + } - if ( ! isBlockDropType( type ) || - isSameBlock( srcClientId, clientId ) || - isSrcBlockAnAncestorOfDstBlock( srcClientId, clientId || rootClientId ) ) { - return; - } + const transformation = findTransform( + getBlockTransforms( 'from' ), + ( transform ) => + transform.type === 'files' && transform.isMatch( files ) + ); + + if ( transformation ) { + const blocks = transformation.transform( + files, + updateBlockAttributes + ); + insertBlocks( blocks, blockIndex, rootClientId ); + } + }, + [ + hasUploadPermissions, + updateBlockAttributes, + insertBlocks, + blockIndex, + rootClientId, + ] + ); + + const onHTMLDrop = useCallback( + ( HTML ) => { + const blocks = pasteHandler( { HTML, mode: 'BLOCKS' } ); + + if ( blocks.length ) { + insertBlocks( blocks, blockIndex, rootClientId ); + } + }, + [ insertBlocks, blockIndex, rootClientId ] + ); + + const onDrop = useCallback( + ( event ) => { + const { + srcRootClientId, + srcClientId, + srcIndex, + type, + } = parseDropEvent( event ); + + const isBlockDropType = ( dropType ) => dropType === 'block'; + const isSameLevel = ( srcRoot, dstRoot ) => { + // Note that rootClientId of top-level blocks will be undefined OR a void string, + // so we also need to account for that case separately. + return ( + srcRoot === dstRoot || + ( ! srcRoot === true && ! dstRoot === true ) + ); + }; + const isSameBlock = ( src, dst ) => src === dst; + const isSrcBlockAnAncestorOfDstBlock = ( src, dst ) => + getClientIdsOfDescendants( [ src ] ).some( + ( id ) => id === dst + ); + + if ( + ! isBlockDropType( type ) || + isSameBlock( srcClientId, clientId ) || + isSrcBlockAnAncestorOfDstBlock( + srcClientId, + clientId || rootClientId + ) + ) { + return; + } - const dstIndex = clientId ? getBlockIndex( clientId, rootClientId ) : undefined; - const positionIndex = blockIndex; - // If the block is kept at the same level and moved downwards, - // subtract to account for blocks shifting upward to occupy its old position. - const insertIndex = dstIndex && srcIndex < dstIndex && isSameLevel( srcRootClientId, rootClientId ) ? positionIndex - 1 : positionIndex; - moveBlockToPosition( srcClientId, srcRootClientId, rootClientId, insertIndex ); - }, [ getClientIdsOfDescendants, getBlockIndex, clientId, blockIndex, moveBlockToPosition, rootClientId ] ); + const dstIndex = clientId + ? getBlockIndex( clientId, rootClientId ) + : undefined; + const positionIndex = blockIndex; + // If the block is kept at the same level and moved downwards, + // subtract to account for blocks shifting upward to occupy its old position. + const insertIndex = + dstIndex && + srcIndex < dstIndex && + isSameLevel( srcRootClientId, rootClientId ) + ? positionIndex - 1 + : positionIndex; + moveBlockToPosition( + srcClientId, + srcRootClientId, + rootClientId, + insertIndex + ); + }, + [ + getClientIdsOfDescendants, + getBlockIndex, + clientId, + blockIndex, + moveBlockToPosition, + rootClientId, + ] + ); const { position } = useDropZone( { element, @@ -128,9 +185,13 @@ export default function useBlockDropZone( { element, rootClientId } ) { const rect = element.current.getBoundingClientRect(); const offset = y - rect.top; - const target = Array.from( element.current.children ).find( ( blockEl ) => { - return blockEl.offsetTop + ( blockEl.offsetHeight / 2 ) > offset; - } ); + const target = Array.from( element.current.children ).find( + ( blockEl ) => { + return ( + blockEl.offsetTop + blockEl.offsetHeight / 2 > offset + ); + } + ); if ( ! target ) { return; diff --git a/packages/block-editor/src/components/block-edit/context.js b/packages/block-editor/src/components/block-edit/context.js index 08f2ac9f1dd6b1..0ab6570bd5da0b 100644 --- a/packages/block-editor/src/components/block-edit/context.js +++ b/packages/block-editor/src/components/block-edit/context.js @@ -39,18 +39,19 @@ export function useBlockEditContext() { * * @return {WPComponent} Enhanced component with injected context as props. */ -export const withBlockEditContext = ( mapContextToProps ) => createHigherOrderComponent( ( OriginalComponent ) => { - return ( props ) => ( - <Consumer> - { ( context ) => ( - <OriginalComponent - { ...props } - { ...mapContextToProps( context, props ) } - /> - ) } - </Consumer> - ); -}, 'withBlockEditContext' ); +export const withBlockEditContext = ( mapContextToProps ) => + createHigherOrderComponent( ( OriginalComponent ) => { + return ( props ) => ( + <Consumer> + { ( context ) => ( + <OriginalComponent + { ...props } + { ...mapContextToProps( context, props ) } + /> + ) } + </Consumer> + ); + }, 'withBlockEditContext' ); /** * A Higher Order Component used to render conditionally the wrapped @@ -60,12 +61,15 @@ export const withBlockEditContext = ( mapContextToProps ) => createHigherOrderCo * * @return {WPComponent} Component which renders only when the BlockEdit is selected. */ -export const ifBlockEditSelected = createHigherOrderComponent( ( OriginalComponent ) => { - return ( props ) => ( - <Consumer> - { ( { isSelected } ) => isSelected && ( - <OriginalComponent { ...props } /> - ) } - </Consumer> - ); -}, 'ifBlockEditSelected' ); +export const ifBlockEditSelected = createHigherOrderComponent( + ( OriginalComponent ) => { + return ( props ) => ( + <Consumer> + { ( { isSelected } ) => + isSelected && <OriginalComponent { ...props } /> + } + </Consumer> + ); + }, + 'ifBlockEditSelected' +); diff --git a/packages/block-editor/src/components/block-edit/edit.js b/packages/block-editor/src/components/block-edit/edit.js index 90944351594859..c31095753d2065 100644 --- a/packages/block-editor/src/components/block-edit/edit.js +++ b/packages/block-editor/src/components/block-edit/edit.js @@ -7,7 +7,11 @@ import classnames from 'classnames'; * WordPress dependencies */ import { withFilters } from '@wordpress/components'; -import { getBlockDefaultClassName, hasBlockSupport, getBlockType } from '@wordpress/blocks'; +import { + getBlockDefaultClassName, + hasBlockSupport, + getBlockType, +} from '@wordpress/blocks'; export const Edit = ( props ) => { const { attributes = {}, name } = props; @@ -18,9 +22,9 @@ export const Edit = ( props ) => { } // Generate a class name for the block's editable form - const generatedClassName = hasBlockSupport( blockType, 'className', true ) ? - getBlockDefaultClassName( name ) : - null; + const generatedClassName = hasBlockSupport( blockType, 'className', true ) + ? getBlockDefaultClassName( name ) + : null; const className = classnames( generatedClassName, attributes.className ); // `edit` and `save` are functions or components describing the markup diff --git a/packages/block-editor/src/components/block-edit/edit.native.js b/packages/block-editor/src/components/block-edit/edit.native.js index 8a4d0ad8350e99..2577ad3e0733e7 100644 --- a/packages/block-editor/src/components/block-edit/edit.native.js +++ b/packages/block-editor/src/components/block-edit/edit.native.js @@ -14,9 +14,7 @@ export const Edit = ( props ) => { const Component = blockType.edit; - return ( - <Component { ...props } /> - ); + return <Component { ...props } />; }; export default withFilters( 'editor.BlockEdit' )( Edit ); diff --git a/packages/block-editor/src/components/block-edit/index.js b/packages/block-editor/src/components/block-edit/index.js index 403a5cd87898e3..3057ff3b4cf72b 100644 --- a/packages/block-editor/src/components/block-edit/index.js +++ b/packages/block-editor/src/components/block-edit/index.js @@ -21,19 +21,42 @@ class BlockEdit extends Component { // It is important to return the same object if props haven't changed // to avoid unnecessary rerenders. // See https://reactjs.org/docs/context.html#caveats. - this.propsToContext = memize( - this.propsToContext.bind( this ), - { maxSize: 1 } - ); + this.propsToContext = memize( this.propsToContext.bind( this ), { + maxSize: 1, + } ); } - propsToContext( name, isSelected, clientId, onFocus, onCaretVerticalPositionChange ) { - return { name, isSelected, clientId, onFocus, onCaretVerticalPositionChange }; + propsToContext( + name, + isSelected, + clientId, + onFocus, + onCaretVerticalPositionChange + ) { + return { + name, + isSelected, + clientId, + onFocus, + onCaretVerticalPositionChange, + }; } render() { - const { name, isSelected, clientId, onFocus, onCaretVerticalPositionChange } = this.props; - const value = this.propsToContext( name, isSelected, clientId, onFocus, onCaretVerticalPositionChange ); + const { + name, + isSelected, + clientId, + onFocus, + onCaretVerticalPositionChange, + } = this.props; + const value = this.propsToContext( + name, + isSelected, + clientId, + onFocus, + onCaretVerticalPositionChange + ); return ( <BlockEditContextProvider value={ value }> diff --git a/packages/block-editor/src/components/block-edit/test/edit.js b/packages/block-editor/src/components/block-edit/test/edit.js index d09ce608cc14bf..0b07eb03b2aad6 100644 --- a/packages/block-editor/src/components/block-edit/test/edit.js +++ b/packages/block-editor/src/components/block-edit/test/edit.js @@ -74,7 +74,9 @@ describe( 'Edit', () => { <Edit name="core/test-block" attributes={ attributes } /> ); - expect( wrapper.find( edit ).hasClass( 'wp-block-test-block' ) ).toBe( true ); + expect( wrapper.find( edit ).hasClass( 'wp-block-test-block' ) ).toBe( + true + ); expect( wrapper.find( edit ).hasClass( 'my-class' ) ).toBe( true ); } ); } ); diff --git a/packages/block-editor/src/components/block-icon/index.js b/packages/block-editor/src/components/block-icon/index.js index 508d48a2211c13..5e953264fc99c3 100644 --- a/packages/block-editor/src/components/block-icon/index.js +++ b/packages/block-editor/src/components/block-icon/index.js @@ -12,24 +12,28 @@ import { Path, Icon, SVG } from '@wordpress/components'; export default function BlockIcon( { icon, showColors = false, className } ) { if ( get( icon, [ 'src' ] ) === 'block-default' ) { icon = { - src: <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M19 7h-1V5h-4v2h-4V5H6v2H5c-1.1 0-2 .9-2 2v10h18V9c0-1.1-.9-2-2-2zm0 10H5V9h14v8z" /></SVG>, + src: ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M19 7h-1V5h-4v2h-4V5H6v2H5c-1.1 0-2 .9-2 2v10h18V9c0-1.1-.9-2-2-2zm0 10H5V9h14v8z" /> + </SVG> + ), }; } const renderedIcon = <Icon icon={ icon && icon.src ? icon.src : icon } />; - const style = showColors ? { - backgroundColor: icon && icon.background, - color: icon && icon.foreground, - } : {}; + const style = showColors + ? { + backgroundColor: icon && icon.background, + color: icon && icon.foreground, + } + : {}; return ( <span style={ style } - className={ classnames( - 'block-editor-block-icon', - className, - { 'has-colors': showColors } - ) } + className={ classnames( 'block-editor-block-icon', className, { + 'has-colors': showColors, + } ) } > { renderedIcon } </span> 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 4dddda5ecce8f0..5708c21d863189 100644 --- a/packages/block-editor/src/components/block-icon/index.native.js +++ b/packages/block-editor/src/components/block-icon/index.native.js @@ -12,19 +12,21 @@ import { Path, Icon, SVG } from '@wordpress/components'; export default function BlockIcon( { icon, showColors = false } ) { if ( get( icon, [ 'src' ] ) === 'block-default' ) { icon = { - src: <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M19 7h-1V5h-4v2h-4V5H6v2H5c-1.1 0-2 .9-2 2v10h18V9c0-1.1-.9-2-2-2zm0 10H5V9h14v8z" /></SVG>, + src: ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M19 7h-1V5h-4v2h-4V5H6v2H5c-1.1 0-2 .9-2 2v10h18V9c0-1.1-.9-2-2-2zm0 10H5V9h14v8z" /> + </SVG> + ), }; } const renderedIcon = <Icon icon={ icon && icon.src ? icon.src : icon } />; - const style = showColors ? { - backgroundColor: icon && icon.background, - color: icon && icon.foreground, - } : {}; + const style = showColors + ? { + backgroundColor: icon && icon.background, + color: icon && icon.foreground, + } + : {}; - return ( - <View style={ style }> - { renderedIcon } - </View> - ); + return <View style={ style }>{ renderedIcon }</View>; } diff --git a/packages/block-editor/src/components/block-icon/test/index.js b/packages/block-editor/src/components/block-icon/test/index.js index 8c74330ad34d49..f7e2317b4e2d4b 100644 --- a/packages/block-editor/src/components/block-icon/test/index.js +++ b/packages/block-editor/src/components/block-icon/test/index.js @@ -17,7 +17,9 @@ describe( 'BlockIcon', () => { it( 'renders a Icon', () => { const wrapper = shallow( <BlockIcon icon="format-image" /> ); - expect( wrapper.containsMatchingElement( <Icon icon="format-image" /> ) ).toBe( true ); + expect( + wrapper.containsMatchingElement( <Icon icon="format-image" /> ) + ).toBe( true ); } ); it( 'renders a span without the has-colors classname', () => { @@ -33,13 +35,20 @@ describe( 'BlockIcon', () => { } ); it( 'skips adding background and foreground styles when colors are not enabled', () => { - const wrapper = shallow( <BlockIcon icon={ { background: 'white', foreground: 'black' } } /> ); + const wrapper = shallow( + <BlockIcon icon={ { background: 'white', foreground: 'black' } } /> + ); expect( wrapper.find( 'span' ).prop( 'style' ) ).toEqual( {} ); } ); it( 'adds background and foreground styles when colors are enabled', () => { - const wrapper = shallow( <BlockIcon icon={ { background: 'white', foreground: 'black' } } showColors /> ); + const wrapper = shallow( + <BlockIcon + icon={ { background: 'white', foreground: 'black' } } + showColors + /> + ); expect( wrapper.find( 'span' ).prop( 'style' ) ).toEqual( { backgroundColor: 'white', diff --git a/packages/block-editor/src/components/block-inspector/index.js b/packages/block-editor/src/components/block-inspector/index.js index 8f58075cfa04c8..0f7fededfb6f9b 100644 --- a/packages/block-editor/src/components/block-inspector/index.js +++ b/packages/block-editor/src/components/block-inspector/index.js @@ -2,7 +2,10 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { getBlockType, getUnregisteredTypeHandlerName } from '@wordpress/blocks'; +import { + getBlockType, + getUnregisteredTypeHandlerName, +} from '@wordpress/blocks'; import { PanelBody, __experimentalSlotFillConsumer, @@ -31,13 +34,18 @@ const BlockInspector = ( { return <MultiSelectionInspector />; } - const isSelectedBlockUnregistered = selectedBlockName === getUnregisteredTypeHandlerName(); + const isSelectedBlockUnregistered = + selectedBlockName === getUnregisteredTypeHandlerName(); /* * If the selected block is of an unregistered type, avoid showing it as an actual selection * because we want the user to focus on the unregistered block warning, not block settings. */ - if ( ! blockType || ! selectedBlockClientId || isSelectedBlockUnregistered ) { + if ( + ! blockType || + ! selectedBlockClientId || + isSelectedBlockUnregistered + ) { if ( showNoBlockSelectedMessage ) { return ( <span className="block-editor-block-inspector__no-blocks"> @@ -53,13 +61,8 @@ const BlockInspector = ( { <BlockCard blockType={ blockType } /> { hasBlockStyles && ( <div> - <PanelBody - title={ __( 'Styles' ) } - initialOpen={ false } - > - <BlockStyles - clientId={ selectedBlockClientId } - /> + <PanelBody title={ __( 'Styles' ) } initialOpen={ false }> + <BlockStyles clientId={ selectedBlockClientId } /> <DefaultStylePicker blockName={ blockType.name } /> </PanelBody> </div> @@ -74,7 +77,9 @@ const BlockInspector = ( { title={ __( 'Advanced' ) } initialOpen={ false } > - <InspectorAdvancedControls.Slot bubblesVirtually /> + <InspectorAdvancedControls.Slot + bubblesVirtually + /> </PanelBody> ) } @@ -85,20 +90,25 @@ const BlockInspector = ( { ); }; -export default withSelect( - ( select ) => { - const { getSelectedBlockClientId, getSelectedBlockCount, getBlockName } = select( 'core/block-editor' ); - const { getBlockStyles } = select( 'core/blocks' ); - const selectedBlockClientId = getSelectedBlockClientId(); - const selectedBlockName = selectedBlockClientId && getBlockName( selectedBlockClientId ); - const blockType = selectedBlockClientId && getBlockType( selectedBlockName ); - const blockStyles = selectedBlockClientId && getBlockStyles( selectedBlockName ); - return { - count: getSelectedBlockCount(), - hasBlockStyles: blockStyles && blockStyles.length > 0, - selectedBlockName, - selectedBlockClientId, - blockType, - }; - } -)( BlockInspector ); +export default withSelect( ( select ) => { + const { + getSelectedBlockClientId, + getSelectedBlockCount, + getBlockName, + } = select( 'core/block-editor' ); + const { getBlockStyles } = select( 'core/blocks' ); + const selectedBlockClientId = getSelectedBlockClientId(); + const selectedBlockName = + selectedBlockClientId && getBlockName( selectedBlockClientId ); + const blockType = + selectedBlockClientId && getBlockType( selectedBlockName ); + const blockStyles = + selectedBlockClientId && getBlockStyles( selectedBlockName ); + return { + count: getSelectedBlockCount(), + hasBlockStyles: blockStyles && blockStyles.length > 0, + selectedBlockName, + selectedBlockClientId, + blockType, + }; +} )( BlockInspector ); diff --git a/packages/block-editor/src/components/block-list-appender/index.js b/packages/block-editor/src/components/block-list-appender/index.js index 6515eea231b4e7..bd226053c6e9b8 100644 --- a/packages/block-editor/src/components/block-list-appender/index.js +++ b/packages/block-editor/src/components/block-list-appender/index.js @@ -78,15 +78,16 @@ function BlockListAppender( { } export default withSelect( ( select, { rootClientId } ) => { - const { - getBlockOrder, - canInsertBlockType, - getTemplateLock, - } = select( 'core/block-editor' ); + const { getBlockOrder, canInsertBlockType, getTemplateLock } = select( + 'core/block-editor' + ); return { isLocked: !! getTemplateLock( rootClientId ), blockClientIds: getBlockOrder( rootClientId ), - canInsertDefaultBlock: canInsertBlockType( getDefaultBlockName(), rootClientId ), + canInsertDefaultBlock: canInsertBlockType( + getDefaultBlockName(), + rootClientId + ), }; } )( BlockListAppender ); diff --git a/packages/block-editor/src/components/block-list-appender/index.native.js b/packages/block-editor/src/components/block-list-appender/index.native.js index 2a77fe797d1321..6b33b1f5c08b7e 100644 --- a/packages/block-editor/src/components/block-list-appender/index.native.js +++ b/packages/block-editor/src/components/block-list-appender/index.native.js @@ -28,9 +28,7 @@ function BlockListAppender( { } if ( CustomAppender ) { - return ( - <CustomAppender showSeparator={ showSeparator } /> - ); + return <CustomAppender showSeparator={ showSeparator } />; } if ( canInsertDefaultBlock ) { @@ -49,15 +47,16 @@ function BlockListAppender( { } export default withSelect( ( select, { rootClientId } ) => { - const { - getBlockOrder, - canInsertBlockType, - getTemplateLock, - } = select( 'core/block-editor' ); + const { getBlockOrder, canInsertBlockType, getTemplateLock } = select( + 'core/block-editor' + ); return { isLocked: !! getTemplateLock( rootClientId ), blockClientIds: getBlockOrder( rootClientId ), - canInsertDefaultBlock: canInsertBlockType( getDefaultBlockName(), rootClientId ), + canInsertDefaultBlock: canInsertBlockType( + getDefaultBlockName(), + rootClientId + ), }; } )( BlockListAppender ); diff --git a/packages/block-editor/src/components/block-list-footer/index.js b/packages/block-editor/src/components/block-list-footer/index.js index 3beba29d90b0ae..5cbfb49f934acd 100644 --- a/packages/block-editor/src/components/block-list-footer/index.js +++ b/packages/block-editor/src/components/block-list-footer/index.js @@ -3,7 +3,9 @@ */ import { createSlotFill } from '@wordpress/components'; -const { Fill: __experimentalBlockListFooter, Slot } = createSlotFill( '__experimentalBlockListFooter' ); +const { Fill: __experimentalBlockListFooter, Slot } = createSlotFill( + '__experimentalBlockListFooter' +); __experimentalBlockListFooter.Slot = Slot; diff --git a/packages/block-editor/src/components/block-list/block-html.js b/packages/block-editor/src/components/block-list/block-html.js index f9a83feaafb3ca..b8f684b62f309f 100644 --- a/packages/block-editor/src/components/block-list/block-html.js +++ b/packages/block-editor/src/components/block-list/block-html.js @@ -1,4 +1,3 @@ - /** * External dependencies */ @@ -9,23 +8,42 @@ import TextareaAutosize from 'react-autosize-textarea'; */ import { useEffect, useState } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; -import { getBlockAttributes, getBlockContent, getBlockType, isValidBlockContent, getSaveContent } from '@wordpress/blocks'; +import { + getBlockAttributes, + getBlockContent, + getBlockType, + isValidBlockContent, + getSaveContent, +} from '@wordpress/blocks'; function BlockHTML( { clientId } ) { const [ html, setHtml ] = useState( '' ); - const { block } = useSelect( ( select ) => ( { - block: select( 'core/block-editor' ).getBlock( clientId ), - } ), [ clientId ] ); + const { block } = useSelect( + ( select ) => ( { + block: select( 'core/block-editor' ).getBlock( clientId ), + } ), + [ clientId ] + ); const { updateBlock } = useDispatch( 'core/block-editor' ); const onChange = () => { const blockType = getBlockType( block.name ); - const attributes = getBlockAttributes( blockType, html, block.attributes ); + const attributes = getBlockAttributes( + blockType, + html, + block.attributes + ); // If html is empty we reset the block to the default HTML and mark it as valid to avoid triggering an error const content = html ? html : getSaveContent( blockType, attributes ); - const isValid = html ? isValidBlockContent( blockType, attributes, content ) : true; + const isValid = html + ? isValidBlockContent( blockType, attributes, content ) + : true; - updateBlock( clientId, { attributes, originalContent: content, isValid } ); + updateBlock( clientId, { + attributes, + originalContent: content, + isValid, + } ); // Ensure the state is updated if we reset so it displays the default content if ( ! html ) { diff --git a/packages/block-editor/src/components/block-list/block-invalid-warning.js b/packages/block-editor/src/components/block-list/block-invalid-warning.js index 0a541263bbb36d..1fa98168e96ed5 100644 --- a/packages/block-editor/src/components/block-list/block-invalid-warning.js +++ b/packages/block-editor/src/components/block-list/block-invalid-warning.js @@ -4,11 +4,7 @@ import { __, _x } from '@wordpress/i18n'; import { Button, Modal } from '@wordpress/components'; import { Component } from '@wordpress/element'; -import { - getBlockType, - createBlock, - rawHandler, -} from '@wordpress/blocks'; +import { getBlockType, createBlock, rawHandler } from '@wordpress/blocks'; import { compose } from '@wordpress/compose'; import { withDispatch, withSelect } from '@wordpress/data'; @@ -36,33 +32,54 @@ export class BlockInvalidWarning extends Component { } render() { - const { convertToHTML, convertToBlocks, convertToClassic, attemptBlockRecovery, block } = this.props; + const { + convertToHTML, + convertToBlocks, + convertToClassic, + attemptBlockRecovery, + block, + } = this.props; const hasHTMLBlock = !! getBlockType( 'core/html' ); const { compare } = this.state; const hiddenActions = [ - { title: __( 'Convert to Classic Block' ), onClick: convertToClassic }, - { title: __( 'Attempt Block Recovery' ), onClick: attemptBlockRecovery }, + { + title: __( 'Convert to Classic Block' ), + onClick: convertToClassic, + }, + { + title: __( 'Attempt Block Recovery' ), + onClick: attemptBlockRecovery, + }, ]; return ( <> <Warning actions={ [ - <Button key="convert" onClick={ this.onCompare } isSecondary={ hasHTMLBlock } isPrimary={ ! hasHTMLBlock }> - { - // translators: Button to fix block content - _x( 'Resolve', 'imperative verb' ) - } + <Button + key="convert" + onClick={ this.onCompare } + isSecondary={ hasHTMLBlock } + isPrimary={ ! hasHTMLBlock } + > + { // translators: Button to fix block content + _x( 'Resolve', 'imperative verb' ) } </Button>, hasHTMLBlock && ( - <Button key="edit" onClick={ convertToHTML } isPrimary> + <Button + key="edit" + onClick={ convertToHTML } + isPrimary + > { __( 'Convert to HTML' ) } </Button> ), ] } secondaryActions={ hiddenActions } > - { __( 'This block contains unexpected or invalid content.' ) } + { __( + 'This block contains unexpected or invalid content.' + ) } </Warning> { compare && ( <Modal @@ -87,16 +104,20 @@ export class BlockInvalidWarning extends Component { } } -const blockToClassic = ( block ) => createBlock( 'core/freeform', { - content: block.originalContent, -} ); -const blockToHTML = ( block ) => createBlock( 'core/html', { - content: block.originalContent, -} ); -const blockToBlocks = ( block ) => rawHandler( { - HTML: block.originalContent, -} ); -const recoverBlock = ( { name, attributes, innerBlocks } ) => createBlock( name, attributes, innerBlocks ); +const blockToClassic = ( block ) => + createBlock( 'core/freeform', { + content: block.originalContent, + } ); +const blockToHTML = ( block ) => + createBlock( 'core/html', { + content: block.originalContent, + } ); +const blockToBlocks = ( block ) => + rawHandler( { + HTML: block.originalContent, + } ); +const recoverBlock = ( { name, attributes, innerBlocks } ) => + createBlock( name, attributes, innerBlocks ); export default compose( [ withSelect( ( select, { clientId } ) => ( { diff --git a/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.js b/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.js index 08edb75976fba0..be93d313342ad7 100644 --- a/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.js +++ b/packages/block-editor/src/components/block-list/block-mobile-floating-toolbar.native.js @@ -11,10 +11,7 @@ import styles from './block-mobile-floating-toolbar.scss'; const FloatingToolbar = ( { children } ) => { return ( <TouchableWithoutFeedback> - <View - style={ styles.floatingToolbar } - >{ children } - </View> + <View style={ styles.floatingToolbar }>{ children }</View> </TouchableWithoutFeedback> ); }; diff --git a/packages/block-editor/src/components/block-list/block-popover.js b/packages/block-editor/src/components/block-list/block-popover.js index fc6834562d8951..2a70b80c2dafa6 100644 --- a/packages/block-editor/src/components/block-list/block-popover.js +++ b/packages/block-editor/src/components/block-list/block-popover.js @@ -67,7 +67,8 @@ function BlockPopover( { const [ isInserterShown, setIsInserterShown ] = useState( false ); const [ blockNodes ] = useContext( BlockNodes ); - const showEmptyBlockSideInserter = ! isNavigationMode && isEmptyDefaultBlock && isValid; + const showEmptyBlockSideInserter = + ! isNavigationMode && isEmptyDefaultBlock && isValid; const shouldShowBreadcrumb = isNavigationMode; const shouldShowContextualToolbar = ! isNavigationMode && @@ -85,7 +86,11 @@ function BlockPopover( { useShortcut( 'core/block-editor/focus-toolbar', useCallback( () => setIsToolbarForced( true ), [] ), - { bindGlobal: true, eventName: 'keydown', isDisabled: ! canFocusHiddenToolbar } + { + bindGlobal: true, + eventName: 'keydown', + isDisabled: ! canFocusHiddenToolbar, + } ); if ( @@ -141,7 +146,9 @@ function BlockPopover( { // left corner. For the side inserter, pop out towards the left, and // position in the right corner. // To do: refactor `Popover` to make this prop clearer. - const popoverPosition = showEmptyBlockSideInserter ? 'top left right' : 'top right left'; + const popoverPosition = showEmptyBlockSideInserter + ? 'top left right' + : 'top right left'; return ( <Popover @@ -154,8 +161,12 @@ function BlockPopover( { __unstableSticky={ ! showEmptyBlockSideInserter } __unstableSlotName="block-toolbar" // Allow subpixel positioning for the block movement animation. - __unstableAllowVerticalSubpixelPosition={ moverDirection !== 'horizontal' && node } - __unstableAllowHorizontalSubpixelPosition={ moverDirection === 'horizontal' && node } + __unstableAllowVerticalSubpixelPosition={ + moverDirection !== 'horizontal' && node + } + __unstableAllowHorizontalSubpixelPosition={ + moverDirection === 'horizontal' && node + } onBlur={ () => setIsToolbarForced( false ) } shouldAnchorIncludePadding > @@ -172,10 +183,15 @@ function BlockPopover( { tabIndex={ -1 } className={ classnames( 'block-editor-block-list__block-popover-inserter', - { 'is-visible': isInserterShown } + { + 'is-visible': isInserterShown, + } ) } > - <Inserter clientId={ clientId } rootClientId={ rootClientId } /> + <Inserter + clientId={ clientId } + rootClientId={ rootClientId } + /> </div> ) } { ( shouldShowContextualToolbar || isToolbarForced ) && ( @@ -219,28 +235,39 @@ function wrapperSelector( select ) { __experimentalGetBlockListSettingsForBlocks, } = select( 'core/block-editor' ); - const clientId = getSelectedBlockClientId() || getFirstMultiSelectedBlockClientId(); + const clientId = + getSelectedBlockClientId() || getFirstMultiSelectedBlockClientId(); if ( ! clientId ) { return; } const rootClientId = getBlockRootClientId( clientId ); - const { name, attributes = {}, isValid } = __unstableGetBlockWithoutInnerBlocks( clientId ) || {}; + const { name, attributes = {}, isValid } = + __unstableGetBlockWithoutInnerBlocks( clientId ) || {}; const blockParentsClientIds = getBlockParents( clientId ); - const { __experimentalMoverDirection } = getBlockListSettings( rootClientId ) || {}; + const { __experimentalMoverDirection } = + getBlockListSettings( rootClientId ) || {}; // Get Block List Settings for all ancestors of the current Block clientId - const ancestorBlockListSettings = __experimentalGetBlockListSettingsForBlocks( blockParentsClientIds ); + const ancestorBlockListSettings = __experimentalGetBlockListSettingsForBlocks( + blockParentsClientIds + ); // Find the index of the first Block with the `captureDescendantsToolbars` prop defined // This will be the top most ancestor because getBlockParents() returns tree from top -> bottom - const topmostAncestorWithCaptureDescendantsToolbarsIndex = findIndex( ancestorBlockListSettings, [ '__experimentalCaptureToolbars', true ] ); + const topmostAncestorWithCaptureDescendantsToolbarsIndex = findIndex( + ancestorBlockListSettings, + [ '__experimentalCaptureToolbars', true ] + ); let capturingClientId; if ( topmostAncestorWithCaptureDescendantsToolbarsIndex !== -1 ) { - capturingClientId = blockParentsClientIds[ topmostAncestorWithCaptureDescendantsToolbarsIndex ]; + capturingClientId = + blockParentsClientIds[ + topmostAncestorWithCaptureDescendantsToolbarsIndex + ]; } return { @@ -250,7 +277,8 @@ function wrapperSelector( select ) { align: attributes.align, isValid, moverDirection: __experimentalMoverDirection, - isEmptyDefaultBlock: name && isUnmodifiedDefaultBlock( { name, attributes } ), + isEmptyDefaultBlock: + name && isUnmodifiedDefaultBlock( { name, attributes } ), capturingClientId, }; } diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index a0ebc634abb526..3fb84e824f1082 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -8,12 +8,14 @@ import { animated } from 'react-spring/web.cjs'; /** * WordPress dependencies */ -import { useRef, useEffect, useLayoutEffect, useState, useContext } from '@wordpress/element'; import { - focus, - isTextField, - placeCaretAtHorizontalEdge, -} from '@wordpress/dom'; + useRef, + useEffect, + useLayoutEffect, + useState, + useContext, +} from '@wordpress/element'; +import { focus, isTextField, placeCaretAtHorizontalEdge } from '@wordpress/dom'; import { BACKSPACE, DELETE, ENTER } from '@wordpress/keycodes'; import { getBlockType, @@ -24,11 +26,7 @@ import { } from '@wordpress/blocks'; import { withFilters } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -import { - withDispatch, - withSelect, - useSelect, -} from '@wordpress/data'; +import { withDispatch, withSelect, useSelect } from '@wordpress/data'; import { withViewportMatch } from '@wordpress/viewport'; import { compose, pure, ifCondition } from '@wordpress/compose'; @@ -133,7 +131,11 @@ function BlockListBlock( { .find( wrapper.current ) .filter( isTextField ) // Exclude inner blocks - .filter( ( node ) => ! ignoreInnerBlocks || isInsideRootBlock( wrapper.current, node ) ); + .filter( + ( node ) => + ! ignoreInnerBlocks || + isInsideRootBlock( wrapper.current, node ) + ); // If reversed (e.g. merge via backspace), use the last in the set of // tabbables. @@ -157,14 +159,16 @@ function BlockListBlock( { } isMounting.current = false; - }, [ - isSelected, - isMultiSelecting, - isNavigationMode, - ] ); + }, [ isSelected, isMultiSelecting, isNavigationMode ] ); // Block Reordering animation - const animationStyle = useMovingAnimation( wrapper, isSelected || isPartOfMultiSelection, isSelected || isFirstMultiSelected, enableAnimation, animateOnChange ); + const animationStyle = useMovingAnimation( + wrapper, + isSelected || isPartOfMultiSelection, + isSelected || isFirstMultiSelected, + enableAnimation, + animateOnChange + ); // Other event handlers @@ -214,14 +218,16 @@ function BlockListBlock( { // If the block is selected and we're typing the block should not appear. // Empty paragraph blocks should always show up as unselected. - const showEmptyBlockSideInserter = ! isNavigationMode && isSelected && isEmptyDefaultBlock && isValid; + const showEmptyBlockSideInserter = + ! isNavigationMode && isSelected && isEmptyDefaultBlock && isValid; const shouldAppearSelected = ! isFocusMode && ! showEmptyBlockSideInserter && isSelected && ! isTypingWithinBlock; - const isDragging = isDraggingBlocks && ( isSelected || isPartOfMultiSelection ); + const isDragging = + isDraggingBlocks && ( isSelected || isPartOfMultiSelection ); // Determine whether the block has props to apply to the wrapper. if ( blockType.getEditWrapperProps ) { @@ -245,7 +251,8 @@ function BlockListBlock( { 'is-reusable': isReusableBlock( blockType ), 'is-dragging': isDragging, 'is-typing': isTypingWithinBlock, - 'is-focused': isFocusMode && ( isSelected || isAncestorOfSelectedBlock ), + 'is-focused': + isFocusMode && ( isSelected || isAncestorOfSelectedBlock ), 'is-focus-mode': isFocusMode, 'has-child-selected': isAncestorOfSelectedBlock, 'is-block-collapsed': isAligned, @@ -301,12 +308,12 @@ function BlockListBlock( { role="group" { ...wrapperProps } style={ - wrapperProps && wrapperProps.style ? - { - ...wrapperProps.style, - ...animationStyle, - } : - animationStyle + wrapperProps && wrapperProps.style + ? { + ...wrapperProps.style, + ...animationStyle, + } + : animationStyle } > <BlockCrashBoundary onError={ onBlockError }> @@ -355,7 +362,10 @@ const applyWithSelect = withSelect( const checkDeep = true; // "ancestor" is the more appropriate label due to "deep" check - const isAncestorOfSelectedBlock = hasSelectedInnerBlock( clientId, checkDeep ); + const isAncestorOfSelectedBlock = hasSelectedInnerBlock( + clientId, + checkDeep + ); // The fallback to `{}` is a temporary fix. // This function should never be called when a block is not present in the state. @@ -365,9 +375,11 @@ const applyWithSelect = withSelect( return { isMultiSelected: isBlockMultiSelected( clientId ), isPartOfMultiSelection: - isBlockMultiSelected( clientId ) || isAncestorMultiSelected( clientId ), + isBlockMultiSelected( clientId ) || + isAncestorMultiSelected( clientId ), isFirstMultiSelected: isFirstMultiSelectedBlock( clientId ), - isLastMultiSelected: getLastMultiSelectedBlockClientId() === clientId, + isLastMultiSelected: + getLastMultiSelectedBlockClientId() === clientId, // We only care about this prop when the block is selected // Thus to avoid unnecessary rerenders we avoid updating the prop if the block is not selected. @@ -376,7 +388,9 @@ const applyWithSelect = withSelect( mode: getBlockMode( clientId ), isSelectionEnabled: isSelectionEnabled(), - initialPosition: isSelected ? getSelectedBlocksInitialCaretPosition() : null, + initialPosition: isSelected + ? getSelectedBlocksInitialCaretPosition() + : null, isEmptyDefaultBlock: name && isUnmodifiedDefaultBlock( { name, attributes } ), isLocked: !! templateLock, @@ -421,17 +435,13 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { }, onInsertDefaultBlockAfter() { const { clientId, rootClientId } = ownProps; - const { - getBlockIndex, - } = select( 'core/block-editor' ); + const { getBlockIndex } = select( 'core/block-editor' ); const index = getBlockIndex( clientId, rootClientId ); insertDefaultBlock( {}, rootClientId, index + 1 ); }, onInsertBlocksAfter( blocks ) { const { clientId, rootClientId } = ownProps; - const { - getBlockIndex, - } = select( 'core/block-editor' ); + const { getBlockIndex } = select( 'core/block-editor' ); const index = getBlockIndex( clientId, rootClientId ); insertBlocks( blocks, index + 1, rootClientId ); }, @@ -440,10 +450,9 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { }, onMerge( forward ) { const { clientId } = ownProps; - const { - getPreviousBlockClientId, - getNextBlockClientId, - } = select( 'core/block-editor' ); + const { getPreviousBlockClientId, getNextBlockClientId } = select( + 'core/block-editor' + ); if ( forward ) { const nextBlockClientId = getNextBlockClientId( clientId ); @@ -451,7 +460,9 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { mergeBlocks( clientId, nextBlockClientId ); } } else { - const previousBlockClientId = getPreviousBlockClientId( clientId ); + const previousBlockClientId = getPreviousBlockClientId( + clientId + ); if ( previousBlockClientId ) { mergeBlocks( previousBlockClientId, clientId ); } diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index ae951ec3848d40..08ec82de98cb74 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -1,11 +1,7 @@ /** * External dependencies */ -import { - View, - Text, - TouchableWithoutFeedback, -} from 'react-native'; +import { View, Text, TouchableWithoutFeedback } from 'react-native'; /** * WordPress dependencies @@ -67,7 +63,9 @@ class BlockListBlock extends Component { onReplace={ this.props.onReplace } insertBlocksAfter={ this.insertBlocksAfter } mergeBlocks={ this.props.mergeBlocks } - onCaretVerticalPositionChange={ this.props.onCaretVerticalPositionChange } + onCaretVerticalPositionChange={ + this.props.onCaretVerticalPositionChange + } clientId={ this.props.clientId } /> ); @@ -89,13 +87,18 @@ class BlockListBlock extends Component { isRootListInnerBlockHolder, } = this.props; - const fullSolidBorderStyle = { // define style for full border + const fullSolidBorderStyle = { + // define style for full border ...styles.fullSolidBordered, - ...getStylesFromColorScheme( styles.solidBorderColor, styles.solidBorderColorDark ), + ...getStylesFromColorScheme( + styles.solidBorderColor, + styles.solidBorderColorDark + ), }; - if ( hasChildren ) { // if block has children apply style for selected parent - return { ...styles.selectedParent, ...fullSolidBorderStyle }; + if ( hasChildren ) { + // if block has children apply style for selected parent + return { ...styles.selectedParent, ...fullSolidBorderStyle }; } // apply semi border selected style when screen is in vertical position @@ -122,6 +125,7 @@ class BlockListBlock extends Component { isAncestorSelected, hasParent, getStylesFromColorScheme, + isLastBlock, } = this.props; // if block does not have parent apply neutral or full @@ -130,23 +134,37 @@ class BlockListBlock extends Component { return hasChildren ? styles.neutral : styles.full; } - if ( isParentSelected ) { // parent of a block is selected - const dashedBorderStyle = { // define style for dashed border + if ( isParentSelected ) { + // parent of a block is selected + const dashedBorderStyle = { + // define style for dashed border ...styles.dashedBordered, - ...getStylesFromColorScheme( styles.dashedBorderColor, styles.dashedBorderColorDark ), + ...getStylesFromColorScheme( + styles.dashedBorderColor, + styles.dashedBorderColorDark + ), }; // return apply childOfSelected or childOfSelectedLeaf // margins depending if block has children or not - return hasChildren ? - { ...styles.childOfSelected, ...dashedBorderStyle } : - { ...styles.childOfSelectedLeaf, ...dashedBorderStyle }; + return { + ...( hasChildren + ? styles.childOfSelected + : styles.childOfSelectedLeaf ), + ...dashedBorderStyle, + ...( ! isLastBlock && styles.marginVerticalChild ), + }; } - if ( isAncestorSelected ) { // ancestor of a block is selected + if ( isAncestorSelected ) { + // ancestor of a block is selected return { ...styles.descendantOfSelectedLeaf, - ...( hasChildren && styles.marginHorizontalNone ), + ...( hasChildren && { + ...styles.marginHorizontalNone, + ...styles.marginVerticalNone, + } ), + ...( ! isLastBlock && styles.marginVerticalDescendant ), }; } @@ -156,22 +174,18 @@ class BlockListBlock extends Component { } applyBlockStyle() { - const { - isSelected, - isDimmed, - } = this.props; + const { isSelected, isDimmed } = this.props; return [ - isSelected ? this.applySelectedBlockStyle() : this.applyUnSelectedBlockStyle(), + isSelected + ? this.applySelectedBlockStyle() + : this.applyUnSelectedBlockStyle(), isDimmed && styles.dimmed, ]; } applyToolbarStyle() { - const { - hasChildren, - isUnregisteredBlock, - } = this.props; + const { hasChildren, isUnregisteredBlock } = this.props; if ( ! hasChildren || isUnregisteredBlock ) { return styles.neutralToolbar; @@ -193,23 +207,29 @@ class BlockListBlock extends Component { isTouchable, } = this.props; - const accessibilityLabel = getAccessibleBlockLabel( blockType, attributes, order + 1 ); + const accessibilityLabel = getAccessibleBlockLabel( + blockType, + attributes, + order + 1 + ); return ( <> - { showFloatingToolbar && - ( <FloatingToolbar> + { showFloatingToolbar && ( + <FloatingToolbar> <Toolbar passedStyle={ styles.toolbar }> <ToolbarButton title={ __( 'Navigate Up' ) } - onClick={ () => this.props.onSelect( parentId ) } + onClick={ () => + this.props.onSelect( parentId ) + } icon={ NavigateUpSVG } /> <View style={ styles.pipe } /> </Toolbar> <Breadcrumbs clientId={ clientId } /> </FloatingToolbar> - ) } + ) } <TouchableWithoutFeedback onPress={ this.onFocus } accessible={ ! isSelected } @@ -220,8 +240,19 @@ class BlockListBlock extends Component { accessibilityLabel={ accessibilityLabel } style={ this.applyBlockStyle() } > - { isValid ? this.getBlockForType() : <BlockInvalidWarning blockTitle={ title } icon={ icon } /> } - <View style={ this.applyToolbarStyle() } >{ isSelected && <BlockMobileToolbar clientId={ clientId } /> }</View> + { isValid ? ( + this.getBlockForType() + ) : ( + <BlockInvalidWarning + blockTitle={ title } + icon={ icon } + /> + ) } + <View style={ this.applyToolbarStyle() }> + { isSelected && ( + <BlockMobileToolbar clientId={ clientId } /> + ) } + </View> </View> </TouchableWithoutFeedback> </> @@ -233,7 +264,6 @@ export default compose( [ withSelect( ( select, { clientId, rootClientId } ) => { const { getBlockIndex, - getBlocks, isBlockSelected, __unstableGetBlockWithoutInnerBlocks, getBlockHierarchyRootClientId, @@ -245,13 +275,11 @@ export default compose( [ getBlockCount, } = select( 'core/block-editor' ); - const { - getGroupingBlockName, - } = select( 'core/blocks' ); + const { getGroupingBlockName } = select( 'core/blocks' ); const order = getBlockIndex( clientId, rootClientId ); const isSelected = isBlockSelected( clientId ); - const isLastBlock = order === getBlocks().length - 1; + const isLastBlock = order === getBlockCount( rootClientId ) - 1; const block = __unstableGetBlockWithoutInnerBlocks( clientId ); const { name, attributes, isValid } = block || {}; @@ -271,25 +299,49 @@ export default compose( [ const selectedBlockClientId = getSelectedBlockClientId(); - const commonAncestor = getLowestCommonAncestorWithSelectedBlock( clientId ); + const commonAncestor = getLowestCommonAncestorWithSelectedBlock( + clientId + ); const commonAncestorIndex = parents.indexOf( commonAncestor ) - 1; - const firstToSelectId = commonAncestor ? parents[ commonAncestorIndex ] : parents[ parents.length - 1 ]; + const firstToSelectId = commonAncestor + ? parents[ commonAncestorIndex ] + : parents[ parents.length - 1 ]; - const hasChildren = ! isUnregisteredBlock && !! getBlockCount( clientId ); + const hasChildren = + ! isUnregisteredBlock && !! getBlockCount( clientId ); const hasParent = !! parentId; - const isParentSelected = selectedBlockClientId && selectedBlockClientId === parentId; - const isAncestorSelected = selectedBlockClientId && parents.includes( selectedBlockClientId ); - const isSelectedBlockNested = !! getBlockRootClientId( selectedBlockClientId ); + const isParentSelected = + selectedBlockClientId && selectedBlockClientId === parentId; + const isAncestorSelected = + selectedBlockClientId && parents.includes( selectedBlockClientId ); + const isSelectedBlockNested = !! getBlockRootClientId( + selectedBlockClientId + ); - const selectedParents = selectedBlockClientId ? getBlockParents( selectedBlockClientId ) : []; + const selectedParents = selectedBlockClientId + ? getBlockParents( selectedBlockClientId ) + : []; const isDescendantSelected = selectedParents.includes( clientId ); - const isDescendantOfParentSelected = selectedParents.includes( parentId ); - const isTouchable = isSelected || isDescendantOfParentSelected || isParentSelected || parentId === ''; - const isDimmed = ! isSelected && isSelectedBlockNested && ! isAncestorSelected && ! isDescendantSelected && ( isDescendantOfParentSelected || rootBlockId === clientId ); + const isDescendantOfParentSelected = selectedParents.includes( + parentId + ); + const isTouchable = + isSelected || + isDescendantOfParentSelected || + isParentSelected || + parentId === ''; + const isDimmed = + ! isSelected && + isSelectedBlockNested && + ! isAncestorSelected && + ! isDescendantSelected && + ( isDescendantOfParentSelected || rootBlockId === clientId ); // TODO: find better way to handle full border for columns ( maybe return array from getGoupingBlockName ) - const isInnerBlockHolder = name === getGroupingBlockName() || name === 'core/columns'; - const isRootListInnerBlockHolder = ! isSelectedBlockNested && isInnerBlockHolder; + const isInnerBlockHolder = + name === getGroupingBlockName() || name === 'core/columns'; + const isRootListInnerBlockHolder = + ! isSelectedBlockNested && isInnerBlockHolder; return { icon, @@ -337,7 +389,9 @@ export default compose( [ mergeBlocks( clientId, nextBlockClientId ); } } else { - const previousBlockClientId = getPreviousBlockClientId( clientId ); + const previousBlockClientId = getPreviousBlockClientId( + clientId + ); if ( previousBlockClientId ) { mergeBlocks( previousBlockClientId, clientId ); } diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index 3867b583105979..f22e0712553535 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -54,6 +54,19 @@ margin-right: 0; } +.marginVerticalDescendant { + margin-bottom: $block-selected-vertical-margin-descendant; +} + +.marginVerticalChild { + margin-bottom: $block-selected-vertical-margin-child; +} + +.marginVerticalNone { + margin-top: 0; + margin-bottom: 0; +} + .blockTitle { background-color: $gray; padding-left: 8px; diff --git a/packages/block-editor/src/components/block-list/breadcrumb.js b/packages/block-editor/src/components/block-list/breadcrumb.js index 2f0c4f28bd9180..a47db75152663e 100644 --- a/packages/block-editor/src/components/block-list/breadcrumb.js +++ b/packages/block-editor/src/components/block-list/breadcrumb.js @@ -25,21 +25,30 @@ import BlockTitle from '../block-title'; * * @return {WPComponent} The component to be rendered. */ -function BlockBreadcrumb( { clientId, rootClientId, moverDirection, ...props } ) { - const selected = useSelect( ( select ) => { - const { - __unstableGetBlockWithoutInnerBlocks, - getBlockIndex, - } = select( 'core/block-editor' ); - const index = getBlockIndex( clientId, rootClientId ); - const { name, attributes } = __unstableGetBlockWithoutInnerBlocks( clientId ); - return { index, name, attributes }; - }, [ clientId, rootClientId ] ); +function BlockBreadcrumb( { + clientId, + rootClientId, + moverDirection, + ...props +} ) { + const selected = useSelect( + ( select ) => { + const { + __unstableGetBlockWithoutInnerBlocks, + getBlockIndex, + } = select( 'core/block-editor' ); + const index = getBlockIndex( clientId, rootClientId ); + const { name, attributes } = __unstableGetBlockWithoutInnerBlocks( + clientId + ); + return { index, name, attributes }; + }, + [ clientId, rootClientId ] + ); const { index, name, attributes } = selected; - const { - setNavigationMode, - removeBlock, - } = useDispatch( 'core/block-editor' ); + const { setNavigationMode, removeBlock } = useDispatch( + 'core/block-editor' + ); const ref = useRef(); // Focus the breadcrumb in navigation mode. @@ -57,7 +66,12 @@ function BlockBreadcrumb( { clientId, rootClientId, moverDirection, ...props } ) } const blockType = getBlockType( name ); - const label = getAccessibleBlockLabel( blockType, attributes, index + 1, moverDirection ); + const label = getAccessibleBlockLabel( + blockType, + attributes, + index + 1, + moverDirection + ); return ( <div className="block-editor-block-list__breadcrumb" { ...props }> diff --git a/packages/block-editor/src/components/block-list/breadcrumb.native.js b/packages/block-editor/src/components/block-list/breadcrumb.native.js index 14e0fd497fe073..5ea87291a65315 100644 --- a/packages/block-editor/src/components/block-list/breadcrumb.native.js +++ b/packages/block-editor/src/components/block-list/breadcrumb.native.js @@ -19,27 +19,49 @@ import SubdirectorSVG from './subdirectory-icon'; import styles from './breadcrumb.scss'; -const BlockBreadcrumb = ( { clientId, blockIcon, rootClientId, rootBlockIcon } ) => { +const BlockBreadcrumb = ( { + clientId, + blockIcon, + rootClientId, + rootBlockIcon, +} ) => { const renderIcon = ( icon, key ) => { if ( typeof icon.src === 'function' ) { - return <Icon key={ key } icon={ icon.src( { size: 24, fill: styles.icon.color } ) } />; + return ( + <Icon + key={ key } + icon={ icon.src( { size: 24, fill: styles.icon.color } ) } + /> + ); } - return <Icon key={ key } size={ 24 } icon={ icon.src } fill={ styles.icon.color } />; + return ( + <Icon + key={ key } + size={ 24 } + icon={ icon.src } + fill={ styles.icon.color } + /> + ); }; return ( <View style={ styles.breadcrumbContainer }> <TouchableOpacity style={ styles.button } - onPress={ () => {/* Open BottomSheet with markup */} } - disabled={ true } /* Disable temporarily since onPress function is empty */ + onPress={ () => { + /* Open BottomSheet with markup */ + } } + disabled={ + true + } /* Disable temporarily since onPress function is empty */ > - { rootClientId && rootBlockIcon && ( - [ + { rootClientId && + rootBlockIcon && [ renderIcon( rootBlockIcon, 'parent-icon' ), - <View key="subdirectory-icon" style={ styles.arrow }><SubdirectorSVG fill={ styles.arrow.color } /></View>, - ] - ) } + <View key="subdirectory-icon" style={ styles.arrow }> + <SubdirectorSVG fill={ styles.arrow.color } /> + </View>, + ] } { renderIcon( blockIcon ) } <Text maxFontSizeMultiplier={ 1.25 } @@ -56,10 +78,9 @@ const BlockBreadcrumb = ( { clientId, blockIcon, rootClientId, rootBlockIcon } ) export default compose( [ withSelect( ( select, { clientId } ) => { - const { - getBlockRootClientId, - getBlockName, - } = select( 'core/block-editor' ); + const { getBlockRootClientId, getBlockName } = select( + 'core/block-editor' + ); const blockName = getBlockName( clientId ); const blockType = getBlockType( blockName ); diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 1fd0f8afa9fc50..ffb5320faa1aae 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -56,10 +56,9 @@ function BlockList( { selectedBlockClientId: getSelectedBlockClientId(), multiSelectedBlockClientIds: getMultiSelectedBlockClientIds(), hasMultiSelection: hasMultiSelection(), - enableAnimation: ( + enableAnimation: ! isTyping() && - getGlobalBlockCount() <= BLOCK_ANIMATION_THRESHOLD - ), + getGlobalBlockCount() <= BLOCK_ANIMATION_THRESHOLD, }; } @@ -88,12 +87,15 @@ function BlockList( { ) } > { blockClientIds.map( ( clientId, index ) => { - const isBlockInSelection = hasMultiSelection ? - multiSelectedBlockClientIds.includes( clientId ) : - selectedBlockClientId === clientId; + const isBlockInSelection = hasMultiSelection + ? multiSelectedBlockClientIds.includes( clientId ) + : selectedBlockClientId === clientId; return ( - <AsyncModeProvider key={ clientId } value={ ! isBlockInSelection }> + <AsyncModeProvider + key={ clientId } + value={ ! isBlockInSelection } + > <BlockListBlock rootClientId={ rootClientId } clientId={ clientId } @@ -104,8 +106,14 @@ function BlockList( { // otherwise there might be a small delay to trigger the animation. animateOnChange={ index } enableAnimation={ enableAnimation } - hasSelectedUI={ __experimentalUIParts.hasSelectedUI } - className={ clientId === targetClientId ? 'is-drop-target' : undefined } + hasSelectedUI={ + __experimentalUIParts.hasSelectedUI + } + className={ + clientId === targetClientId + ? 'is-drop-target' + : undefined + } /> </AsyncModeProvider> ); @@ -113,7 +121,9 @@ function BlockList( { <BlockListAppender rootClientId={ rootClientId } renderAppender={ renderAppender } - className={ targetClientId === null ? 'is-drop-target' : undefined } + className={ + targetClientId === null ? 'is-drop-target' : undefined + } /> <__experimentalBlockListFooter.Slot /> </Container> diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 51597180232f28..91c0a3a6921766 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -11,7 +11,10 @@ import { Component } from '@wordpress/element'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { createBlock } from '@wordpress/blocks'; -import { KeyboardAwareFlatList, ReadableContentView } from '@wordpress/components'; +import { + KeyboardAwareFlatList, + ReadableContentView, +} from '@wordpress/components'; /** * Internal dependencies @@ -30,12 +33,20 @@ export class BlockList extends Component { this.renderItem = this.renderItem.bind( this ); this.renderBlockListFooter = this.renderBlockListFooter.bind( this ); - this.renderDefaultBlockAppender = this.renderDefaultBlockAppender.bind( this ); - this.onCaretVerticalPositionChange = this.onCaretVerticalPositionChange.bind( this ); + this.renderDefaultBlockAppender = this.renderDefaultBlockAppender.bind( + this + ); + this.onCaretVerticalPositionChange = this.onCaretVerticalPositionChange.bind( + this + ); this.scrollViewInnerRef = this.scrollViewInnerRef.bind( this ); this.addBlockToEndOfPost = this.addBlockToEndOfPost.bind( this ); - this.shouldFlatListPreventAutomaticScroll = this.shouldFlatListPreventAutomaticScroll.bind( this ); - this.shouldShowInnerBlockAppender = this.shouldShowInnerBlockAppender.bind( this ); + this.shouldFlatListPreventAutomaticScroll = this.shouldFlatListPreventAutomaticScroll.bind( + this + ); + this.shouldShowInnerBlockAppender = this.shouldShowInnerBlockAppender.bind( + this + ); } addBlockToEndOfPost( newBlock ) { @@ -43,7 +54,12 @@ export class BlockList extends Component { } onCaretVerticalPositionChange( targetId, caretY, previousCaretY ) { - KeyboardAwareFlatList.handleCaretVerticalPositionChange( this.scrollViewRef, targetId, caretY, previousCaretY ); + KeyboardAwareFlatList.handleCaretVerticalPositionChange( + this.scrollViewRef, + targetId, + caretY, + previousCaretY + ); } scrollViewInnerRef( ref ) { @@ -59,7 +75,7 @@ export class BlockList extends Component { const willShowInsertionPoint = shouldShowInsertionPointBefore(); // call without the client_id argument since this is the appender return ( <ReadableContentView> - <BlockListAppender // show the default appender, anormal, when not inserting a block + <BlockListAppender // show the default appender, anormal, when not inserting a block rootClientId={ this.props.rootClientId } renderAppender={ this.props.renderAppender } showSeparator={ willShowInsertionPoint } @@ -69,11 +85,8 @@ export class BlockList extends Component { } shouldShowInnerBlockAppender() { - const { - blockClientIds, - renderAppender, - } = this.props; - return ( renderAppender && blockClientIds.length > 0 ); + const { blockClientIds, renderAppender } = this.props; + return renderAppender && blockClientIds.length > 0; } render() { @@ -95,7 +108,9 @@ export class BlockList extends Component { onAccessibilityEscape={ clearSelectedBlock } > <KeyboardAwareFlatList - { ...( Platform.OS === 'android' ? { removeClippedSubviews: false } : {} ) } // Disable clipping on Android to fix focus losing. See https://github.com/wordpress-mobile/gutenberg-mobile/pull/741#issuecomment-472746541 + { ...( Platform.OS === 'android' + ? { removeClippedSubviews: false } + : {} ) } // Disable clipping on Android to fix focus losing. See https://github.com/wordpress-mobile/gutenberg-mobile/pull/741#issuecomment-472746541 accessibilityLabel="block-list" autoScroll={ this.props.autoScroll } innerRef={ this.scrollViewInnerRef } @@ -107,11 +122,17 @@ export class BlockList extends Component { extraData={ [ isFullyBordered ] } keyExtractor={ identity } renderItem={ this.renderItem } - shouldPreventAutomaticScroll={ this.shouldFlatListPreventAutomaticScroll } + shouldPreventAutomaticScroll={ + this.shouldFlatListPreventAutomaticScroll + } title={ title } ListHeaderComponent={ ! isReadOnly && header } - ListEmptyComponent={ ! isReadOnly && this.renderDefaultBlockAppender } - ListFooterComponent={ ! isReadOnly && withFooter && this.renderBlockListFooter } + ListEmptyComponent={ + ! isReadOnly && this.renderDefaultBlockAppender + } + ListFooterComponent={ + ! isReadOnly && withFooter && this.renderBlockListFooter + } /> { this.shouldShowInnerBlockAppender() && ( @@ -122,9 +143,7 @@ export class BlockList extends Component { showSeparator /> </View> - ) - } - + ) } </View> ); } @@ -162,22 +181,33 @@ export class BlockList extends Component { const attributes = getBlockAttributes( clientId ); let columnContainerStyle = {}; if ( attributes ) { - columnContainerStyle = getVerticalAlignmentRemap( attributes.verticalAlignment ); + columnContainerStyle = getVerticalAlignmentRemap( + attributes.verticalAlignment + ); } return ( - <ReadableContentView style={ containerStyle ? columnContainerStyle : undefined } > - <View pointerEvents={ isReadOnly ? 'box-only' : 'auto' } > - { shouldShowInsertionPointBefore( clientId ) && <BlockInsertionPoint /> } + <ReadableContentView + style={ containerStyle ? columnContainerStyle : undefined } + > + <View pointerEvents={ isReadOnly ? 'box-only' : 'auto' }> + { shouldShowInsertionPointBefore( clientId ) && ( + <BlockInsertionPoint /> + ) } <BlockListBlock key={ clientId } showTitle={ false } clientId={ clientId } rootClientId={ this.props.rootClientId } - onCaretVerticalPositionChange={ this.onCaretVerticalPositionChange } + onCaretVerticalPositionChange={ + this.onCaretVerticalPositionChange + } isSmallScreen={ ! this.props.isFullyBordered } /> - { ! this.shouldShowInnerBlockAppender() && shouldShowInsertionPointAfter( clientId ) && <BlockInsertionPoint /> } + { ! this.shouldShowInnerBlockAppender() && + shouldShowInsertionPointAfter( clientId ) && ( + <BlockInsertionPoint /> + ) } </View> </ReadableContentView> ); @@ -187,9 +217,11 @@ export class BlockList extends Component { const paragraphBlock = createBlock( 'core/paragraph' ); return ( <> - <TouchableWithoutFeedback onPress={ () => { - this.addBlockToEndOfPost( paragraphBlock ); - } } > + <TouchableWithoutFeedback + onPress={ () => { + this.addBlockToEndOfPost( paragraphBlock ); + } } + > <View style={ styles.blockListFooter } /> </TouchableWithoutFeedback> <__experimentalBlockListFooter.Slot /> @@ -218,22 +250,18 @@ export default compose( [ return ( blockInsertionPointIsVisible && insertionPoint.rootClientId === rootClientId && - ( - // if list is empty, show the insertion point (via the default appender) - blockClientIds.length === 0 || + // if list is empty, show the insertion point (via the default appender) + ( blockClientIds.length === 0 || // or if the insertion point is right before the denoted block - blockClientIds[ insertionPoint.index ] === clientId - ) + blockClientIds[ insertionPoint.index ] === clientId ) ); }; const shouldShowInsertionPointAfter = ( clientId ) => { return ( blockInsertionPointIsVisible && insertionPoint.rootClientId === rootClientId && - // if the insertion point is at the end of the list blockClientIds.length === insertionPoint.index && - // and the denoted block is the last one on the list, show the indicator at the end of the block blockClientIds[ insertionPoint.index - 1 ] === clientId ); @@ -241,7 +269,9 @@ export default compose( [ const isReadOnly = getSettings().readOnly; - const getBlockAttributes = ( clientId ) => ( __unstableGetBlockWithoutInnerBlocks( clientId ) || {} ).attributes; + const getBlockAttributes = ( clientId ) => + ( __unstableGetBlockWithoutInnerBlocks( clientId ) || {} ) + .attributes; return { blockClientIds, @@ -256,11 +286,9 @@ export default compose( [ }; } ), withDispatch( ( dispatch ) => { - const { - insertBlock, - replaceBlock, - clearSelectedBlock, - } = dispatch( 'core/block-editor' ); + const { insertBlock, replaceBlock, clearSelectedBlock } = dispatch( + 'core/block-editor' + ); return { clearSelectedBlock, diff --git a/packages/block-editor/src/components/block-list/insertion-point.js b/packages/block-editor/src/components/block-list/insertion-point.js index 7ca4d53ca065ce..11ef726060509a 100644 --- a/packages/block-editor/src/components/block-list/insertion-point.js +++ b/packages/block-editor/src/components/block-list/insertion-point.js @@ -19,28 +19,33 @@ import { getClosestTabbable } from '../writing-flow'; import { getBlockDOMNode } from '../../utils/dom'; function Indicator( { clientId } ) { - const showInsertionPoint = useSelect( ( select ) => { - const { - getBlockIndex, - getBlockInsertionPoint, - isBlockInsertionPointVisible, - getBlockRootClientId, - } = select( 'core/block-editor' ); - const rootClientId = getBlockRootClientId( clientId ); - const blockIndex = getBlockIndex( clientId, rootClientId ); - const insertionPoint = getBlockInsertionPoint(); - return ( - isBlockInsertionPointVisible() && - insertionPoint.index === blockIndex && - insertionPoint.rootClientId === rootClientId - ); - }, [ clientId ] ); + const showInsertionPoint = useSelect( + ( select ) => { + const { + getBlockIndex, + getBlockInsertionPoint, + isBlockInsertionPointVisible, + getBlockRootClientId, + } = select( 'core/block-editor' ); + const rootClientId = getBlockRootClientId( clientId ); + const blockIndex = getBlockIndex( clientId, rootClientId ); + const insertionPoint = getBlockInsertionPoint(); + return ( + isBlockInsertionPointVisible() && + insertionPoint.index === blockIndex && + insertionPoint.rootClientId === rootClientId + ); + }, + [ clientId ] + ); if ( ! showInsertionPoint ) { return null; } - return <div className="block-editor-block-list__insertion-point-indicator" />; + return ( + <div className="block-editor-block-list__insertion-point-indicator" /> + ); } export default function InsertionPoint( { @@ -66,9 +71,11 @@ export default function InsertionPoint( { const rect = event.target.getBoundingClientRect(); const offset = event.clientY - rect.top; - const element = Array.from( event.target.children ).find( ( blockEl ) => { - return blockEl.offsetTop > offset; - } ); + const element = Array.from( event.target.children ).find( + ( blockEl ) => { + return blockEl.offsetTop > offset; + } + ); if ( ! element ) { return; @@ -82,7 +89,10 @@ export default function InsertionPoint( { const elementRect = element.getBoundingClientRect(); - if ( event.clientX > elementRect.right || event.clientX < elementRect.left ) { + if ( + event.clientX > elementRect.right || + event.clientX < elementRect.left + ) { if ( isInserterShown ) { setIsInserterShown( false ); } @@ -104,7 +114,7 @@ export default function InsertionPoint( { } const targetRect = target.getBoundingClientRect(); - const isReverse = clientY < targetRect.top + ( targetRect.height / 2 ); + const isReverse = clientY < targetRect.top + targetRect.height / 2; const blockNode = getBlockDOMNode( inserterClientId ); const container = isReverse ? containerRef.current : blockNode; const closest = getClosestTabbable( blockNode, true, container ); @@ -115,46 +125,61 @@ export default function InsertionPoint( { } } - return <> - { ! isMultiSelecting && ( isInserterShown || isInserterForced ) && <Popover - noArrow - animate={ false } - anchorRef={ inserterElement } - position="top right left" - focusOnMount={ false } - className="block-editor-block-list__insertion-point-popover" - __unstableSlotName="block-toolbar" - __unstableFixedPosition={ false } - > - <div className="block-editor-block-list__insertion-point" style={ { width: inserterElement.offsetWidth } }> - <Indicator clientId={ inserterClientId } /> - { /* eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ } - <div - ref={ ref } - onFocus={ () => setIsInserterForced( true ) } - onBlur={ () => setIsInserterForced( false ) } - onClick={ focusClosestTabbable } - // While ideally it would be enough to capture the - // bubbling focus event from the Inserter, due to the - // characteristics of click focusing of `button`s in - // Firefox and Safari, it is not reliable. - // - // See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus - tabIndex={ -1 } - className={ classnames( - 'block-editor-block-list__insertion-point-inserter', - { - // Hide the inserter above the selected block. - 'is-inserter-hidden': inserterClientId === selectedBlockClientId, - } - ) } + return ( + <> + { ! isMultiSelecting && ( isInserterShown || isInserterForced ) && ( + <Popover + noArrow + animate={ false } + anchorRef={ inserterElement } + position="top right left" + focusOnMount={ false } + className="block-editor-block-list__insertion-point-popover" + __unstableSlotName="block-toolbar" + __unstableFixedPosition={ false } > - <Inserter clientId={ inserterClientId } /> - </div> + <div + className="block-editor-block-list__insertion-point" + style={ { width: inserterElement.offsetWidth } } + > + <Indicator clientId={ inserterClientId } /> + { /* eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ } + <div + ref={ ref } + onFocus={ () => setIsInserterForced( true ) } + onBlur={ () => setIsInserterForced( false ) } + onClick={ focusClosestTabbable } + // While ideally it would be enough to capture the + // bubbling focus event from the Inserter, due to the + // characteristics of click focusing of `button`s in + // Firefox and Safari, it is not reliable. + // + // See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus + tabIndex={ -1 } + className={ classnames( + 'block-editor-block-list__insertion-point-inserter', + { + // Hide the inserter above the selected block. + 'is-inserter-hidden': + inserterClientId === + selectedBlockClientId, + } + ) } + > + <Inserter clientId={ inserterClientId } /> + </div> + </div> + </Popover> + ) } + <div + onMouseMove={ + ! isInserterForced && ! isMultiSelecting + ? onMouseMove + : undefined + } + > + { children } </div> - </Popover> } - <div onMouseMove={ ! isInserterForced && ! isMultiSelecting ? onMouseMove : undefined }> - { children } - </div> - </>; + </> + ); } diff --git a/packages/block-editor/src/components/block-list/insertion-point.native.js b/packages/block-editor/src/components/block-list/insertion-point.native.js index a9a27415147836..5f19827ba6840e 100644 --- a/packages/block-editor/src/components/block-list/insertion-point.native.js +++ b/packages/block-editor/src/components/block-list/insertion-point.native.js @@ -15,13 +15,19 @@ import { withPreferredColorScheme } from '@wordpress/compose'; import styles from './style.scss'; const BlockInsertionPoint = ( { getStylesFromColorScheme } ) => { - const lineStyle = getStylesFromColorScheme( styles.lineStyleAddHere, styles.lineStyleAddHereDark ); - const labelStyle = getStylesFromColorScheme( styles.labelStyleAddHere, styles.labelStyleAddHereDark ); + const lineStyle = getStylesFromColorScheme( + styles.lineStyleAddHere, + styles.lineStyleAddHereDark + ); + const labelStyle = getStylesFromColorScheme( + styles.labelStyleAddHere, + styles.labelStyleAddHereDark + ); return ( - <View style={ styles.containerStyleAddHere } > + <View style={ styles.containerStyleAddHere }> <View style={ lineStyle }></View> - <Text style={ labelStyle } >{ __( 'ADD BLOCK HERE' ) }</Text> + <Text style={ labelStyle }>{ __( 'ADD BLOCK HERE' ) }</Text> <View style={ lineStyle }></View> </View> ); diff --git a/packages/block-editor/src/components/block-list/moving-animation.js b/packages/block-editor/src/components/block-list/moving-animation.js index afa36bb40304d2..6af4ba78ea6348 100644 --- a/packages/block-editor/src/components/block-list/moving-animation.js +++ b/packages/block-editor/src/components/block-list/moving-animation.js @@ -6,7 +6,12 @@ import { useSpring, interpolate } from 'react-spring/web.cjs'; /** * WordPress dependencies */ -import { useState, useLayoutEffect, useReducer, useMemo } from '@wordpress/element'; +import { + useState, + useLayoutEffect, + useReducer, + useMemo, +} from '@wordpress/element'; import { useReducedMotion } from '@wordpress/compose'; import { getScrollContainer } from '@wordpress/dom'; @@ -44,11 +49,24 @@ const getAbsolutePosition = ( element ) => { * * @return {Object} Style object. */ -function useMovingAnimation( ref, isSelected, adjustScrolling, enableAnimation, triggerAnimationOnChange ) { +function useMovingAnimation( + ref, + isSelected, + adjustScrolling, + enableAnimation, + triggerAnimationOnChange +) { const prefersReducedMotion = useReducedMotion() || ! enableAnimation; - const [ triggeredAnimation, triggerAnimation ] = useReducer( counterReducer, 0 ); + const [ triggeredAnimation, triggerAnimation ] = useReducer( + counterReducer, + 0 + ); const [ finishedAnimation, endAnimation ] = useReducer( counterReducer, 0 ); - const [ transform, setTransform ] = useState( { x: 0, y: 0, scrollTop: 0 } ); + const [ transform, setTransform ] = useState( { + x: 0, + y: 0, + scrollTop: 0, + } ); const previous = ref.current ? getAbsolutePosition( ref.current ) : null; const scrollContainer = useMemo( () => { @@ -70,7 +88,8 @@ function useMovingAnimation( ref, isSelected, adjustScrolling, enableAnimation, // just move directly to the final scroll position ref.current.style.transform = 'none'; const destination = getAbsolutePosition( ref.current ); - scrollContainer.scrollTop = scrollContainer.scrollTop - previous.top + destination.top; + scrollContainer.scrollTop = + scrollContainer.scrollTop - previous.top + destination.top; } return; @@ -80,11 +99,15 @@ function useMovingAnimation( ref, isSelected, adjustScrolling, enableAnimation, const newTransform = { x: previous ? previous.left - destination.left : 0, y: previous ? previous.top - destination.top : 0, - scrollTop: previous && scrollContainer ? scrollContainer.scrollTop - previous.top + destination.top : 0, + scrollTop: + previous && scrollContainer + ? scrollContainer.scrollTop - previous.top + destination.top + : 0, }; - ref.current.style.transform = newTransform.x === 0 && newTransform.y === 0 ? - undefined : - `translate3d(${ newTransform.x }px,${ newTransform.y }px,0)`; + ref.current.style.transform = + newTransform.x === 0 && newTransform.y === 0 + ? undefined + : `translate3d(${ newTransform.x }px,${ newTransform.y }px,0)`; triggerAnimation(); setTransform( newTransform ); }, [ triggerAnimationOnChange ] ); @@ -102,32 +125,35 @@ function useMovingAnimation( ref, isSelected, adjustScrolling, enableAnimation, config: { mass: 5, tension: 2000, friction: 200 }, immediate: prefersReducedMotion, onFrame: ( props ) => { - if ( adjustScrolling && scrollContainer && ! prefersReducedMotion && props.y ) { + if ( + adjustScrolling && + scrollContainer && + ! prefersReducedMotion && + props.y + ) { scrollContainer.scrollTop = transform.scrollTop + props.y; } }, } ); // Dismiss animations if disabled. - return prefersReducedMotion ? - {} : - { - transformOrigin: 'center', - transform: interpolate( - [ - animationProps.x, - animationProps.y, - ], - ( x, y ) => x === 0 && y === 0 ? undefined : `translate3d(${ x }px,${ y }px,0)` - ), - zIndex: interpolate( - [ - animationProps.x, - animationProps.y, - ], - ( x, y ) => ! isSelected || ( x === 0 && y === 0 ) ? undefined : `1` - ), - }; + return prefersReducedMotion + ? {} + : { + transformOrigin: 'center', + transform: interpolate( + [ animationProps.x, animationProps.y ], + ( x, y ) => + x === 0 && y === 0 + ? undefined + : `translate3d(${ x }px,${ y }px,0)` + ), + zIndex: interpolate( + [ animationProps.x, animationProps.y ], + ( x, y ) => + ! isSelected || ( x === 0 && y === 0 ) ? undefined : `1` + ), + }; } export default useMovingAnimation; diff --git a/packages/block-editor/src/components/block-list/nav-up-icon.js b/packages/block-editor/src/components/block-list/nav-up-icon.js index 8fa1c8d20bd761..ae1d897660af7b 100644 --- a/packages/block-editor/src/components/block-list/nav-up-icon.js +++ b/packages/block-editor/src/components/block-list/nav-up-icon.js @@ -3,6 +3,21 @@ */ import { SVG, Path } from '@wordpress/components'; -const NavigateUp = <SVG width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><Path fill="white" fillRule="evenodd" clipRule="evenodd" d="M17,11 z L15.58,12.42 L12,8.83 L12,18 L22,18 L22,20 L10,20 L10,8.83 L6.42,12.42 L5,11 L11,5 L17,11" /></SVG>; +const NavigateUp = ( + <SVG + width="24" + height="24" + viewBox="0 0 24 24" + xmlns="http://www.w3.org/2000/svg" + > + <Path fill="none" d="M0 0h24v24H0V0z" /> + <Path + fill="white" + fillRule="evenodd" + clipRule="evenodd" + d="M17,11 z L15.58,12.42 L12,8.83 L12,18 L22,18 L22,20 L10,20 L10,8.83 L6.42,12.42 L5,11 L11,5 L17,11" + /> + </SVG> +); export default NavigateUp; diff --git a/packages/block-editor/src/components/block-list/subdirectory-icon.js b/packages/block-editor/src/components/block-list/subdirectory-icon.js index 9da2bc4a1c88a9..f024c51880e4b2 100644 --- a/packages/block-editor/src/components/block-list/subdirectory-icon.js +++ b/packages/block-editor/src/components/block-list/subdirectory-icon.js @@ -12,7 +12,6 @@ const Subdirectory = ( { ...extraProps } ) => ( { ...extraProps } > <Path d="M19 15l-6 6-1.42-1.42L15.17 16H4V4h2v10h9.17l-3.59-3.58L13 9l6 6z" /> - </SVG> ) -; - + </SVG> +); export default Subdirectory; diff --git a/packages/block-editor/src/components/block-list/use-multi-selection.js b/packages/block-editor/src/components/block-list/use-multi-selection.js index a0ba7de65a0f01..1e77f8711a51de 100644 --- a/packages/block-editor/src/components/block-list/use-multi-selection.js +++ b/packages/block-editor/src/components/block-list/use-multi-selection.js @@ -56,15 +56,15 @@ function selector( select ) { } function toggleRichText( container, toggle ) { - Array - .from( container.querySelectorAll( '.rich-text' ) ) - .forEach( ( node ) => { + Array.from( container.querySelectorAll( '.rich-text' ) ).forEach( + ( node ) => { if ( toggle ) { node.setAttribute( 'contenteditable', true ); } else { node.removeAttribute( 'contenteditable' ); } - } ); + } + ); } export default function useMultiSelection( ref ) { @@ -100,7 +100,9 @@ export default function useMultiSelection( ref ) { if ( selection.rangeCount && ! selection.isCollapsed ) { const blockNode = getBlockDOMNode( selectedBlockClientId ); - const { startContainer, endContainer } = selection.getRangeAt( 0 ); + const { startContainer, endContainer } = selection.getRangeAt( + 0 + ); if ( ! blockNode.contains( startContainer ) || @@ -147,46 +149,58 @@ export default function useMultiSelection( ref ) { selectedBlockClientId, ] ); - const onSelectionChange = useCallback( ( { isSelectionEnd } ) => { - const selection = window.getSelection(); - - // If no selection is found, end multi selection and enable all rich - // text areas. - if ( ! selection.rangeCount || selection.isCollapsed ) { - toggleRichText( ref.current, true ); - return; - } - - const clientId = getBlockClientId( selection.focusNode ); - const isSingularSelection = startClientId.current === clientId; - - if ( isSingularSelection ) { - selectBlock( clientId ); + const onSelectionChange = useCallback( + ( { isSelectionEnd } ) => { + const selection = window.getSelection(); - // If the selection is complete (on mouse up), and no multiple - // blocks have been selected, set focus back to the anchor element - // if the anchor element contains the selection. Additionally, rich - // text elements that were previously disabled can now be enabled - // again. - if ( isSelectionEnd ) { + // If no selection is found, end multi selection and enable all rich + // text areas. + if ( ! selection.rangeCount || selection.isCollapsed ) { toggleRichText( ref.current, true ); + return; + } - if ( selection.rangeCount ) { - const { commonAncestorContainer } = selection.getRangeAt( 0 ); - - if ( anchorElement.current.contains( commonAncestorContainer ) ) { - anchorElement.current.focus(); + const clientId = getBlockClientId( selection.focusNode ); + const isSingularSelection = startClientId.current === clientId; + + if ( isSingularSelection ) { + selectBlock( clientId ); + + // If the selection is complete (on mouse up), and no multiple + // blocks have been selected, set focus back to the anchor element + // if the anchor element contains the selection. Additionally, rich + // text elements that were previously disabled can now be enabled + // again. + if ( isSelectionEnd ) { + toggleRichText( ref.current, true ); + + if ( selection.rangeCount ) { + const { + commonAncestorContainer, + } = selection.getRangeAt( 0 ); + + if ( + anchorElement.current.contains( + commonAncestorContainer + ) + ) { + anchorElement.current.focus(); + } } } + } else { + const startPath = [ + ...getBlockParents( startClientId.current ), + startClientId.current, + ]; + const endPath = [ ...getBlockParents( clientId ), clientId ]; + const depth = Math.min( startPath.length, endPath.length ) - 1; + + multiSelect( startPath[ depth ], endPath[ depth ] ); } - } else { - const startPath = [ ...getBlockParents( startClientId.current ), startClientId.current ]; - const endPath = [ ...getBlockParents( clientId ), clientId ]; - const depth = Math.min( startPath.length, endPath.length ) - 1; - - multiSelect( startPath[ depth ], endPath[ depth ] ); - } - }, [ selectBlock, getBlockParents, multiSelect ] ); + }, + [ selectBlock, getBlockParents, multiSelect ] + ); /** * Handles a mouseup event to end the current mouse multi-selection. @@ -204,39 +218,48 @@ export default function useMultiSelection( ref ) { }, [ onSelectionChange, stopMultiSelect ] ); // Only clean up when unmounting, these are added and cleaned up elsewhere. - useEffect( () => () => { - document.removeEventListener( 'selectionchange', onSelectionChange ); - window.removeEventListener( 'mouseup', onSelectionEnd ); - window.cancelAnimationFrame( rafId.current ); - }, [ onSelectionChange, onSelectionEnd ] ); + useEffect( + () => () => { + document.removeEventListener( + 'selectionchange', + onSelectionChange + ); + window.removeEventListener( 'mouseup', onSelectionEnd ); + window.cancelAnimationFrame( rafId.current ); + }, + [ onSelectionChange, onSelectionEnd ] + ); /** * Binds event handlers to the document for tracking a pending multi-select * in response to a mousedown event occurring in a rendered block. */ - return useCallback( ( clientId ) => { - if ( ! isSelectionEnabled ) { - return; - } + return useCallback( + ( clientId ) => { + if ( ! isSelectionEnabled ) { + return; + } - startClientId.current = clientId; - anchorElement.current = document.activeElement; - startMultiSelect(); - - // `onSelectionStart` is called after `mousedown` and `mouseleave` - // (from a block). The selection ends when `mouseup` happens anywhere - // in the window. - document.addEventListener( 'selectionchange', onSelectionChange ); - window.addEventListener( 'mouseup', onSelectionEnd ); - - // Removing the contenteditable attributes within the block editor is - // essential for selection to work across editable areas. The edible - // hosts are removed, allowing selection to be extended outside the - // DOM element. `startMultiSelect` sets a flag in the store so the rich - // text components are updated, but the rerender may happen very slowly, - // especially in Safari for the blocks that are asynchonously rendered. - // To ensure the browser instantly removes the selection boundaries, we - // remove the contenteditable attributes manually. - toggleRichText( ref.current, false ); - }, [ isSelectionEnabled, startMultiSelect, onSelectionEnd ] ); + startClientId.current = clientId; + anchorElement.current = document.activeElement; + startMultiSelect(); + + // `onSelectionStart` is called after `mousedown` and `mouseleave` + // (from a block). The selection ends when `mouseup` happens anywhere + // in the window. + document.addEventListener( 'selectionchange', onSelectionChange ); + window.addEventListener( 'mouseup', onSelectionEnd ); + + // Removing the contenteditable attributes within the block editor is + // essential for selection to work across editable areas. The edible + // hosts are removed, allowing selection to be extended outside the + // DOM element. `startMultiSelect` sets a flag in the store so the rich + // text components are updated, but the rerender may happen very slowly, + // especially in Safari for the blocks that are asynchonously rendered. + // To ensure the browser instantly removes the selection boundaries, we + // remove the contenteditable attributes manually. + toggleRichText( ref.current, false ); + }, + [ isSelectionEnabled, startMultiSelect, onSelectionEnd ] + ); } diff --git a/packages/block-editor/src/components/block-mobile-toolbar/index.js b/packages/block-editor/src/components/block-mobile-toolbar/index.js index 689e455d833885..968b645ce72f3f 100644 --- a/packages/block-editor/src/components/block-mobile-toolbar/index.js +++ b/packages/block-editor/src/components/block-mobile-toolbar/index.js @@ -16,7 +16,10 @@ function BlockMobileToolbar( { clientId, moverDirection } ) { return ( <div className="block-editor-block-mobile-toolbar"> - <BlockMover clientIds={ [ clientId ] } __experimentalOrientation={ moverDirection } /> + <BlockMover + clientIds={ [ clientId ] } + __experimentalOrientation={ moverDirection } + /> </div> ); } diff --git a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js index 4be16304d9937d..43a34c0466b54d 100644 --- a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js +++ b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js @@ -19,11 +19,7 @@ import styles from './style.scss'; import BlockMover from '../block-mover'; import { BlockSettingsButton } from '../block-settings'; -const BlockMobileToolbar = ( { - clientId, - onDelete, - order, -} ) => ( +const BlockMobileToolbar = ( { clientId, onDelete, order } ) => ( <View style={ styles.toolbar }> <BlockMover clientIds={ [ clientId ] } /> @@ -32,13 +28,11 @@ const BlockMobileToolbar = ( { <BlockSettingsButton.Slot /> <ToolbarButton - title={ - sprintf( - /* translators: accessibility text. %s: current block position (number). */ - __( 'Remove block at row %s' ), - order + 1 - ) - } + title={ sprintf( + /* translators: accessibility text. %s: current block position (number). */ + __( 'Remove block at row %s' ), + order + 1 + ) } onClick={ onDelete } icon={ trash } extraProps={ { hint: __( 'Double tap to remove the block' ) } } @@ -48,9 +42,7 @@ const BlockMobileToolbar = ( { export default compose( withSelect( ( select, { clientId } ) => { - const { - getBlockIndex, - } = select( 'core/block-editor' ); + const { getBlockIndex } = select( 'core/block-editor' ); return { order: getBlockIndex( clientId ), @@ -64,5 +56,5 @@ export default compose( removeBlock( clientId, rootClientId ); }, }; - } ), + } ) )( BlockMobileToolbar ); diff --git a/packages/block-editor/src/components/block-mover/icons.js b/packages/block-editor/src/components/block-mover/icons.js index f3b575bd57a869..1ebd7922cd4b44 100644 --- a/packages/block-editor/src/components/block-mover/icons.js +++ b/packages/block-editor/src/components/block-mover/icons.js @@ -4,21 +4,38 @@ import { Path, SVG } from '@wordpress/components'; export const leftArrow = ( - <SVG width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"> + <SVG + width="18" + height="18" + viewBox="0 0 18 18" + xmlns="http://www.w3.org/2000/svg" + > <Path d="M4.5 9l5.6-5.7 1.4 1.5L7.3 9l4.2 4.2-1.4 1.5L4.5 9z" /> </SVG> ); export const rightArrow = ( - <SVG width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"> + <SVG + width="18" + height="18" + viewBox="0 0 18 18" + xmlns="http://www.w3.org/2000/svg" + > <Path d="M13.5 9L7.9 3.3 6.5 4.8 10.7 9l-4.2 4.2 1.4 1.5L13.5 9z" /> </SVG> ); export const dragHandle = ( - <SVG width="18" height="18" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"> - <Path d="M13,8c0.6,0,1-0.4,1-1s-0.4-1-1-1s-1,0.4-1,1S12.4,8,13,8z M5,6C4.4,6,4,6.4,4,7s0.4,1,1,1s1-0.4,1-1S5.6,6,5,6z M5,10 + <SVG + width="18" + height="18" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 18 18" + > + <Path + d="M13,8c0.6,0,1-0.4,1-1s-0.4-1-1-1s-1,0.4-1,1S12.4,8,13,8z M5,6C4.4,6,4,6.4,4,7s0.4,1,1,1s1-0.4,1-1S5.6,6,5,6z M5,10 c-0.6,0-1,0.4-1,1s0.4,1,1,1s1-0.4,1-1S5.6,10,5,10z M13,10c-0.6,0-1,0.4-1,1s0.4,1,1,1s1-0.4,1-1S13.6,10,13,10z M9,6 - C8.4,6,8,6.4,8,7s0.4,1,1,1s1-0.4,1-1S9.6,6,9,6z M9,10c-0.6,0-1,0.4-1,1s0.4,1,1,1s1-0.4,1-1S9.6,10,9,10z" /> + C8.4,6,8,6.4,8,7s0.4,1,1,1s1-0.4,1-1S9.6,6,9,6z M9,10c-0.6,0-1,0.4-1,1s0.4,1,1,1s1-0.4,1-1S9.6,10,9,10z" + /> </SVG> ); diff --git a/packages/block-editor/src/components/block-mover/index.js b/packages/block-editor/src/components/block-mover/index.js index ea5bb8c36d8fe3..7e29326458fd13 100644 --- a/packages/block-editor/src/components/block-mover/index.js +++ b/packages/block-editor/src/components/block-mover/index.js @@ -45,7 +45,21 @@ export class BlockMover extends Component { } render() { - const { onMoveUp, onMoveDown, __experimentalOrientation: orientation, isRTL, isFirst, isLast, clientIds, blockType, firstIndex, isLocked, instanceId, isHidden, rootClientId } = this.props; + const { + onMoveUp, + onMoveDown, + __experimentalOrientation: orientation, + isRTL, + isFirst, + isLast, + clientIds, + blockType, + firstIndex, + isLocked, + instanceId, + isHidden, + rootClientId, + } = this.props; const { isFocused } = this.state; const blocksCount = castArray( clientIds ).length; if ( isLocked || ( isFirst && isLast && ! rootClientId ) ) { @@ -87,13 +101,21 @@ export class BlockMover extends Component { // to an unfocused state (body as active element) without firing blur on, // the rendering parent, leaving it unable to react to focus out. return ( - <ToolbarGroup className={ classnames( 'block-editor-block-mover', { 'is-visible': isFocused || ! isHidden, 'is-horizontal': orientation === 'horizontal' } ) }> + <ToolbarGroup + className={ classnames( 'block-editor-block-mover', { + 'is-visible': isFocused || ! isHidden, + 'is-horizontal': orientation === 'horizontal', + } ) } + > <Button className="block-editor-block-mover__control" onClick={ isFirst ? null : onMoveUp } icon={ getArrowIcon( 'up' ) } // translators: %s: Horizontal direction of block movement ( left, right ) - label={ sprintf( __( 'Move %s' ), getMovementDirection( 'up' ) ) } + label={ sprintf( + __( 'Move %s' ), + getMovementDirection( 'up' ) + ) } aria-describedby={ `block-editor-block-mover__up-description-${ instanceId }` } aria-disabled={ isFirst } onFocus={ this.onFocus } @@ -121,39 +143,44 @@ export class BlockMover extends Component { onClick={ isLast ? null : onMoveDown } icon={ getArrowIcon( 'down' ) } // translators: %s: Horizontal direction of block movement ( left, right ) - label={ sprintf( __( 'Move %s' ), getMovementDirection( 'down' ) ) } + label={ sprintf( + __( 'Move %s' ), + getMovementDirection( 'down' ) + ) } aria-describedby={ `block-editor-block-mover__down-description-${ instanceId }` } aria-disabled={ isLast } onFocus={ this.onFocus } onBlur={ this.onBlur } /> - <span id={ `block-editor-block-mover__up-description-${ instanceId }` } className="block-editor-block-mover__description"> - { - getBlockMoverDescription( - blocksCount, - blockType && blockType.title, - firstIndex, - isFirst, - isLast, - -1, - orientation, - isRTL, - ) - } + <span + id={ `block-editor-block-mover__up-description-${ instanceId }` } + className="block-editor-block-mover__description" + > + { getBlockMoverDescription( + blocksCount, + blockType && blockType.title, + firstIndex, + isFirst, + isLast, + -1, + orientation, + isRTL + ) } </span> - <span id={ `block-editor-block-mover__down-description-${ instanceId }` } className="block-editor-block-mover__description"> - { - getBlockMoverDescription( - blocksCount, - blockType && blockType.title, - firstIndex, - isFirst, - isLast, - 1, - orientation, - isRTL, - ) - } + <span + id={ `block-editor-block-mover__down-description-${ instanceId }` } + className="block-editor-block-mover__description" + > + { getBlockMoverDescription( + blocksCount, + blockType && blockType.title, + firstIndex, + isFirst, + isLast, + 1, + orientation, + isRTL + ) } </span> </ToolbarGroup> ); @@ -162,18 +189,27 @@ export class BlockMover extends Component { export default compose( withSelect( ( select, { clientIds } ) => { - const { getBlock, getBlockIndex, getTemplateLock, getBlockRootClientId, getBlockOrder } = select( 'core/block-editor' ); + const { + getBlock, + getBlockIndex, + getTemplateLock, + getBlockRootClientId, + getBlockOrder, + } = select( 'core/block-editor' ); const normalizedClientIds = castArray( clientIds ); const firstClientId = first( normalizedClientIds ); const block = getBlock( firstClientId ); - const rootClientId = getBlockRootClientId( first( normalizedClientIds ) ); + const rootClientId = getBlockRootClientId( + first( normalizedClientIds ) + ); const blockOrder = getBlockOrder( rootClientId ); const firstIndex = getBlockIndex( firstClientId, rootClientId ); - const lastIndex = getBlockIndex( last( normalizedClientIds ), rootClientId ); + const lastIndex = getBlockIndex( + last( normalizedClientIds ), + rootClientId + ); const { getSettings } = select( 'core/block-editor' ); - const { - isRTL, - } = getSettings(); + const { isRTL } = getSettings(); return { blockType: block ? getBlockType( block.name ) : null, @@ -186,11 +222,13 @@ export default compose( }; } ), withDispatch( ( dispatch, { clientIds, rootClientId } ) => { - const { moveBlocksDown, moveBlocksUp } = dispatch( 'core/block-editor' ); + const { moveBlocksDown, moveBlocksUp } = dispatch( + 'core/block-editor' + ); return { onMoveDown: partial( moveBlocksDown, clientIds, rootClientId ), onMoveUp: partial( moveBlocksUp, clientIds, rootClientId ), }; } ), - withInstanceId, + withInstanceId )( BlockMover ); diff --git a/packages/block-editor/src/components/block-mover/index.native.js b/packages/block-editor/src/components/block-mover/index.native.js index 40bc4d550b7ab6..27bf29650e9a92 100644 --- a/packages/block-editor/src/components/block-mover/index.native.js +++ b/packages/block-editor/src/components/block-mover/index.native.js @@ -27,14 +27,15 @@ const BlockMover = ( { return ( <> <ToolbarButton - title={ ! isFirst ? - sprintf( - /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ - __( 'Move block up from row %1$s to row %2$s' ), - firstIndex + 1, - firstIndex - ) : - __( 'Move block up' ) + title={ + ! isFirst + ? sprintf( + /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ + __( 'Move block up from row %1$s to row %2$s' ), + firstIndex + 1, + firstIndex + ) + : __( 'Move block up' ) } isDisabled={ isFirst } onClick={ onMoveUp } @@ -43,19 +44,24 @@ const BlockMover = ( { /> <ToolbarButton - title={ ! isLast ? - sprintf( - /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ - __( 'Move block down from row %1$s to row %2$s' ), - firstIndex + 1, - firstIndex + 2 - ) : - __( 'Move block down' ) + title={ + ! isLast + ? sprintf( + /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ + __( + 'Move block down from row %1$s to row %2$s' + ), + firstIndex + 1, + firstIndex + 2 + ) + : __( 'Move block down' ) } isDisabled={ isLast } onClick={ onMoveDown } icon="arrow-down-alt" - extraProps={ { hint: __( 'Double tap to move the block down' ) } } + extraProps={ { + hint: __( 'Double tap to move the block down' ), + } } /> </> ); @@ -63,13 +69,21 @@ const BlockMover = ( { export default compose( withSelect( ( select, { clientIds } ) => { - const { getBlockIndex, getTemplateLock, getBlockRootClientId, getBlockOrder } = select( 'core/block-editor' ); + const { + getBlockIndex, + getTemplateLock, + getBlockRootClientId, + getBlockOrder, + } = select( 'core/block-editor' ); const normalizedClientIds = castArray( clientIds ); const firstClientId = first( normalizedClientIds ); const rootClientId = getBlockRootClientId( firstClientId ); const blockOrder = getBlockOrder( rootClientId ); const firstIndex = getBlockIndex( firstClientId, rootClientId ); - const lastIndex = getBlockIndex( last( normalizedClientIds ), rootClientId ); + const lastIndex = getBlockIndex( + last( normalizedClientIds ), + rootClientId + ); return { firstIndex, @@ -80,11 +94,13 @@ export default compose( }; } ), withDispatch( ( dispatch, { clientIds, rootClientId } ) => { - const { moveBlocksDown, moveBlocksUp } = dispatch( 'core/block-editor' ); + const { moveBlocksDown, moveBlocksUp } = dispatch( + 'core/block-editor' + ); return { onMoveDown: partial( moveBlocksDown, clientIds, rootClientId ), onMoveUp: partial( moveBlocksUp, clientIds, rootClientId ), }; } ), - withInstanceId, + withInstanceId )( BlockMover ); diff --git a/packages/block-editor/src/components/block-mover/mover-description.js b/packages/block-editor/src/components/block-mover/mover-description.js index cb0203f8c60cfc..88e67e5ff4a715 100644 --- a/packages/block-editor/src/components/block-mover/mover-description.js +++ b/packages/block-editor/src/components/block-mover/mover-description.js @@ -20,8 +20,17 @@ import { __, _n, sprintf } from '@wordpress/i18n'; * * @return {string} Label for the block movement controls. */ -export function getBlockMoverDescription( selectedCount, type, firstIndex, isFirst, isLast, dir, orientation, isRTL ) { - const position = ( firstIndex + 1 ); +export function getBlockMoverDescription( + selectedCount, + type, + firstIndex, + isFirst, + isLast, + dir, + orientation, + isRTL +) { + const position = firstIndex + 1; const getMovementDirection = ( moveDirection ) => { if ( moveDirection === 'up' ) { @@ -39,12 +48,21 @@ export function getBlockMoverDescription( selectedCount, type, firstIndex, isFir }; if ( selectedCount > 1 ) { - return getMultiBlockMoverDescription( selectedCount, firstIndex, isFirst, isLast, dir ); + return getMultiBlockMoverDescription( + selectedCount, + firstIndex, + isFirst, + isLast, + dir + ); } if ( isFirst && isLast ) { // translators: %s: Type of block (i.e. Text, Image etc) - return sprintf( __( 'Block %s is the only block, and cannot be moved' ), type ); + return sprintf( + __( 'Block %s is the only block, and cannot be moved' ), + type + ); } if ( dir > 0 && ! isLast ) { @@ -54,30 +72,36 @@ export function getBlockMoverDescription( selectedCount, type, firstIndex, isFir if ( movementDirection === 'down' ) { return sprintf( // translators: 1: Type of block (i.e. Text, Image etc), 2: Position of selected block, 3: New position - __( 'Move %1$s block from position %2$d down to position %3$d' ), + __( + 'Move %1$s block from position %2$d down to position %3$d' + ), type, position, - ( position + 1 ), + position + 1 ); } if ( movementDirection === 'left' ) { return sprintf( // translators: 1: Type of block (i.e. Text, Image etc), 2: Position of selected block, 3: New position - __( 'Move %1$s block from position %2$d left to position %3$d' ), + __( + 'Move %1$s block from position %2$d left to position %3$d' + ), type, position, - ( position + 1 ), + position + 1 ); } if ( movementDirection === 'right' ) { return sprintf( // translators: 1: Type of block (i.e. Text, Image etc), 2: Position of selected block, 3: New position - __( 'Move %1$s block from position %2$d right to position %3$d' ), + __( + 'Move %1$s block from position %2$d right to position %3$d' + ), type, position, - ( position + 1 ), + position + 1 ); } } @@ -89,24 +113,30 @@ export function getBlockMoverDescription( selectedCount, type, firstIndex, isFir if ( movementDirection === 'down' ) { return sprintf( // translators: 1: Type of block (i.e. Text, Image etc) - __( 'Block %1$s is at the end of the content and can’t be moved down' ), - type, + __( + 'Block %1$s is at the end of the content and can’t be moved down' + ), + type ); } if ( movementDirection === 'left' ) { return sprintf( // translators: 1: Type of block (i.e. Text, Image etc) - __( 'Block %1$s is at the end of the content and can’t be moved left' ), - type, + __( + 'Block %1$s is at the end of the content and can’t be moved left' + ), + type ); } if ( movementDirection === 'right' ) { return sprintf( // translators: 1: Type of block (i.e. Text, Image etc) - __( 'Block %1$s is at the end of the content and can’t be moved right' ), - type, + __( + 'Block %1$s is at the end of the content and can’t be moved right' + ), + type ); } } @@ -121,27 +151,31 @@ export function getBlockMoverDescription( selectedCount, type, firstIndex, isFir __( 'Move %1$s block from position %2$d up to position %3$d' ), type, position, - ( position - 1 ), + position - 1 ); } if ( movementDirection === 'left' ) { return sprintf( // translators: 1: Type of block (i.e. Text, Image etc), 2: Position of selected block, 3: New position - __( 'Move %1$s block from position %2$d left to position %3$d' ), + __( + 'Move %1$s block from position %2$d left to position %3$d' + ), type, position, - ( position - 1 ), + position - 1 ); } if ( movementDirection === 'right' ) { return sprintf( // translators: 1: Type of block (i.e. Text, Image etc), 2: Position of selected block, 3: New position - __( 'Move %1$s block from position %2$d right to position %3$d' ), + __( + 'Move %1$s block from position %2$d right to position %3$d' + ), type, position, - ( position - 1 ), + position - 1 ); } } @@ -153,24 +187,30 @@ export function getBlockMoverDescription( selectedCount, type, firstIndex, isFir if ( movementDirection === 'up' ) { return sprintf( // translators: 1: Type of block (i.e. Text, Image etc) - __( 'Block %1$s is at the beginning of the content and can’t be moved up' ), - type, + __( + 'Block %1$s is at the beginning of the content and can’t be moved up' + ), + type ); } if ( movementDirection === 'left' ) { return sprintf( // translators: 1: Type of block (i.e. Text, Image etc) - __( 'Block %1$s is at the beginning of the content and can’t be moved left' ), - type, + __( + 'Block %1$s is at the beginning of the content and can’t be moved left' + ), + type ); } if ( movementDirection === 'right' ) { return sprintf( // translators: 1: Type of block (i.e. Text, Image etc) - __( 'Block %1$s is at the beginning of the content and can’t be moved right' ), - type, + __( + 'Block %1$s is at the beginning of the content and can’t be moved right' + ), + type ); } } @@ -188,15 +228,23 @@ export function getBlockMoverDescription( selectedCount, type, firstIndex, isFir * * @return {string} Label for the block movement controls. */ -export function getMultiBlockMoverDescription( selectedCount, firstIndex, isFirst, isLast, dir ) { - const position = ( firstIndex + 1 ); +export function getMultiBlockMoverDescription( + selectedCount, + firstIndex, + isFirst, + isLast, + dir +) { + const position = firstIndex + 1; if ( dir < 0 && isFirst ) { return __( 'Blocks cannot be moved up as they are already at the top' ); } if ( dir > 0 && isLast ) { - return __( 'Blocks cannot be moved down as they are already at the bottom' ); + return __( + 'Blocks cannot be moved down as they are already at the bottom' + ); } if ( dir < 0 && ! isFirst ) { diff --git a/packages/block-editor/src/components/block-mover/test/index.js b/packages/block-editor/src/components/block-mover/test/index.js index e413dc6e8680bd..0f78a8250bee47 100644 --- a/packages/block-editor/src/components/block-mover/test/index.js +++ b/packages/block-editor/src/components/block-mover/test/index.js @@ -35,7 +35,9 @@ describe( 'BlockMover', () => { instanceId={ 1 } /> ); - expect( blockMover.hasClass( 'block-editor-block-mover' ) ).toBe( true ); + expect( blockMover.hasClass( 'block-editor-block-mover' ) ).toBe( + true + ); const moveUp = blockMover.childAt( 0 ); const drag = blockMover.childAt( 1 ); @@ -51,7 +53,8 @@ describe( 'BlockMover', () => { label: 'Move up', icon: chevronUp, 'aria-disabled': undefined, - 'aria-describedby': 'block-editor-block-mover__up-description-1', + 'aria-describedby': + 'block-editor-block-mover__up-description-1', } ); expect( moveDown.props() ).toMatchObject( { className: 'block-editor-block-mover__control', @@ -59,10 +62,15 @@ describe( 'BlockMover', () => { label: 'Move down', icon: chevronDown, 'aria-disabled': undefined, - 'aria-describedby': 'block-editor-block-mover__down-description-1', + 'aria-describedby': + 'block-editor-block-mover__down-description-1', } ); - expect( moveUpDesc.text() ).toBe( 'Move 2 blocks from position 1 up by one place' ); - expect( moveDownDesc.text() ).toBe( 'Move 2 blocks from position 1 down by one place' ); + expect( moveUpDesc.text() ).toBe( + 'Move 2 blocks from position 1 up by one place' + ); + expect( moveDownDesc.text() ).toBe( + 'Move 2 blocks from position 1 down by one place' + ); } ); it( 'should render the up arrow with a onMoveUp callback', () => { diff --git a/packages/block-editor/src/components/block-mover/test/mover-description.js b/packages/block-editor/src/components/block-mover/test/mover-description.js index 4f491512d7049d..6098dc5619e0a0 100644 --- a/packages/block-editor/src/components/block-mover/test/mover-description.js +++ b/packages/block-editor/src/components/block-mover/test/mover-description.js @@ -1,7 +1,10 @@ /** * Internal dependencies */ -import { getBlockMoverDescription, getMultiBlockMoverDescription } from '../mover-description'; +import { + getBlockMoverDescription, + getMultiBlockMoverDescription, +} from '../mover-description'; describe( 'block mover', () => { const negativeDirection = -1, @@ -11,152 +14,192 @@ describe( 'block mover', () => { const label = 'Header: Some Header Text'; it( 'generates a title for the first item moving up', () => { - expect( getBlockMoverDescription( - 1, - label, - 0, - true, - false, - negativeDirection - ) ).toBe( + expect( + getBlockMoverDescription( + 1, + label, + 0, + true, + false, + negativeDirection + ) + ).toBe( `Block ${ label } is at the beginning of the content and can’t be moved up` ); } ); it( 'generates a title for the last item moving down', () => { - expect( getBlockMoverDescription( - 1, - label, - 3, - false, - true, - positiveDirection, - ) ).toBe( `Block ${ label } is at the end of the content and can’t be moved down` ); + expect( + getBlockMoverDescription( + 1, + label, + 3, + false, + true, + positiveDirection + ) + ).toBe( + `Block ${ label } is at the end of the content and can’t be moved down` + ); } ); it( 'generates a title for the second item moving up', () => { - expect( getBlockMoverDescription( - 1, - label, - 1, - false, - false, - negativeDirection, - ) ).toBe( `Move ${ label } block from position 2 up to position 1` ); + expect( + getBlockMoverDescription( + 1, + label, + 1, + false, + false, + negativeDirection + ) + ).toBe( `Move ${ label } block from position 2 up to position 1` ); } ); it( 'generates a title for the second item moving down', () => { - expect( getBlockMoverDescription( - 1, - label, - 1, - false, - false, - positiveDirection, - ) ).toBe( `Move ${ label } block from position 2 down to position 3` ); + expect( + getBlockMoverDescription( + 1, + label, + 1, + false, + false, + positiveDirection + ) + ).toBe( + `Move ${ label } block from position 2 down to position 3` + ); } ); it( 'generates a title for the only item in the list', () => { - expect( getBlockMoverDescription( - 1, - label, - 0, - true, - true, - positiveDirection, - ) ).toBe( `Block ${ label } is the only block, and cannot be moved` ); + expect( + getBlockMoverDescription( + 1, + label, + 0, + true, + true, + positiveDirection + ) + ).toBe( `Block ${ label } is the only block, and cannot be moved` ); } ); it( 'indicates that the block can be moved left when the orientation is horizontal and the direction is negative', () => { - expect( getBlockMoverDescription( - 1, - label, - 1, - false, - false, - negativeDirection, - 'horizontal' - ) ).toBe( `Move ${ label } block from position 2 left to position 1` ); + expect( + getBlockMoverDescription( + 1, + label, + 1, + false, + false, + negativeDirection, + 'horizontal' + ) + ).toBe( + `Move ${ label } block from position 2 left to position 1` + ); } ); it( 'indicates that the block can be moved right when the orientation is horizontal and the direction is positive', () => { - expect( getBlockMoverDescription( - 1, - label, - 1, - false, - false, - positiveDirection, - 'horizontal' - ) ).toBe( `Move ${ label } block from position 2 right to position 3` ); + expect( + getBlockMoverDescription( + 1, + label, + 1, + false, + false, + positiveDirection, + 'horizontal' + ) + ).toBe( + `Move ${ label } block from position 2 right to position 3` + ); } ); it( 'indicates that the block cannot be moved left when the orientation is horizontal and the block is the first block', () => { - expect( getBlockMoverDescription( - 1, - label, - 0, - true, - false, - negativeDirection, - 'horizontal' - ) ).toBe( + expect( + getBlockMoverDescription( + 1, + label, + 0, + true, + false, + negativeDirection, + 'horizontal' + ) + ).toBe( `Block ${ label } is at the beginning of the content and can’t be moved left` ); } ); it( 'indicates that the block cannot be moved right when the orientation is horizontal and the block is the last block', () => { - expect( getBlockMoverDescription( - 1, - label, - 3, - false, - true, - positiveDirection, - 'horizontal' - ) ).toBe( `Block ${ label } is at the end of the content and can’t be moved right` ); + expect( + getBlockMoverDescription( + 1, + label, + 3, + false, + true, + positiveDirection, + 'horizontal' + ) + ).toBe( + `Block ${ label } is at the end of the content and can’t be moved right` + ); } ); } ); describe( 'getMultiBlockMoverDescription', () => { it( 'generates a title moving multiple blocks up', () => { - expect( getMultiBlockMoverDescription( - 4, - 1, - false, - true, - negativeDirection - ) ).toBe( 'Move 4 blocks from position 2 up by one place' ); + expect( + getMultiBlockMoverDescription( + 4, + 1, + false, + true, + negativeDirection + ) + ).toBe( 'Move 4 blocks from position 2 up by one place' ); } ); it( 'generates a title moving multiple blocks down', () => { - expect( getMultiBlockMoverDescription( - 4, - 0, - true, - false, - positiveDirection - ) ).toBe( 'Move 4 blocks from position 1 down by one place' ); + expect( + getMultiBlockMoverDescription( + 4, + 0, + true, + false, + positiveDirection + ) + ).toBe( 'Move 4 blocks from position 1 down by one place' ); } ); it( 'generates a title for a selection of blocks at the top', () => { - expect( getMultiBlockMoverDescription( - 4, - 1, - true, - true, - negativeDirection - ) ).toBe( 'Blocks cannot be moved up as they are already at the top' ); + expect( + getMultiBlockMoverDescription( + 4, + 1, + true, + true, + negativeDirection + ) + ).toBe( + 'Blocks cannot be moved up as they are already at the top' + ); } ); it( 'generates a title for a selection of blocks at the bottom', () => { - expect( getMultiBlockMoverDescription( - 4, - 2, - false, - true, - positiveDirection - ) ).toBe( 'Blocks cannot be moved down as they are already at the bottom' ); + expect( + getMultiBlockMoverDescription( + 4, + 2, + false, + true, + positiveDirection + ) + ).toBe( + 'Blocks cannot be moved down as they are already at the bottom' + ); } ); } ); } ); diff --git a/packages/block-editor/src/components/block-navigation/dropdown.js b/packages/block-editor/src/components/block-navigation/dropdown.js index a76a7ab563e1c1..24ebb27244dc92 100644 --- a/packages/block-editor/src/components/block-navigation/dropdown.js +++ b/packages/block-editor/src/components/block-navigation/dropdown.js @@ -13,15 +13,31 @@ import { useCallback } from '@wordpress/element'; import BlockNavigation from './'; const MenuIcon = ( - <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20"> + <SVG + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + width="20" + height="20" + > <Path d="M5 5H3v2h2V5zm3 8h11v-2H8v2zm9-8H6v2h11V5zM7 11H5v2h2v-2zm0 8h2v-2H7v2zm3-2v2h11v-2H10z" /> </SVG> ); function BlockNavigationDropdownToggle( { isEnabled, onToggle, isOpen } ) { - useShortcut( 'core/edit-post/toggle-block-navigation', useCallback( onToggle, [ onToggle ] ), { bindGlobal: true, isDisabled: ! isEnabled } ); - const shortcut = useSelect( ( select ) => - select( 'core/keyboard-shortcuts' ).getShortcutRepresentation( 'core/edit-post/toggle-block-navigation' ), [] + useShortcut( + 'core/edit-post/toggle-block-navigation', + useCallback( onToggle, [ onToggle ] ), + { + bindGlobal: true, + isDisabled: ! isEnabled, + } + ); + const shortcut = useSelect( + ( select ) => + select( 'core/keyboard-shortcuts' ).getShortcutRepresentation( + 'core/edit-post/toggle-block-navigation' + ), + [] ); return ( @@ -38,14 +54,20 @@ function BlockNavigationDropdownToggle( { isEnabled, onToggle, isOpen } ) { } function BlockNavigationDropdown( { isDisabled } ) { - const hasBlocks = useSelect( ( select ) => !! select( 'core/block-editor' ).getBlockCount(), [] ); + const hasBlocks = useSelect( + ( select ) => !! select( 'core/block-editor' ).getBlockCount(), + [] + ); const isEnabled = hasBlocks && ! isDisabled; - return ( + return ( <Dropdown contentClassName="block-editor-block-navigation__popover" renderToggle={ ( toggleProps ) => ( - <BlockNavigationDropdownToggle { ...toggleProps } isEnabled={ isEnabled } /> + <BlockNavigationDropdownToggle + { ...toggleProps } + isEnabled={ isEnabled } + /> ) } renderContent={ ( { onClose } ) => ( <BlockNavigation onSelect={ onClose } /> diff --git a/packages/block-editor/src/components/block-navigation/index.js b/packages/block-editor/src/components/block-navigation/index.js index 0e4413c2d85a34..4d25ffda1bb01e 100644 --- a/packages/block-editor/src/components/block-navigation/index.js +++ b/packages/block-editor/src/components/block-navigation/index.js @@ -16,24 +16,29 @@ import { __ } from '@wordpress/i18n'; */ import BlockNavigationList from './list'; -function BlockNavigation( { rootBlock, rootBlocks, selectedBlockClientId, selectBlock } ) { +function BlockNavigation( { + rootBlock, + rootBlocks, + selectedBlockClientId, + selectBlock, +} ) { if ( ! rootBlocks || rootBlocks.length === 0 ) { return null; } - const hasHierarchy = ( - rootBlock && ( - rootBlock.clientId !== selectedBlockClientId || - ( rootBlock.innerBlocks && rootBlock.innerBlocks.length !== 0 ) - ) - ); + const hasHierarchy = + rootBlock && + ( rootBlock.clientId !== selectedBlockClientId || + ( rootBlock.innerBlocks && rootBlock.innerBlocks.length !== 0 ) ); return ( <NavigableMenu role="presentation" className="block-editor-block-navigation__container" > - <p className="block-editor-block-navigation__label">{ __( 'Block navigation' ) }</p> + <p className="block-editor-block-navigation__label"> + { __( 'Block navigation' ) } + </p> { hasHierarchy && ( <BlockNavigationList blocks={ [ rootBlock ] } @@ -64,7 +69,11 @@ export default compose( const selectedBlockClientId = getSelectedBlockClientId(); return { rootBlocks: getBlocks(), - rootBlock: selectedBlockClientId ? getBlock( getBlockHierarchyRootClientId( selectedBlockClientId ) ) : null, + rootBlock: selectedBlockClientId + ? getBlock( + getBlockHierarchyRootClientId( selectedBlockClientId ) + ) + : null, selectedBlockClientId, }; } ), diff --git a/packages/block-editor/src/components/block-navigation/list.js b/packages/block-editor/src/components/block-navigation/list.js index 92a8dc13765add..32c8eba5049e89 100644 --- a/packages/block-editor/src/components/block-navigation/list.js +++ b/packages/block-editor/src/components/block-navigation/list.js @@ -47,26 +47,37 @@ export default function BlockNavigationList( { <li key={ block.clientId }> <div className="block-editor-block-navigation__item"> <Button - className={ classnames( 'block-editor-block-navigation__item-button', { - 'is-selected': isSelected, - } ) } + className={ classnames( + 'block-editor-block-navigation__item-button', + { + 'is-selected': isSelected, + } + ) } onClick={ () => selectBlock( block.clientId ) } > <BlockIcon icon={ blockType.icon } showColors /> { getBlockLabel( blockType, block.attributes ) } - { isSelected && <span className="screen-reader-text">{ __( '(selected block)' ) }</span> } + { isSelected && ( + <span className="screen-reader-text"> + { __( '(selected block)' ) } + </span> + ) } </Button> </div> - { showNestedBlocks && !! block.innerBlocks && !! block.innerBlocks.length && ( - <BlockNavigationList - blocks={ block.innerBlocks } - selectedBlockClientId={ selectedBlockClientId } - selectBlock={ selectBlock } - parentBlockClientId={ block.clientId } - showAppender={ showAppender } - showNestedBlocks - /> - ) } + { showNestedBlocks && + !! block.innerBlocks && + !! block.innerBlocks.length && ( + <BlockNavigationList + blocks={ block.innerBlocks } + selectedBlockClientId={ + selectedBlockClientId + } + selectBlock={ selectBlock } + parentBlockClientId={ block.clientId } + showAppender={ showAppender } + showNestedBlocks + /> + ) } </li> ); } ) } diff --git a/packages/block-editor/src/components/block-pattern-picker/index.js b/packages/block-editor/src/components/block-pattern-picker/index.js deleted file mode 100644 index 5e2080b4cea3e8..00000000000000 --- a/packages/block-editor/src/components/block-pattern-picker/index.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; -import { Button, Placeholder } from '@wordpress/components'; - -function BlockPatternPicker( { - icon = 'layout', - label = __( 'Choose pattern' ), - instructions = __( 'Select a pattern to start with.' ), - patterns, - onSelect, - allowSkip, -} ) { - const classes = classnames( 'block-editor-block-pattern-picker', { - 'has-many-patterns': patterns.length > 4, - } ); - - return ( - <Placeholder - icon={ icon } - label={ label } - instructions={ instructions } - className={ classes } - > - { - /* - * Disable reason: The `list` ARIA role is redundant but - * Safari+VoiceOver won't announce the list otherwise. - */ - /* eslint-disable jsx-a11y/no-redundant-roles */ - } - <ul className="block-editor-block-pattern-picker__patterns" role="list"> - { patterns.map( ( pattern ) => ( - <li key={ pattern.name }> - <Button - isSecondary - icon={ pattern.icon } - iconSize={ 48 } - onClick={ () => onSelect( pattern ) } - className="block-editor-block-pattern-picker__pattern" - label={ pattern.title } - /> - </li> - ) ) } - </ul> - { /* eslint-enable jsx-a11y/no-redundant-roles */ } - { allowSkip && ( - <div className="block-editor-block-pattern-picker__skip"> - <Button - isLink - onClick={ () => onSelect() } - > - { __( 'Skip' ) } - </Button> - </div> - ) } - </Placeholder> - ); -} - -export default BlockPatternPicker; diff --git a/packages/block-editor/src/components/block-preview/index.js b/packages/block-editor/src/components/block-preview/index.js index e745dedc49f20a..9215221096e95a 100644 --- a/packages/block-editor/src/components/block-preview/index.js +++ b/packages/block-editor/src/components/block-preview/index.js @@ -9,7 +9,13 @@ import classnames from 'classnames'; */ import { Disabled } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; -import { useLayoutEffect, useState, useRef, useReducer, useMemo } from '@wordpress/element'; +import { + useLayoutEffect, + useState, + useRef, + useReducer, + useMemo, +} from '@wordpress/element'; /** * Internal dependencies @@ -37,24 +43,36 @@ function ScaledBlockPreview( { blocks, viewportWidth, padding = 0 } ) { // If we're previewing a single block, scale the preview to fit it. if ( blocks.length === 1 ) { const block = blocks[ 0 ]; - const previewElement = getBlockPreviewContainerDOMNode( block.clientId ); + const previewElement = getBlockPreviewContainerDOMNode( + block.clientId + ); if ( ! previewElement ) { return; } let containerElementRect = containerElement.getBoundingClientRect(); containerElementRect = { - width: containerElementRect.width - ( padding * 2 ), - height: containerElementRect.height - ( padding * 2 ), + width: containerElementRect.width - padding * 2, + height: containerElementRect.height - padding * 2, left: containerElementRect.left, top: containerElementRect.top, }; const scaledElementRect = previewElement.getBoundingClientRect(); - const scale = containerElementRect.width / scaledElementRect.width || 1; - const offsetX = ( -( scaledElementRect.left - containerElementRect.left ) * scale ) + padding; - const offsetY = ( containerElementRect.height > scaledElementRect.height * scale ) ? - ( ( containerElementRect.height - ( scaledElementRect.height * scale ) ) / 2 ) + padding : 0; + const scale = + containerElementRect.width / scaledElementRect.width || 1; + const offsetX = + -( scaledElementRect.left - containerElementRect.left ) * + scale + + padding; + const offsetY = + containerElementRect.height > + scaledElementRect.height * scale + ? ( containerElementRect.height - + scaledElementRect.height * scale ) / + 2 + + padding + : 0; setPreviewScale( scale ); setPosition( { x: offsetX, y: offsetY } ); @@ -92,34 +110,43 @@ function ScaledBlockPreview( { blocks, viewportWidth, padding = 0 } ) { return ( <div ref={ previewRef } - className={ classnames( 'block-editor-block-preview__container editor-styles-wrapper', { - 'is-ready': isReady, - } ) } + className={ classnames( + 'block-editor-block-preview__container editor-styles-wrapper', + { + 'is-ready': isReady, + } + ) } aria-hidden > - <Disabled style={ previewStyles } className="block-editor-block-preview__content"> + <Disabled + style={ previewStyles } + className="block-editor-block-preview__content" + > <BlockList /> </Disabled> </div> ); } -export function BlockPreview( { blocks, viewportWidth = 700, padding, settings } ) { +export function BlockPreview( { + blocks, + viewportWidth = 700, + padding, + settings, +} ) { const renderedBlocks = useMemo( () => castArray( blocks ), [ blocks ] ); - const [ recompute, triggerRecompute ] = useReducer( ( state ) => state + 1, 0 ); + const [ recompute, triggerRecompute ] = useReducer( + ( state ) => state + 1, + 0 + ); useLayoutEffect( triggerRecompute, [ blocks ] ); return ( - <BlockEditorProvider - value={ renderedBlocks } - settings={ settings } - > - { - /* - * The key prop is used to force recomputing the preview - * by remounting the component, ScaledBlockPreview is not meant to - * be rerendered. - */ - } + <BlockEditorProvider value={ renderedBlocks } settings={ settings }> + { /* + * The key prop is used to force recomputing the preview + * by remounting the component, ScaledBlockPreview is not meant to + * be rerendered. + */ } <ScaledBlockPreview key={ recompute } blocks={ renderedBlocks } diff --git a/packages/block-editor/src/components/block-selection-clearer/index.js b/packages/block-editor/src/components/block-selection-clearer/index.js index be39e5df9e7de5..69f49a37b4dcee 100644 --- a/packages/block-editor/src/components/block-selection-clearer/index.js +++ b/packages/block-editor/src/components/block-selection-clearer/index.js @@ -15,7 +15,9 @@ class BlockSelectionClearer extends Component { super( ...arguments ); this.bindContainer = this.bindContainer.bind( this ); - this.clearSelectionIfFocusTarget = this.clearSelectionIfFocusTarget.bind( this ); + this.clearSelectionIfFocusTarget = this.clearSelectionIfFocusTarget.bind( + this + ); } bindContainer( ref ) { @@ -36,7 +38,7 @@ class BlockSelectionClearer extends Component { clearSelectedBlock, } = this.props; - const hasSelection = ( hasSelectedBlock || hasMultiSelection ); + const hasSelection = hasSelectedBlock || hasMultiSelection; if ( event.target === this.container && hasSelection ) { clearSelectedBlock(); } @@ -60,7 +62,9 @@ class BlockSelectionClearer extends Component { export default compose( [ withSelect( ( select ) => { - const { hasSelectedBlock, hasMultiSelection } = select( 'core/block-editor' ); + const { hasSelectedBlock, hasMultiSelection } = select( + 'core/block-editor' + ); return { hasSelectedBlock: hasSelectedBlock(), diff --git a/packages/block-editor/src/components/block-settings-menu/block-convert-button.js b/packages/block-editor/src/components/block-settings-menu/block-convert-button.js index d0e6f8323bbd28..3d321a2010ec01 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-convert-button.js +++ b/packages/block-editor/src/components/block-settings-menu/block-convert-button.js @@ -11,10 +11,7 @@ export default function BlockConvertButton( { shouldRender, onClick, small } ) { const label = __( 'Convert to Blocks' ); return ( - <MenuItem - onClick={ onClick } - icon="screenoptions" - > + <MenuItem onClick={ onClick } icon="screenoptions"> { ! small && label } </MenuItem> ); diff --git a/packages/block-editor/src/components/block-settings-menu/block-html-convert-button.js b/packages/block-editor/src/components/block-settings-menu/block-html-convert-button.js index 3527d7d0d460c0..339b6cdbd11e79 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-html-convert-button.js +++ b/packages/block-editor/src/components/block-settings-menu/block-html-convert-button.js @@ -16,13 +16,14 @@ export default compose( return { block, - shouldRender: ( block && block.name === 'core/html' ), + shouldRender: block && block.name === 'core/html', }; } ), withDispatch( ( dispatch, { block } ) => ( { - onClick: () => dispatch( 'core/block-editor' ).replaceBlocks( - block.clientId, - rawHandler( { HTML: getBlockContent( block ) } ), - ), - } ) ), + onClick: () => + dispatch( 'core/block-editor' ).replaceBlocks( + block.clientId, + rawHandler( { HTML: getBlockContent( block ) } ) + ), + } ) ) )( BlockConvertButton ); diff --git a/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js b/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js index 57f285024538c0..acf1f37830e135 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js +++ b/packages/block-editor/src/components/block-settings-menu/block-mode-toggle.js @@ -12,20 +12,25 @@ import { getBlockType, hasBlockSupport } from '@wordpress/blocks'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; -export function BlockModeToggle( { blockType, mode, onToggleMode, small = false, isCodeEditingEnabled = true } ) { - if ( ! hasBlockSupport( blockType, 'html', true ) || ! isCodeEditingEnabled ) { +export function BlockModeToggle( { + blockType, + mode, + onToggleMode, + small = false, + isCodeEditingEnabled = true, +} ) { + if ( + ! hasBlockSupport( blockType, 'html', true ) || + ! isCodeEditingEnabled + ) { return null; } - const label = mode === 'visual' ? - __( 'Edit as HTML' ) : - __( 'Edit visually' ); + const label = + mode === 'visual' ? __( 'Edit as HTML' ) : __( 'Edit visually' ); return ( - <MenuItem - onClick={ onToggleMode } - icon="html" - > + <MenuItem onClick={ onToggleMode } icon="html"> { ! small && label } </MenuItem> ); @@ -33,7 +38,9 @@ export function BlockModeToggle( { blockType, mode, onToggleMode, small = false, export default compose( [ withSelect( ( select, { clientId } ) => { - const { getBlock, getBlockMode, getSettings } = select( 'core/block-editor' ); + const { getBlock, getBlockMode, getSettings } = select( + 'core/block-editor' + ); const block = getBlock( clientId ); const isCodeEditingEnabled = getSettings().codeEditingEnabled; diff --git a/packages/block-editor/src/components/block-settings-menu/block-settings-menu-first-item.js b/packages/block-editor/src/components/block-settings-menu/block-settings-menu-first-item.js index e01f943684ffe9..43f83d136abe78 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-settings-menu-first-item.js +++ b/packages/block-editor/src/components/block-settings-menu/block-settings-menu-first-item.js @@ -3,7 +3,9 @@ */ import { createSlotFill } from '@wordpress/components'; -const { Fill: __experimentalBlockSettingsMenuFirstItem, Slot } = createSlotFill( '__experimentalBlockSettingsMenuFirstItem' ); +const { Fill: __experimentalBlockSettingsMenuFirstItem, Slot } = createSlotFill( + '__experimentalBlockSettingsMenuFirstItem' +); __experimentalBlockSettingsMenuFirstItem.Slot = Slot; diff --git a/packages/block-editor/src/components/block-settings-menu/block-settings-menu-plugins-extension.js b/packages/block-editor/src/components/block-settings-menu/block-settings-menu-plugins-extension.js index a750b5b36cc185..7a3058b4c945b5 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-settings-menu-plugins-extension.js +++ b/packages/block-editor/src/components/block-settings-menu/block-settings-menu-plugins-extension.js @@ -3,7 +3,10 @@ */ import { createSlotFill } from '@wordpress/components'; -const { Fill: __experimentalBlockSettingsMenuPluginsExtension, Slot } = createSlotFill( '__experimentalBlockSettingsMenuPluginsExtension' ); +const { + Fill: __experimentalBlockSettingsMenuPluginsExtension, + Slot, +} = createSlotFill( '__experimentalBlockSettingsMenuPluginsExtension' ); __experimentalBlockSettingsMenuPluginsExtension.Slot = Slot; diff --git a/packages/block-editor/src/components/block-settings-menu/block-unknown-convert-button.js b/packages/block-editor/src/components/block-settings-menu/block-unknown-convert-button.js index 46eda45141ad12..7bbce60bf6dfe4 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-unknown-convert-button.js +++ b/packages/block-editor/src/components/block-settings-menu/block-unknown-convert-button.js @@ -1,7 +1,11 @@ /** * WordPress dependencies */ -import { getFreeformContentHandlerName, rawHandler, serialize } from '@wordpress/blocks'; +import { + getFreeformContentHandlerName, + rawHandler, + serialize, +} from '@wordpress/blocks'; import { compose } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; @@ -16,13 +20,15 @@ export default compose( return { block, - shouldRender: ( block && block.name === getFreeformContentHandlerName() ), + shouldRender: + block && block.name === getFreeformContentHandlerName(), }; } ), withDispatch( ( dispatch, { block } ) => ( { - onClick: () => dispatch( 'core/block-editor' ).replaceBlocks( - block.clientId, - rawHandler( { HTML: serialize( block ) } ) - ), - } ) ), + onClick: () => + dispatch( 'core/block-editor' ).replaceBlocks( + block.clientId, + rawHandler( { HTML: serialize( block ) } ) + ), + } ) ) )( BlockConvertButton ); diff --git a/packages/block-editor/src/components/block-settings-menu/index.js b/packages/block-editor/src/components/block-settings-menu/index.js index 375c585a5d4124..8bd25660890eb4 100644 --- a/packages/block-editor/src/components/block-settings-menu/index.js +++ b/packages/block-editor/src/components/block-settings-menu/index.js @@ -37,12 +37,20 @@ export function BlockSettingsMenu( { clientIds } ) { const firstBlockClientId = blockClientIds[ 0 ]; const shortcuts = useSelect( ( select ) => { - const { getShortcutRepresentation } = select( 'core/keyboard-shortcuts' ); + const { getShortcutRepresentation } = select( + 'core/keyboard-shortcuts' + ); return { - duplicate: getShortcutRepresentation( 'core/block-editor/duplicate' ), + duplicate: getShortcutRepresentation( + 'core/block-editor/duplicate' + ), remove: getShortcutRepresentation( 'core/block-editor/remove' ), - insertAfter: getShortcutRepresentation( 'core/block-editor/insert-after' ), - insertBefore: getShortcutRepresentation( 'core/block-editor/insert-before' ), + insertAfter: getShortcutRepresentation( + 'core/block-editor/insert-after' + ), + insertBefore: getShortcutRepresentation( + 'core/block-editor/insert-before' + ), }; }, [] ); @@ -82,7 +90,10 @@ export function BlockSettingsMenu( { clientIds } ) { ) } { canDuplicate && ( <MenuItem - onClick={ flow( onClose, onDuplicate ) } + onClick={ flow( + onClose, + onDuplicate + ) } icon="admin-page" shortcut={ shortcuts.duplicate } > @@ -92,16 +103,26 @@ export function BlockSettingsMenu( { clientIds } ) { { canInsertDefaultBlock && ( <> <MenuItem - onClick={ flow( onClose, onInsertBefore ) } + onClick={ flow( + onClose, + onInsertBefore + ) } icon="insert-before" - shortcut={ shortcuts.insertBefore } + shortcut={ + shortcuts.insertBefore + } > { __( 'Insert Before' ) } </MenuItem> <MenuItem - onClick={ flow( onClose, onInsertAfter ) } + onClick={ flow( + onClose, + onInsertAfter + ) } icon="insert-after" - shortcut={ shortcuts.insertAfter } + shortcut={ + shortcuts.insertAfter + } > { __( 'Insert After' ) } </MenuItem> @@ -120,11 +141,18 @@ export function BlockSettingsMenu( { clientIds } ) { <MenuGroup> { ! isLocked && ( <MenuItem - onClick={ flow( onClose, onRemove ) } + onClick={ flow( + onClose, + onRemove + ) } icon={ trash } shortcut={ shortcuts.remove } > - { _n( 'Remove Block', 'Remove Blocks', count ) } + { _n( + 'Remove Block', + 'Remove Blocks', + count + ) } </MenuItem> ) } </MenuGroup> diff --git a/packages/block-editor/src/components/block-settings/button.native.js b/packages/block-editor/src/components/block-settings/button.native.js index ab2e1601a88800..5225e8bb988c44 100644 --- a/packages/block-editor/src/components/block-settings/button.native.js +++ b/packages/block-editor/src/components/block-settings/button.native.js @@ -7,12 +7,13 @@ import { withDispatch } from '@wordpress/data'; const { Fill, Slot } = createSlotFill( 'SettingsToolbarButton' ); -const SettingsButton = ( { openGeneralSidebar } ) => +const SettingsButton = ( { openGeneralSidebar } ) => ( <ToolbarButton title={ __( 'Open Settings' ) } icon="admin-generic" onClick={ openGeneralSidebar } - />; + /> +); const SettingsButtonFill = ( props ) => ( <Fill> diff --git a/packages/block-editor/src/components/block-settings/container.native.js b/packages/block-editor/src/components/block-settings/container.native.js index 1b59377f29161c..fe59d3060a29fc 100644 --- a/packages/block-editor/src/components/block-settings/container.native.js +++ b/packages/block-editor/src/components/block-settings/container.native.js @@ -11,7 +11,11 @@ import { InspectorControls } from '@wordpress/block-editor'; */ import styles from './container.native.scss'; -function BottomSheetSettings( { editorSidebarOpened, closeGeneralSidebar, ...props } ) { +function BottomSheetSettings( { + editorSidebarOpened, + closeGeneralSidebar, + ...props +} ) { return ( <BottomSheet isVisible={ editorSidebarOpened } @@ -20,16 +24,14 @@ function BottomSheetSettings( { editorSidebarOpened, closeGeneralSidebar, ...pro contentStyle={ styles.content } { ...props } > - <InspectorControls.Slot /> + <InspectorControls.Slot /> </BottomSheet> ); } export default compose( [ withSelect( ( select ) => { - const { - isEditorSidebarOpened, - } = select( 'core/edit-post' ); + const { isEditorSidebarOpened } = select( 'core/edit-post' ); return { editorSidebarOpened: isEditorSidebarOpened(), @@ -42,5 +44,4 @@ export default compose( [ closeGeneralSidebar, }; } ), -] -)( BottomSheetSettings ); +] )( BottomSheetSettings ); diff --git a/packages/block-editor/src/components/block-styles/index.js b/packages/block-editor/src/components/block-styles/index.js index f3fd2b2f85a8ad..0c4352d41530ac 100644 --- a/packages/block-editor/src/components/block-styles/index.js +++ b/packages/block-editor/src/components/block-styles/index.js @@ -12,7 +12,11 @@ import { withSelect, withDispatch } from '@wordpress/data'; import TokenList from '@wordpress/token-list'; import { ENTER, SPACE } from '@wordpress/keycodes'; import { _x } from '@wordpress/i18n'; -import { getBlockType, cloneBlock, getBlockFromExample } from '@wordpress/blocks'; +import { + getBlockType, + cloneBlock, + getBlockFromExample, +} from '@wordpress/blocks'; /** * Internal dependencies @@ -90,7 +94,11 @@ function BlockStyles( { const activeStyle = getActiveStyle( styles, className ); function updateClassName( style ) { - const updatedClassName = replaceActiveStyle( className, activeStyle, style ); + const updatedClassName = replaceActiveStyle( + className, + activeStyle, + style + ); onChangeClassName( updatedClassName ); onHoverClassName( null ); onSwitch(); @@ -99,23 +107,33 @@ function BlockStyles( { return ( <div className="block-editor-block-styles"> { styles.map( ( style ) => { - const styleClassName = replaceActiveStyle( className, activeStyle, style ); + const styleClassName = replaceActiveStyle( + className, + activeStyle, + style + ); return ( <div key={ style.name } className={ classnames( - 'block-editor-block-styles__item', { + 'block-editor-block-styles__item', + { 'is-active': activeStyle === style, } ) } onClick={ () => updateClassName( style ) } onKeyDown={ ( event ) => { - if ( ENTER === event.keyCode || SPACE === event.keyCode ) { + if ( + ENTER === event.keyCode || + SPACE === event.keyCode + ) { event.preventDefault(); updateClassName( style ); } } } - onMouseEnter={ () => onHoverClassName( styleClassName ) } + onMouseEnter={ () => + onHoverClassName( styleClassName ) + } onMouseLeave={ () => onHoverClassName( null ) } role="button" tabIndex="0" @@ -125,12 +143,18 @@ function BlockStyles( { <BlockPreview viewportWidth={ 500 } blocks={ - type.example ? - getBlockFromExample( block.name, { - attributes: { ...type.example.attributes, className: styleClassName }, - innerBlocks: type.example.innerBlocks, - } ) : - cloneBlock( block, { className: styleClassName } ) + type.example + ? getBlockFromExample( block.name, { + attributes: { + ...type.example.attributes, + className: styleClassName, + }, + innerBlocks: + type.example.innerBlocks, + } ) + : cloneBlock( block, { + className: styleClassName, + } ) } /> </div> @@ -161,9 +185,12 @@ export default compose( [ withDispatch( ( dispatch, { clientId } ) => { return { onChangeClassName( newClassName ) { - dispatch( 'core/block-editor' ).updateBlockAttributes( clientId, { - className: newClassName, - } ); + dispatch( 'core/block-editor' ).updateBlockAttributes( + clientId, + { + className: newClassName, + } + ); }, }; } ), diff --git a/packages/block-editor/src/components/block-styles/test/index.js b/packages/block-editor/src/components/block-styles/test/index.js index 639d92ed4aeaa3..6b73864d08c94d 100644 --- a/packages/block-editor/src/components/block-styles/test/index.js +++ b/packages/block-editor/src/components/block-styles/test/index.js @@ -5,40 +5,28 @@ import { getActiveStyle, replaceActiveStyle } from '../'; describe( 'getActiveStyle', () => { it( 'Should return the undefined if no active style', () => { - const styles = [ - { name: 'small' }, - { name: 'big' }, - ]; + const styles = [ { name: 'small' }, { name: 'big' } ]; const className = 'custom-className'; expect( getActiveStyle( styles, className ) ).toBeUndefined(); } ); it( 'Should return the default style if no active style', () => { - const styles = [ - { name: 'small' }, - { name: 'big', isDefault: true }, - ]; + const styles = [ { name: 'small' }, { name: 'big', isDefault: true } ]; const className = 'custom-className'; expect( getActiveStyle( styles, className ).name ).toBe( 'big' ); } ); it( 'Should return the active style', () => { - const styles = [ - { name: 'small' }, - { name: 'big', isDefault: true }, - ]; + const styles = [ { name: 'small' }, { name: 'big', isDefault: true } ]; const className = 'this-is-custom is-style-small'; expect( getActiveStyle( styles, className ).name ).toBe( 'small' ); } ); it( 'Should return the first active style', () => { - const styles = [ - { name: 'small' }, - { name: 'big', isDefault: true }, - ]; + const styles = [ { name: 'small' }, { name: 'big', isDefault: true } ]; const className = 'this-is-custom is-style-small is-style-big'; expect( getActiveStyle( styles, className ).name ).toBe( 'small' ); @@ -51,8 +39,9 @@ describe( 'replaceActiveStyle', () => { const newStyle = { name: 'small' }; const className = 'custom-class'; - expect( replaceActiveStyle( className, activeStyle, newStyle ) ) - .toBe( 'custom-class is-style-small' ); + expect( replaceActiveStyle( className, activeStyle, newStyle ) ).toBe( + 'custom-class is-style-small' + ); } ); it( 'Should add the new style if no active style (no existing class)', () => { @@ -60,8 +49,9 @@ describe( 'replaceActiveStyle', () => { const newStyle = { name: 'small' }; const className = ''; - expect( replaceActiveStyle( className, activeStyle, newStyle ) ) - .toBe( 'is-style-small' ); + expect( replaceActiveStyle( className, activeStyle, newStyle ) ).toBe( + 'is-style-small' + ); } ); it( 'Should add the new style if no active style (unassigned default)', () => { @@ -69,8 +59,9 @@ describe( 'replaceActiveStyle', () => { const newStyle = { name: 'small' }; const className = ''; - expect( replaceActiveStyle( className, activeStyle, newStyle ) ) - .toBe( 'is-style-small' ); + expect( replaceActiveStyle( className, activeStyle, newStyle ) ).toBe( + 'is-style-small' + ); } ); it( 'Should replace the previous active style', () => { @@ -78,7 +69,8 @@ describe( 'replaceActiveStyle', () => { const newStyle = { name: 'small' }; const className = 'custom-class is-style-large'; - expect( replaceActiveStyle( className, activeStyle, newStyle ) ) - .toBe( 'custom-class is-style-small' ); + expect( replaceActiveStyle( className, activeStyle, newStyle ) ).toBe( + 'custom-class is-style-small' + ); } ); } ); diff --git a/packages/block-editor/src/components/block-switcher/index.js b/packages/block-editor/src/components/block-switcher/index.js index 09561f63199cd2..421c26ebe09b59 100644 --- a/packages/block-editor/src/components/block-switcher/index.js +++ b/packages/block-editor/src/components/block-switcher/index.js @@ -7,8 +7,21 @@ import { castArray, filter, first, mapKeys, orderBy, uniq, map } from 'lodash'; * WordPress dependencies */ import { __, _n, sprintf } from '@wordpress/i18n'; -import { Dropdown, Button, Toolbar, PanelBody, Path, SVG } from '@wordpress/components'; -import { getBlockType, getPossibleBlockTransformations, switchToBlockType, cloneBlock, getBlockFromExample } from '@wordpress/blocks'; +import { + Dropdown, + Button, + Toolbar, + PanelBody, + Path, + SVG, +} from '@wordpress/components'; +import { + getBlockType, + getPossibleBlockTransformations, + switchToBlockType, + cloneBlock, + getBlockFromExample, +} from '@wordpress/blocks'; import { Component } from '@wordpress/element'; import { DOWN } from '@wordpress/keycodes'; import { withSelect, withDispatch } from '@wordpress/data'; @@ -36,7 +49,12 @@ export class BlockSwitcher extends Component { } render() { - const { blocks, onTransform, inserterItems, hasBlockStyles } = this.props; + const { + blocks, + onTransform, + inserterItems, + hasBlockStyles, + } = this.props; const { hoveredClassName } = this.state; if ( ! blocks || ! blocks.length ) { @@ -44,7 +62,9 @@ export class BlockSwitcher extends Component { } const hoveredBlock = hoveredClassName ? blocks[ 0 ] : null; - const hoveredBlockType = hoveredClassName ? getBlockType( hoveredBlock.name ) : null; + const hoveredBlockType = hoveredClassName + ? getBlockType( hoveredBlock.name ) + : null; const itemsByName = mapKeys( inserterItems, ( { name } ) => name ); const possibleBlockTransformations = orderBy( @@ -58,7 +78,8 @@ export class BlockSwitcher extends Component { // When selection consists of blocks of multiple types, display an // appropriate icon to communicate the non-uniformity. - const isSelectionOfSameType = uniq( map( blocks, 'name' ) ).length === 1; + const isSelectionOfSameType = + uniq( map( blocks, 'name' ) ).length === 1; let icon; if ( isSelectionOfSameType ) { @@ -95,18 +116,17 @@ export class BlockSwitcher extends Component { onToggle(); } }; - const label = ( - 1 === blocks.length ? - __( 'Change block type or style' ) : - sprintf( - _n( - 'Change type of %d block', - 'Change type of %d blocks', + const label = + 1 === blocks.length + ? __( 'Change block type or style' ) + : sprintf( + _n( + 'Change type of %d block', + 'Change type of %d blocks', + blocks.length + ), blocks.length - ), - blocks.length - ) - ); + ); return ( <Toolbar> @@ -118,21 +138,28 @@ export class BlockSwitcher extends Component { label={ label } onKeyDown={ openOnArrowDown } showTooltip - icon={ ( + icon={ <> <BlockIcon icon={ icon } showColors /> - <SVG className="block-editor-block-switcher__transform" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M6.5 8.9c.6-.6 1.4-.9 2.2-.9h6.9l-1.3 1.3 1.4 1.4L19.4 7l-3.7-3.7-1.4 1.4L15.6 6H8.7c-1.4 0-2.6.5-3.6 1.5l-2.8 2.8 1.4 1.4 2.8-2.8zm13.8 2.4l-2.8 2.8c-.6.6-1.3.9-2.1.9h-7l1.3-1.3-1.4-1.4L4.6 16l3.7 3.7 1.4-1.4L8.4 17h6.9c1.3 0 2.6-.5 3.5-1.5l2.8-2.8-1.3-1.4z" /></SVG> + <SVG + className="block-editor-block-switcher__transform" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + > + <Path d="M6.5 8.9c.6-.6 1.4-.9 2.2-.9h6.9l-1.3 1.3 1.4 1.4L19.4 7l-3.7-3.7-1.4 1.4L15.6 6H8.7c-1.4 0-2.6.5-3.6 1.5l-2.8 2.8 1.4 1.4 2.8-2.8zm13.8 2.4l-2.8 2.8c-.6.6-1.3.9-2.1.9h-7l1.3-1.3-1.4-1.4L4.6 16l3.7 3.7 1.4-1.4L8.4 17h6.9c1.3 0 2.6-.5 3.5-1.5l2.8-2.8-1.3-1.4z" /> + </SVG> </> - ) } + } /> </Toolbar> ); } } renderContent={ ( { onClose } ) => ( <> - { ( hasBlockStyles || possibleBlockTransformations.length !== 0 ) && + { ( hasBlockStyles || + possibleBlockTransformations.length !== 0 ) && ( <div className="block-editor-block-switcher__container"> - { hasBlockStyles && + { hasBlockStyles && ( <PanelBody title={ __( 'Block Styles' ) } initialOpen @@ -140,46 +167,68 @@ export class BlockSwitcher extends Component { <BlockStyles clientId={ blocks[ 0 ].clientId } onSwitch={ onClose } - onHoverClassName={ this.onHoverClassName } + onHoverClassName={ + this.onHoverClassName + } /> </PanelBody> - } - { possibleBlockTransformations.length !== 0 && + ) } + { possibleBlockTransformations.length !== 0 && ( <PanelBody title={ __( 'Transform To:' ) } initialOpen > <BlockTypesList - items={ possibleBlockTransformations.map( ( destinationBlockType ) => ( { - id: destinationBlockType.name, - icon: destinationBlockType.icon, - title: destinationBlockType.title, - } ) ) } + items={ possibleBlockTransformations.map( + ( destinationBlockType ) => ( { + id: + destinationBlockType.name, + icon: + destinationBlockType.icon, + title: + destinationBlockType.title, + } ) + ) } onSelect={ ( item ) => { onTransform( blocks, item.id ); onClose(); } } /> </PanelBody> - } + ) } </div> - } - { ( hoveredClassName !== null ) && + ) } + { hoveredClassName !== null && ( <div className="block-editor-block-switcher__preview"> - <div className="block-editor-block-switcher__preview-title">{ __( 'Preview' ) }</div> + <div className="block-editor-block-switcher__preview-title"> + { __( 'Preview' ) } + </div> <BlockPreview viewportWidth={ 500 } blocks={ - hoveredBlockType.example ? - getBlockFromExample( hoveredBlock.name, { - attributes: { ...hoveredBlockType.example.attributes, className: hoveredClassName }, - innerBlocks: hoveredBlockType.example.innerBlocks, - } ) : - cloneBlock( hoveredBlock, { className: hoveredClassName } ) + hoveredBlockType.example + ? getBlockFromExample( + hoveredBlock.name, + { + attributes: { + ...hoveredBlockType + .example + .attributes, + className: hoveredClassName, + }, + innerBlocks: + hoveredBlockType + .example + .innerBlocks, + } + ) + : cloneBlock( hoveredBlock, { + className: hoveredClassName, + } ) } /> </div> - } + ) } </> ) } /> @@ -189,9 +238,15 @@ export class BlockSwitcher extends Component { export default compose( withSelect( ( select, { clientIds } ) => { - const { getBlocksByClientId, getBlockRootClientId, getInserterItems } = select( 'core/block-editor' ); + const { + getBlocksByClientId, + getBlockRootClientId, + getInserterItems, + } = select( 'core/block-editor' ); const { getBlockStyles } = select( 'core/blocks' ); - const rootClientId = getBlockRootClientId( first( castArray( clientIds ) ) ); + const rootClientId = getBlockRootClientId( + first( castArray( clientIds ) ) + ); const blocks = getBlocksByClientId( clientIds ); const firstBlock = blocks && blocks.length === 1 ? blocks[ 0 ] : null; const styles = firstBlock && getBlockStyles( firstBlock.name ); @@ -208,5 +263,5 @@ export default compose( switchToBlockType( blocks, name ) ); }, - } ) ), + } ) ) )( BlockSwitcher ); diff --git a/packages/block-editor/src/components/block-switcher/multi-blocks-switcher.js b/packages/block-editor/src/components/block-switcher/multi-blocks-switcher.js index 8adae420dab998..a68093f982b0f2 100644 --- a/packages/block-editor/src/components/block-switcher/multi-blocks-switcher.js +++ b/packages/block-editor/src/components/block-switcher/multi-blocks-switcher.js @@ -8,7 +8,10 @@ import { withSelect } from '@wordpress/data'; */ import BlockSwitcher from './'; -export function MultiBlocksSwitcher( { isMultiBlockSelection, selectedBlockClientIds } ) { +export function MultiBlocksSwitcher( { + isMultiBlockSelection, + selectedBlockClientIds, +} ) { if ( ! isMultiBlockSelection ) { return null; } @@ -17,12 +20,12 @@ export function MultiBlocksSwitcher( { isMultiBlockSelection, selectedBlockClien ); } -export default withSelect( - ( select ) => { - const selectedBlockClientIds = select( 'core/block-editor' ).getMultiSelectedBlockClientIds(); - return { - isMultiBlockSelection: selectedBlockClientIds.length > 1, - selectedBlockClientIds, - }; - } -)( MultiBlocksSwitcher ); +export default withSelect( ( select ) => { + const selectedBlockClientIds = select( + 'core/block-editor' + ).getMultiSelectedBlockClientIds(); + return { + isMultiBlockSelection: selectedBlockClientIds.length > 1, + selectedBlockClientIds, + }; +} )( MultiBlocksSwitcher ); diff --git a/packages/block-editor/src/components/block-switcher/test/index.js b/packages/block-editor/src/components/block-switcher/test/index.js index daa39038cbbf21..dea53770ca7d07 100644 --- a/packages/block-editor/src/components/block-switcher/test/index.js +++ b/packages/block-editor/src/components/block-switcher/test/index.js @@ -51,7 +51,7 @@ describe( 'BlockSwitcher', () => { registerBlockType( 'core/heading', { category: 'common', title: 'Heading', - edit: () => { }, + edit: () => {}, save: () => {}, transforms: { to: [ @@ -73,14 +73,16 @@ describe( 'BlockSwitcher', () => { registerBlockType( 'core/paragraph', { category: 'common', title: 'Paragraph', - edit: () => { }, + edit: () => {}, save: () => {}, transforms: { - to: [ { - type: 'block', - blocks: [ 'core/heading' ], - transform: () => {}, - } ], + to: [ + { + type: 'block', + blocks: [ 'core/heading' ], + transform: () => {}, + }, + ], }, } ); } ); @@ -97,15 +99,15 @@ describe( 'BlockSwitcher', () => { } ); test( 'should render switcher with blocks', () => { - const blocks = [ - headingBlock1, - ]; + const blocks = [ headingBlock1 ]; const inserterItems = [ { name: 'core/heading', frecency: 1 }, { name: 'core/paragraph', frecency: 1 }, ]; - const wrapper = shallow( <BlockSwitcher blocks={ blocks } inserterItems={ inserterItems } /> ); + const wrapper = shallow( + <BlockSwitcher blocks={ blocks } inserterItems={ inserterItems } /> + ); expect( wrapper ).toMatchSnapshot(); } ); @@ -117,7 +119,9 @@ describe( 'BlockSwitcher', () => { { name: 'core/paragraph', frecency: 1 }, ]; - const wrapper = shallow( <BlockSwitcher blocks={ blocks } inserterItems={ inserterItems } /> ); + const wrapper = shallow( + <BlockSwitcher blocks={ blocks } inserterItems={ inserterItems } /> + ); expect( wrapper ).toMatchSnapshot(); } ); @@ -129,15 +133,15 @@ describe( 'BlockSwitcher', () => { { name: 'core/paragraph', frecency: 1 }, ]; - const wrapper = shallow( <BlockSwitcher blocks={ blocks } inserterItems={ inserterItems } /> ); + const wrapper = shallow( + <BlockSwitcher blocks={ blocks } inserterItems={ inserterItems } /> + ); expect( wrapper ).toMatchSnapshot(); } ); describe( 'Dropdown', () => { - const blocks = [ - headingBlock1, - ]; + const blocks = [ headingBlock1 ]; const inserterItems = [ { name: 'core/quote', frecency: 1 }, @@ -149,7 +153,13 @@ describe( 'BlockSwitcher', () => { const onTransformStub = jest.fn(); const getDropdown = () => { - const blockSwitcher = shallow( <BlockSwitcher blocks={ blocks } onTransform={ onTransformStub } inserterItems={ inserterItems } /> ); + const blockSwitcher = shallow( + <BlockSwitcher + blocks={ blocks } + onTransform={ onTransformStub } + inserterItems={ inserterItems } + /> + ); return blockSwitcher.find( 'Dropdown' ); }; @@ -170,8 +180,17 @@ describe( 'BlockSwitcher', () => { } ); test( 'should simulate a keydown event, which should call onToggle and open transform toggle.', () => { - const toggleClosed = shallow( getDropdown().props().renderToggle( { onToggle: onToggleStub, isOpen: false } ) ); - const iconButtonClosed = toggleClosed.find( 'ForwardRef(Button)' ); + const toggleClosed = shallow( + getDropdown() + .props() + .renderToggle( { + onToggle: onToggleStub, + isOpen: false, + } ) + ); + const iconButtonClosed = toggleClosed.find( + 'ForwardRef(Button)' + ); iconButtonClosed.simulate( 'keydown', mockKeyDown ); @@ -179,7 +198,14 @@ describe( 'BlockSwitcher', () => { } ); test( 'should simulate a click event, which should call onToggle.', () => { - const toggleOpen = shallow( getDropdown().props().renderToggle( { onToggle: onToggleStub, isOpen: true } ) ); + const toggleOpen = shallow( + getDropdown() + .props() + .renderToggle( { + onToggle: onToggleStub, + isOpen: true, + } ) + ); const iconButtonOpen = toggleOpen.find( 'ForwardRef(Button)' ); iconButtonOpen.simulate( 'keydown', mockKeyDown ); @@ -191,7 +217,13 @@ describe( 'BlockSwitcher', () => { describe( '.renderContent', () => { test( 'should create the transform items for the chosen block. A heading block will have 3 items', () => { const onCloseStub = jest.fn(); - const content = shallow( <div>{ getDropdown().props().renderContent( { onClose: onCloseStub } ) }</div> ); + const content = shallow( + <div> + { getDropdown() + .props() + .renderContent( { onClose: onCloseStub } ) } + </div> + ); const blockList = content.find( 'BlockTypesList' ); expect( blockList.prop( 'items' ) ).toHaveLength( 1 ); } ); diff --git a/packages/block-editor/src/components/block-switcher/test/multi-blocks-switcher.js b/packages/block-editor/src/components/block-switcher/test/multi-blocks-switcher.js index 30199f6c598529..5e99eaba7f675d 100644 --- a/packages/block-editor/src/components/block-switcher/test/multi-blocks-switcher.js +++ b/packages/block-editor/src/components/block-switcher/test/multi-blocks-switcher.js @@ -11,9 +11,7 @@ import { MultiBlocksSwitcher } from '../multi-blocks-switcher'; describe( 'MultiBlocksSwitcher', () => { test( 'should return null when the selection is not a multi block selection.', () => { const isMultiBlockSelection = false; - const selectedBlockClientIds = [ - 'clientid', - ]; + const selectedBlockClientIds = [ 'clientid' ]; const wrapper = shallow( <MultiBlocksSwitcher isMultiBlockSelection={ isMultiBlockSelection } @@ -26,10 +24,7 @@ describe( 'MultiBlocksSwitcher', () => { test( 'should return a BlockSwitcher element matching the snapshot.', () => { const isMultiBlockSelection = true; - const selectedBlockClientIds = [ - 'clientid-1', - 'clientid-2', - ]; + const selectedBlockClientIds = [ 'clientid-1', 'clientid-2' ]; const wrapper = shallow( <MultiBlocksSwitcher isMultiBlockSelection={ isMultiBlockSelection } diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js index debc88f96bbb39..656527954ed840 100644 --- a/packages/block-editor/src/components/block-toolbar/index.js +++ b/packages/block-editor/src/components/block-toolbar/index.js @@ -30,22 +30,24 @@ export default function BlockToolbar() { getBlockListSettings, } = select( 'core/block-editor' ); const selectedBlockClientIds = getSelectedBlockClientIds(); - const blockRootClientId = getBlockRootClientId( selectedBlockClientIds[ 0 ] ); + const blockRootClientId = getBlockRootClientId( + selectedBlockClientIds[ 0 ] + ); - const { - __experimentalMoverDirection, - __experimentalUIParts = {}, - } = getBlockListSettings( blockRootClientId ) || {}; + const { __experimentalMoverDirection, __experimentalUIParts = {} } = + getBlockListSettings( blockRootClientId ) || {}; return { blockClientIds: selectedBlockClientIds, rootClientId: blockRootClientId, - isValid: selectedBlockClientIds.length === 1 ? - isBlockValid( selectedBlockClientIds[ 0 ] ) : - null, - mode: selectedBlockClientIds.length === 1 ? - getBlockMode( selectedBlockClientIds[ 0 ] ) : - null, + isValid: + selectedBlockClientIds.length === 1 + ? isBlockValid( selectedBlockClientIds[ 0 ] ) + : null, + mode: + selectedBlockClientIds.length === 1 + ? getBlockMode( selectedBlockClientIds[ 0 ] ) + : null, moverDirection: __experimentalMoverDirection, hasMovers: __experimentalUIParts.hasMovers, }; @@ -58,10 +60,12 @@ export default function BlockToolbar() { if ( blockClientIds.length > 1 ) { return ( <div className="block-editor-block-toolbar"> - { hasMovers && ( <BlockMover - clientIds={ blockClientIds } - __experimentalOrientation={ moverDirection } - /> ) } + { hasMovers && ( + <BlockMover + clientIds={ blockClientIds } + __experimentalOrientation={ moverDirection } + /> + ) } <MultiBlocksSwitcher /> <BlockSettingsMenu clientIds={ blockClientIds } /> </div> @@ -70,15 +74,23 @@ export default function BlockToolbar() { return ( <div className="block-editor-block-toolbar"> - { hasMovers && ( <BlockMover - clientIds={ blockClientIds } - __experimentalOrientation={ moverDirection } - /> ) } + { hasMovers && ( + <BlockMover + clientIds={ blockClientIds } + __experimentalOrientation={ moverDirection } + /> + ) } { mode === 'visual' && isValid && ( <> <BlockSwitcher clientIds={ blockClientIds } /> - <BlockControls.Slot bubblesVirtually className="block-editor-block-toolbar__slot" /> - <BlockFormatControls.Slot bubblesVirtually className="block-editor-block-toolbar__slot" /> + <BlockControls.Slot + bubblesVirtually + className="block-editor-block-toolbar__slot" + /> + <BlockFormatControls.Slot + bubblesVirtually + className="block-editor-block-toolbar__slot" + /> </> ) } <BlockSettingsMenu clientIds={ blockClientIds } /> diff --git a/packages/block-editor/src/components/block-toolbar/index.native.js b/packages/block-editor/src/components/block-toolbar/index.native.js index 13627f86d6f431..1819c5b2e5ba1a 100644 --- a/packages/block-editor/src/components/block-toolbar/index.native.js +++ b/packages/block-editor/src/components/block-toolbar/index.native.js @@ -21,12 +21,14 @@ export default function BlockToolbar() { return { blockClientIds: selectedBlockClientIds, - isValid: selectedBlockClientIds.length === 1 ? - isBlockValid( selectedBlockClientIds[ 0 ] ) : - null, - mode: selectedBlockClientIds.length === 1 ? - getBlockMode( selectedBlockClientIds[ 0 ] ) : - null, + isValid: + selectedBlockClientIds.length === 1 + ? isBlockValid( selectedBlockClientIds[ 0 ] ) + : null, + mode: + selectedBlockClientIds.length === 1 + ? getBlockMode( selectedBlockClientIds[ 0 ] ) + : null, }; }, [] ); diff --git a/packages/block-editor/src/components/block-types-list/index.js b/packages/block-editor/src/components/block-types-list/index.js index 7a743a7694d0ab..b806457e71a406 100644 --- a/packages/block-editor/src/components/block-types-list/index.js +++ b/packages/block-editor/src/components/block-types-list/index.js @@ -8,34 +8,45 @@ import { getBlockMenuDefaultClassName } from '@wordpress/blocks'; */ import InserterListItem from '../inserter-list-item'; -function BlockTypesList( { items = [], onSelect, onHover = () => {}, children } ) { +function BlockTypesList( { + items = [], + onSelect, + onHover = () => {}, + children, +} ) { const normalizedItems = items.reduce( ( result, item ) => { - const { patterns = [] } = item; - const hasDefaultPattern = patterns.some( ( { isDefault } ) => isDefault ); + const { variations = [] } = item; + const hasDefaultVariation = variations.some( + ( { isDefault } ) => isDefault + ); - // If there is no default inserter pattern provided, + // If there is no default inserter variation provided, // then default block type is displayed. - if ( ! hasDefaultPattern ) { + if ( ! hasDefaultVariation ) { result.push( item ); } - if ( patterns.length ) { - result = result.concat( patterns.map( ( pattern ) => { - return { - ...item, - id: `${ item.id }-${ pattern.name }`, - icon: pattern.icon || item.icon, - title: pattern.title || item.title, - description: pattern.description || item.description, - // If `example` is explicitly undefined for the pattern, the preview will not be shown. - example: pattern.hasOwnProperty( 'example' ) ? pattern.example : item.example, - initialAttributes: { - ...item.initialAttributes, - ...pattern.attributes, - }, - innerBlocks: pattern.innerBlocks, - }; - } ) ); + if ( variations.length ) { + result = result.concat( + variations.map( ( variation ) => { + return { + ...item, + id: `${ item.id }-${ variation.name }`, + icon: variation.icon || item.icon, + title: variation.title || item.title, + description: variation.description || item.description, + // If `example` is explicitly undefined for the variation, the preview will not be shown. + example: variation.hasOwnProperty( 'example' ) + ? variation.example + : item.example, + initialAttributes: { + ...item.initialAttributes, + ...variation.attributes, + }, + innerBlocks: variation.innerBlocks, + }; + } ) + ); } return result; diff --git a/packages/block-editor/src/components/block-variation-picker/index.js b/packages/block-editor/src/components/block-variation-picker/index.js new file mode 100644 index 00000000000000..6bf2646f83349b --- /dev/null +++ b/packages/block-editor/src/components/block-variation-picker/index.js @@ -0,0 +1,65 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Button, Placeholder } from '@wordpress/components'; + +function BlockVariationPicker( { + icon = 'layout', + label = __( 'Choose variation' ), + instructions = __( 'Select a variation to start with.' ), + variations, + onSelect, + allowSkip, +} ) { + const classes = classnames( 'block-editor-block-variation-picker', { + 'has-many-variations': variations.length > 4, + } ); + + return ( + <Placeholder + icon={ icon } + label={ label } + instructions={ instructions } + className={ classes } + > + { /* + * Disable reason: The `list` ARIA role is redundant but + * Safari+VoiceOver won't announce the list otherwise. + */ + /* eslint-disable jsx-a11y/no-redundant-roles */ } + <ul + className="block-editor-block-variation-picker__variations" + role="list" + > + { variations.map( ( variation ) => ( + <li key={ variation.name }> + <Button + isSecondary + icon={ variation.icon } + iconSize={ 48 } + onClick={ () => onSelect( variation ) } + className="block-editor-block-variation-picker__variation" + label={ variation.title } + /> + </li> + ) ) } + </ul> + { /* eslint-enable jsx-a11y/no-redundant-roles */ } + { allowSkip && ( + <div className="block-editor-block-variation-picker__skip"> + <Button isLink onClick={ () => onSelect() }> + { __( 'Skip' ) } + </Button> + </div> + ) } + </Placeholder> + ); +} + +export default BlockVariationPicker; diff --git a/packages/block-editor/src/components/block-pattern-picker/style.scss b/packages/block-editor/src/components/block-variation-picker/style.scss similarity index 81% rename from packages/block-editor/src/components/block-pattern-picker/style.scss rename to packages/block-editor/src/components/block-variation-picker/style.scss index feaca246095fb3..63460d6fcfe981 100644 --- a/packages/block-editor/src/components/block-pattern-picker/style.scss +++ b/packages/block-editor/src/components/block-variation-picker/style.scss @@ -1,4 +1,4 @@ -.block-editor-block-pattern-picker { +.block-editor-block-variation-picker { .components-placeholder__instructions { // Defer to vertical margins applied by template picker options. margin-bottom: 0; @@ -10,14 +10,14 @@ flex-direction: column; } - &.has-many-patterns .components-placeholder__fieldset { + &.has-many-variations .components-placeholder__fieldset { // Allow options to occupy a greater amount of the available space if // many options exist. max-width: 90%; } } -.block-editor-block-pattern-picker__patterns.block-editor-block-pattern-picker__patterns { +.block-editor-block-variation-picker__variations.block-editor-block-variation-picker__variations { display: flex; justify-content: flex-start; flex-direction: row; @@ -34,12 +34,12 @@ max-width: 100px; } - .block-editor-block-pattern-picker__pattern { + .block-editor-block-variation-picker__variation { padding: $grid-size; } } -.block-editor-block-pattern-picker__pattern { +.block-editor-block-variation-picker__variation { width: 100%; &.components-button.has-icon { diff --git a/packages/block-editor/src/components/block-vertical-alignment-toolbar/icons.js b/packages/block-editor/src/components/block-vertical-alignment-toolbar/icons.js index 4642eddb367c71..1404e63a332050 100644 --- a/packages/block-editor/src/components/block-vertical-alignment-toolbar/icons.js +++ b/packages/block-editor/src/components/block-vertical-alignment-toolbar/icons.js @@ -4,22 +4,36 @@ import { Path, SVG } from '@wordpress/components'; export const alignBottom = ( - <SVG xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"> + <SVG + xmlns="http://www.w3.org/2000/svg" + width="20" + height="20" + viewBox="0 0 24 24" + > <Path fill="none" d="M0 0h24v24H0V0z" /> <Path d="M16 13h-3V3h-2v10H8l4 4 4-4zM4 19v2h16v-2H4z" /> </SVG> ); export const alignCenter = ( - <SVG xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"> + <SVG + xmlns="http://www.w3.org/2000/svg" + width="20" + height="20" + viewBox="0 0 24 24" + > <Path fill="none" d="M0 0h24v24H0V0z" /> - <Path d="M8 19h3v4h2v-4h3l-4-4-4 4zm8-14h-3V1h-2v4H8l4 4 4-4zM4 11v2h16v-2H4z" - /> + <Path d="M8 19h3v4h2v-4h3l-4-4-4 4zm8-14h-3V1h-2v4H8l4 4 4-4zM4 11v2h16v-2H4z" /> </SVG> ); export const alignTop = ( - <SVG xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"> + <SVG + xmlns="http://www.w3.org/2000/svg" + width="20" + height="20" + viewBox="0 0 24 24" + > <Path fill="none" d="M0 0h24v24H0V0z" /> <Path d="M8 11h3v10h2V11h3l-4-4-4 4zM4 3v2h16V3H4z" /> </SVG> diff --git a/packages/block-editor/src/components/block-vertical-alignment-toolbar/index.js b/packages/block-editor/src/components/block-vertical-alignment-toolbar/index.js index 917353c5a50dd7..ee48eae9ee5ede 100644 --- a/packages/block-editor/src/components/block-vertical-alignment-toolbar/index.js +++ b/packages/block-editor/src/components/block-vertical-alignment-toolbar/index.js @@ -16,40 +16,57 @@ const BLOCK_ALIGNMENTS_CONTROLS = { }, center: { icon: alignCenter, - title: _x( 'Vertically Align Middle', 'Block vertical alignment setting' ), + title: _x( + 'Vertically Align Middle', + 'Block vertical alignment setting' + ), }, bottom: { icon: alignBottom, - title: _x( 'Vertically Align Bottom', 'Block vertical alignment setting' ), + title: _x( + 'Vertically Align Bottom', + 'Block vertical alignment setting' + ), }, }; const DEFAULT_CONTROLS = [ 'top', 'center', 'bottom' ]; const DEFAULT_CONTROL = 'top'; -export function BlockVerticalAlignmentToolbar( { value, onChange, controls = DEFAULT_CONTROLS, isCollapsed = true } ) { +export function BlockVerticalAlignmentToolbar( { + value, + onChange, + controls = DEFAULT_CONTROLS, + isCollapsed = true, +} ) { function applyOrUnset( align ) { return () => onChange( value === align ? undefined : align ); } const activeAlignment = BLOCK_ALIGNMENTS_CONTROLS[ value ]; - const defaultAlignmentControl = BLOCK_ALIGNMENTS_CONTROLS[ DEFAULT_CONTROL ]; + const defaultAlignmentControl = + BLOCK_ALIGNMENTS_CONTROLS[ DEFAULT_CONTROL ]; return ( <Toolbar isCollapsed={ isCollapsed } - icon={ activeAlignment ? activeAlignment.icon : defaultAlignmentControl.icon } - label={ _x( 'Change vertical alignment', 'Block vertical alignment setting label' ) } - controls={ - controls.map( ( control ) => { - return { - ...BLOCK_ALIGNMENTS_CONTROLS[ control ], - isActive: value === control, - role: isCollapsed ? 'menuitemradio' : undefined, - onClick: applyOrUnset( control ), - }; - } ) + icon={ + activeAlignment + ? activeAlignment.icon + : defaultAlignmentControl.icon } + label={ _x( + 'Change vertical alignment', + 'Block vertical alignment setting label' + ) } + controls={ controls.map( ( control ) => { + return { + ...BLOCK_ALIGNMENTS_CONTROLS[ control ], + isActive: value === control, + role: isCollapsed ? 'menuitemradio' : undefined, + onClick: applyOrUnset( control ), + }; + } ) } /> ); } diff --git a/packages/block-editor/src/components/block-vertical-alignment-toolbar/test/index.js b/packages/block-editor/src/components/block-vertical-alignment-toolbar/test/index.js index 6840e35ba6331c..60a84fbe872c6e 100644 --- a/packages/block-editor/src/components/block-vertical-alignment-toolbar/test/index.js +++ b/packages/block-editor/src/components/block-vertical-alignment-toolbar/test/index.js @@ -12,7 +12,12 @@ describe( 'BlockVerticalAlignmentToolbar', () => { const alignment = 'top'; const onChange = jest.fn(); - const wrapper = shallow( <BlockVerticalAlignmentToolbar value={ alignment } onChange={ onChange } /> ); + const wrapper = shallow( + <BlockVerticalAlignmentToolbar + value={ alignment } + onChange={ onChange } + /> + ); const controls = wrapper.props().controls; @@ -25,7 +30,9 @@ describe( 'BlockVerticalAlignmentToolbar', () => { } ); it( 'should call onChange with undefined, when the control is already active', () => { - const activeControl = controls.find( ( { title } ) => title.toLowerCase().includes( alignment ) ); + const activeControl = controls.find( ( { title } ) => + title.toLowerCase().includes( alignment ) + ); activeControl.onClick(); expect( activeControl.isActive ).toBe( true ); @@ -35,7 +42,9 @@ describe( 'BlockVerticalAlignmentToolbar', () => { it( 'should call onChange with alignment value when the control is inactive', () => { // note "middle" alias for "center" - const inactiveCenterControl = controls.find( ( { title } ) => title.toLowerCase().includes( 'middle' ) ); + const inactiveCenterControl = controls.find( ( { title } ) => + title.toLowerCase().includes( 'middle' ) + ); inactiveCenterControl.onClick(); diff --git a/packages/block-editor/src/components/button-block-appender/index.js b/packages/block-editor/src/components/button-block-appender/index.js index abff6aea2d7d46..3991b5bb530f35 100644 --- a/packages/block-editor/src/components/button-block-appender/index.js +++ b/packages/block-editor/src/components/button-block-appender/index.js @@ -15,31 +15,56 @@ import { Icon, plusCircle } from '@wordpress/icons'; */ import Inserter from '../inserter'; -function ButtonBlockAppender( { rootClientId, className, __experimentalSelectBlockOnInsert: selectBlockOnInsert } ) { +function ButtonBlockAppender( { + rootClientId, + className, + __experimentalSelectBlockOnInsert: selectBlockOnInsert, +} ) { return ( <Inserter rootClientId={ rootClientId } __experimentalSelectBlockOnInsert={ selectBlockOnInsert } - renderToggle={ ( { onToggle, disabled, isOpen, blockTitle, hasSingleBlockType } ) => { + renderToggle={ ( { + onToggle, + disabled, + isOpen, + blockTitle, + hasSingleBlockType, + } ) => { let label; if ( hasSingleBlockType ) { // translators: %s: the name of the block when there is only one - label = sprintf( _x( 'Add %s', 'directly add the only allowed block' ), blockTitle ); + label = sprintf( + _x( 'Add %s', 'directly add the only allowed block' ), + blockTitle + ); } else { - label = _x( 'Add block', 'Generic label for block inserter button' ); + label = _x( + 'Add block', + 'Generic label for block inserter button' + ); } const isToggleButton = ! hasSingleBlockType; return ( <Tooltip text={ label }> <Button - className={ classnames( className, 'block-editor-button-block-appender' ) } + className={ classnames( + className, + 'block-editor-button-block-appender' + ) } onClick={ onToggle } - aria-haspopup={ isToggleButton ? 'true' : undefined } - aria-expanded={ isToggleButton ? isOpen : undefined } + aria-haspopup={ + isToggleButton ? 'true' : undefined + } + aria-expanded={ + isToggleButton ? isOpen : undefined + } disabled={ disabled } label={ label } > - <span className="screen-reader-text">{ label }</span> + <span className="screen-reader-text"> + { label } + </span> <Icon icon={ plusCircle } /> </Button> </Tooltip> diff --git a/packages/block-editor/src/components/button-block-appender/index.native.js b/packages/block-editor/src/components/button-block-appender/index.native.js index 2a3f6ef2ea9fb2..070993d3d9848f 100644 --- a/packages/block-editor/src/components/button-block-appender/index.native.js +++ b/packages/block-editor/src/components/button-block-appender/index.native.js @@ -15,9 +15,22 @@ import { Button, Dashicon } from '@wordpress/components'; import Inserter from '../inserter'; import styles from './styles.scss'; -function ButtonBlockAppender( { rootClientId, getStylesFromColorScheme, showSeparator } ) { - const appenderStyle = { ...styles.appender, ...getStylesFromColorScheme( styles.appenderLight, styles.appenderDark ) }; - const addBlockButtonStyle = getStylesFromColorScheme( styles.addBlockButton, styles.addBlockButtonDark ); +function ButtonBlockAppender( { + rootClientId, + getStylesFromColorScheme, + showSeparator, +} ) { + const appenderStyle = { + ...styles.appender, + ...getStylesFromColorScheme( + styles.appenderLight, + styles.appenderDark + ), + }; + const addBlockButtonStyle = getStylesFromColorScheme( + styles.addBlockButton, + styles.addBlockButtonDark + ); return ( <> diff --git a/packages/block-editor/src/components/caption/index.native.js b/packages/block-editor/src/components/caption/index.native.js index eb782c46e519ca..4669f1b8a4c3e8 100644 --- a/packages/block-editor/src/components/caption/index.native.js +++ b/packages/block-editor/src/components/caption/index.native.js @@ -24,7 +24,11 @@ const Caption = ( { value, } ) => ( <View - accessibilityLabel={ accessibilityLabelCreator ? accessibilityLabelCreator( value ) : undefined } + accessibilityLabel={ + accessibilityLabelCreator + ? accessibilityLabelCreator( value ) + : undefined + } accessibilityRole="button" accessible={ accessible } style={ { flex: 1, display: shouldDisplay ? 'flex' : 'none' } } diff --git a/packages/block-editor/src/components/color-palette/with-color-context.js b/packages/block-editor/src/components/color-palette/with-color-context.js index 1c32141f2a3f13..1198a304d67359 100644 --- a/packages/block-editor/src/components/color-palette/with-color-context.js +++ b/packages/block-editor/src/components/color-palette/with-color-context.js @@ -1,4 +1,3 @@ - /** * External dependencies */ @@ -11,20 +10,20 @@ import { createHigherOrderComponent } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; export default createHigherOrderComponent( - withSelect( - ( select, ownProps ) => { - const settings = select( 'core/block-editor' ).getSettings(); - const colors = ownProps.colors === undefined ? - settings.colors : ownProps.colors; + withSelect( ( select, ownProps ) => { + const settings = select( 'core/block-editor' ).getSettings(); + const colors = + ownProps.colors === undefined ? settings.colors : ownProps.colors; - const disableCustomColors = ownProps.disableCustomColors === undefined ? - settings.disableCustomColors : ownProps.disableCustomColors; - return { - colors, - disableCustomColors, - hasColorsToChoose: ! isEmpty( colors ) || ! disableCustomColors, - }; - } - ), + const disableCustomColors = + ownProps.disableCustomColors === undefined + ? settings.disableCustomColors + : ownProps.disableCustomColors; + return { + colors, + disableCustomColors, + hasColorsToChoose: ! isEmpty( colors ) || ! disableCustomColors, + }; + } ), 'withColorContext' ); diff --git a/packages/block-editor/src/components/colors-gradients/control.js b/packages/block-editor/src/components/colors-gradients/control.js index 14fce71c662ea6..e91250ca77f2d0 100644 --- a/packages/block-editor/src/components/colors-gradients/control.js +++ b/packages/block-editor/src/components/colors-gradients/control.js @@ -8,7 +8,14 @@ import { every, isEmpty, pick } from 'lodash'; * WordPress dependencies */ import { useState } from '@wordpress/element'; -import { BaseControl, Button, ButtonGroup, ColorIndicator, ColorPalette, __experimentalGradientPicker as GradientPicker } from '@wordpress/components'; +import { + BaseControl, + Button, + ButtonGroup, + ColorIndicator, + ColorPalette, + __experimentalGradientPicker as GradientPicker, +} from '@wordpress/components'; import { sprintf, __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; @@ -24,9 +31,21 @@ const colorIndicatorAriaLabel = __( '(Color: %s)' ); // translators: first %s: the gradient name or value (e.g. red to green or linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%) const gradientIndicatorAriaLabel = __( '(Gradient: %s)' ); -const colorsAndGradientKeys = [ 'colors', 'disableCustomColors', 'gradients', 'disableCustomGradients' ]; +const colorsAndGradientKeys = [ + 'colors', + 'disableCustomColors', + 'gradients', + 'disableCustomGradients', +]; -function VisualLabel( { colors, gradients, label, currentTab, colorValue, gradientValue } ) { +function VisualLabel( { + colors, + gradients, + label, + currentTab, + colorValue, + gradientValue, +} ) { let value, ariaLabel; if ( currentTab === 'color' ) { if ( colorValue ) { @@ -37,19 +56,22 @@ function VisualLabel( { colors, gradients, label, currentTab, colorValue, gradie } } else if ( currentTab === 'gradient' && gradientValue ) { value = gradientValue; - const gradientObject = __experimentalGetGradientObjectByGradientValue( gradients, value ); + const gradientObject = __experimentalGetGradientObjectByGradientValue( + gradients, + value + ); const gradientName = gradientObject && gradientObject.name; - ariaLabel = sprintf( gradientIndicatorAriaLabel, gradientName || value ); + ariaLabel = sprintf( + gradientIndicatorAriaLabel, + gradientName || value + ); } return ( <> { label } { !! value && ( - <ColorIndicator - colorValue={ value } - aria-label={ ariaLabel } - /> + <ColorIndicator colorValue={ value } aria-label={ ariaLabel } /> ) } </> ); @@ -67,16 +89,24 @@ function ColorGradientControlInner( { colorValue, gradientValue, } ) { - const canChooseAColor = onColorChange && ( ! isEmpty( colors ) || ! disableCustomColors ); - const canChooseAGradient = onGradientChange && ( ! isEmpty( gradients ) || ! disableCustomGradients ); - const [ currentTab, setCurrentTab ] = useState( gradientValue ? 'gradient' : !! canChooseAColor && 'color' ); + const canChooseAColor = + onColorChange && ( ! isEmpty( colors ) || ! disableCustomColors ); + const canChooseAGradient = + onGradientChange && + ( ! isEmpty( gradients ) || ! disableCustomGradients ); + const [ currentTab, setCurrentTab ] = useState( + gradientValue ? 'gradient' : !! canChooseAColor && 'color' + ); if ( ! canChooseAColor && ! canChooseAGradient ) { return null; } return ( <BaseControl - className={ classnames( 'block-editor-color-gradient-control', className ) } + className={ classnames( + 'block-editor-color-gradient-control', + className + ) } > <fieldset> <legend> @@ -95,7 +125,7 @@ function ColorGradientControlInner( { isLarge isPrimary={ currentTab === 'color' } isSecondary={ currentTab !== 'color' } - onClick={ () => ( setCurrentTab( 'color' ) ) } + onClick={ () => setCurrentTab( 'color' ) } > { __( 'Solid Color' ) } </Button> @@ -103,7 +133,7 @@ function ColorGradientControlInner( { isLarge isPrimary={ currentTab === 'gradient' } isSecondary={ currentTab !== 'gradient' } - onClick={ () => ( setCurrentTab( 'gradient' ) ) } + onClick={ () => setCurrentTab( 'gradient' ) } > { __( 'Gradient' ) } </Button> @@ -112,27 +142,29 @@ function ColorGradientControlInner( { { ( currentTab === 'color' || ! canChooseAGradient ) && ( <ColorPalette value={ colorValue } - onChange={ canChooseAGradient ? - ( newColor ) => { - onColorChange( newColor ); - onGradientChange(); - } : - onColorChange + onChange={ + canChooseAGradient + ? ( newColor ) => { + onColorChange( newColor ); + onGradientChange(); + } + : onColorChange } - { ... { colors, disableCustomColors } } + { ...{ colors, disableCustomColors } } /> ) } { ( currentTab === 'gradient' || ! canChooseAColor ) && ( <GradientPicker value={ gradientValue } - onChange={ canChooseAColor ? - ( newGradient ) => { - onGradientChange( newGradient ); - onColorChange(); - } : - onGradientChange + onChange={ + canChooseAColor + ? ( newGradient ) => { + onGradientChange( newGradient ); + onColorChange(); + } + : onGradientChange } - { ... { gradients, disableCustomGradients } } + { ...{ gradients, disableCustomGradients } } /> ) } </fieldset> @@ -141,17 +173,21 @@ function ColorGradientControlInner( { } function ColorGradientControlSelect( props ) { - const colorGradientSettings = useSelect( - ( select ) => { - const settings = select( 'core/block-editor' ).getSettings(); - return pick( settings, colorsAndGradientKeys ); - } + const colorGradientSettings = useSelect( ( select ) => { + const settings = select( 'core/block-editor' ).getSettings(); + return pick( settings, colorsAndGradientKeys ); + } ); + return ( + <ColorGradientControlInner + { ...{ ...colorGradientSettings, ...props } } + /> ); - return <ColorGradientControlInner { ...{ ...colorGradientSettings, ...props } } />; } function ColorGradientControl( props ) { - if ( every( colorsAndGradientKeys, ( key ) => ( props.hasOwnProperty( key ) ) ) ) { + if ( + every( colorsAndGradientKeys, ( key ) => props.hasOwnProperty( key ) ) + ) { return <ColorGradientControlInner { ...props } />; } return <ColorGradientControlSelect { ...props } />; diff --git a/packages/block-editor/src/components/colors-gradients/panel-color-gradient-settings.js b/packages/block-editor/src/components/colors-gradients/panel-color-gradient-settings.js index d8354bd8e028b8..6c36cd10e7a45a 100644 --- a/packages/block-editor/src/components/colors-gradients/panel-color-gradient-settings.js +++ b/packages/block-editor/src/components/colors-gradients/panel-color-gradient-settings.js @@ -24,50 +24,60 @@ const colorIndicatorAriaLabel = __( '(%s: color %s)' ); // translators: first %s: The type of color or gradient (e.g. background, overlay...), second %s: the color name or value (e.g. red or #ff0000) const gradientIndicatorAriaLabel = __( '(%s: gradient %s)' ); -const colorsAndGradientKeys = [ 'colors', 'disableCustomColors', 'gradients', 'disableCustomGradients' ]; +const colorsAndGradientKeys = [ + 'colors', + 'disableCustomColors', + 'gradients', + 'disableCustomGradients', +]; const Indicators = ( { colors, gradients, settings } ) => { - return settings.map( ( { - colorValue, - gradientValue, - label, - colors: availableColors, - gradients: availableGradients, - }, index ) => { - if ( ! colorValue && ! gradientValue ) { - return null; - } - let ariaLabel; - if ( colorValue ) { - const colorObject = getColorObjectByColorValue( - availableColors || colors, - colorValue - ); - ariaLabel = sprintf( - colorIndicatorAriaLabel, - label.toLowerCase(), - ( colorObject && colorObject.name ) || colorValue - ); - } else { - const gradientObject = __experimentalGetGradientObjectByGradientValue( - availableGradients || gradients, - colorValue - ); - ariaLabel = sprintf( - gradientIndicatorAriaLabel, - label.toLowerCase(), - ( gradientObject && gradientObject.name ) || gradientValue + return settings.map( + ( + { + colorValue, + gradientValue, + label, + colors: availableColors, + gradients: availableGradients, + }, + index + ) => { + if ( ! colorValue && ! gradientValue ) { + return null; + } + let ariaLabel; + if ( colorValue ) { + const colorObject = getColorObjectByColorValue( + availableColors || colors, + colorValue + ); + ariaLabel = sprintf( + colorIndicatorAriaLabel, + label.toLowerCase(), + ( colorObject && colorObject.name ) || colorValue + ); + } else { + const gradientObject = __experimentalGetGradientObjectByGradientValue( + availableGradients || gradients, + colorValue + ); + ariaLabel = sprintf( + gradientIndicatorAriaLabel, + label.toLowerCase(), + ( gradientObject && gradientObject.name ) || gradientValue + ); + } + + return ( + <ColorIndicator + key={ index } + colorValue={ colorValue || gradientValue } + aria-label={ ariaLabel } + /> ); } - - return ( - <ColorIndicator - key={ index } - colorValue={ colorValue || gradientValue } - aria-label={ ariaLabel } - /> - ); - } ); + ); }; export const PanelColorGradientSettingsInner = ( { @@ -88,12 +98,13 @@ export const PanelColorGradientSettingsInner = ( { disableCustomGradients && every( settings, - ( setting ) => ( + ( setting ) => isEmpty( setting.colors ) && isEmpty( setting.gradients ) && - ( setting.disableCustomColors === undefined || setting.disableCustomColors ) && - ( setting.disableCustomGradients === undefined || setting.disableCustomGradients ) - ) + ( setting.disableCustomColors === undefined || + setting.disableCustomColors ) && + ( setting.disableCustomGradients === undefined || + setting.disableCustomGradients ) ) ) { return null; @@ -102,12 +113,19 @@ export const PanelColorGradientSettingsInner = ( { const titleElement = ( <span className="block-editor-panel-color-gradient-settings__panel-title"> { title } - <Indicators colors={ colors } gradients={ gradients } settings={ settings } /> + <Indicators + colors={ colors } + gradients={ gradients } + settings={ settings } + /> </span> ); return ( <PanelBody - className={ classnames( 'block-editor-panel-color-gradient-settings', className ) } + className={ classnames( + 'block-editor-panel-color-gradient-settings', + className + ) } title={ titleElement } { ...props } > @@ -129,17 +147,21 @@ export const PanelColorGradientSettingsInner = ( { }; const PanelColorGradientSettingsSelect = ( props ) => { - const colorGradientSettings = useSelect( - ( select ) => { - const settings = select( 'core/block-editor' ).getSettings(); - return pick( settings, colorsAndGradientKeys ); - } + const colorGradientSettings = useSelect( ( select ) => { + const settings = select( 'core/block-editor' ).getSettings(); + return pick( settings, colorsAndGradientKeys ); + } ); + return ( + <PanelColorGradientSettingsInner + { ...{ ...colorGradientSettings, ...props } } + /> ); - return <PanelColorGradientSettingsInner { ...{ ...colorGradientSettings, ...props } } />; }; const PanelColorGradientSettings = ( props ) => { - if ( every( colorsAndGradientKeys, ( key ) => ( props.hasOwnProperty( key ) ) ) ) { + if ( + every( colorsAndGradientKeys, ( key ) => props.hasOwnProperty( key ) ) + ) { return <PanelColorGradientSettingsInner { ...props } />; } return <PanelColorGradientSettingsSelect { ...props } />; diff --git a/packages/block-editor/src/components/colors-gradients/test/control.js b/packages/block-editor/src/components/colors-gradients/test/control.js index cedd530a217bc8..b3396bb0ce2e7f 100644 --- a/packages/block-editor/src/components/colors-gradients/test/control.js +++ b/packages/block-editor/src/components/colors-gradients/test/control.js @@ -17,7 +17,9 @@ const getButtonWithTestPredicate = ( text ) => ( element ) => { ); }; -const getButtonWithAriaLabelStartPredicate = ( ariaLabelStart ) => ( element ) => { +const getButtonWithAriaLabelStartPredicate = ( ariaLabelStart ) => ( + element +) => { return ( element.type === 'button' && element.props[ 'aria-label' ] && @@ -37,16 +39,24 @@ describe( 'ColorPaletteControl', () => { <ColorGradientControl label="Test Color Gradient" colorValue="#f00" - colors={ [ { color: '#f00', name: 'red' }, { color: '#0f0', name: 'green' } ] } - gradients={ [ { - gradient: 'linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%', - name: 'Vivid cyan blue to vivid purple', - slug: 'vivid-cyan-blue-to-vivid-purple', - }, { - gradient: 'linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%)', - name: 'Light green cyan to vivid green cyan', - slug: 'light-green-cyan-to-vivid-green-cyan', - } ] } + colors={ [ + { color: '#f00', name: 'red' }, + { color: '#0f0', name: 'green' }, + ] } + gradients={ [ + { + gradient: + 'linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%', + name: 'Vivid cyan blue to vivid purple', + slug: 'vivid-cyan-blue-to-vivid-purple', + }, + { + gradient: + 'linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%)', + name: 'Light green cyan to vivid green cyan', + slug: 'light-green-cyan-to-vivid-green-cyan', + }, + ] } disableCustomColors={ false } disableCustomGradients={ false } onColorChange={ noop } @@ -56,18 +66,28 @@ describe( 'ColorPaletteControl', () => { } ); // Is showing the two tab buttons. - expect( wrapper.root.findAll( colorTabButtonPredicate ) ).toHaveLength( 1 ); - expect( wrapper.root.findAll( gradientTabButtonPredicate ) ).toHaveLength( 1 ); + expect( wrapper.root.findAll( colorTabButtonPredicate ) ).toHaveLength( + 1 + ); + expect( + wrapper.root.findAll( gradientTabButtonPredicate ) + ).toHaveLength( 1 ); // Is showing the two predefined Colors. - expect( wrapper.root.findAll( - ( element ) => ( element.type === 'button' && element.props && element.props[ 'aria-label' ] && element.props[ 'aria-label' ].startsWith( 'Color:' ) ) - ) ).toHaveLength( 2 ); + expect( + wrapper.root.findAll( + ( element ) => + element.type === 'button' && + element.props && + element.props[ 'aria-label' ] && + element.props[ 'aria-label' ].startsWith( 'Color:' ) + ) + ).toHaveLength( 2 ); // Is showing the custom color picker. - expect( wrapper.root.findAll( - getButtonWithTestPredicate( 'Custom Color' ) - ) ).toHaveLength( 1 ); + expect( + wrapper.root.findAll( getButtonWithTestPredicate( 'Custom Color' ) ) + ).toHaveLength( 1 ); } ); it( 'renders the color picker and does not render tabs if it is only possible to select a color', async () => { @@ -78,7 +98,10 @@ describe( 'ColorPaletteControl', () => { <ColorGradientControl label="Test Color Gradient" colorValue="#f00" - colors={ [ { color: '#f00', name: 'red' }, { color: '#0f0', name: 'green' } ] } + colors={ [ + { color: '#f00', name: 'red' }, + { color: '#0f0', name: 'green' }, + ] } gradients={ [] } disableCustomColors={ false } disableCustomGradients={ true } @@ -89,18 +112,24 @@ describe( 'ColorPaletteControl', () => { } ); // Is not showing the two tab buttons. - expect( wrapper.root.findAll( colorTabButtonPredicate ) ).toHaveLength( 0 ); - expect( wrapper.root.findAll( gradientTabButtonPredicate ) ).toHaveLength( 0 ); + expect( wrapper.root.findAll( colorTabButtonPredicate ) ).toHaveLength( + 0 + ); + expect( + wrapper.root.findAll( gradientTabButtonPredicate ) + ).toHaveLength( 0 ); // Is showing the two predefined Colors. - expect( wrapper.root.findAll( - getButtonWithAriaLabelStartPredicate( 'Color:' ) - ) ).toHaveLength( 2 ); + expect( + wrapper.root.findAll( + getButtonWithAriaLabelStartPredicate( 'Color:' ) + ) + ).toHaveLength( 2 ); // Is showing the custom color picker. - expect( wrapper.root.findAll( - getButtonWithTestPredicate( 'Custom Color' ) - ) ).toHaveLength( 1 ); + expect( + wrapper.root.findAll( getButtonWithTestPredicate( 'Custom Color' ) ) + ).toHaveLength( 1 ); } ); it( 'renders the gradient picker and does not render tabs if it is only possible to select a gradient', async () => { @@ -112,15 +141,20 @@ describe( 'ColorPaletteControl', () => { label="Test Color Gradient" colorValue="#f00" colors={ [] } - gradients={ [ { - gradient: 'linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%', - name: 'Vivid cyan blue to vivid purple', - slug: 'vivid-cyan-blue-to-vivid-purple', - }, { - gradient: 'linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%)', - name: 'Light green cyan to vivid green cyan', - slug: 'light-green-cyan-to-vivid-green-cyan', - } ] } + gradients={ [ + { + gradient: + 'linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%', + name: 'Vivid cyan blue to vivid purple', + slug: 'vivid-cyan-blue-to-vivid-purple', + }, + { + gradient: + 'linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%)', + name: 'Light green cyan to vivid green cyan', + slug: 'light-green-cyan-to-vivid-green-cyan', + }, + ] } disableCustomColors={ true } disableCustomGradients={ false } onColorChange={ noop } @@ -130,17 +164,30 @@ describe( 'ColorPaletteControl', () => { } ); // Is not showing the two tab buttons. - expect( wrapper.root.findAll( colorTabButtonPredicate ) ).toHaveLength( 0 ); - expect( wrapper.root.findAll( gradientTabButtonPredicate ) ).toHaveLength( 0 ); + expect( wrapper.root.findAll( colorTabButtonPredicate ) ).toHaveLength( + 0 + ); + expect( + wrapper.root.findAll( gradientTabButtonPredicate ) + ).toHaveLength( 0 ); // Is showing the two predefined Gradients. - expect( wrapper.root.findAll( - getButtonWithAriaLabelStartPredicate( 'Gradient:' ) - ) ).toHaveLength( 2 ); + expect( + wrapper.root.findAll( + getButtonWithAriaLabelStartPredicate( 'Gradient:' ) + ) + ).toHaveLength( 2 ); // Is showing the custom gradient picker. - expect( wrapper.root.findAll( - ( element ) => ( element.props && element.props.className && element.props.className.includes( 'components-custom-gradient-picker' ) ) - ).length ).toBeGreaterThanOrEqual( 1 ); + expect( + wrapper.root.findAll( + ( element ) => + element.props && + element.props.className && + element.props.className.includes( + 'components-custom-gradient-picker' + ) + ).length + ).toBeGreaterThanOrEqual( 1 ); } ); } ); diff --git a/packages/block-editor/src/components/colors/index.js b/packages/block-editor/src/components/colors/index.js index cadb34a8c9aa37..91cbb9beca7261 100644 --- a/packages/block-editor/src/components/colors/index.js +++ b/packages/block-editor/src/components/colors/index.js @@ -3,8 +3,5 @@ export { getColorObjectByAttributeValues, getColorObjectByColorValue, } from './utils'; -export { - createCustomColorsHOC, - default as withColors, -} from './with-colors'; +export { createCustomColorsHOC, default as withColors } from './with-colors'; export { default as __experimentalUseColors } from './use-colors'; diff --git a/packages/block-editor/src/components/colors/test/utils.js b/packages/block-editor/src/components/colors/test/utils.js index 8eb370394aae83..1ed18cef831c8d 100644 --- a/packages/block-editor/src/components/colors/test/utils.js +++ b/packages/block-editor/src/components/colors/test/utils.js @@ -17,7 +17,15 @@ describe( 'color utils', () => { ]; const customColor = '#ffffff'; - expect( getColorObjectByAttributeValues( colors, undefined, customColor ) ).toEqual( { color: customColor } ); + expect( + getColorObjectByAttributeValues( + colors, + undefined, + customColor + ) + ).toEqual( { + color: customColor, + } ); } ); it( 'should return the custom color object when definedColor was not found', () => { @@ -29,7 +37,15 @@ describe( 'color utils', () => { const definedColor = 'purple'; const customColor = '#ffffff'; - expect( getColorObjectByAttributeValues( colors, definedColor, customColor ) ).toEqual( { color: customColor } ); + expect( + getColorObjectByAttributeValues( + colors, + definedColor, + customColor + ) + ).toEqual( { + color: customColor, + } ); } ); it( 'should return the found color object', () => { @@ -41,7 +57,15 @@ describe( 'color utils', () => { const definedColor = 'blue'; const customColor = '#ffffff'; - expect( getColorObjectByAttributeValues( colors, definedColor, customColor ) ).toEqual( { slug: 'blue' } ); + expect( + getColorObjectByAttributeValues( + colors, + definedColor, + customColor + ) + ).toEqual( { + slug: 'blue', + } ); } ); } ); @@ -53,7 +77,9 @@ describe( 'color utils', () => { { slug: 'blue', color: '#0000ff' }, ]; - expect( getColorObjectByColorValue( colors, '#ffffff' ) ).toBeUndefined(); + expect( + getColorObjectByColorValue( colors, '#ffffff' ) + ).toBeUndefined(); } ); it( 'should return a color object for the given color value', () => { @@ -63,21 +89,30 @@ describe( 'color utils', () => { { slug: 'blue', color: '#0000ff' }, ]; - expect( getColorObjectByColorValue( colors, '#00ff00' ) ).toEqual( { slug: 'green', color: '#00ff00' } ); + expect( getColorObjectByColorValue( colors, '#00ff00' ) ).toEqual( { + slug: 'green', + color: '#00ff00', + } ); } ); } ); describe( 'getColorClassName', () => { it( 'should return undefined if colorContextName is missing', () => { - expect( getColorClassName( undefined, 'Light Purple' ) ).toBeUndefined(); + expect( + getColorClassName( undefined, 'Light Purple' ) + ).toBeUndefined(); } ); it( 'should return undefined if colorSlug is missing', () => { - expect( getColorClassName( 'background', undefined ) ).toBeUndefined(); + expect( + getColorClassName( 'background', undefined ) + ).toBeUndefined(); } ); it( 'should return a class name with the color slug in kebab case', () => { - expect( getColorClassName( 'background', 'Light Purple' ) ).toBe( 'has-light-purple-background' ); + expect( getColorClassName( 'background', 'Light Purple' ) ).toBe( + 'has-light-purple-background' + ); } ); } ); } ); diff --git a/packages/block-editor/src/components/colors/test/with-colors.js b/packages/block-editor/src/components/colors/test/with-colors.js index 07c19ce0c11e7a..acbc8659953ce0 100644 --- a/packages/block-editor/src/components/colors/test/with-colors.js +++ b/packages/block-editor/src/components/colors/test/with-colors.js @@ -10,7 +10,9 @@ import { createCustomColorsHOC } from '../with-colors'; describe( 'createCustomColorsHOC', () => { it( 'provides the the wrapped component with color values and setter functions as props', () => { - const withCustomColors = createCustomColorsHOC( [ { name: 'Red', slug: 'red', color: 'ff0000' } ] ); + const withCustomColors = createCustomColorsHOC( [ + { name: 'Red', slug: 'red', color: 'ff0000' }, + ] ); const EnhancedComponent = withCustomColors( 'backgroundColor' )( () => ( <div /> ) ); @@ -23,34 +25,58 @@ describe( 'createCustomColorsHOC', () => { } ); it( 'setting the color to a value in the provided custom color array updated the backgroundColor attribute', () => { - const withCustomColors = createCustomColorsHOC( [ { name: 'Red', slug: 'red', color: 'ff0000' } ] ); - const EnhancedComponent = withCustomColors( 'backgroundColor' )( ( props ) => ( - <button onClick={ () => props.setBackgroundColor( 'ff0000' ) }>Test Me</button> + const withCustomColors = createCustomColorsHOC( [ + { name: 'Red', slug: 'red', color: 'ff0000' }, + ] ); + const EnhancedComponent = withCustomColors( + 'backgroundColor' + )( ( props ) => ( + <button onClick={ () => props.setBackgroundColor( 'ff0000' ) }> + Test Me + </button> ) ); const setAttributes = jest.fn(); const wrapper = mount( - <EnhancedComponent attributes={ { backgroundColor: null } } setAttributes={ setAttributes } /> + <EnhancedComponent + attributes={ { backgroundColor: null } } + setAttributes={ setAttributes } + /> ); wrapper.find( 'button' ).simulate( 'click' ); - expect( setAttributes ).toHaveBeenCalledWith( { backgroundColor: 'red', customBackgroundColor: undefined } ); + expect( setAttributes ).toHaveBeenCalledWith( { + backgroundColor: 'red', + customBackgroundColor: undefined, + } ); } ); it( 'setting the color to a value not in the provided custom color array updates customBackgroundColor attribute', () => { - const withCustomColors = createCustomColorsHOC( [ { name: 'Red', slug: 'red', color: 'ff0000' } ] ); - const EnhancedComponent = withCustomColors( 'backgroundColor' )( ( props ) => ( - <button onClick={ () => props.setBackgroundColor( '000000' ) }>Test Me</button> + const withCustomColors = createCustomColorsHOC( [ + { name: 'Red', slug: 'red', color: 'ff0000' }, + ] ); + const EnhancedComponent = withCustomColors( + 'backgroundColor' + )( ( props ) => ( + <button onClick={ () => props.setBackgroundColor( '000000' ) }> + Test Me + </button> ) ); const setAttributes = jest.fn(); const wrapper = mount( - <EnhancedComponent attributes={ { backgroundColor: null } } setAttributes={ setAttributes } /> + <EnhancedComponent + attributes={ { backgroundColor: null } } + setAttributes={ setAttributes } + /> ); wrapper.find( 'button' ).simulate( 'click' ); - expect( setAttributes ).toHaveBeenCalledWith( { backgroundColor: undefined, customBackgroundColor: '000000' } ); + expect( setAttributes ).toHaveBeenCalledWith( { + backgroundColor: undefined, + customBackgroundColor: '000000', + } ); } ); } ); diff --git a/packages/block-editor/src/components/colors/use-colors.js b/packages/block-editor/src/components/colors/use-colors.js index e5eaf380d9af02..8ff23ec2ab8d64 100644 --- a/packages/block-editor/src/components/colors/use-colors.js +++ b/packages/block-editor/src/components/colors/use-colors.js @@ -60,51 +60,53 @@ const ColorPanel = ( { { ...colorPanelProps } > { contrastCheckers && - ( Array.isArray( contrastCheckers ) ? - contrastCheckers.map( ( { backgroundColor, textColor, ...rest } ) => { - backgroundColor = resolveContrastCheckerColor( - backgroundColor, - colorSettings, - detectedBackgroundColor - ); - textColor = resolveContrastCheckerColor( - textColor, - colorSettings, - detectedColor - ); - return ( - <ContrastChecker - key={ `${ backgroundColor }-${ textColor }` } - backgroundColor={ backgroundColor } - textColor={ textColor } - { ...rest } - /> - ); - } ) : - map( colorSettings, ( { value } ) => { - let { backgroundColor, textColor } = contrastCheckers; - backgroundColor = resolveContrastCheckerColor( - backgroundColor || value, - colorSettings, - detectedBackgroundColor - ); - textColor = resolveContrastCheckerColor( - textColor || value, - colorSettings, - detectedColor - ); - return ( - <ContrastChecker - { ...contrastCheckers } - key={ `${ backgroundColor }-${ textColor }` } - backgroundColor={ backgroundColor } - textColor={ textColor } - /> - ); - } ) ) } - { typeof panelChildren === 'function' ? - panelChildren( colorSettings ) : - panelChildren } + ( Array.isArray( contrastCheckers ) + ? contrastCheckers.map( + ( { backgroundColor, textColor, ...rest } ) => { + backgroundColor = resolveContrastCheckerColor( + backgroundColor, + colorSettings, + detectedBackgroundColor + ); + textColor = resolveContrastCheckerColor( + textColor, + colorSettings, + detectedColor + ); + return ( + <ContrastChecker + key={ `${ backgroundColor }-${ textColor }` } + backgroundColor={ backgroundColor } + textColor={ textColor } + { ...rest } + /> + ); + } + ) + : map( colorSettings, ( { value } ) => { + let { backgroundColor, textColor } = contrastCheckers; + backgroundColor = resolveContrastCheckerColor( + backgroundColor || value, + colorSettings, + detectedBackgroundColor + ); + textColor = resolveContrastCheckerColor( + textColor || value, + colorSettings, + detectedColor + ); + return ( + <ContrastChecker + { ...contrastCheckers } + key={ `${ backgroundColor }-${ textColor }` } + backgroundColor={ backgroundColor } + textColor={ textColor } + /> + ); + } ) ) } + { typeof panelChildren === 'function' + ? panelChildren( colorSettings ) + : panelChildren } </PanelColorSettings> ); const InspectorControlsColorPanel = ( props ) => ( @@ -133,11 +135,14 @@ export default function __experimentalUseColors( const { clientId } = useBlockEditContext(); const { attributes, settingsColors } = useSelect( ( select ) => { - const { getBlockAttributes, getSettings } = select( 'core/block-editor' ); + const { getBlockAttributes, getSettings } = select( + 'core/block-editor' + ); const colors = getSettings().colors; return { attributes: getBlockAttributes( clientId ), - settingsColors: ! colors || colors === true ? DEFAULT_COLORS : colors, + settingsColors: + ! colors || colors === true ? DEFAULT_COLORS : colors, }; }, [ clientId ] @@ -151,7 +156,14 @@ export default function __experimentalUseColors( const createComponent = useMemo( () => memoize( - ( name, property, className, color, colorValue, customColor ) => ( { + ( + name, + property, + className, + color, + colorValue, + customColor + ) => ( { children, className: componentClassName = '', style: componentStyle = {}, @@ -167,10 +179,18 @@ export default function __experimentalUseColors( } return cloneElement( child, { - className: classnames( componentClassName, child.props.className, { - [ `has-${ kebabCase( color ) }-${ kebabCase( property ) }` ]: color, - [ className || `has-${ kebabCase( name ) }` ]: color || customColor, - } ), + className: classnames( + componentClassName, + child.props.className, + { + [ `has-${ kebabCase( color ) }-${ kebabCase( + property + ) }` ]: color, + [ className || + `has-${ kebabCase( name ) }` ]: + color || customColor, + } + ), style: { ...colorStyle, ...componentStyle, @@ -186,14 +206,20 @@ export default function __experimentalUseColors( () => memoize( ( name, colors ) => ( newColor ) => { - const color = colors.find( ( _color ) => _color.color === newColor ); + const color = colors.find( + ( _color ) => _color.color === newColor + ); setAttributes( { - [ color ? camelCase( `custom ${ name }` ) : name ]: undefined, + [ color + ? camelCase( `custom ${ name }` ) + : name ]: undefined, } ); setAttributes( { - [ color ? name : camelCase( `custom ${ name }` ) ]: color ? - color.slug : - newColor, + [ color + ? name + : camelCase( `custom ${ name }` ) ]: color + ? color.slug + : newColor, } ); }, { @@ -212,7 +238,9 @@ export default function __experimentalUseColors( } let needsBackgroundColor = false; let needsColor = false; - for ( const { backgroundColor, textColor } of castArray( contrastCheckers ) ) { + for ( const { backgroundColor, textColor } of castArray( + contrastCheckers + ) ) { if ( ! needsBackgroundColor ) { needsBackgroundColor = backgroundColor === true; } @@ -225,7 +253,9 @@ export default function __experimentalUseColors( } if ( needsColor ) { - setDetectedColor( getComputedStyle( textColorTargetRef.current ).color ); + setDetectedColor( + getComputedStyle( textColorTargetRef.current ).color + ); } if ( needsBackgroundColor ) { @@ -280,9 +310,9 @@ export default function __experimentalUseColors( const customColor = attributes[ camelCase( `custom ${ name }` ) ]; // We memoize the non-primitives to avoid unnecessary updates // when they are used as props for other components. - const _color = customColor ? - undefined : - colors.find( ( __color ) => __color.slug === color ); + const _color = customColor + ? undefined + : colors.find( ( __color ) => __color.slug === color ); acc[ componentName ] = createComponent( name, property, @@ -292,14 +322,16 @@ export default function __experimentalUseColors( customColor ); acc[ componentName ].displayName = componentName; - acc[ componentName ].color = customColor ? - customColor : - _color && _color.color; + acc[ componentName ].color = customColor + ? customColor + : _color && _color.color; acc[ componentName ].slug = color; acc[ componentName ].setColor = createSetColor( name, colors ); colorSettings[ componentName ] = { - value: _color ? _color.color : attributes[ camelCase( `custom ${ name }` ) ], + value: _color + ? _color.color + : attributes[ camelCase( `custom ${ name }` ) ], onChange: acc[ componentName ].setColor, label: panelLabel, colors, @@ -332,5 +364,11 @@ export default function __experimentalUseColors( <InspectorControlsColorPanel { ...wrappedColorPanelProps } /> ), }; - }, [ attributes, setAttributes, detectedColor, detectedBackgroundColor, ...deps ] ); + }, [ + attributes, + setAttributes, + detectedColor, + detectedBackgroundColor, + ...deps, + ] ); } diff --git a/packages/block-editor/src/components/colors/utils.js b/packages/block-editor/src/components/colors/utils.js index 764b02acde2822..903d7033eedee9 100644 --- a/packages/block-editor/src/components/colors/utils.js +++ b/packages/block-editor/src/components/colors/utils.js @@ -16,7 +16,11 @@ import tinycolor from 'tinycolor2'; * the color object exactly as set by the theme or editor defaults is returned. * Otherwise, an object that just sets the color is defined. */ -export const getColorObjectByAttributeValues = ( colors, definedColor, customColor ) => { +export const getColorObjectByAttributeValues = ( + colors, + definedColor, + customColor +) => { if ( definedColor ) { const colorObj = find( colors, { slug: definedColor } ); @@ -68,8 +72,7 @@ export function getColorClassName( colorContextName, colorSlug ) { * @return {string} String with the color value of the most readable color. */ export function getMostReadableColor( colors, colorValue ) { - return tinycolor.mostReadable( - colorValue, - map( colors, 'color' ) - ).toHexString(); + return tinycolor + .mostReadable( colorValue, map( colors, 'color' ) ) + .toHexString(); } diff --git a/packages/block-editor/src/components/colors/with-colors.js b/packages/block-editor/src/components/colors/with-colors.js index 4544c6c8c49e1e..6bd4a66d5c40eb 100644 --- a/packages/block-editor/src/components/colors/with-colors.js +++ b/packages/block-editor/src/components/colors/with-colors.js @@ -13,7 +13,12 @@ import { compose, createHigherOrderComponent } from '@wordpress/compose'; /** * Internal dependencies */ -import { getColorClassName, getColorObjectByColorValue, getColorObjectByAttributeValues, getMostReadableColor } from './utils'; +import { + getColorClassName, + getColorObjectByColorValue, + getColorObjectByAttributeValues, + getMostReadableColor, +} from './utils'; const DEFAULT_COLORS = []; @@ -25,9 +30,13 @@ const DEFAULT_COLORS = []; * * @return {Function} The higher order component. */ -const withCustomColorPalette = ( colorsArray ) => createHigherOrderComponent( ( WrappedComponent ) => ( props ) => ( - <WrappedComponent { ...props } colors={ colorsArray } /> -), 'withCustomColorPalette' ); +const withCustomColorPalette = ( colorsArray ) => + createHigherOrderComponent( + ( WrappedComponent ) => ( props ) => ( + <WrappedComponent { ...props } colors={ colorsArray } /> + ), + 'withCustomColorPalette' + ); /** * Higher order component factory for injecting the editor colors as the @@ -35,12 +44,13 @@ const withCustomColorPalette = ( colorsArray ) => createHigherOrderComponent( ( * * @return {Function} The higher order component. */ -const withEditorColorPalette = () => withSelect( ( select ) => { - const settings = select( 'core/block-editor' ).getSettings(); - return { - colors: get( settings, [ 'colors' ], DEFAULT_COLORS ), - }; -} ); +const withEditorColorPalette = () => + withSelect( ( select ) => { + const settings = select( 'core/block-editor' ).getSettings(); + return { + colors: get( settings, [ 'colors' ], DEFAULT_COLORS ), + }; + } ); /** * Helper function used with `createHigherOrderComponent` to create @@ -52,12 +62,18 @@ const withEditorColorPalette = () => withSelect( ( select ) => { * @return {WPComponent} The component that can be used as a HOC. */ function createColorHOC( colorTypes, withColorPalette ) { - const colorMap = reduce( colorTypes, ( colorObject, colorType ) => { - return { - ...colorObject, - ...( isString( colorType ) ? { [ colorType ]: kebabCase( colorType ) } : colorType ), - }; - }, {} ); + const colorMap = reduce( + colorTypes, + ( colorObject, colorType ) => { + return { + ...colorObject, + ...( isString( colorType ) + ? { [ colorType ]: kebabCase( colorType ) } + : colorType ), + }; + }, + {} + ); return compose( [ withColorPalette, @@ -68,7 +84,9 @@ function createColorHOC( colorTypes, withColorPalette ) { this.setters = this.createSetters(); this.colorUtils = { - getMostReadableColor: this.getMostReadableColor.bind( this ), + getMostReadableColor: this.getMostReadableColor.bind( + this + ), }; this.state = {}; @@ -80,50 +98,95 @@ function createColorHOC( colorTypes, withColorPalette ) { } createSetters() { - return reduce( colorMap, ( settersAccumulator, colorContext, colorAttributeName ) => { - const upperFirstColorAttributeName = upperFirst( colorAttributeName ); - const customColorAttributeName = `custom${ upperFirstColorAttributeName }`; - settersAccumulator[ `set${ upperFirstColorAttributeName }` ] = - this.createSetColor( colorAttributeName, customColorAttributeName ); - return settersAccumulator; - }, {} ); + return reduce( + colorMap, + ( + settersAccumulator, + colorContext, + colorAttributeName + ) => { + const upperFirstColorAttributeName = upperFirst( + colorAttributeName + ); + const customColorAttributeName = `custom${ upperFirstColorAttributeName }`; + settersAccumulator[ + `set${ upperFirstColorAttributeName }` + ] = this.createSetColor( + colorAttributeName, + customColorAttributeName + ); + return settersAccumulator; + }, + {} + ); } createSetColor( colorAttributeName, customColorAttributeName ) { return ( colorValue ) => { - const colorObject = getColorObjectByColorValue( this.props.colors, colorValue ); + const colorObject = getColorObjectByColorValue( + this.props.colors, + colorValue + ); this.props.setAttributes( { - [ colorAttributeName ]: colorObject && colorObject.slug ? colorObject.slug : undefined, - [ customColorAttributeName ]: colorObject && colorObject.slug ? undefined : colorValue, + [ colorAttributeName ]: + colorObject && colorObject.slug + ? colorObject.slug + : undefined, + [ customColorAttributeName ]: + colorObject && colorObject.slug + ? undefined + : colorValue, } ); }; } - static getDerivedStateFromProps( { attributes, colors }, previousState ) { - return reduce( colorMap, ( newState, colorContext, colorAttributeName ) => { - const colorObject = getColorObjectByAttributeValues( - colors, - attributes[ colorAttributeName ], - attributes[ `custom${ upperFirst( colorAttributeName ) }` ], - ); + static getDerivedStateFromProps( + { attributes, colors }, + previousState + ) { + return reduce( + colorMap, + ( newState, colorContext, colorAttributeName ) => { + const colorObject = getColorObjectByAttributeValues( + colors, + attributes[ colorAttributeName ], + attributes[ + `custom${ upperFirst( + colorAttributeName + ) }` + ] + ); - const previousColorObject = previousState[ colorAttributeName ]; - const previousColor = get( 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 - * bus as previousColorObject does not exist we should compute the object. - */ - if ( previousColor === colorObject.color && previousColorObject ) { - newState[ colorAttributeName ] = previousColorObject; - } else { - newState[ colorAttributeName ] = { - ...colorObject, - class: getColorClassName( colorContext, colorObject.slug ), - }; - } - return newState; - }, {} ); + const previousColorObject = + previousState[ colorAttributeName ]; + const previousColor = get( 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 + * bus as previousColorObject does not exist we should compute the object. + */ + if ( + previousColor === colorObject.color && + previousColorObject + ) { + newState[ + colorAttributeName + ] = previousColorObject; + } else { + newState[ colorAttributeName ] = { + ...colorObject, + class: getColorClassName( + colorContext, + colorObject.slug + ), + }; + } + return newState; + }, + {} + ); } render() { @@ -169,7 +232,10 @@ function createColorHOC( colorTypes, withColorPalette ) { export function createCustomColorsHOC( colorsArray ) { return ( ...colorTypes ) => { const withColorPalette = withCustomColorPalette( colorsArray ); - return createHigherOrderComponent( createColorHOC( colorTypes, withColorPalette ), 'withCustomColors' ); + return createHigherOrderComponent( + createColorHOC( colorTypes, withColorPalette ), + 'withCustomColors' + ); }; } @@ -199,5 +265,8 @@ export function createCustomColorsHOC( colorsArray ) { */ export default function withColors( ...colorTypes ) { const withColorPalette = withEditorColorPalette(); - return createHigherOrderComponent( createColorHOC( colorTypes, withColorPalette ), 'withColors' ); + return createHigherOrderComponent( + createColorHOC( colorTypes, withColorPalette ), + 'withColors' + ); } diff --git a/packages/block-editor/src/components/contrast-checker/index.js b/packages/block-editor/src/components/contrast-checker/index.js index 251b0023c024bc..e75eb24278dd9b 100644 --- a/packages/block-editor/src/components/contrast-checker/index.js +++ b/packages/block-editor/src/components/contrast-checker/index.js @@ -11,10 +11,20 @@ import { __ } from '@wordpress/i18n'; import { Notice } from '@wordpress/components'; import { useEffect } from '@wordpress/element'; -function ContrastCheckerMessage( { tinyBackgroundColor, tinyTextColor, backgroundColor, textColor } ) { - const msg = tinyBackgroundColor.getBrightness() < tinyTextColor.getBrightness() ? - __( 'This color combination may be hard for people to read. Try using a darker background color and/or a brighter text color.' ) : - __( 'This color combination may be hard for people to read. Try using a brighter background color and/or a darker text color.' ); +function ContrastCheckerMessage( { + tinyBackgroundColor, + tinyTextColor, + backgroundColor, + textColor, +} ) { + const msg = + tinyBackgroundColor.getBrightness() < tinyTextColor.getBrightness() + ? __( + 'This color combination may be hard for people to read. Try using a darker background color and/or a brighter text color.' + ) + : __( + 'This color combination may be hard for people to read. Try using a brighter background color and/or a darker text color.' + ); useEffect( () => { speak( __( 'This color combination may be hard for people to read.' ) ); }, [ backgroundColor, textColor ] ); @@ -35,18 +45,29 @@ function ContrastChecker( { isLargeText, textColor, } ) { - if ( ! ( backgroundColor || fallbackBackgroundColor ) || ! ( textColor || fallbackTextColor ) ) { + if ( + ! ( backgroundColor || fallbackBackgroundColor ) || + ! ( textColor || fallbackTextColor ) + ) { return null; } - const tinyBackgroundColor = tinycolor( backgroundColor || fallbackBackgroundColor ); + const tinyBackgroundColor = tinycolor( + backgroundColor || fallbackBackgroundColor + ); const tinyTextColor = tinycolor( textColor || fallbackTextColor ); - const hasTransparency = tinyBackgroundColor.getAlpha() !== 1 || tinyTextColor.getAlpha() !== 1; + const hasTransparency = + tinyBackgroundColor.getAlpha() !== 1 || tinyTextColor.getAlpha() !== 1; - if ( hasTransparency || tinycolor.isReadable( - tinyBackgroundColor, - tinyTextColor, - { level: 'AA', size: ( isLargeText || ( isLargeText !== false && fontSize >= 24 ) ? 'large' : 'small' ) } - ) ) { + if ( + hasTransparency || + tinycolor.isReadable( tinyBackgroundColor, tinyTextColor, { + level: 'AA', + size: + isLargeText || ( isLargeText !== false && fontSize >= 24 ) + ? 'large' + : 'small', + } ) + ) { return null; } diff --git a/packages/block-editor/src/components/contrast-checker/test/index.js b/packages/block-editor/src/components/contrast-checker/test/index.js index c0f3887782309f..8d358437e4d9c5 100644 --- a/packages/block-editor/src/components/contrast-checker/test/index.js +++ b/packages/block-editor/src/components/contrast-checker/test/index.js @@ -33,7 +33,8 @@ describe( 'ContrastChecker', () => { textColor={ textColor } isLargeText={ isLargeText } fallbackBackgroundColor={ fallbackBackgroundColor } - fallbackTextColor={ fallbackTextColor } /> + fallbackTextColor={ fallbackTextColor } + /> ); expect( wrapper.html() ).toBeNull(); @@ -46,10 +47,18 @@ describe( 'ContrastChecker', () => { textColor={ sameShade } isLargeText={ isLargeText } fallbackBackgroundColor={ fallbackBackgroundColor } - fallbackTextColor={ fallbackTextColor } /> + fallbackTextColor={ fallbackTextColor } + /> ); - expect( wrapper.find( Notice ).children().text() ).toBe( 'This color combination may be hard for people to read. Try using a brighter background color and/or a darker text color.' ); + expect( + wrapper + .find( Notice ) + .children() + .text() + ).toBe( + 'This color combination may be hard for people to read. Try using a brighter background color and/or a darker text color.' + ); } ); test( 'should render render null if background color contains a transparency', () => { @@ -59,7 +68,8 @@ describe( 'ContrastChecker', () => { textColor={ sameShade } isLargeText={ isLargeText } fallbackBackgroundColor={ fallbackBackgroundColor } - fallbackTextColor={ fallbackTextColor } /> + fallbackTextColor={ fallbackTextColor } + /> ); expect( wrapper.html() ).toBeNull(); @@ -72,7 +82,8 @@ describe( 'ContrastChecker', () => { textColor={ colorWithTransparency } isLargeText={ isLargeText } fallbackBackgroundColor={ fallbackBackgroundColor } - fallbackTextColor={ fallbackTextColor } /> + fallbackTextColor={ fallbackTextColor } + /> ); expect( wrapper.html() ).toBeNull(); @@ -87,10 +98,18 @@ describe( 'ContrastChecker', () => { textColor={ sameShade } isLargeText={ ! isLargeText } fallbackBackgroundColor={ fallbackBackgroundColor } - fallbackTextColor={ fallbackTextColor } /> + fallbackTextColor={ fallbackTextColor } + /> ); - expect( wrapper.find( Notice ).children().text() ).toBe( 'This color combination may be hard for people to read. Try using a darker background color and/or a brighter text color.' ); + expect( + wrapper + .find( Notice ) + .children() + .text() + ).toBe( + 'This color combination may be hard for people to read. Try using a darker background color and/or a brighter text color.' + ); } ); test( 'should take into consideration wherever text is large or not', () => { @@ -102,7 +121,14 @@ describe( 'ContrastChecker', () => { /> ); - expect( wrapperSmallText.find( Notice ).children().text() ).toBe( 'This color combination may be hard for people to read. Try using a brighter background color and/or a darker text color.' ); + expect( + wrapperSmallText + .find( Notice ) + .children() + .text() + ).toBe( + 'This color combination may be hard for people to read. Try using a brighter background color and/or a darker text color.' + ); const wrapperLargeText = mount( <ContrastChecker @@ -124,7 +150,14 @@ describe( 'ContrastChecker', () => { /> ); - expect( wrapperSmallFontSize.find( Notice ).children().text() ).toBe( 'This color combination may be hard for people to read. Try using a brighter background color and/or a darker text color.' ); + expect( + wrapperSmallFontSize + .find( Notice ) + .children() + .text() + ).toBe( + 'This color combination may be hard for people to read. Try using a brighter background color and/or a darker text color.' + ); const wrapperLargeText = mount( <ContrastChecker @@ -158,7 +191,14 @@ describe( 'ContrastChecker', () => { /> ); - expect( wrapperNoLargeText.find( Notice ).children().text() ).toBe( 'This color combination may be hard for people to read. Try using a brighter background color and/or a darker text color.' ); + expect( + wrapperNoLargeText + .find( Notice ) + .children() + .text() + ).toBe( + 'This color combination may be hard for people to read. Try using a brighter background color and/or a darker text color.' + ); } ); test( 'should render null when the colors meet AA WCAG guidelines, with only fallback colors.', () => { @@ -166,7 +206,8 @@ describe( 'ContrastChecker', () => { <ContrastChecker isLargeText={ isLargeText } fallbackBackgroundColor={ fallbackBackgroundColor } - fallbackTextColor={ fallbackTextColor } /> + fallbackTextColor={ fallbackTextColor } + /> ); expect( wrapper.html() ).toBeNull(); @@ -176,9 +217,17 @@ describe( 'ContrastChecker', () => { const wrapper = mount( <ContrastChecker textColor={ textColor } - fallbackBackgroundColor={ textColor } /> + fallbackBackgroundColor={ textColor } + /> ); - expect( wrapper.find( Notice ).children().text() ).toBe( 'This color combination may be hard for people to read. Try using a brighter background color and/or a darker text color.' ); + expect( + wrapper + .find( Notice ) + .children() + .text() + ).toBe( + 'This color combination may be hard for people to read. Try using a brighter background color and/or a darker text color.' + ); } ); } ); diff --git a/packages/block-editor/src/components/copy-handler/index.js b/packages/block-editor/src/components/copy-handler/index.js index a369144b2c6a90..be6e03097a4671 100644 --- a/packages/block-editor/src/components/copy-handler/index.js +++ b/packages/block-editor/src/components/copy-handler/index.js @@ -49,7 +49,9 @@ export default compose( [ event.preventDefault(); if ( event.type === 'copy' || event.type === 'cut' ) { - const blocks = getBlocksByClientId( selectedBlockClientIds ); + const blocks = getBlocksByClientId( + selectedBlockClientIds + ); const serialized = serialize( blocks ); event.clipboardData.setData( 'text/plain', serialized ); diff --git a/packages/block-editor/src/components/default-block-appender/index.js b/packages/block-editor/src/components/default-block-appender/index.js index 29262787f095dc..865f9a76f226e1 100644 --- a/packages/block-editor/src/components/default-block-appender/index.js +++ b/packages/block-editor/src/components/default-block-appender/index.js @@ -29,7 +29,9 @@ export function DefaultBlockAppender( { return null; } - const value = decodeEntities( placeholder ) || __( 'Start writing or type / to choose a block' ); + const value = + decodeEntities( placeholder ) || + __( 'Start writing or type / to choose a block' ); // The appender "button" is in-fact a text field so as to support // transitions by WritingFlow occurring by arrow key press. WritingFlow @@ -59,17 +61,29 @@ export function DefaultBlockAppender( { onFocus={ onAppend } value={ showPrompt ? value : '' } /> - <Inserter rootClientId={ rootClientId } position="top right" isAppender /> + <Inserter + rootClientId={ rootClientId } + position="top right" + isAppender + /> </div> ); } export default compose( withSelect( ( select, ownProps ) => { - const { getBlockCount, getBlockName, isBlockValid, getSettings, getTemplateLock } = select( 'core/block-editor' ); + const { + getBlockCount, + getBlockName, + isBlockValid, + getSettings, + getTemplateLock, + } = select( 'core/block-editor' ); const isEmpty = ! getBlockCount( ownProps.rootClientId ); - const isLastBlockDefault = getBlockName( ownProps.lastBlockClientId ) === getDefaultBlockName(); + const isLastBlockDefault = + getBlockName( ownProps.lastBlockClientId ) === + getDefaultBlockName(); const isLastBlockValid = isBlockValid( ownProps.lastBlockClientId ); const { bodyPlaceholder } = getSettings(); @@ -81,10 +95,9 @@ export default compose( }; } ), withDispatch( ( dispatch, ownProps ) => { - const { - insertDefaultBlock, - startTyping, - } = dispatch( 'core/block-editor' ); + const { insertDefaultBlock, startTyping } = dispatch( + 'core/block-editor' + ); return { onAppend() { @@ -94,5 +107,5 @@ export default compose( startTyping(); }, }; - } ), + } ) )( DefaultBlockAppender ); diff --git a/packages/block-editor/src/components/default-block-appender/index.native.js b/packages/block-editor/src/components/default-block-appender/index.native.js index 4f361376f6d581..726c060241d63e 100644 --- a/packages/block-editor/src/components/default-block-appender/index.native.js +++ b/packages/block-editor/src/components/default-block-appender/index.native.js @@ -29,17 +29,18 @@ export function DefaultBlockAppender( { return null; } - const value = typeof placeholder === 'string' ? decodeEntities( placeholder ) : __( 'Start writing…' ); + const value = + typeof placeholder === 'string' + ? decodeEntities( placeholder ) + : __( 'Start writing…' ); return ( - <TouchableWithoutFeedback - onPress={ onAppend } - > - <View style={ [ styles.blockHolder, containerStyle ] } pointerEvents="box-only"> - <RichText - placeholder={ value } - onChange={ () => {} } - /> + <TouchableWithoutFeedback onPress={ onAppend }> + <View + style={ [ styles.blockHolder, containerStyle ] } + pointerEvents="box-only" + > + <RichText placeholder={ value } onChange={ () => {} } /> </View> </TouchableWithoutFeedback> ); @@ -47,10 +48,17 @@ export function DefaultBlockAppender( { export default compose( withSelect( ( select, ownProps ) => { - const { getBlockCount, getBlockName, isBlockValid, getTemplateLock } = select( 'core/block-editor' ); + const { + getBlockCount, + getBlockName, + isBlockValid, + getTemplateLock, + } = select( 'core/block-editor' ); const isEmpty = ! getBlockCount( ownProps.rootClientId ); - const isLastBlockDefault = getBlockName( ownProps.lastBlockClientId ) === getDefaultBlockName(); + const isLastBlockDefault = + getBlockName( ownProps.lastBlockClientId ) === + getDefaultBlockName(); const isLastBlockValid = isBlockValid( ownProps.lastBlockClientId ); return { @@ -59,10 +67,9 @@ export default compose( }; } ), withDispatch( ( dispatch, ownProps ) => { - const { - insertDefaultBlock, - startTyping, - } = dispatch( 'core/block-editor' ); + const { insertDefaultBlock, startTyping } = dispatch( + 'core/block-editor' + ); return { onAppend() { @@ -72,5 +79,5 @@ export default compose( startTyping(); }, }; - } ), + } ) )( DefaultBlockAppender ); diff --git a/packages/block-editor/src/components/default-block-appender/test/index.js b/packages/block-editor/src/components/default-block-appender/test/index.js index 80517843ee661e..407adb838f9aae 100644 --- a/packages/block-editor/src/components/default-block-appender/test/index.js +++ b/packages/block-editor/src/components/default-block-appender/test/index.js @@ -22,14 +22,18 @@ describe( 'DefaultBlockAppender', () => { it( 'should match snapshot', () => { const onAppend = jest.fn(); - const wrapper = shallow( <DefaultBlockAppender isVisible onAppend={ onAppend } showPrompt /> ); + const wrapper = shallow( + <DefaultBlockAppender isVisible onAppend={ onAppend } showPrompt /> + ); expect( wrapper ).toMatchSnapshot(); } ); it( 'should append a default block when input focused', () => { const onAppend = jest.fn(); - const wrapper = shallow( <DefaultBlockAppender isVisible onAppend={ onAppend } showPrompt /> ); + const wrapper = shallow( + <DefaultBlockAppender isVisible onAppend={ onAppend } showPrompt /> + ); wrapper.find( 'TextareaAutosize' ).simulate( 'focus' ); @@ -40,7 +44,13 @@ describe( 'DefaultBlockAppender', () => { it( 'should optionally show without prompt', () => { const onAppend = jest.fn(); - const wrapper = shallow( <DefaultBlockAppender isVisible onAppend={ onAppend } showPrompt={ false } /> ); + const wrapper = shallow( + <DefaultBlockAppender + isVisible + onAppend={ onAppend } + showPrompt={ false } + /> + ); const input = wrapper.find( 'TextareaAutosize' ); expect( input.prop( 'value' ) ).toEqual( '' ); 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 7be972419f67bb..5880d8bd5850b3 100644 --- a/packages/block-editor/src/components/default-style-picker/index.js +++ b/packages/block-editor/src/components/default-style-picker/index.js @@ -19,12 +19,13 @@ export default function DefaultStylePicker( { blockName } ) { } = useSelect( ( select ) => { const settings = select( 'core/block-editor' ).getSettings(); - const preferredStyleVariations = settings.__experimentalPreferredStyleVariations; + const preferredStyleVariations = + settings.__experimentalPreferredStyleVariations; return { - preferredStyle: get( - preferredStyleVariations, - [ 'value', blockName ] - ), + preferredStyle: get( preferredStyleVariations, [ + 'value', + blockName, + ] ), onUpdatePreferredStyleVariations: get( preferredStyleVariations, [ 'onChange' ], @@ -36,11 +37,11 @@ export default function DefaultStylePicker( { blockName } ) { [ blockName ] ); const selectOptions = useMemo( - () => ( [ + () => [ { label: __( 'Not set' ), value: '' }, ...styles.map( ( { label, name } ) => ( { label, value: name } ) ), - ] ), - [ styles ], + ], + [ styles ] ); const selectOnChange = useCallback( ( blockStyle ) => { @@ -49,12 +50,14 @@ export default function DefaultStylePicker( { blockName } ) { [ blockName, onUpdatePreferredStyleVariations ] ); - return onUpdatePreferredStyleVariations && ( - <SelectControl - options={ selectOptions } - value={ preferredStyle || '' } - label={ __( 'Default Style' ) } - onChange={ selectOnChange } - /> + return ( + onUpdatePreferredStyleVariations && ( + <SelectControl + options={ selectOptions } + value={ preferredStyle || '' } + label={ __( 'Default Style' ) } + onChange={ selectOnChange } + /> + ) ); } diff --git a/packages/block-editor/src/components/font-sizes/font-size-picker.js b/packages/block-editor/src/components/font-sizes/font-size-picker.js index e2dbe3dba8134b..9200924ad22be8 100644 --- a/packages/block-editor/src/components/font-sizes/font-size-picker.js +++ b/packages/block-editor/src/components/font-sizes/font-size-picker.js @@ -4,16 +4,13 @@ import { FontSizePicker } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; -export default withSelect( - ( select ) => { - const { - disableCustomFontSizes, - fontSizes, - } = select( 'core/block-editor' ).getSettings(); +export default withSelect( ( select ) => { + const { disableCustomFontSizes, fontSizes } = select( + 'core/block-editor' + ).getSettings(); - return { - disableCustomFontSizes, - fontSizes, - }; - } -)( FontSizePicker ); + return { + disableCustomFontSizes, + fontSizes, + }; +} )( FontSizePicker ); diff --git a/packages/block-editor/src/components/font-sizes/utils.js b/packages/block-editor/src/components/font-sizes/utils.js index 09de76dcf7f6aa..17f3cfe680dc75 100644 --- a/packages/block-editor/src/components/font-sizes/utils.js +++ b/packages/block-editor/src/components/font-sizes/utils.js @@ -14,7 +14,11 @@ import { find, kebabCase } from 'lodash'; * @return {?string} If fontSizeAttribute is set and an equal slug is found in fontSizes it returns the font size object for that slug. * Otherwise, an object with just the size value based on customFontSize is returned. */ -export const getFontSize = ( fontSizes, fontSizeAttribute, customFontSizeAttribute ) => { +export const getFontSize = ( + fontSizes, + fontSizeAttribute, + customFontSizeAttribute +) => { if ( fontSizeAttribute ) { const fontSizeObject = find( fontSizes, { slug: fontSizeAttribute } ); if ( fontSizeObject ) { diff --git a/packages/block-editor/src/components/font-sizes/with-font-sizes.js b/packages/block-editor/src/components/font-sizes/with-font-sizes.js index f64fe8d4f76006..4476484d30a3d6 100644 --- a/packages/block-editor/src/components/font-sizes/with-font-sizes.js +++ b/packages/block-editor/src/components/font-sizes/with-font-sizes.js @@ -27,19 +27,27 @@ import { getFontSize, getFontSizeClass } from './utils'; */ export default ( ...fontSizeNames ) => { /* - * Computes an object whose key is the font size attribute name as passed in the array, - * and the value is the custom font size attribute name. - * Custom font size is automatically compted by appending custom followed by the font size attribute name in with the first letter capitalized. - */ - const fontSizeAttributeNames = reduce( fontSizeNames, ( fontSizeAttributeNamesAccumulator, fontSizeAttributeName ) => { - fontSizeAttributeNamesAccumulator[ fontSizeAttributeName ] = `custom${ upperFirst( fontSizeAttributeName ) }`; - return fontSizeAttributeNamesAccumulator; - }, {} ); + * Computes an object whose key is the font size attribute name as passed in the array, + * and the value is the custom font size attribute name. + * Custom font size is automatically compted by appending custom followed by the font size attribute name in with the first letter capitalized. + */ + const fontSizeAttributeNames = reduce( + fontSizeNames, + ( fontSizeAttributeNamesAccumulator, fontSizeAttributeName ) => { + fontSizeAttributeNamesAccumulator[ + fontSizeAttributeName + ] = `custom${ upperFirst( fontSizeAttributeName ) }`; + return fontSizeAttributeNamesAccumulator; + }, + {} + ); return createHigherOrderComponent( compose( [ withSelect( ( select ) => { - const { fontSizes } = select( 'core/block-editor' ).getSettings(); + const { fontSizes } = select( + 'core/block-editor' + ).getSettings(); return { fontSizes, }; @@ -55,46 +63,98 @@ export default ( ...fontSizeNames ) => { } createSetters() { - return reduce( fontSizeAttributeNames, ( settersAccumulator, customFontSizeAttributeName, fontSizeAttributeName ) => { - const upperFirstFontSizeAttributeName = upperFirst( fontSizeAttributeName ); - settersAccumulator[ `set${ upperFirstFontSizeAttributeName }` ] = - this.createSetFontSize( fontSizeAttributeName, customFontSizeAttributeName ); - return settersAccumulator; - }, {} ); + return reduce( + fontSizeAttributeNames, + ( + settersAccumulator, + customFontSizeAttributeName, + fontSizeAttributeName + ) => { + const upperFirstFontSizeAttributeName = upperFirst( + fontSizeAttributeName + ); + settersAccumulator[ + `set${ upperFirstFontSizeAttributeName }` + ] = this.createSetFontSize( + fontSizeAttributeName, + customFontSizeAttributeName + ); + return settersAccumulator; + }, + {} + ); } - createSetFontSize( fontSizeAttributeName, customFontSizeAttributeName ) { + createSetFontSize( + fontSizeAttributeName, + customFontSizeAttributeName + ) { return ( fontSizeValue ) => { - const fontSizeObject = find( this.props.fontSizes, { size: Number( fontSizeValue ) } ); + const fontSizeObject = find( this.props.fontSizes, { + size: Number( fontSizeValue ), + } ); this.props.setAttributes( { - [ fontSizeAttributeName ]: fontSizeObject && fontSizeObject.slug ? fontSizeObject.slug : undefined, - [ customFontSizeAttributeName ]: fontSizeObject && fontSizeObject.slug ? undefined : fontSizeValue, + [ fontSizeAttributeName ]: + fontSizeObject && fontSizeObject.slug + ? fontSizeObject.slug + : undefined, + [ customFontSizeAttributeName ]: + fontSizeObject && fontSizeObject.slug + ? undefined + : fontSizeValue, } ); }; } - static getDerivedStateFromProps( { attributes, fontSizes }, previousState ) { - const didAttributesChange = ( customFontSizeAttributeName, fontSizeAttributeName ) => { + static getDerivedStateFromProps( + { attributes, fontSizes }, + previousState + ) { + const didAttributesChange = ( + customFontSizeAttributeName, + fontSizeAttributeName + ) => { if ( previousState[ fontSizeAttributeName ] ) { // if new font size is name compare with the previous slug if ( attributes[ fontSizeAttributeName ] ) { - return attributes[ fontSizeAttributeName ] !== previousState[ fontSizeAttributeName ].slug; + return ( + attributes[ fontSizeAttributeName ] !== + previousState[ fontSizeAttributeName ] + .slug + ); } // if font size is not named, update when the font size value changes. - return previousState[ fontSizeAttributeName ].size !== attributes[ customFontSizeAttributeName ]; + return ( + previousState[ fontSizeAttributeName ] + .size !== + attributes[ customFontSizeAttributeName ] + ); } // in this case we need to build the font size object return true; }; - if ( ! some( fontSizeAttributeNames, didAttributesChange ) ) { + if ( + ! some( + fontSizeAttributeNames, + didAttributesChange + ) + ) { return null; } const newState = reduce( - pickBy( fontSizeAttributeNames, didAttributesChange ), - ( newStateAccumulator, customFontSizeAttributeName, fontSizeAttributeName ) => { - const fontSizeAttributeValue = attributes[ fontSizeAttributeName ]; + pickBy( + fontSizeAttributeNames, + didAttributesChange + ), + ( + newStateAccumulator, + customFontSizeAttributeName, + fontSizeAttributeName + ) => { + const fontSizeAttributeValue = + attributes[ fontSizeAttributeName ]; const fontSizeObject = getFontSize( fontSizes, fontSizeAttributeValue, @@ -102,7 +162,9 @@ export default ( ...fontSizeNames ) => { ); newStateAccumulator[ fontSizeAttributeName ] = { ...fontSizeObject, - class: getFontSizeClass( fontSizeAttributeValue ), + class: getFontSizeClass( + fontSizeAttributeValue + ), }; return newStateAccumulator; }, diff --git a/packages/block-editor/src/components/gradient-picker/control.js b/packages/block-editor/src/components/gradient-picker/control.js index 16179ff0bb9be6..f57802aada39b1 100644 --- a/packages/block-editor/src/components/gradient-picker/control.js +++ b/packages/block-editor/src/components/gradient-picker/control.js @@ -1,4 +1,3 @@ - /** * External dependencies */ @@ -17,13 +16,21 @@ import { useSelect } from '@wordpress/data'; */ import GradientPicker from './'; -export default function( { className, value, onChange, label = __( 'Gradient Presets' ), ...props } ) { - const { gradients = [], disableCustomGradients } = useSelect( ( select ) => ( - pick( - select( 'core/block-editor' ).getSettings(), - [ 'gradients', 'disableCustomGradients' ] - ) - ), [] ); +export default function( { + className, + value, + onChange, + label = __( 'Gradient Presets' ), + ...props +} ) { + const { gradients = [], disableCustomGradients } = useSelect( + ( select ) => + pick( select( 'core/block-editor' ).getSettings(), [ + 'gradients', + 'disableCustomGradients', + ] ), + [] + ); if ( isEmpty( gradients ) && disableCustomGradients ) { return null; } @@ -34,9 +41,7 @@ export default function( { className, value, onChange, label = __( 'Gradient Pre className ) } > - <BaseControl.VisualLabel> - { label } - </BaseControl.VisualLabel> + <BaseControl.VisualLabel>{ label }</BaseControl.VisualLabel> <GradientPicker value={ value } onChange={ onChange } diff --git a/packages/block-editor/src/components/gradient-picker/index.js b/packages/block-editor/src/components/gradient-picker/index.js index 972e1aa6c763fd..407c5e387f4a82 100644 --- a/packages/block-editor/src/components/gradient-picker/index.js +++ b/packages/block-editor/src/components/gradient-picker/index.js @@ -1,4 +1,3 @@ - /** * External dependencies */ @@ -11,24 +10,34 @@ import { __experimentalGradientPicker } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; function GradientPickerWithGradients( props ) { - const { gradients, disableCustomGradients } = useSelect( ( select ) => ( - pick( - select( 'core/block-editor' ).getSettings(), - [ 'gradients', 'disableCustomGradients' ] - ) - ), [] ); + const { gradients, disableCustomGradients } = useSelect( + ( select ) => + pick( select( 'core/block-editor' ).getSettings(), [ + 'gradients', + 'disableCustomGradients', + ] ), + [] + ); return ( <__experimentalGradientPicker - gradients={ props.gradients !== undefined ? props.gradient : gradients } - disableCustomGradients={ props.disableCustomGradients !== undefined ? props.disableCustomGradients : disableCustomGradients } + gradients={ + props.gradients !== undefined ? props.gradient : gradients + } + disableCustomGradients={ + props.disableCustomGradients !== undefined + ? props.disableCustomGradients + : disableCustomGradients + } { ...props } /> ); } export default function( props ) { - const ComponentToUse = props.gradients !== undefined && props.disableCustomGradients !== undefined ? - __experimentalGradientPicker : - GradientPickerWithGradients; - return ( <ComponentToUse { ...props } /> ); + const ComponentToUse = + props.gradients !== undefined && + props.disableCustomGradients !== undefined + ? __experimentalGradientPicker + : GradientPickerWithGradients; + return <ComponentToUse { ...props } />; } diff --git a/packages/block-editor/src/components/gradient-picker/panel.js b/packages/block-editor/src/components/gradient-picker/panel.js index 25d07234e9cf50..de754d46cb85fb 100644 --- a/packages/block-editor/src/components/gradient-picker/panel.js +++ b/packages/block-editor/src/components/gradient-picker/panel.js @@ -16,17 +16,16 @@ import { __ } from '@wordpress/i18n'; import GradientPicker from './control'; export default function GradientPanel( props ) { - const gradients = useSelect( ( select ) => ( - select( 'core/block-editor' ).getSettings().gradients - ), [] ); + const gradients = useSelect( + ( select ) => select( 'core/block-editor' ).getSettings().gradients, + [] + ); if ( isEmpty( gradients ) ) { return null; } return ( <PanelBody title={ __( 'Gradient' ) }> - <GradientPicker - { ...props } - /> + <GradientPicker { ...props } /> </PanelBody> ); } diff --git a/packages/block-editor/src/components/gradients/index.js b/packages/block-editor/src/components/gradients/index.js index abd623c817786a..988ca37da2cfe3 100644 --- a/packages/block-editor/src/components/gradients/index.js +++ b/packages/block-editor/src/components/gradients/index.js @@ -26,13 +26,19 @@ function getGradientValueBySlug( gradients, slug ) { return gradient && gradient.gradient; } -export function __experimentalGetGradientObjectByGradientValue( gradients, value ) { +export function __experimentalGetGradientObjectByGradientValue( + gradients, + value +) { const gradient = find( gradients, [ 'gradient', value ] ); return gradient; } function getGradientSlugByValue( gradients, value ) { - const gradient = __experimentalGetGradientObjectByGradientValue( gradients, value ); + const gradient = __experimentalGetGradientObjectByGradientValue( + gradients, + value + ); return gradient && gradient.slug; } @@ -42,15 +48,20 @@ export function __experimentalUseGradient( { } = {} ) { const { clientId } = useBlockEditContext(); - const { gradients, gradient, customGradient } = useSelect( ( select ) => { - const { getBlockAttributes, getSettings } = select( 'core/block-editor' ); - const attributes = getBlockAttributes( clientId ); - return { - gradient: attributes[ gradientAttribute ], - customGradient: attributes[ customGradientAttribute ], - gradients: getSettings().gradients, - }; - }, [ clientId, gradientAttribute, customGradientAttribute ] ); + const { gradients, gradient, customGradient } = useSelect( + ( select ) => { + const { getBlockAttributes, getSettings } = select( + 'core/block-editor' + ); + const attributes = getBlockAttributes( clientId ); + return { + gradient: attributes[ gradientAttribute ], + customGradient: attributes[ customGradientAttribute ], + gradients: getSettings().gradients, + }; + }, + [ clientId, gradientAttribute, customGradientAttribute ] + ); const { updateBlockAttributes } = useDispatch( 'core/block-editor' ); const setGradient = useCallback( diff --git a/packages/block-editor/src/components/image-size-control/index.js b/packages/block-editor/src/components/image-size-control/index.js index f77227e650eb12..1637c6c6115327 100644 --- a/packages/block-editor/src/components/image-size-control/index.js +++ b/packages/block-editor/src/components/image-size-control/index.js @@ -7,7 +7,12 @@ import { isEmpty, noop } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Button, ButtonGroup, SelectControl, TextControl } from '@wordpress/components'; +import { + Button, + ButtonGroup, + SelectControl, + TextControl, +} from '@wordpress/components'; import { Component } from '@wordpress/element'; class ImageSizeControl extends Component { @@ -63,7 +68,9 @@ class ImageSizeControl extends Component { label={ __( 'Width' ) } value={ width || imageWidth || '' } min={ 1 } - onChange={ ( value ) => onChange( { width: parseInt( value, 10 ) } ) } + onChange={ ( value ) => + onChange( { width: parseInt( value, 10 ) } ) + } /> <TextControl type="number" @@ -71,16 +78,26 @@ class ImageSizeControl extends Component { label={ __( 'Height' ) } value={ height || imageHeight || '' } min={ 1 } - onChange={ ( value ) => onChange( { height: parseInt( value, 10 ) } ) } + onChange={ ( value ) => + onChange( { + height: parseInt( value, 10 ), + } ) + } /> </div> <div className="block-editor-image-size-control__row"> <ButtonGroup aria-label={ __( 'Image Size' ) }> { [ 25, 50, 75, 100 ].map( ( scale ) => { - const scaledWidth = Math.round( imageWidth * ( scale / 100 ) ); - const scaledHeight = Math.round( imageHeight * ( scale / 100 ) ); + const scaledWidth = Math.round( + imageWidth * ( scale / 100 ) + ); + const scaledHeight = Math.round( + imageHeight * ( scale / 100 ) + ); - const isCurrent = width === scaledWidth && height === scaledHeight; + const isCurrent = + width === scaledWidth && + height === scaledHeight; return ( <Button @@ -88,17 +105,17 @@ class ImageSizeControl extends Component { isSmall isPrimary={ isCurrent } isPressed={ isCurrent } - onClick={ this.updateDimensions( scaledWidth, scaledHeight ) } + onClick={ this.updateDimensions( + scaledWidth, + scaledHeight + ) } > { scale }% </Button> ); } ) } </ButtonGroup> - <Button - isSmall - onClick={ this.updateDimensions() } - > + <Button isSmall onClick={ this.updateDimensions() }> { __( 'Reset' ) } </Button> </div> diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index 48e8559d7990b9..b5039d38b46496 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -15,7 +15,7 @@ export { default as BlockFormatControls } from './block-format-controls'; export { default as BlockIcon } from './block-icon'; export { default as BlockNavigationDropdown } from './block-navigation/dropdown'; export { default as __experimentalBlockNavigationList } from './block-navigation/list'; -export { default as __experimentalBlockPatternPicker } from './block-pattern-picker'; +export { default as __experimentalBlockVariationPicker } from './block-variation-picker'; export { default as BlockVerticalAlignmentToolbar } from './block-vertical-alignment-toolbar'; export { default as ButtonBlockerAppender } from './button-block-appender'; export { default as ColorPalette } from './color-palette'; diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js index 59c30699c588f1..0227ceabcc6470 100644 --- a/packages/block-editor/src/components/index.native.js +++ b/packages/block-editor/src/components/index.native.js @@ -18,7 +18,11 @@ export { __unstableRichTextInputEvent, } from './rich-text'; export { default as MediaPlaceholder } from './media-placeholder'; -export { default as MediaUpload, MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO } from './media-upload'; +export { + default as MediaUpload, + MEDIA_TYPE_IMAGE, + MEDIA_TYPE_VIDEO, +} from './media-upload'; export { default as MediaUploadProgress } from './media-upload-progress'; export { default as MediaEdit } from './media-edit'; export { default as URLInput } from './url-input'; diff --git a/packages/block-editor/src/components/inner-blocks/button-block-appender.js b/packages/block-editor/src/components/inner-blocks/button-block-appender.js index 6e26bb0856b5ac..5823a5e42c7b0c 100644 --- a/packages/block-editor/src/components/inner-blocks/button-block-appender.js +++ b/packages/block-editor/src/components/inner-blocks/button-block-appender.js @@ -6,7 +6,10 @@ import withClientId from './with-client-id'; export const ButtonBlockAppender = ( { clientId, showSeparator } ) => { return ( - <BaseButtonBlockAppender rootClientId={ clientId } showSeparator={ showSeparator } /> + <BaseButtonBlockAppender + rootClientId={ clientId } + showSeparator={ showSeparator } + /> ); }; diff --git a/packages/block-editor/src/components/inner-blocks/default-block-appender.js b/packages/block-editor/src/components/inner-blocks/default-block-appender.js index 43a98be106a2b3..4874704148e918 100644 --- a/packages/block-editor/src/components/inner-blocks/default-block-appender.js +++ b/packages/block-editor/src/components/inner-blocks/default-block-appender.js @@ -27,9 +27,7 @@ export const DefaultBlockAppender = ( { clientId, lastBlockClientId } ) => { export default compose( [ withClientId, withSelect( ( select, { clientId } ) => { - const { - getBlockOrder, - } = select( 'core/block-editor' ); + const { getBlockOrder } = select( 'core/block-editor' ); const blockClientIds = getBlockOrder( clientId ); diff --git a/packages/block-editor/src/components/inner-blocks/index.js b/packages/block-editor/src/components/inner-blocks/index.js index a9dc9ae26ddb0f..c8a54479d420df 100644 --- a/packages/block-editor/src/components/inner-blocks/index.js +++ b/packages/block-editor/src/components/inner-blocks/index.js @@ -10,7 +10,10 @@ import classnames from 'classnames'; import { withViewportMatch } from '@wordpress/viewport'; import { Component } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; -import { synchronizeBlocksWithTemplate, withBlockContentContext } from '@wordpress/blocks'; +import { + synchronizeBlocksWithTemplate, + withBlockContentContext, +} from '@wordpress/blocks'; import isShallowEqual from '@wordpress/is-shallow-equal'; import { compose } from '@wordpress/compose'; @@ -76,7 +79,10 @@ class InnerBlocks extends Component { this.updateNestedSettings(); // Only synchronize innerBlocks with template if innerBlocks are empty or a locking all exists directly on the block. if ( innerBlocks.length === 0 || templateLock === 'all' ) { - const hasTemplateChanged = ! isEqual( template, prevProps.template ); + const hasTemplateChanged = ! isEqual( + template, + prevProps.template + ); if ( hasTemplateChanged ) { this.synchronizeBlocksWithTemplate(); } @@ -101,8 +107,11 @@ class InnerBlocks extends Component { const { innerBlocks } = block; // Synchronize with templates. If the next set differs, replace. - const nextBlocks = synchronizeBlocksWithTemplate( innerBlocks, template ); - if ( ! isEqual( nextBlocks, innerBlocks ) ) { + const nextBlocks = synchronizeBlocksWithTemplate( + innerBlocks, + template + ); + if ( ! isEqual( nextBlocks, innerBlocks ) ) { replaceInnerBlocks( nextBlocks ); } } @@ -121,8 +130,10 @@ class InnerBlocks extends Component { const newSettings = { allowedBlocks, - templateLock: templateLock === undefined ? parentLock : templateLock, - __experimentalCaptureToolbars: __experimentalCaptureToolbars || false, + templateLock: + templateLock === undefined ? parentLock : templateLock, + __experimentalCaptureToolbars: + __experimentalCaptureToolbars || false, __experimentalMoverDirection, __experimentalUIParts, }; @@ -150,10 +161,7 @@ class InnerBlocks extends Component { return ( <div className={ classes }> { ! templateInProcess && ( - <BlockList - rootClientId={ clientId } - { ...props } - /> + <BlockList rootClientId={ clientId } { ...props } /> ) } </div> ); @@ -181,7 +189,10 @@ InnerBlocks = compose( [ return { block, blockListSettings: getBlockListSettings( clientId ), - hasOverlay: block.name !== 'core/template' && ! isBlockSelected( clientId ) && ! hasSelectedInnerBlock( clientId, true ), + hasOverlay: + block.name !== 'core/template' && + ! isBlockSelected( clientId ) && + ! hasSelectedInnerBlock( clientId, true ), parentLock: getTemplateLock( rootClientId ), enableClickThrough: isNavigationMode() || isSmallScreen, isLastBlockChangePersistent: isLastBlockChangePersistent(), @@ -193,7 +204,11 @@ InnerBlocks = compose( [ __unstableMarkNextChangeAsNotPersistent, updateBlockListSettings, } = dispatch( 'core/block-editor' ); - const { block, clientId, templateInsertUpdatesSelection = true } = ownProps; + const { + block, + clientId, + templateInsertUpdatesSelection = true, + } = ownProps; return { replaceInnerBlocks( blocks ) { @@ -217,9 +232,9 @@ InnerBlocks = compose( [ InnerBlocks.DefaultBlockAppender = DefaultBlockAppender; InnerBlocks.ButtonBlockAppender = ButtonBlockAppender; -InnerBlocks.Content = withBlockContentContext( - ( { BlockContent } ) => <BlockContent /> -); +InnerBlocks.Content = withBlockContentContext( ( { BlockContent } ) => ( + <BlockContent /> +) ); /** * @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/inner-blocks/README.md diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index a8bfd45e1d9abe..efdaa4cd8e9e24 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -8,7 +8,10 @@ import { pick, isEqual } from 'lodash'; */ import { Component } from '@wordpress/element'; import { withSelect, withDispatch } from '@wordpress/data'; -import { synchronizeBlocksWithTemplate, withBlockContentContext } from '@wordpress/blocks'; +import { + synchronizeBlocksWithTemplate, + withBlockContentContext, +} from '@wordpress/blocks'; import isShallowEqual from '@wordpress/is-shallow-equal'; import { compose } from '@wordpress/compose'; @@ -34,10 +37,7 @@ class InnerBlocks extends Component { } getTemplateLock() { - const { - templateLock, - parentLock, - } = this.props; + const { templateLock, parentLock } = this.props; return templateLock === undefined ? parentLock : templateLock; } @@ -62,7 +62,10 @@ class InnerBlocks extends Component { this.updateNestedSettings(); // only synchronize innerBlocks with template if innerBlocks are empty or a locking all exists if ( innerBlocks.length === 0 || this.getTemplateLock() === 'all' ) { - const hasTemplateChanged = ! isEqual( template, prevProps.template ); + const hasTemplateChanged = ! isEqual( + template, + prevProps.template + ); if ( hasTemplateChanged ) { this.synchronizeBlocksWithTemplate(); } @@ -79,8 +82,11 @@ class InnerBlocks extends Component { const { innerBlocks } = block; // Synchronize with templates. If the next set differs, replace. - const nextBlocks = synchronizeBlocksWithTemplate( innerBlocks, template ); - if ( ! isEqual( nextBlocks, innerBlocks ) ) { + const nextBlocks = synchronizeBlocksWithTemplate( + innerBlocks, + template + ); + if ( ! isEqual( nextBlocks, innerBlocks ) ) { replaceInnerBlocks( nextBlocks ); } } @@ -103,11 +109,7 @@ class InnerBlocks extends Component { } render() { - const { - clientId, - renderAppender, - containerStyle, - } = this.props; + const { clientId, renderAppender, containerStyle } = this.props; const { templateInProcess } = this.state; return ( @@ -144,20 +146,31 @@ InnerBlocks = compose( [ return { block, blockListSettings: getBlockListSettings( clientId ), - hasOverlay: block.name !== 'core/template' && ! isBlockSelected( clientId ) && ! hasSelectedInnerBlock( clientId, true ), + hasOverlay: + block.name !== 'core/template' && + ! isBlockSelected( clientId ) && + ! hasSelectedInnerBlock( clientId, true ), parentLock: getTemplateLock( rootClientId ), }; } ), withDispatch( ( dispatch, ownProps ) => { + const { replaceInnerBlocks, updateBlockListSettings } = dispatch( + 'core/block-editor' + ); const { - replaceInnerBlocks, - updateBlockListSettings, - } = dispatch( 'core/block-editor' ); - const { block, clientId, templateInsertUpdatesSelection = true } = ownProps; + block, + clientId, + templateInsertUpdatesSelection = true, + } = ownProps; return { replaceInnerBlocks( blocks ) { - replaceInnerBlocks( clientId, blocks, block.innerBlocks.length === 0 && templateInsertUpdatesSelection ); + replaceInnerBlocks( + clientId, + blocks, + block.innerBlocks.length === 0 && + templateInsertUpdatesSelection + ); }, updateNestedSettings( settings ) { dispatch( updateBlockListSettings( clientId, settings ) ); @@ -170,9 +183,9 @@ InnerBlocks = compose( [ InnerBlocks.DefaultBlockAppender = DefaultBlockAppender; InnerBlocks.ButtonBlockAppender = ButtonBlockAppender; -InnerBlocks.Content = withBlockContentContext( - ( { BlockContent } ) => <BlockContent /> -); +InnerBlocks.Content = withBlockContentContext( ( { BlockContent } ) => ( + <BlockContent /> +) ); /** * @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/inner-blocks/README.md diff --git a/packages/block-editor/src/components/inner-blocks/test/index.js b/packages/block-editor/src/components/inner-blocks/test/index.js index 19bfd1bc383d7c..ac242fe9ddb112 100644 --- a/packages/block-editor/src/components/inner-blocks/test/index.js +++ b/packages/block-editor/src/components/inner-blocks/test/index.js @@ -50,11 +50,9 @@ describe( 'InnerBlocks', () => { } ); const saved = renderToString( - getSaveElement( - 'core/fruit', - { fruit: 'Bananas' }, - [ createBlock( 'core/fruit', { fruit: 'Apples' } ) ], - ) + getSaveElement( 'core/fruit', { fruit: 'Bananas' }, [ + createBlock( 'core/fruit', { fruit: 'Apples' } ), + ] ) ); expect( saved ).toMatchSnapshot(); @@ -94,11 +92,9 @@ describe( 'InnerBlocks', () => { title: 'block title', }; registerBlockType( 'core/test-block', blockType ); - const block = createBlock( - 'core/test-block', - { content: 'Invalid' }, - [ createBlock( 'core/test-block' ) ] - ); + const block = createBlock( 'core/test-block', { content: 'Invalid' }, [ + createBlock( 'core/test-block' ), + ] ); block.isValid = false; block.originalContent = 'Original'; diff --git a/packages/block-editor/src/components/inner-blocks/with-client-id.js b/packages/block-editor/src/components/inner-blocks/with-client-id.js index 35672ba1681f25..6a49c22abe3e30 100644 --- a/packages/block-editor/src/components/inner-blocks/with-client-id.js +++ b/packages/block-editor/src/components/inner-blocks/with-client-id.js @@ -14,7 +14,10 @@ import { createHigherOrderComponent } from '@wordpress/compose'; import { withBlockEditContext } from '../block-edit/context'; const withClientId = createHigherOrderComponent( - ( WrappedComponent ) => withBlockEditContext( ( context ) => pick( context, [ 'clientId' ] ) )( WrappedComponent ), + ( WrappedComponent ) => + withBlockEditContext( ( context ) => pick( context, [ 'clientId' ] ) )( + WrappedComponent + ), 'withClientId' ); diff --git a/packages/block-editor/src/components/inserter-list-item/index.js b/packages/block-editor/src/components/inserter-list-item/index.js index 674db0f6deedc4..b4c8e40cf5efea 100644 --- a/packages/block-editor/src/components/inserter-list-item/index.js +++ b/packages/block-editor/src/components/inserter-list-item/index.js @@ -21,20 +21,20 @@ function InserterListItem( { className, ...props } ) { - const itemIconStyle = icon ? { - backgroundColor: icon.background, - color: icon.foreground, - } : {}; + const itemIconStyle = icon + ? { + backgroundColor: icon.background, + color: icon.foreground, + } + : {}; return ( <li className="block-editor-block-types-list__list-item"> <Button - className={ - classnames( - 'block-editor-block-types-list__item', - className - ) - } + className={ classnames( + 'block-editor-block-types-list__item', + className + ) } onClick={ ( event ) => { event.preventDefault(); onClick(); diff --git a/packages/block-editor/src/components/inserter-menu-extension/index.js b/packages/block-editor/src/components/inserter-menu-extension/index.js index bc493945d9f363..c93b6fda2850b9 100644 --- a/packages/block-editor/src/components/inserter-menu-extension/index.js +++ b/packages/block-editor/src/components/inserter-menu-extension/index.js @@ -3,7 +3,9 @@ */ import { createSlotFill } from '@wordpress/components'; -const { Fill: __experimentalInserterMenuExtension, Slot } = createSlotFill( '__experimentalInserterMenuExtension' ); +const { Fill: __experimentalInserterMenuExtension, Slot } = createSlotFill( + '__experimentalInserterMenuExtension' +); __experimentalInserterMenuExtension.Slot = Slot; diff --git a/packages/block-editor/src/components/inserter/child-blocks.js b/packages/block-editor/src/components/inserter/child-blocks.js index 94764a94b4fe7d..362a56b79035a9 100644 --- a/packages/block-editor/src/components/inserter/child-blocks.js +++ b/packages/block-editor/src/components/inserter/child-blocks.js @@ -27,17 +27,13 @@ function ChildBlocks( { rootBlockIcon, rootBlockTitle, items, ...props } ) { export default compose( ifCondition( ( { items } ) => items && items.length > 0 ), withSelect( ( select, { rootClientId } ) => { - const { - getBlockType, - } = select( 'core/blocks' ); - const { - getBlockName, - } = select( 'core/block-editor' ); + const { getBlockType } = select( 'core/blocks' ); + const { getBlockName } = select( 'core/block-editor' ); const rootBlockName = getBlockName( rootClientId ); const rootBlockType = getBlockType( rootBlockName ); return { rootBlockTitle: rootBlockType && rootBlockType.title, rootBlockIcon: rootBlockType && rootBlockType.icon, }; - } ), + } ) )( ChildBlocks ); diff --git a/packages/block-editor/src/components/inserter/index.js b/packages/block-editor/src/components/inserter/index.js index a5a8de9ace8161..34a430a04545f5 100644 --- a/packages/block-editor/src/components/inserter/index.js +++ b/packages/block-editor/src/components/inserter/index.js @@ -19,11 +19,20 @@ import { plusCircle } from '@wordpress/icons'; */ import InserterMenu from './menu'; -const defaultRenderToggle = ( { onToggle, disabled, isOpen, blockTitle, hasSingleBlockType } ) => { +const defaultRenderToggle = ( { + onToggle, + disabled, + isOpen, + blockTitle, + hasSingleBlockType, +} ) => { let label; if ( hasSingleBlockType ) { // translators: %s: the name of the block when there is only one - label = sprintf( _x( 'Add %s', 'directly add the only allowed block' ), blockTitle ); + label = sprintf( + _x( 'Add %s', 'directly add the only allowed block' ), + blockTitle + ); } else { label = _x( 'Add block', 'Generic label for block inserter button' ); } @@ -77,7 +86,13 @@ class Inserter extends Component { renderToggle = defaultRenderToggle, } = this.props; - return renderToggle( { onToggle, isOpen, disabled, blockTitle, hasSingleBlockType } ); + return renderToggle( { + onToggle, + isOpen, + disabled, + blockTitle, + hasSingleBlockType, + } ); } /** @@ -111,7 +126,11 @@ class Inserter extends Component { } render() { - const { position, hasSingleBlockType, insertOnlyAllowedBlock } = this.props; + const { + position, + hasSingleBlockType, + insertOnlyAllowedBlock, + } = this.props; if ( hasSingleBlockType ) { return this.renderToggle( { onToggle: insertOnlyAllowedBlock } ); @@ -139,18 +158,20 @@ export default compose( [ hasInserterItems, __experimentalGetAllowedBlocks, } = select( 'core/block-editor' ); - const { - __experimentalGetBlockPatterns: getBlockPatterns, - } = select( 'core/blocks' ); + const { __experimentalGetBlockVariations: getBlockVariations } = select( + 'core/blocks' + ); - rootClientId = rootClientId || getBlockRootClientId( clientId ) || undefined; + rootClientId = + rootClientId || getBlockRootClientId( clientId ) || undefined; const allowedBlocks = __experimentalGetAllowedBlocks( rootClientId ); - const hasSingleBlockType = ( + const hasSingleBlockType = size( allowedBlocks ) === 1 && - size( getBlockPatterns( allowedBlocks[ 0 ].name, 'inserter' ) ) === 0 - ); + size( + getBlockVariations( allowedBlocks[ 0 ].name, 'inserter' ) + ) === 0; let allowedBlockType = false; if ( hasSingleBlockType ) { @@ -201,9 +222,7 @@ export default compose( [ return getBlockOrder( rootClientId ).length; } - const { - insertBlock, - } = dispatch( 'core/block-editor' ); + const { insertBlock } = dispatch( 'core/block-editor' ); const blockToInsert = createBlock( allowedBlockType.name ); @@ -216,7 +235,10 @@ export default compose( [ if ( ! selectBlockOnInsert ) { // translators: %s: the name of the block that has been added - const message = sprintf( __( '%s block added' ), allowedBlockType.title ); + const message = sprintf( + __( '%s block added' ), + allowedBlockType.title + ); speak( message ); } }, diff --git a/packages/block-editor/src/components/inserter/index.native.js b/packages/block-editor/src/components/inserter/index.native.js index 905a92c30cfe7d..49530fdd4049d6 100644 --- a/packages/block-editor/src/components/inserter/index.native.js +++ b/packages/block-editor/src/components/inserter/index.native.js @@ -2,7 +2,12 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Dropdown, ToolbarButton, Dashicon, Picker } from '@wordpress/components'; +import { + Dropdown, + ToolbarButton, + Dashicon, + Picker, +} from '@wordpress/components'; import { Component } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; @@ -18,7 +23,9 @@ import BlockInsertionPoint from '../block-list/insertion-point'; const defaultRenderToggle = ( { onToggle, disabled, style, onLongPress } ) => ( <ToolbarButton title={ __( 'Add block' ) } - icon={ ( <Dashicon icon="plus-alt" style={ style } color={ style.color } /> ) } + icon={ + <Dashicon icon="plus-alt" style={ style } color={ style.color } /> + } onClick={ onToggle } extraProps={ { hint: __( 'Double tap to add a block' ), @@ -74,7 +81,11 @@ export class Inserter extends Component { const { isAnyBlockSelected, isSelectedBlockReplaceable } = this.props; if ( isAnyBlockSelected ) { if ( isSelectedBlockReplaceable ) { - return [ addBeforeOption, replaceCurrentOption, addAfterOption ]; + return [ + addBeforeOption, + replaceCurrentOption, + addAfterOption, + ]; } return [ addBeforeOption, addAfterOption ]; } @@ -97,9 +108,7 @@ export class Inserter extends Component { } shouldReplaceBlock( insertionType ) { - const { - isSelectedBlockReplaceable, - } = this.props; + const { isSelectedBlockReplaceable } = this.props; if ( insertionType === 'replace' ) { return true; } @@ -138,14 +147,20 @@ export class Inserter extends Component { if ( showSeparator && isOpen ) { return <BlockInsertionPoint />; } - const style = getStylesFromColorScheme( styles.addBlockButton, styles.addBlockButtonDark ); + const style = getStylesFromColorScheme( + styles.addBlockButton, + styles.addBlockButtonDark + ); const onPress = () => { - this.setState( { - destinationRootClientId: this.props.destinationRootClientId, - shouldReplaceBlock: this.shouldReplaceBlock( 'default' ), - insertionIndex: this.getInsertionIndex( 'default' ), - }, onToggle ); + this.setState( + { + destinationRootClientId: this.props.destinationRootClientId, + shouldReplaceBlock: this.shouldReplaceBlock( 'default' ), + insertionIndex: this.getInsertionIndex( 'default' ), + }, + onToggle + ); }; const onLongPress = () => { @@ -155,11 +170,16 @@ export class Inserter extends Component { }; const onPickerSelect = ( insertionType ) => { - this.setState( { - destinationRootClientId: this.props.destinationRootClientId, - shouldReplaceBlock: this.shouldReplaceBlock( insertionType ), - insertionIndex: this.getInsertionIndex( insertionType ), - }, onToggle ); + this.setState( + { + destinationRootClientId: this.props.destinationRootClientId, + shouldReplaceBlock: this.shouldReplaceBlock( + insertionType + ), + insertionIndex: this.getInsertionIndex( insertionType ), + }, + onToggle + ); }; return ( @@ -191,10 +211,7 @@ export class Inserter extends Component { * @return {WPElement} Dropdown content element. */ renderContent( { onClose, isOpen } ) { - const { - clientId, - isAppender, - } = this.props; + const { clientId, isAppender } = this.props; const { destinationRootClientId, shouldReplaceBlock, @@ -240,22 +257,25 @@ export default compose( [ // `end` argument (id) can refer to the component which is removed // due to pressing `undo` button, that's why we need to check // if `getBlock( end) is valid, otherwise `null` is passed - const isAnyBlockSelected = ( ! isAppender && end && getBlock( end ) ); - const destinationRootClientId = isAnyBlockSelected ? - getBlockRootClientId( end ) : - rootClientId; - const selectedBlockIndex = getBlockIndex( end, destinationRootClientId ); + const isAnyBlockSelected = ! isAppender && end && getBlock( end ); + const destinationRootClientId = isAnyBlockSelected + ? getBlockRootClientId( end ) + : rootClientId; + const selectedBlockIndex = getBlockIndex( + end, + destinationRootClientId + ); const endOfRootIndex = getBlockOrder( rootClientId ).length; - const isSelectedUnmodifiedDefaultBlock = isAnyBlockSelected ? - isUnmodifiedDefaultBlock( getBlock( end ) ) : - undefined; + const isSelectedUnmodifiedDefaultBlock = isAnyBlockSelected + ? isUnmodifiedDefaultBlock( getBlock( end ) ) + : undefined; function getDefaultInsertionIndex() { - const { - getSettings, - } = select( 'core/block-editor' ); + const { getSettings } = select( 'core/block-editor' ); - const { __experimentalShouldInsertAtTheTop: shouldInsertAtTheTop } = getSettings(); + const { + __experimentalShouldInsertAtTheTop: shouldInsertAtTheTop, + } = getSettings(); // if post title is selected insert as first block if ( shouldInsertAtTheTop ) { @@ -282,13 +302,13 @@ export default compose( [ return endOfRootIndex; } - const insertionIndexBefore = isAnyBlockSelected ? - selectedBlockIndex : - 0; + const insertionIndexBefore = isAnyBlockSelected + ? selectedBlockIndex + : 0; - const insertionIndexAfter = isAnyBlockSelected ? - selectedBlockIndex + 1 : - endOfRootIndex; + const insertionIndexAfter = isAnyBlockSelected + ? selectedBlockIndex + 1 + : endOfRootIndex; return { destinationRootClientId, diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index 73a38286a15456..1baeaffc0f9a6f 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -25,11 +25,7 @@ import { __experimentalCreateInterpolateElement, createRef, } from '@wordpress/element'; -import { - PanelBody, - withSpokenMessages, - Tip, -} from '@wordpress/components'; +import { PanelBody, withSpokenMessages, Tip } from '@wordpress/components'; import { isReusableBlock, createBlock, @@ -63,7 +59,11 @@ const createBlocksFromInnerBlocksTemplate = ( innerBlocksTemplate ) => { return map( innerBlocksTemplate, ( [ name, attributes, innerBlocks = [] ] ) => - createBlock( name, attributes, createBlocksFromInnerBlocksTemplate( innerBlocks ) ) + createBlock( + name, + attributes, + createBlocksFromInnerBlocksTemplate( innerBlocks ) + ) ); }; @@ -131,23 +131,30 @@ export class InserterMenu extends Component { } ); } else { this.setState( { - openPanels: [ - ...this.state.openPanels, - panel, - ], + openPanels: [ ...this.state.openPanels, panel ], } ); this.props.setTimeout( () => { // We need a generic way to access the panel's container - scrollIntoView( this.panels[ panel ], this.inserterResults.current, { - alignWithTop: true, - } ); + scrollIntoView( + this.panels[ panel ], + this.inserterResults.current, + { + alignWithTop: true, + } + ); } ); } }; } - filterOpenPanels( filterValue, itemsPerCategory, itemsPerCollection, filteredItems, reusableItems ) { + filterOpenPanels( + filterValue, + itemsPerCategory, + itemsPerCollection, + filteredItems, + reusableItems + ) { if ( filterValue === this.state.filterValue ) { return this.state.openPanels; } @@ -169,25 +176,46 @@ export class InserterMenu extends Component { } filter( filterValue = '' ) { - const { categories, collections, debouncedSpeak, items, rootChildBlocks } = this.props; - - const filteredItems = searchItems( items, categories, collections, filterValue ); + const { + categories, + collections, + debouncedSpeak, + items, + rootChildBlocks, + } = this.props; + + const filteredItems = searchItems( + items, + categories, + collections, + filterValue + ); - const childItems = filter( filteredItems, ( { name } ) => includes( rootChildBlocks, name ) ); + const childItems = filter( filteredItems, ( { name } ) => + includes( rootChildBlocks, name ) + ); let suggestedItems = []; if ( ! filterValue ) { - const maxSuggestedItems = this.props.maxSuggestedItems || MAX_SUGGESTED_ITEMS; - suggestedItems = filter( items, ( item ) => item.utility > 0 ).slice( 0, maxSuggestedItems ); + const maxSuggestedItems = + this.props.maxSuggestedItems || MAX_SUGGESTED_ITEMS; + suggestedItems = filter( + items, + ( item ) => item.utility > 0 + ).slice( 0, maxSuggestedItems ); } const reusableItems = filter( filteredItems, { category: 'reusable' } ); const getCategoryIndex = ( item ) => { - return findIndex( categories, ( category ) => category.slug === item.category ); + return findIndex( + categories, + ( category ) => category.slug === item.category + ); }; const itemsPerCategory = flow( - ( itemList ) => filter( itemList, ( item ) => item.category !== 'reusable' ), + ( itemList ) => + filter( itemList, ( item ) => item.category !== 'reusable' ), ( itemList ) => sortBy( itemList, getCategoryIndex ), ( itemList ) => groupBy( itemList, 'category' ) )( filteredItems ); @@ -195,7 +223,9 @@ export class InserterMenu extends Component { // Create a new Object to avoid mutating this.props.collection const itemsPerCollection = { ...collections }; Object.keys( collections ).forEach( ( namespace ) => { - itemsPerCollection[ namespace ] = filteredItems.filter( ( item ) => getBlockNamespace( item ) === namespace ); + itemsPerCollection[ namespace ] = filteredItems.filter( + ( item ) => getBlockNamespace( item ) === namespace + ); if ( itemsPerCollection[ namespace ].length === 0 ) { delete itemsPerCollection[ namespace ]; } @@ -218,9 +248,14 @@ export class InserterMenu extends Component { ), } ); - const resultCount = Object.keys( itemsPerCategory ).reduce( ( accumulator, currentCategorySlug ) => { - return accumulator + itemsPerCategory[ currentCategorySlug ].length; - }, 0 ); + const resultCount = Object.keys( itemsPerCategory ).reduce( + ( accumulator, currentCategorySlug ) => { + return ( + accumulator + itemsPerCategory[ currentCategorySlug ].length + ); + }, + 0 + ); const resultsFoundMessage = sprintf( _n( '%d result found.', '%d results found.', resultCount ), @@ -230,14 +265,26 @@ export class InserterMenu extends Component { } onKeyDown( event ) { - if ( includes( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ], event.keyCode ) ) { + if ( + includes( + [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ], + event.keyCode + ) + ) { // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. event.stopPropagation(); } } render() { - const { categories, collections, instanceId, onSelect, rootClientId, showInserterHelpPanel } = this.props; + const { + categories, + collections, + instanceId, + onSelect, + rootClientId, + showInserterHelpPanel, + } = this.props; const { childItems, hoveredItem, @@ -249,8 +296,14 @@ export class InserterMenu extends Component { filterValue, } = this.state; const isPanelOpen = ( panel ) => openPanels.indexOf( panel ) !== -1; - const hasItems = ! isEmpty( suggestedItems ) || ! isEmpty( reusableItems ) || ! isEmpty( itemsPerCategory ) || ! isEmpty( itemsPerCollection ); - const hoveredItemBlockType = hoveredItem ? getBlockType( hoveredItem.name ) : null; + const hasItems = + ! isEmpty( suggestedItems ) || + ! isEmpty( reusableItems ) || + ! isEmpty( itemsPerCategory ) || + ! isEmpty( itemsPerCollection ); + const hoveredItemBlockType = hoveredItem + ? getBlockType( hoveredItem.name ) + : null; const hasHelpPanel = hasItems && showInserterHelpPanel; // Disable reason (no-autofocus): The inserter menu is a modal display, not one which @@ -268,7 +321,10 @@ export class InserterMenu extends Component { onKeyDown={ this.onKeyDown } > <div className="block-editor-inserter__main-area"> - <label htmlFor={ `block-editor-inserter__search-${ instanceId }` } className="screen-reader-text"> + <label + htmlFor={ `block-editor-inserter__search-${ instanceId }` } + className="screen-reader-text" + > { __( 'Search for a block' ) } </label> <input @@ -287,7 +343,6 @@ export class InserterMenu extends Component { role="region" aria-label={ __( 'Available block types' ) } > - <ChildBlocks rootClientId={ rootClientId } items={ childItems } @@ -295,19 +350,24 @@ export class InserterMenu extends Component { onHover={ this.onHover } /> - { !! suggestedItems.length && + { !! suggestedItems.length && ( <PanelBody title={ _x( 'Most used', 'blocks' ) } opened={ isPanelOpen( 'suggested' ) } onToggle={ this.onTogglePanel( 'suggested' ) } ref={ this.bindPanel( 'suggested' ) } > - <BlockTypesList items={ suggestedItems } onSelect={ onSelect } onHover={ this.onHover } /> + <BlockTypesList + items={ suggestedItems } + onSelect={ onSelect } + onHover={ this.onHover } + /> </PanelBody> - } + ) } { map( categories, ( category ) => { - const categoryItems = itemsPerCategory[ category.slug ]; + const categoryItems = + itemsPerCategory[ category.slug ]; if ( ! categoryItems || ! categoryItems.length ) { return null; } @@ -317,17 +377,27 @@ export class InserterMenu extends Component { title={ category.title } icon={ category.icon } opened={ isPanelOpen( category.slug ) } - onToggle={ this.onTogglePanel( category.slug ) } + onToggle={ this.onTogglePanel( + category.slug + ) } ref={ this.bindPanel( category.slug ) } > - <BlockTypesList items={ categoryItems } onSelect={ onSelect } onHover={ this.onHover } /> + <BlockTypesList + items={ categoryItems } + onSelect={ onSelect } + onHover={ this.onHover } + /> </PanelBody> ); } ) } { map( collections, ( collection, namespace ) => { - const collectionItems = itemsPerCollection[ namespace ]; - if ( ! collectionItems || ! collectionItems.length ) { + const collectionItems = + itemsPerCollection[ namespace ]; + if ( + ! collectionItems || + ! collectionItems.length + ) { return null; } @@ -340,7 +410,11 @@ export class InserterMenu extends Component { onToggle={ this.onTogglePanel( namespace ) } ref={ this.bindPanel( namespace ) } > - <BlockTypesList items={ collectionItems } onSelect={ onSelect } onHover={ this.onHover } /> + <BlockTypesList + items={ collectionItems } + onSelect={ onSelect } + onHover={ this.onHover } + /> </PanelBody> ); } ) } @@ -354,10 +428,16 @@ export class InserterMenu extends Component { icon="controls-repeat" ref={ this.bindPanel( 'reusable' ) } > - <BlockTypesList items={ reusableItems } onSelect={ onSelect } onHover={ this.onHover } /> + <BlockTypesList + items={ reusableItems } + onSelect={ onSelect } + onHover={ this.onHover } + /> <a className="block-editor-inserter__manage-reusable-blocks" - href={ addQueryArgs( 'edit.php', { post_type: 'wp_block' } ) } + href={ addQueryArgs( 'edit.php', { + post_type: 'wp_block', + } ) } > { __( 'Manage all reusable blocks' ) } </a> @@ -378,7 +458,9 @@ export class InserterMenu extends Component { } if ( ! hasItems ) { return ( - <p className="block-editor-inserter__no-results">{ __( 'No blocks found.' ) }</p> + <p className="block-editor-inserter__no-results"> + { __( 'No blocks found.' ) } + </p> ); } return null; @@ -395,21 +477,33 @@ export class InserterMenu extends Component { <BlockCard blockType={ hoveredItem } /> ) } <div className="block-editor-inserter__preview"> - { ( isReusableBlock( hoveredItem ) || hoveredItemBlockType.example ) ? ( + { isReusableBlock( hoveredItem ) || + hoveredItemBlockType.example ? ( <div className="block-editor-inserter__preview-content"> <BlockPreview padding={ 10 } viewportWidth={ 500 } blocks={ - hoveredItemBlockType.example ? - getBlockFromExample( hoveredItem.name, { - attributes: { - ...hoveredItemBlockType.example.attributes, - ...hoveredItem.initialAttributes, - }, - innerBlocks: hoveredItemBlockType.example.innerBlocks, - } ) : - createBlock( hoveredItem.name, hoveredItem.initialAttributes ) + hoveredItemBlockType.example + ? getBlockFromExample( + hoveredItem.name, + { + attributes: { + ...hoveredItemBlockType + .example + .attributes, + ...hoveredItem.initialAttributes, + }, + innerBlocks: + hoveredItemBlockType + .example + .innerBlocks, + } + ) + : createBlock( + hoveredItem.name, + hoveredItem.initialAttributes + ) } /> </div> @@ -424,7 +518,9 @@ export class InserterMenu extends Component { { ! hoveredItem && ( <div className="block-editor-inserter__menu-help-panel-no-block"> <div className="block-editor-inserter__menu-help-panel-no-block-text"> - <div className="block-editor-inserter__menu-help-panel-title">{ __( 'Content blocks' ) }</div> + <div className="block-editor-inserter__menu-help-panel-title"> + { __( 'Content blocks' ) } + </div> <p> { __( 'Welcome to the wonderful world of blocks! Blocks are the basis of all content within the editor.' @@ -443,7 +539,9 @@ export class InserterMenu extends Component { </div> <Tip> { __experimentalCreateInterpolateElement( - __( 'While writing, you can press <kbd>/</kbd> to quickly insert new blocks.' ), + __( + 'While writing, you can press <kbd>/</kbd> to quickly insert new blocks.' + ), { kbd: <kbd /> } ) } </Tip> @@ -458,49 +556,57 @@ export class InserterMenu extends Component { } export default compose( - withSelect( ( select, { clientId, isAppender, rootClientId, showInserterHelpPanel } ) => { - const { - getInserterItems, - getBlockName, - getBlockRootClientId, - getBlockSelectionEnd, - getSettings, - } = select( 'core/block-editor' ); - const { - getCategories, - getCollections, - getChildBlockNames, - } = select( 'core/blocks' ); - - let destinationRootClientId = rootClientId; - if ( ! destinationRootClientId && ! clientId && ! isAppender ) { - const end = getBlockSelectionEnd(); - if ( end ) { - destinationRootClientId = getBlockRootClientId( end ) || undefined; + withSelect( + ( + select, + { clientId, isAppender, rootClientId, showInserterHelpPanel } + ) => { + const { + getInserterItems, + getBlockName, + getBlockRootClientId, + getBlockSelectionEnd, + getSettings, + } = select( 'core/block-editor' ); + const { + getCategories, + getCollections, + getChildBlockNames, + } = select( 'core/blocks' ); + + let destinationRootClientId = rootClientId; + if ( ! destinationRootClientId && ! clientId && ! isAppender ) { + const end = getBlockSelectionEnd(); + if ( end ) { + destinationRootClientId = + getBlockRootClientId( end ) || undefined; + } } - } - const destinationRootBlockName = getBlockName( destinationRootClientId ); - - const { - showInserterHelpPanel: showInserterHelpPanelSetting, - __experimentalFetchReusableBlocks: fetchReusableBlocks, - } = getSettings(); + const destinationRootBlockName = getBlockName( + destinationRootClientId + ); - return { - categories: getCategories(), - collections: getCollections(), - rootChildBlocks: getChildBlockNames( destinationRootBlockName ), - items: getInserterItems( destinationRootClientId ), - showInserterHelpPanel: showInserterHelpPanel && showInserterHelpPanelSetting, - destinationRootClientId, - fetchReusableBlocks, - }; - } ), + const { + showInserterHelpPanel: showInserterHelpPanelSetting, + __experimentalFetchReusableBlocks: fetchReusableBlocks, + } = getSettings(); + + return { + categories: getCategories(), + collections: getCollections(), + rootChildBlocks: getChildBlockNames( destinationRootBlockName ), + items: getInserterItems( destinationRootClientId ), + showInserterHelpPanel: + showInserterHelpPanel && showInserterHelpPanelSetting, + destinationRootClientId, + fetchReusableBlocks, + }; + } + ), withDispatch( ( dispatch, ownProps, { select } ) => { - const { - showInsertionPoint, - hideInsertionPoint, - } = dispatch( 'core/block-editor' ); + const { showInsertionPoint, hideInsertionPoint } = dispatch( + 'core/block-editor' + ); // To avoid duplication, getInsertionIndex is extracted and used in two event handlers // This breaks the withDispatch not containing any logic rule. @@ -537,13 +643,10 @@ export default compose( }, hideInsertionPoint, onSelect( item ) { - const { - replaceBlocks, - insertBlock, - } = dispatch( 'core/block-editor' ); - const { - getSelectedBlock, - } = select( 'core/block-editor' ); + const { replaceBlocks, insertBlock } = dispatch( + 'core/block-editor' + ); + const { getSelectedBlock } = select( 'core/block-editor' ); const { isAppender, onSelect, @@ -557,7 +660,11 @@ export default compose( createBlocksFromInnerBlocksTemplate( innerBlocks ) ); - if ( ! isAppender && selectedBlock && isUnmodifiedDefaultBlock( selectedBlock ) ) { + if ( + ! isAppender && + selectedBlock && + isUnmodifiedDefaultBlock( selectedBlock ) + ) { replaceBlocks( selectedBlock.clientId, insertedBlock ); } else { insertBlock( @@ -569,7 +676,10 @@ export default compose( if ( ! selectBlockOnInsert ) { // translators: %s: the name of the block that has been added - const message = sprintf( __( '%s block added' ), title ); + const message = sprintf( + __( '%s block added' ), + title + ); speak( message ); } } diff --git a/packages/block-editor/src/components/inserter/menu.native.js b/packages/block-editor/src/components/inserter/menu.native.js index 79d3ffe7520ac3..27573aeca96a7a 100644 --- a/packages/block-editor/src/components/inserter/menu.native.js +++ b/packages/block-editor/src/components/inserter/menu.native.js @@ -9,7 +9,11 @@ import { FlatList, View, Text, TouchableHighlight } from 'react-native'; import { Component } from '@wordpress/element'; import { createBlock } from '@wordpress/blocks'; import { withDispatch, withSelect } from '@wordpress/data'; -import { withInstanceId, compose, withPreferredColorScheme } from '@wordpress/compose'; +import { + withInstanceId, + compose, + withPreferredColorScheme, +} from '@wordpress/compose'; import { BottomSheet, Icon } from '@wordpress/components'; /** @@ -38,11 +42,18 @@ export class InserterMenu extends Component { calculateNumberOfColumns() { const bottomSheetWidth = BottomSheet.getWidth(); - const { paddingLeft: itemPaddingLeft, paddingRight: itemPaddingRight } = styles.modalItem; - const { paddingLeft: containerPaddingLeft, paddingRight: containerPaddingRight } = styles.content; + const { + paddingLeft: itemPaddingLeft, + paddingRight: itemPaddingRight, + } = styles.modalItem; + const { + paddingLeft: containerPaddingLeft, + paddingRight: containerPaddingRight, + } = styles.content; const { width: itemWidth } = styles.modalIconWrapper; const itemTotalWidth = itemWidth + itemPaddingLeft + itemPaddingRight; - const containerTotalWidth = bottomSheetWidth - ( containerPaddingLeft + containerPaddingRight ); + const containerTotalWidth = + bottomSheetWidth - ( containerPaddingLeft + containerPaddingRight ); return Math.floor( containerTotalWidth / itemTotalWidth ); } @@ -63,9 +74,18 @@ export class InserterMenu extends Component { render() { const { getStylesFromColorScheme } = this.props; const bottomPadding = styles.contentBottomPadding; - const modalIconWrapperStyle = getStylesFromColorScheme( styles.modalIconWrapper, styles.modalIconWrapperDark ); - const modalIconStyle = getStylesFromColorScheme( styles.modalIcon, styles.modalIconDark ); - const modalItemLabelStyle = getStylesFromColorScheme( styles.modalItemLabel, styles.modalItemLabelDark ); + const modalIconWrapperStyle = getStylesFromColorScheme( + styles.modalIconWrapper, + styles.modalIconWrapperDark + ); + const modalIconStyle = getStylesFromColorScheme( + styles.modalIcon, + styles.modalIconDark + ); + const modalItemLabelStyle = getStylesFromColorScheme( + styles.modalItemLabel, + styles.modalItemLabelDark + ); return ( <BottomSheet @@ -81,27 +101,34 @@ export class InserterMenu extends Component { keyboardShouldPersistTaps="always" numColumns={ this.state.numberOfColumns } data={ this.props.items } - ItemSeparatorComponent={ () => + ItemSeparatorComponent={ () => ( <View style={ styles.rowSeparator } /> - } + ) } keyExtractor={ ( item ) => item.name } - renderItem={ ( { item } ) => + renderItem={ ( { item } ) => ( <TouchableHighlight style={ styles.touchableArea } underlayColor="transparent" - activeOpacity={ .5 } + activeOpacity={ 0.5 } accessibilityLabel={ item.title } - onPress={ () => this.props.onSelect( item ) }> + onPress={ () => this.props.onSelect( item ) } + > <View style={ styles.modalItem }> <View style={ modalIconWrapperStyle }> <View style={ modalIconStyle }> - <Icon icon={ item.icon.src } fill={ modalIconStyle.fill } size={ modalIconStyle.width } /> + <Icon + icon={ item.icon.src } + fill={ modalIconStyle.fill } + size={ modalIconStyle.width } + /> </View> </View> - <Text style={ modalItemLabelStyle }>{ item.title }</Text> + <Text style={ modalItemLabelStyle }> + { item.title } + </Text> </View> </TouchableHighlight> - } + ) } /> </BottomSheet> ); @@ -117,20 +144,23 @@ export default compose( getBlockSelectionEnd, getSettings, } = select( 'core/block-editor' ); - const { - getChildBlockNames, - } = select( 'core/blocks' ); + const { getChildBlockNames } = select( 'core/blocks' ); let destinationRootClientId = rootClientId; if ( ! destinationRootClientId && ! clientId && ! isAppender ) { const end = getBlockSelectionEnd(); if ( end ) { - destinationRootClientId = getBlockRootClientId( end ) || undefined; + destinationRootClientId = + getBlockRootClientId( end ) || undefined; } } - const destinationRootBlockName = getBlockName( destinationRootClientId ); + const destinationRootBlockName = getBlockName( + destinationRootClientId + ); - const { __experimentalShouldInsertAtTheTop: shouldInsertAtTheTop } = getSettings(); + const { + __experimentalShouldInsertAtTheTop: shouldInsertAtTheTop, + } = getSettings(); return { rootChildBlocks: getChildBlockNames( destinationRootBlockName ), @@ -153,10 +183,9 @@ export default compose( return { showInsertionPoint() { if ( ownProps.shouldReplaceBlock ) { - const { - getBlockOrder, - getBlockCount, - } = select( 'core/block-editor' ); + const { getBlockOrder, getBlockCount } = select( + 'core/block-editor' + ); const count = getBlockCount(); if ( count === 1 ) { @@ -174,7 +203,7 @@ export default compose( } showInsertionPoint( ownProps.destinationRootClientId, - ownProps.insertionIndex, + ownProps.insertionIndex ); }, hideInsertionPoint, @@ -195,11 +224,11 @@ export default compose( insertDefaultBlock( {}, ownProps.destinationRootClientId, - ownProps.insertionIndex, + ownProps.insertionIndex ); }, }; } ), withInstanceId, - withPreferredColorScheme, + withPreferredColorScheme )( InserterMenu ); diff --git a/packages/block-editor/src/components/inserter/search-items.js b/packages/block-editor/src/components/inserter/search-items.js index a114b4ee0a66e1..23bae5d9b3860d 100644 --- a/packages/block-editor/src/components/inserter/search-items.js +++ b/packages/block-editor/src/components/inserter/search-items.js @@ -39,7 +39,8 @@ const removeMatchingTerms = ( unmatchedTerms, unprocessedTerms ) => { return differenceWith( unmatchedTerms, normalizeSearchTerm( unprocessedTerms ), - ( unmatchedTerm, unprocessedTerm ) => unprocessedTerm.includes( unmatchedTerm ) + ( unmatchedTerm, unprocessedTerm ) => + unprocessedTerm.includes( unmatchedTerm ) ); }; @@ -60,68 +61,77 @@ export const searchItems = ( items, categories, collections, searchTerm ) => { return items; } - return items.filter( ( { name, title, category, keywords = [], patterns = [] } ) => { - let unmatchedTerms = removeMatchingTerms( - normalizedSearchTerms, - title - ); - - if ( unmatchedTerms.length === 0 ) { - return true; - } - - unmatchedTerms = removeMatchingTerms( - unmatchedTerms, - keywords.join( ' ' ), - ); - - if ( unmatchedTerms.length === 0 ) { - return true; - } - - unmatchedTerms = removeMatchingTerms( - unmatchedTerms, - get( find( categories, { slug: category } ), [ 'title' ] ), - ); - - const itemCollection = collections[ name.split( '/' )[ 0 ] ]; - if ( itemCollection ) { - unmatchedTerms = removeMatchingTerms( - unmatchedTerms, - itemCollection.title - ); - } - - if ( unmatchedTerms.length === 0 ) { - return true; - } - - unmatchedTerms = removeMatchingTerms( - unmatchedTerms, - patterns.map( ( pattern ) => pattern.title ).join( ' ' ), - ); - - return unmatchedTerms.length === 0; - } ).map( ( item ) => { - if ( isEmpty( item.patterns ) ) { - return item; - } - - const matchedPatterns = item.patterns.filter( ( pattern ) => { - return intersectionWith( - normalizedSearchTerms, - normalizeSearchTerm( pattern.title ), - ( termToMatch, labelTerm ) => labelTerm.includes( termToMatch ) - ).length > 0; + return items + .filter( + ( { name, title, category, keywords = [], variations = [] } ) => { + let unmatchedTerms = removeMatchingTerms( + normalizedSearchTerms, + title + ); + + if ( unmatchedTerms.length === 0 ) { + return true; + } + + unmatchedTerms = removeMatchingTerms( + unmatchedTerms, + keywords.join( ' ' ) + ); + + if ( unmatchedTerms.length === 0 ) { + return true; + } + + unmatchedTerms = removeMatchingTerms( + unmatchedTerms, + get( find( categories, { slug: category } ), [ 'title' ] ) + ); + + const itemCollection = collections[ name.split( '/' )[ 0 ] ]; + if ( itemCollection ) { + unmatchedTerms = removeMatchingTerms( + unmatchedTerms, + itemCollection.title + ); + } + + if ( unmatchedTerms.length === 0 ) { + return true; + } + + unmatchedTerms = removeMatchingTerms( + unmatchedTerms, + variations + .map( ( variation ) => variation.title ) + .join( ' ' ) + ); + + return unmatchedTerms.length === 0; + } + ) + .map( ( item ) => { + if ( isEmpty( item.variations ) ) { + return item; + } + + const matchedVariations = item.variations.filter( ( variation ) => { + return ( + intersectionWith( + normalizedSearchTerms, + normalizeSearchTerm( variation.title ), + ( termToMatch, labelTerm ) => + labelTerm.includes( termToMatch ) + ).length > 0 + ); + } ); + // When no partterns matched, fallback to all variations. + if ( isEmpty( matchedVariations ) ) { + return item; + } + + return { + ...item, + variations: matchedVariations, + }; } ); - // When no partterns matched, fallback to all patterns. - if ( isEmpty( matchedPatterns ) ) { - return item; - } - - return { - ...item, - patterns: matchedPatterns, - }; - } ); }; diff --git a/packages/block-editor/src/components/inserter/test/fixtures/index.js b/packages/block-editor/src/components/inserter/test/fixtures/index.js index e9df0a3dbc75ed..d3b46654736c93 100644 --- a/packages/block-editor/src/components/inserter/test/fixtures/index.js +++ b/packages/block-editor/src/components/inserter/test/fixtures/index.js @@ -24,26 +24,26 @@ export const textItem = { utility: 1, }; -export const withPatternsItem = { - id: 'core/block-with-patterns', - name: 'core/block-with-patterns', +export const withVariationsItem = { + id: 'core/block-with-variations', + name: 'core/block-with-variations', initialAttributes: {}, - title: 'With Patterns', + title: 'With Variations', category: 'widgets', isDisabled: false, utility: 0, - patterns: [ + variations: [ { - name: 'pattern-one', - title: 'Pattern One', + name: 'variation-one', + title: 'Variation One', }, { - name: 'pattern-two', - title: 'Pattern Two', + name: 'variation-two', + title: 'Variation Two', }, { - name: 'pattern-three', - title: 'Pattern Three', + name: 'variation-three', + title: 'Variation Three', }, ], }; @@ -111,7 +111,7 @@ export const reusableItem = { export default [ textItem, - withPatternsItem, + withVariationsItem, advancedTextItem, someOtherItem, moreItem, diff --git a/packages/block-editor/src/components/inserter/test/menu.js b/packages/block-editor/src/components/inserter/test/menu.js index 4722a392d89efa..70881f2f30ce3a 100644 --- a/packages/block-editor/src/components/inserter/test/menu.js +++ b/packages/block-editor/src/components/inserter/test/menu.js @@ -59,8 +59,9 @@ const assertNoResultsMessageNotToBePresent = ( element ) => { }; const assertOpenedPanels = ( element, expectedOpen = 0 ) => { - expect( element.querySelectorAll( '.components-panel__body.is-opened ' ) ) - .toHaveLength( expectedOpen ); + expect( + element.querySelectorAll( '.components-panel__body.is-opened ' ) + ).toHaveLength( expectedOpen ); }; const getTabButtonWithContent = ( element, content ) => { @@ -75,8 +76,12 @@ const getTabButtonWithContent = ( element, content ) => { }; const performSearchWithText = ( element, searchText ) => { - const searchElement = element.querySelector( '.block-editor-inserter__search' ); - TestUtils.Simulate.change( searchElement, { target: { value: searchText } } ); + const searchElement = element.querySelector( + '.block-editor-inserter__search' + ); + TestUtils.Simulate.change( searchElement, { + target: { value: searchText }, + } ); }; describe( 'InserterMenu', () => { @@ -89,9 +94,9 @@ describe( 'InserterMenu', () => { } ); it( 'should show nothing if there are no items', () => { - const element = initializeMenuDefaultStateAndReturnElement( - { items: [] } - ); + const element = initializeMenuDefaultStateAndReturnElement( { + items: [], + } ); const visibleBlocks = element.querySelector( '.block-editor-block-types-list__item' ); @@ -113,9 +118,9 @@ describe( 'InserterMenu', () => { } ); it( 'should limit the number of items shown in the suggested tab', () => { - const element = initializeMenuDefaultStateAndReturnElement( - { maxSuggestedItems: 2 } - ); + const element = initializeMenuDefaultStateAndReturnElement( { + maxSuggestedItems: 2, + } ); const visibleBlocks = element.querySelectorAll( '.block-editor-block-types-list__list-item' ); @@ -161,7 +166,10 @@ describe( 'InserterMenu', () => { it( 'should show the common category blocks', () => { const element = initializeAllClosedMenuStateAndReturnElement(); - const commonBlocksTab = getTabButtonWithContent( element, 'Common blocks' ); + const commonBlocksTab = getTabButtonWithContent( + element, + 'Common blocks' + ); TestUtils.Simulate.click( commonBlocksTab ); diff --git a/packages/block-editor/src/components/inserter/test/search-items.js b/packages/block-editor/src/components/inserter/test/search-items.js index c42b18da678b27..01c0bace323283 100644 --- a/packages/block-editor/src/components/inserter/test/search-items.js +++ b/packages/block-editor/src/components/inserter/test/search-items.js @@ -10,40 +10,29 @@ import items, { youtubeItem, textEmbedItem, } from './fixtures'; -import { - normalizeSearchTerm, - searchItems, -} from '../search-items'; +import { normalizeSearchTerm, searchItems } from '../search-items'; describe( 'normalizeSearchTerm', () => { it( 'should return an empty array when no words detected', () => { - expect( normalizeSearchTerm( ' - !? *** ' ) ).toEqual( - [] - ); + expect( normalizeSearchTerm( ' - !? *** ' ) ).toEqual( [] ); } ); it( 'should remove diacritics', () => { - expect( normalizeSearchTerm( 'média' ) ).toEqual( - [ 'media' ] - ); + expect( normalizeSearchTerm( 'média' ) ).toEqual( [ 'media' ] ); } ); it( 'should trim whitespace', () => { - expect( normalizeSearchTerm( ' média ' ) ).toEqual( - [ 'media' ] - ); + expect( normalizeSearchTerm( ' média ' ) ).toEqual( [ 'media' ] ); } ); it( 'should convert to lowercase', () => { - expect( normalizeSearchTerm( ' Média ' ) ).toEqual( - [ 'media' ] - ); + expect( normalizeSearchTerm( ' Média ' ) ).toEqual( [ 'media' ] ); } ); it( 'should extract only words', () => { - expect( normalizeSearchTerm( ' Média & Text Tag-Cloud > 123' ) ).toEqual( - [ 'media', 'text', 'tag', 'cloud', '123' ] - ); + expect( + normalizeSearchTerm( ' Média & Text Tag-Cloud > 123' ) + ).toEqual( [ 'media', 'text', 'tag', 'cloud', '123' ] ); } ); } ); @@ -55,48 +44,62 @@ describe( 'searchItems', () => { } ); it( 'should search items using the title ignoring case', () => { - expect( searchItems( items, categories, collections, 'TEXT' ) ).toEqual( - [ textItem, advancedTextItem, textEmbedItem ] - ); + expect( + searchItems( items, categories, collections, 'TEXT' ) + ).toEqual( [ textItem, advancedTextItem, textEmbedItem ] ); } ); it( 'should search items using the keywords and partial terms', () => { - expect( searchItems( items, categories, collections, 'GOOGL' ) ).toEqual( - [ youtubeItem ] - ); + expect( + searchItems( items, categories, collections, 'GOOGL' ) + ).toEqual( [ youtubeItem ] ); } ); it( 'should search items using the categories', () => { - expect( searchItems( items, categories, collections, 'LAYOUT' ) ).toEqual( - [ moreItem ] - ); + expect( + searchItems( items, categories, collections, 'LAYOUT' ) + ).toEqual( [ moreItem ] ); } ); it( 'should ignore a leading slash on a search term', () => { - expect( searchItems( items, categories, collections, '/GOOGL' ) ).toEqual( - [ youtubeItem ] - ); + expect( + searchItems( items, categories, collections, '/GOOGL' ) + ).toEqual( [ youtubeItem ] ); } ); it( 'should match words using the mix of the title, category and keywords', () => { - expect( searchItems( items, categories, collections, 'youtube embed video' ) ).toEqual( - [ youtubeItem ] - ); + expect( + searchItems( items, categories, collections, 'youtube embed video' ) + ).toEqual( [ youtubeItem ] ); } ); - it( 'should match words using also patterns and return all matched patterns', () => { - const filteredItems = searchItems( items, categories, collections, 'pattern' ); + it( 'should match words using also variations and return all matched variations', () => { + const filteredItems = searchItems( + items, + categories, + collections, + 'variation' + ); expect( filteredItems ).toHaveLength( 1 ); - expect( filteredItems[ 0 ].patterns ).toHaveLength( 3 ); + expect( filteredItems[ 0 ].variations ).toHaveLength( 3 ); } ); - it( 'should match words using also patterns and filter out unmatched patterns', () => { - const filteredItems = searchItems( items, categories, collections, 'patterns two three' ); + it( 'should match words using also variations and filter out unmatched variations', () => { + const filteredItems = searchItems( + items, + categories, + collections, + 'variations two three' + ); expect( filteredItems ).toHaveLength( 1 ); - expect( filteredItems[ 0 ].patterns ).toHaveLength( 2 ); - expect( filteredItems[ 0 ].patterns[ 0 ].title ).toBe( 'Pattern Two' ); - expect( filteredItems[ 0 ].patterns[ 1 ].title ).toBe( 'Pattern Three' ); + expect( filteredItems[ 0 ].variations ).toHaveLength( 2 ); + expect( filteredItems[ 0 ].variations[ 0 ].title ).toBe( + 'Variation Two' + ); + expect( filteredItems[ 0 ].variations[ 1 ].title ).toBe( + 'Variation Three' + ); } ); } ); diff --git a/packages/block-editor/src/components/inspector-controls/index.native.js b/packages/block-editor/src/components/inspector-controls/index.native.js index 341a7bcc049e7a..730dfaba5b37d3 100644 --- a/packages/block-editor/src/components/inspector-controls/index.native.js +++ b/packages/block-editor/src/components/inspector-controls/index.native.js @@ -20,7 +20,7 @@ const FillWithSettingsButton = ( { children, ...props } ) => { return ( <> <Fill { ...props }>{ children }</Fill> - { React.Children.count( children ) > 0 && ( <BlockSettingsButton /> ) } + { React.Children.count( children ) > 0 && <BlockSettingsButton /> } </> ); }; @@ -33,4 +33,3 @@ InspectorControls.Slot = Slot; * @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/inspector-controls/README.md */ export default InspectorControls; - diff --git a/packages/block-editor/src/components/keyboard-shortcuts/index.js b/packages/block-editor/src/components/keyboard-shortcuts/index.js index 7363c5235b6c08..3259707632cd94 100644 --- a/packages/block-editor/src/components/keyboard-shortcuts/index.js +++ b/packages/block-editor/src/components/keyboard-shortcuts/index.js @@ -13,7 +13,9 @@ import { __ } from '@wordpress/i18n'; function KeyboardShortcuts() { // Shortcuts Logic const { clientIds, rootBlocksClientIds } = useSelect( ( select ) => { - const { getSelectedBlockClientIds, getBlockOrder } = select( 'core/block-editor' ); + const { getSelectedBlockClientIds, getBlockOrder } = select( + 'core/block-editor' + ); return { clientIds: getSelectedBlockClientIds(), rootBlocksClientIds: getBlockOrder(), @@ -33,10 +35,13 @@ function KeyboardShortcuts() { // Prevents reposition Chrome devtools pane shortcut when devtools are open. useShortcut( 'core/block-editor/duplicate', - useCallback( ( event ) => { - event.preventDefault(); - duplicateBlocks( clientIds ); - }, [ clientIds, duplicateBlocks ] ), + useCallback( + ( event ) => { + event.preventDefault(); + duplicateBlocks( clientIds ); + }, + [ clientIds, duplicateBlocks ] + ), { bindGlobal: true, isDisabled: clientIds.length === 0 } ); @@ -44,10 +49,13 @@ function KeyboardShortcuts() { // is used to prevent any obscure unknown shortcuts from triggering. useShortcut( 'core/block-editor/remove', - useCallback( ( event ) => { - event.preventDefault(); - removeBlocks( clientIds ); - }, [ clientIds, removeBlocks ] ), + useCallback( + ( event ) => { + event.preventDefault(); + removeBlocks( clientIds ); + }, + [ clientIds, removeBlocks ] + ), { bindGlobal: true, isDisabled: clientIds.length === 0 } ); @@ -55,44 +63,65 @@ function KeyboardShortcuts() { // is used to prevent any obscure unknown shortcuts from triggering. useShortcut( 'core/block-editor/insert-after', - useCallback( ( event ) => { - event.preventDefault(); - insertAfterBlock( last( clientIds ) ); - }, [ clientIds, insertAfterBlock ] ), + useCallback( + ( event ) => { + event.preventDefault(); + insertAfterBlock( last( clientIds ) ); + }, + [ clientIds, insertAfterBlock ] + ), { bindGlobal: true, isDisabled: clientIds.length === 0 } ); // Prevent 'view recently closed tabs' in Opera using preventDefault. useShortcut( 'core/block-editor/insert-before', - useCallback( ( event ) => { - event.preventDefault(); - insertBeforeBlock( first( clientIds ) ); - }, [ clientIds, insertBeforeBlock ] ), + useCallback( + ( event ) => { + event.preventDefault(); + insertBeforeBlock( first( clientIds ) ); + }, + [ clientIds, insertBeforeBlock ] + ), { bindGlobal: true, isDisabled: clientIds.length === 0 } ); useShortcut( 'core/block-editor/delete-multi-selection', - useCallback( ( event ) => { - event.preventDefault(); - removeBlocks( clientIds ); - }, [ clientIds, removeBlocks ] ), + useCallback( + ( event ) => { + event.preventDefault(); + removeBlocks( clientIds ); + }, + [ clientIds, removeBlocks ] + ), { isDisabled: clientIds.length < 1 } ); - useShortcut( 'core/block-editor/select-all', useCallback( ( event ) => { - event.preventDefault(); - multiSelect( first( rootBlocksClientIds ), last( rootBlocksClientIds ) ); - }, [ rootBlocksClientIds, multiSelect ] ) ); + useShortcut( + 'core/block-editor/select-all', + useCallback( + ( event ) => { + event.preventDefault(); + multiSelect( + first( rootBlocksClientIds ), + last( rootBlocksClientIds ) + ); + }, + [ rootBlocksClientIds, multiSelect ] + ) + ); useShortcut( 'core/block-editor/unselect', - useCallback( ( event ) => { - event.preventDefault(); - clearSelectedBlock(); - window.getSelection().removeAllRanges(); - }, [ clientIds, clearSelectedBlock ] ), + useCallback( + ( event ) => { + event.preventDefault(); + clearSelectedBlock(); + window.getSelection().removeAllRanges(); + }, + [ clientIds, clearSelectedBlock ] + ), { isDisabled: clientIds.length < 2 } ); @@ -126,7 +155,9 @@ function KeyboardShortcutsRegister() { registerShortcut( { name: 'core/block-editor/insert-before', category: 'block', - description: __( 'Insert a new block before the selected block(s).' ), + description: __( + 'Insert a new block before the selected block(s).' + ), keyCombination: { modifier: 'primaryAlt', character: 't', @@ -136,7 +167,9 @@ function KeyboardShortcutsRegister() { registerShortcut( { name: 'core/block-editor/insert-after', category: 'block', - description: __( 'Insert a new block after the selected block(s).' ), + description: __( + 'Insert a new block after the selected block(s).' + ), keyCombination: { modifier: 'primaryAlt', character: 'y', @@ -150,15 +183,19 @@ function KeyboardShortcutsRegister() { keyCombination: { character: 'del', }, - aliases: [ { - character: 'backspace', - } ], + aliases: [ + { + character: 'backspace', + }, + ], } ); registerShortcut( { name: 'core/block-editor/select-all', category: 'selection', - description: __( 'Select all text when typing. Press again to select all blocks.' ), + description: __( + 'Select all text when typing. Press again to select all blocks.' + ), keyCombination: { modifier: 'primary', character: 'a', diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index d56dcc55f7b383..cda7986a9f8c44 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -9,7 +9,13 @@ import { noop, startsWith } from 'lodash'; */ import { Button, ExternalLink, VisuallyHidden } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -import { useRef, useCallback, useState, Fragment, useEffect } from '@wordpress/element'; +import { + useRef, + useCallback, + useState, + Fragment, + useEffect, +} from '@wordpress/element'; import { safeDecodeURI, filterURLForDisplay, @@ -92,16 +98,22 @@ function LinkControl( { } ) { const wrapperNode = useRef(); const instanceId = useInstanceId( LinkControl ); - const [ inputValue, setInputValue ] = useState( ( value && value.url ) || '' ); - const [ isEditingLink, setIsEditingLink ] = useState( ! value || ! value.url ); + const [ inputValue, setInputValue ] = useState( + ( value && value.url ) || '' + ); + const [ isEditingLink, setIsEditingLink ] = useState( + ! value || ! value.url + ); const isEndingEditWithFocus = useRef( false ); const { fetchSearchSuggestions } = useSelect( ( select ) => { const { getSettings } = select( 'core/block-editor' ); return { - fetchSearchSuggestions: getSettings().__experimentalFetchLinkSuggestions, + fetchSearchSuggestions: getSettings() + .__experimentalFetchLinkSuggestions, }; }, [] ); - const displayURL = ( value && filterURLForDisplay( safeDecodeURI( value.url ) ) ) || ''; + const displayURL = + ( value && filterURLForDisplay( safeDecodeURI( value.url ) ) ) || ''; useEffect( () => { // When `isEditingLink` is set to `false`, a focus loss could occur @@ -111,19 +123,17 @@ function LinkControl( { // edit mode would render the read-only mode's link element, it isn't // guaranteed. The link input may continue to be shown if the next value // is still unassigned after calling `onChange`. - const hadFocusLoss = ( + const hadFocusLoss = isEndingEditWithFocus.current && wrapperNode.current && - ! wrapperNode.current.contains( document.activeElement ) - ); + ! wrapperNode.current.contains( document.activeElement ); if ( hadFocusLoss ) { // Prefer to focus a natural focusable descendent of the wrapper, // but settle for the wrapper if there are no other options. - const nextFocusTarget = ( + const nextFocusTarget = focus.focusable.find( wrapperNode.current )[ 0 ] || - wrapperNode.current - ); + wrapperNode.current; nextFocusTarget.focus(); } @@ -161,14 +171,14 @@ function LinkControl( { type = 'internal'; } - return Promise.resolve( - [ { + return Promise.resolve( [ + { id: '-1', title: val, url: type === 'URL' ? prependHTTP( val ) : val, type, - } ] - ); + }, + ] ); }; const handleEntitySearch = async ( val, args ) => { @@ -184,7 +194,9 @@ function LinkControl( { // If it's potentially a URL search then concat on a URL search suggestion // just for good measure. That way once the actual results run out we always // have a URL option to fallback on. - return couldBeURL && ! args.isInitialSuggestions ? results[ 0 ].concat( results[ 1 ] ) : results[ 0 ]; + return couldBeURL && ! args.isInitialSuggestions + ? results[ 0 ].concat( results[ 1 ] ) + : results[ 0 ]; }; /** @@ -192,61 +204,101 @@ function LinkControl( { * the next render, if focus was within the wrapper when editing finished. */ function stopEditing() { - isEndingEditWithFocus.current = ( + isEndingEditWithFocus.current = !! wrapperNode.current && - wrapperNode.current.contains( document.activeElement ) - ); + wrapperNode.current.contains( document.activeElement ); setIsEditingLink( false ); } // Effects - const getSearchHandler = useCallback( ( val, args ) => { - const protocol = getProtocol( val ) || ''; - const isMailto = protocol.includes( 'mailto' ); - const isInternal = startsWith( val, '#' ); - const isTel = protocol.includes( 'tel' ); - - const handleManualEntry = isInternal || isMailto || isTel || isURL( val ) || ( val && val.includes( 'www.' ) ); - - return ( handleManualEntry ) ? handleDirectEntry( val, args ) : handleEntitySearch( val, args ); - }, [ handleDirectEntry, fetchSearchSuggestions ] ); + const getSearchHandler = useCallback( + ( val, args ) => { + const protocol = getProtocol( val ) || ''; + const isMailto = protocol.includes( 'mailto' ); + const isInternal = startsWith( val, '#' ); + const isTel = protocol.includes( 'tel' ); + + const handleManualEntry = + isInternal || + isMailto || + isTel || + isURL( val ) || + ( val && val.includes( 'www.' ) ); + + return handleManualEntry + ? handleDirectEntry( val, args ) + : handleEntitySearch( val, args ); + }, + [ handleDirectEntry, fetchSearchSuggestions ] + ); // Render Components - const renderSearchResults = ( { suggestionsListProps, buildSuggestionItemProps, suggestions, selectedSuggestion, isLoading, isInitialSuggestions } ) => { - const resultsListClasses = classnames( 'block-editor-link-control__search-results', { - 'is-loading': isLoading, - } ); + const renderSearchResults = ( { + suggestionsListProps, + buildSuggestionItemProps, + suggestions, + selectedSuggestion, + isLoading, + isInitialSuggestions, + } ) => { + const resultsListClasses = classnames( + 'block-editor-link-control__search-results', + { + 'is-loading': isLoading, + } + ); const manualLinkEntryTypes = [ 'url', 'mailto', 'tel', 'internal' ]; - const searchResultsLabelId = isInitialSuggestions ? `block-editor-link-control-search-results-label-${ instanceId }` : undefined; - const labelText = isInitialSuggestions ? __( 'Recently updated' ) : sprintf( __( 'Search results for %s' ), inputValue ); + const searchResultsLabelId = isInitialSuggestions + ? `block-editor-link-control-search-results-label-${ instanceId }` + : undefined; + const labelText = isInitialSuggestions + ? __( 'Recently updated' ) + : sprintf( __( 'Search results for %s' ), inputValue ); // According to guidelines aria-label should be added if the label // itself is not visible. // See: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/listbox_role const ariaLabel = isInitialSuggestions ? undefined : labelText; const SearchResultsLabel = ( - <span className="block-editor-link-control__search-results-label" id={ searchResultsLabelId } aria-label={ ariaLabel } > + <span + className="block-editor-link-control__search-results-label" + id={ searchResultsLabelId } + aria-label={ ariaLabel } + > { labelText } </span> ); return ( <div className="block-editor-link-control__search-results-wrapper"> - { isInitialSuggestions ? SearchResultsLabel : <VisuallyHidden>{ SearchResultsLabel }</VisuallyHidden> } - - <div { ...suggestionsListProps } className={ resultsListClasses } aria-labelledby={ searchResultsLabelId }> + { isInitialSuggestions ? ( + SearchResultsLabel + ) : ( + <VisuallyHidden>{ SearchResultsLabel }</VisuallyHidden> + ) } + + <div + { ...suggestionsListProps } + className={ resultsListClasses } + aria-labelledby={ searchResultsLabelId } + > { suggestions.map( ( suggestion, index ) => ( <LinkControlSearchItem key={ `${ suggestion.id }-${ suggestion.type }` } - itemProps={ buildSuggestionItemProps( suggestion, index ) } + itemProps={ buildSuggestionItemProps( + suggestion, + index + ) } suggestion={ suggestion } onClick={ () => { stopEditing(); onChange( { ...value, ...suggestion } ); } } isSelected={ index === selectedSuggestion } - isURL={ manualLinkEntryTypes.includes( suggestion.type.toLowerCase() ) } + isURL={ manualLinkEntryTypes.includes( + suggestion.type.toLowerCase() + ) } searchTerm={ inputValue } /> ) ) } @@ -261,7 +313,7 @@ function LinkControl( { ref={ wrapperNode } className="block-editor-link-control" > - { isEditingLink || ! value ? + { isEditingLink || ! value ? ( <LinkControlSearchInput value={ inputValue } onChange={ onInputChange } @@ -273,17 +325,24 @@ function LinkControl( { fetchSuggestions={ getSearchHandler } onReset={ resetInput } showInitialSuggestions={ showInitialSuggestions } - /> : + /> + ) : ( <Fragment> - <p className="screen-reader-text" id={ `current-link-label-${ instanceId }` }> + <p + className="screen-reader-text" + id={ `current-link-label-${ instanceId }` } + > { __( 'Currently selected' ) }: </p> <div aria-labelledby={ `current-link-label-${ instanceId }` } aria-selected="true" - className={ classnames( 'block-editor-link-control__search-item', { - 'is-current': true, - } ) } + className={ classnames( + 'block-editor-link-control__search-item', + { + 'is-current': true, + } + ) } > <span className="block-editor-link-control__search-item-header"> <ExternalLink @@ -313,7 +372,7 @@ function LinkControl( { onChange={ onChange } /> </Fragment> - } + ) } </div> ); } diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index 4bda15f1f1eb7b..4d8f39f9489d02 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -4,13 +4,7 @@ import { useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { Button } from '@wordpress/components'; -import { LEFT, - RIGHT, - UP, - DOWN, - BACKSPACE, - ENTER, -} from '@wordpress/keycodes'; +import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; /** * Internal dependencies @@ -32,7 +26,6 @@ const handleLinkControlOnKeyPress = ( event ) => { event.stopPropagation(); if ( keyCode === ENTER ) { - } }; @@ -89,7 +82,6 @@ const LinkControlSearchInput = ( { className="block-editor-link-control__search-reset" onClick={ onReset } /> - </form> ); }; diff --git a/packages/block-editor/src/components/link-control/search-item.js b/packages/block-editor/src/components/link-control/search-item.js index 43d9233ab2c877..a3c4fb37f4eb5d 100644 --- a/packages/block-editor/src/components/link-control/search-item.js +++ b/packages/block-editor/src/components/link-control/search-item.js @@ -8,13 +8,16 @@ import classnames from 'classnames'; */ import { safeDecodeURI } from '@wordpress/url'; import { __ } from '@wordpress/i18n'; -import { - Button, - Icon, - TextHighlight, -} from '@wordpress/components'; +import { Button, Icon, TextHighlight } from '@wordpress/components'; -export const LinkControlSearchItem = ( { itemProps, suggestion, isSelected = false, onClick, isURL = false, searchTerm = '' } ) => { +export const LinkControlSearchItem = ( { + itemProps, + suggestion, + isSelected = false, + onClick, + isURL = false, + searchTerm = '', +} ) => { return ( <Button { ...itemProps } @@ -26,25 +29,33 @@ export const LinkControlSearchItem = ( { itemProps, suggestion, isSelected = fal } ) } > { isURL && ( - <Icon className="block-editor-link-control__search-item-icon" icon="admin-site-alt3" /> + <Icon + className="block-editor-link-control__search-item-icon" + icon="admin-site-alt3" + /> ) } <span className="block-editor-link-control__search-item-header"> <span className="block-editor-link-control__search-item-title"> - <TextHighlight text={ suggestion.title } highlight={ searchTerm } /> + <TextHighlight + text={ suggestion.title } + highlight={ searchTerm } + /> </span> - <span aria-hidden={ ! isURL } className="block-editor-link-control__search-item-info"> + <span + aria-hidden={ ! isURL } + className="block-editor-link-control__search-item-info" + > { ! isURL && ( safeDecodeURI( suggestion.url ) || '' ) } - { isURL && ( - __( 'Press ENTER to add this link' ) - ) } + { isURL && __( 'Press ENTER to add this link' ) } </span> </span> { suggestion.type && ( - <span className="block-editor-link-control__search-item-type">{ suggestion.type }</span> + <span className="block-editor-link-control__search-item-type"> + { suggestion.type } + </span> ) } </Button> ); }; export default LinkControlSearchItem; - diff --git a/packages/block-editor/src/components/link-control/settings-drawer.js b/packages/block-editor/src/components/link-control/settings-drawer.js index 3100ea87ef6612..0df253d11a1ebc 100644 --- a/packages/block-editor/src/components/link-control/settings-drawer.js +++ b/packages/block-editor/src/components/link-control/settings-drawer.js @@ -7,9 +7,7 @@ import { noop } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { - ToggleControl, -} from '@wordpress/components'; +import { ToggleControl } from '@wordpress/components'; const defaultSettings = [ { @@ -18,7 +16,11 @@ const defaultSettings = [ }, ]; -const LinkControlSettingsDrawer = ( { value, onChange = noop, settings = defaultSettings } ) => { +const LinkControlSettingsDrawer = ( { + value, + onChange = noop, + settings = defaultSettings, +} ) => { if ( ! settings || ! settings.length ) { return null; } @@ -36,7 +38,8 @@ const LinkControlSettingsDrawer = ( { value, onChange = noop, settings = default key={ setting.id } label={ setting.title } onChange={ handleSettingChange( setting ) } - checked={ value ? value[ setting.id ] : false } /> + checked={ value ? value[ setting.id ] : false } + /> ) ); return ( diff --git a/packages/block-editor/src/components/link-control/test/fixtures/index.js b/packages/block-editor/src/components/link-control/test/fixtures/index.js index 9c926a8f629cbe..b470155977a30b 100644 --- a/packages/block-editor/src/components/link-control/test/fixtures/index.js +++ b/packages/block-editor/src/components/link-control/test/fixtures/index.js @@ -27,7 +27,8 @@ export const fauxEntitySuggestions = [ }, { id: uniqueId(), - title: 'This is another Post with a much longer title just to be really annoying and to try and break the UI', + title: + 'This is another Post with a much longer title just to be really annoying and to try and break the UI', type: 'Post', info: '1 month ago', url: `?p=${ uniqueId() }`, @@ -35,10 +36,13 @@ export const fauxEntitySuggestions = [ ]; /* eslint-disable no-unused-vars */ -export const fetchFauxEntitySuggestions = ( val = '', { - perPage = null, -} = {} ) => { - const suggestions = perPage ? take( fauxEntitySuggestions, perPage ) : fauxEntitySuggestions; +export const fetchFauxEntitySuggestions = ( + val = '', + { perPage = null } = {} +) => { + const suggestions = perPage + ? take( fauxEntitySuggestions, perPage ) + : fauxEntitySuggestions; return Promise.resolve( suggestions ); }; /* eslint-enable no-unused-vars */ diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index daf5dbbbf32cc2..44424efe334295 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -57,7 +57,9 @@ describe( 'Basic rendering', () => { } ); // Search Input UI - const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + const searchInput = container.querySelector( + 'input[aria-label="URL"]' + ); expect( searchInput ).not.toBeNull(); expect( container.innerHTML ).toMatchSnapshot(); @@ -70,9 +72,10 @@ describe( 'Searching for a link', () => { let resolver; - const fauxRequest = () => new Promise( ( resolve ) => { - resolver = resolve; - } ); + const fauxRequest = () => + new Promise( ( resolve ) => { + resolver = resolve; + } ); mockFetchSearchSuggestions.mockImplementation( fauxRequest ); @@ -81,7 +84,9 @@ describe( 'Searching for a link', () => { } ); // Search Input UI - const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + const searchInput = container.querySelector( + 'input[aria-label="URL"]' + ); // Simulate searching for a term act( () => { @@ -92,7 +97,9 @@ describe( 'Searching for a link', () => { await eventLoopTick(); // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - const searchResultElements = container.querySelectorAll( '[role="menu"] button[role="menuitem"]' ); + const searchResultElements = container.querySelectorAll( + '[role="menu"] button[role="menuitem"]' + ); let loadingUI = container.querySelector( '.components-spinner' ); @@ -111,7 +118,7 @@ describe( 'Searching for a link', () => { expect( loadingUI ).toBeNull(); } ); - it( 'should display only search suggestions when current input value is not URL-like', async ( ) => { + it( 'should display only search suggestions when current input value is not URL-like', async () => { const searchTerm = 'Hello world'; const firstFauxSuggestion = first( fauxEntitySuggestions ); @@ -120,7 +127,9 @@ describe( 'Searching for a link', () => { } ); // Search Input UI - const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + const searchInput = container.querySelector( + 'input[aria-label="URL"]' + ); // Simulate searching for a term act( () => { @@ -131,55 +140,85 @@ describe( 'Searching for a link', () => { await eventLoopTick(); // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - const firstSearchResultItemHTML = first( searchResultElements ).innerHTML; + const searchResultElements = container.querySelectorAll( + '[role="listbox"] [role="option"]' + ); + const firstSearchResultItemHTML = first( searchResultElements ) + .innerHTML; const lastSearchResultItemHTML = last( searchResultElements ).innerHTML; - expect( searchResultElements ).toHaveLength( fauxEntitySuggestions.length ); + expect( searchResultElements ).toHaveLength( + fauxEntitySuggestions.length + ); // Sanity check that a search suggestion shows up corresponding to the data - expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( firstFauxSuggestion.title ) ); - expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( firstFauxSuggestion.type ) ); + expect( firstSearchResultItemHTML ).toEqual( + expect.stringContaining( firstFauxSuggestion.title ) + ); + expect( firstSearchResultItemHTML ).toEqual( + expect.stringContaining( firstFauxSuggestion.type ) + ); // The fallback URL suggestion should not be shown when input is not URL-like - expect( lastSearchResultItemHTML ).not.toEqual( expect.stringContaining( 'URL' ) ); + expect( lastSearchResultItemHTML ).not.toEqual( + expect.stringContaining( 'URL' ) + ); } ); it.each( [ [ 'couldbeurlorentitysearchterm' ], [ 'ThisCouldAlsoBeAValidURL' ], - ] )( 'should display a URL suggestion as a default fallback for the search term "%s" which could potentially be a valid url.', async ( searchTerm ) => { - act( () => { - render( <LinkControl />, container ); - } ); - - // Search Input UI - const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + ] )( + 'should display a URL suggestion as a default fallback for the search term "%s" which could potentially be a valid url.', + async ( searchTerm ) => { + act( () => { + render( <LinkControl />, container ); + } ); - // Simulate searching for a term - act( () => { - Simulate.change( searchInput, { target: { value: searchTerm } } ); - } ); + // Search Input UI + const searchInput = container.querySelector( + 'input[aria-label="URL"]' + ); - // fetchFauxEntitySuggestions resolves on next "tick" of event loop - await eventLoopTick(); + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { + target: { value: searchTerm }, + } ); + } ); - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - const lastSearchResultItemHTML = last( searchResultElements ).innerHTML; - const additionalDefaultFallbackURLSuggestionLength = 1; + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); - // We should see a search result for each of the expect search suggestions - // plus 1 additional one for the fallback URL suggestion - expect( searchResultElements ).toHaveLength( fauxEntitySuggestions.length + additionalDefaultFallbackURLSuggestionLength ); + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( + '[role="listbox"] [role="option"]' + ); + const lastSearchResultItemHTML = last( searchResultElements ) + .innerHTML; + const additionalDefaultFallbackURLSuggestionLength = 1; + + // We should see a search result for each of the expect search suggestions + // plus 1 additional one for the fallback URL suggestion + expect( searchResultElements ).toHaveLength( + fauxEntitySuggestions.length + + additionalDefaultFallbackURLSuggestionLength + ); - // The last item should be a URL search suggestion - expect( lastSearchResultItemHTML ).toEqual( expect.stringContaining( searchTerm ) ); - expect( lastSearchResultItemHTML ).toEqual( expect.stringContaining( 'URL' ) ); - expect( lastSearchResultItemHTML ).toEqual( expect.stringContaining( 'Press ENTER to add this link' ) ); - } ); + // The last item should be a URL search suggestion + expect( lastSearchResultItemHTML ).toEqual( + expect.stringContaining( searchTerm ) + ); + expect( lastSearchResultItemHTML ).toEqual( + expect.stringContaining( 'URL' ) + ); + expect( lastSearchResultItemHTML ).toEqual( + expect.stringContaining( 'Press ENTER to add this link' ) + ); + } + ); - it( 'should reset the input field and the search results when search term is cleared or reset', async ( ) => { + it( 'should reset the input field and the search results when search term is cleared or reset', async () => { const searchTerm = 'Hello world'; act( () => { @@ -201,10 +240,14 @@ describe( 'Searching for a link', () => { await eventLoopTick(); // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + searchResultElements = container.querySelectorAll( + '[role="listbox"] [role="option"]' + ); // Check we have definitely rendered some suggestions - expect( searchResultElements ).toHaveLength( fauxEntitySuggestions.length ); + expect( searchResultElements ).toHaveLength( + fauxEntitySuggestions.length + ); // Grab the reset button now it's available const resetUI = container.querySelector( '[aria-label="Reset"]' ); @@ -216,7 +259,9 @@ describe( 'Searching for a link', () => { await eventLoopTick(); // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + searchResultElements = container.querySelectorAll( + '[role="listbox"] [role="option"]' + ); searchInput = container.querySelector( 'input[aria-label="URL"]' ); expect( searchInput.value ).toBe( '' ); @@ -229,66 +274,100 @@ describe( 'Manual link entry', () => { [ 'https://make.wordpress.org' ], // explicit https [ 'http://make.wordpress.org' ], // explicit http [ 'www.wordpress.org' ], // usage of "www" - ] )( 'should display a single suggestion result when the current input value is URL-like (eg: %s)', async ( searchTerm ) => { - act( () => { - render( <LinkControl />, container ); - } ); - - // Search Input UI - const searchInput = container.querySelector( 'input[aria-label="URL"]' ); - - // Simulate searching for a term - act( () => { - Simulate.change( searchInput, { target: { value: searchTerm } } ); - } ); - - // fetchFauxEntitySuggestions resolves on next "tick" of event loop - await eventLoopTick(); - - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - const firstSearchResultItemHTML = searchResultElements[ 0 ].innerHTML; - const expectedResultsLength = 1; - - expect( searchResultElements ).toHaveLength( expectedResultsLength ); - expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( searchTerm ) ); - expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( 'URL' ) ); - expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( 'Press ENTER to add this link' ) ); - } ); - - describe( 'Alternative link protocols and formats', () => { - it.each( [ - [ 'mailto:example123456@wordpress.org', 'mailto' ], - [ 'tel:example123456@wordpress.org', 'tel' ], - [ '#internal-anchor', 'internal' ], - ] )( 'should recognise "%s" as a %s link and handle as manual entry by displaying a single suggestion', async ( searchTerm, searchType ) => { + ] )( + 'should display a single suggestion result when the current input value is URL-like (eg: %s)', + async ( searchTerm ) => { act( () => { - render( - <LinkControl />, container - ); + render( <LinkControl />, container ); } ); // Search Input UI - const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + const searchInput = container.querySelector( + 'input[aria-label="URL"]' + ); // Simulate searching for a term act( () => { - Simulate.change( searchInput, { target: { value: searchTerm } } ); + Simulate.change( searchInput, { + target: { value: searchTerm }, + } ); } ); // fetchFauxEntitySuggestions resolves on next "tick" of event loop await eventLoopTick(); // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - const firstSearchResultItemHTML = searchResultElements[ 0 ].innerHTML; + const searchResultElements = container.querySelectorAll( + '[role="listbox"] [role="option"]' + ); + const firstSearchResultItemHTML = + searchResultElements[ 0 ].innerHTML; const expectedResultsLength = 1; - expect( searchResultElements ).toHaveLength( expectedResultsLength ); - expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( searchTerm ) ); - expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( searchType ) ); - expect( firstSearchResultItemHTML ).toEqual( expect.stringContaining( 'Press ENTER to add this link' ) ); - } ); + expect( searchResultElements ).toHaveLength( + expectedResultsLength + ); + expect( firstSearchResultItemHTML ).toEqual( + expect.stringContaining( searchTerm ) + ); + expect( firstSearchResultItemHTML ).toEqual( + expect.stringContaining( 'URL' ) + ); + expect( firstSearchResultItemHTML ).toEqual( + expect.stringContaining( 'Press ENTER to add this link' ) + ); + } + ); + + describe( 'Alternative link protocols and formats', () => { + it.each( [ + [ 'mailto:example123456@wordpress.org', 'mailto' ], + [ 'tel:example123456@wordpress.org', 'tel' ], + [ '#internal-anchor', 'internal' ], + ] )( + 'should recognise "%s" as a %s link and handle as manual entry by displaying a single suggestion', + async ( searchTerm, searchType ) => { + act( () => { + render( <LinkControl />, container ); + } ); + + // Search Input UI + const searchInput = container.querySelector( + 'input[aria-label="URL"]' + ); + + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { + target: { value: searchTerm }, + } ); + } ); + + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); + + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( + '[role="listbox"] [role="option"]' + ); + const firstSearchResultItemHTML = + searchResultElements[ 0 ].innerHTML; + const expectedResultsLength = 1; + + expect( searchResultElements ).toHaveLength( + expectedResultsLength + ); + expect( firstSearchResultItemHTML ).toEqual( + expect.stringContaining( searchTerm ) + ); + expect( firstSearchResultItemHTML ).toEqual( + expect.stringContaining( searchType ) + ); + expect( firstSearchResultItemHTML ).toEqual( + expect.stringContaining( 'Press ENTER to add this link' ) + ); + } + ); } ); } ); @@ -297,21 +376,27 @@ describe( 'Default search suggestions', () => { const expectedResultsLength = 3; // set within `LinkControl` act( () => { - render( - <LinkControl showInitialSuggestions />, container - ); + render( <LinkControl showInitialSuggestions />, container ); } ); await eventLoopTick(); // Search Input UI - const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + const searchInput = container.querySelector( + 'input[aria-label="URL"]' + ); // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - const searchResultsWrapper = container.querySelector( '[role="listbox"]' ); - const initialSearchResultElements = searchResultsWrapper.querySelectorAll( '[role="option"]' ); + const searchResultsWrapper = container.querySelector( + '[role="listbox"]' + ); + const initialSearchResultElements = searchResultsWrapper.querySelectorAll( + '[role="option"]' + ); - const searchResultsLabel = container.querySelector( `#${ searchResultsWrapper.getAttribute( 'aria-labelledby' ) }` ); + const searchResultsLabel = container.querySelector( + `#${ searchResultsWrapper.getAttribute( 'aria-labelledby' ) }` + ); // Verify input has no value has default suggestions should only show // when this does not have a value @@ -323,7 +408,9 @@ describe( 'Default search suggestions', () => { expect( mockFetchSearchSuggestions ).toHaveBeenCalledTimes( 1 ); // Verify the search results already display the initial suggestions - expect( initialSearchResultElements ).toHaveLength( expectedResultsLength ); + expect( initialSearchResultElements ).toHaveLength( + expectedResultsLength + ); expect( searchResultsLabel.innerHTML ).toEqual( 'Recently updated' ); } ); @@ -339,7 +426,8 @@ describe( 'Default search suggestions', () => { <LinkControl showInitialSuggestions value={ fauxEntitySuggestions[ 0 ] } - />, container + />, + container ); } ); @@ -351,7 +439,9 @@ describe( 'Default search suggestions', () => { // Click the "Edit/Change" button and check initial suggestions are not // shown. // - const currentLinkUI = container.querySelector( '.block-editor-link-control__search-item.is-current' ); + const currentLinkUI = container.querySelector( + '.block-editor-link-control__search-item.is-current' + ); const currentLinkBtn = currentLinkUI.querySelector( 'button' ); act( () => { @@ -360,7 +450,9 @@ describe( 'Default search suggestions', () => { await eventLoopTick(); - searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + searchResultElements = container.querySelectorAll( + '[role="listbox"] [role="option"]' + ); expect( searchResultElements ).toHaveLength( 0 ); @@ -377,7 +469,9 @@ describe( 'Default search suggestions', () => { await eventLoopTick(); - searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + searchResultElements = container.querySelectorAll( + '[role="listbox"] [role="option"]' + ); expect( searchResultElements ).toHaveLength( 3 ); @@ -403,12 +497,20 @@ describe( 'Selecting links', () => { } ); // TODO: select by aria role or visible text - const currentLink = container.querySelector( '.block-editor-link-control__search-item.is-current' ); + const currentLink = container.querySelector( + '.block-editor-link-control__search-item.is-current' + ); const currentLinkHTML = currentLink.innerHTML; - const currentLinkAnchor = currentLink.querySelector( `[href="${ selectedLink.url }"]` ); - - expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.title ) ); - expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.type ) ); + const currentLinkAnchor = currentLink.querySelector( + `[href="${ selectedLink.url }"]` + ); + + expect( currentLinkHTML ).toEqual( + expect.stringContaining( selectedLink.title ) + ); + expect( currentLinkHTML ).toEqual( + expect.stringContaining( selectedLink.type ) + ); expect( currentLinkHTML ).toEqual( expect.stringContaining( 'Edit' ) ); expect( currentLinkAnchor ).not.toBeNull(); } ); @@ -432,7 +534,9 @@ describe( 'Selecting links', () => { } ); // Required in order to select the button below - let currentLinkUI = container.querySelector( '.block-editor-link-control__search-item.is-current' ); + let currentLinkUI = container.querySelector( + '.block-editor-link-control__search-item.is-current' + ); const currentLinkBtn = currentLinkUI.querySelector( 'button' ); // Simulate searching for a term @@ -440,8 +544,12 @@ describe( 'Selecting links', () => { Simulate.click( currentLinkBtn ); } ); - const searchInput = container.querySelector( 'input[aria-label="URL"]' ); - currentLinkUI = container.querySelector( '.block-editor-link-control__search-item.is-current' ); + const searchInput = container.querySelector( + 'input[aria-label="URL"]' + ); + currentLinkUI = container.querySelector( + '.block-editor-link-control__search-item.is-current' + ); // We should be back to showing the search input expect( searchInput ).not.toBeNull(); @@ -452,159 +560,211 @@ describe( 'Selecting links', () => { describe( 'Selection using mouse click', () => { it.each( [ [ 'entity', 'hello world', first( fauxEntitySuggestions ) ], // entity search - [ 'url', 'https://www.wordpress.org', { - id: '1', - title: 'https://www.wordpress.org', - url: 'https://www.wordpress.org', - type: 'URL', - } ], // url - ] )( 'should display a current selected link UI when a %s suggestion for the search "%s" is clicked', async ( type, searchTerm, selectedLink ) => { - const LinkControlConsumer = () => { - const [ link, setLink ] = useState(); - - return ( - <LinkControl - value={ link } - onChange={ ( suggestion ) => setLink( suggestion ) } - /> - ); - }; + [ + 'url', + 'https://www.wordpress.org', + { + id: '1', + title: 'https://www.wordpress.org', + url: 'https://www.wordpress.org', + type: 'URL', + }, + ], // url + ] )( + 'should display a current selected link UI when a %s suggestion for the search "%s" is clicked', + async ( type, searchTerm, selectedLink ) => { + const LinkControlConsumer = () => { + const [ link, setLink ] = useState(); + + return ( + <LinkControl + value={ link } + onChange={ ( suggestion ) => setLink( suggestion ) } + /> + ); + }; - act( () => { - render( - <LinkControlConsumer />, container - ); - } ); + act( () => { + render( <LinkControlConsumer />, container ); + } ); - // Search Input UI - const searchInput = container.querySelector( 'input[aria-label="URL"]' ); + // Search Input UI + const searchInput = container.querySelector( + 'input[aria-label="URL"]' + ); - // Simulate searching for a term - act( () => { - Simulate.change( searchInput, { target: { value: searchTerm } } ); - } ); + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { + target: { value: searchTerm }, + } ); + } ); - // fetchFauxEntitySuggestions resolves on next "tick" of event loop - await eventLoopTick(); + // fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( + '[role="listbox"] [role="option"]' + ); - const firstSearchSuggestion = first( searchResultElements ); + const firstSearchSuggestion = first( searchResultElements ); - // Simulate selecting the first of the search suggestions - act( () => { - Simulate.click( firstSearchSuggestion ); - } ); + // Simulate selecting the first of the search suggestions + act( () => { + Simulate.click( firstSearchSuggestion ); + } ); - const currentLink = container.querySelector( '.block-editor-link-control__search-item.is-current' ); - const currentLinkHTML = currentLink.innerHTML; - const currentLinkAnchor = currentLink.querySelector( `[href="${ selectedLink.url }"]` ); + const currentLink = container.querySelector( + '.block-editor-link-control__search-item.is-current' + ); + const currentLinkHTML = currentLink.innerHTML; + const currentLinkAnchor = currentLink.querySelector( + `[href="${ selectedLink.url }"]` + ); - // Check that this suggestion is now shown as selected - expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.title ) ); - expect( currentLinkHTML ).toEqual( expect.stringContaining( 'Edit' ) ); - expect( currentLinkAnchor ).not.toBeNull(); - } ); + // Check that this suggestion is now shown as selected + expect( currentLinkHTML ).toEqual( + expect.stringContaining( selectedLink.title ) + ); + expect( currentLinkHTML ).toEqual( + expect.stringContaining( 'Edit' ) + ); + expect( currentLinkAnchor ).not.toBeNull(); + } + ); } ); describe( 'Selection using keyboard', () => { it.each( [ [ 'entity', 'hello world', first( fauxEntitySuggestions ) ], // entity search - [ 'url', 'https://www.wordpress.org', { - id: '1', - title: 'https://www.wordpress.org', - url: 'https://www.wordpress.org', - type: 'URL', - } ], // url - ] )( 'should display a current selected link UI when an %s suggestion for the search "%s" is selected using the keyboard', async ( type, searchTerm, selectedLink ) => { - const LinkControlConsumer = () => { - const [ link, setLink ] = useState(); - - return ( - <LinkControl - value={ link } - onChange={ ( suggestion ) => setLink( suggestion ) } - /> - ); - }; + [ + 'url', + 'https://www.wordpress.org', + { + id: '1', + title: 'https://www.wordpress.org', + url: 'https://www.wordpress.org', + type: 'URL', + }, + ], // url + ] )( + 'should display a current selected link UI when an %s suggestion for the search "%s" is selected using the keyboard', + async ( type, searchTerm, selectedLink ) => { + const LinkControlConsumer = () => { + const [ link, setLink ] = useState(); + + return ( + <LinkControl + value={ link } + onChange={ ( suggestion ) => setLink( suggestion ) } + /> + ); + }; - act( () => { - render( - <LinkControlConsumer />, container - ); - } ); - - // Search Input UI - const searchInput = container.querySelector( 'input[aria-label="URL"]' ); - const form = container.querySelector( 'form' ); - - // Simulate searching for a term - act( () => { - Simulate.change( searchInput, { target: { value: searchTerm } } ); - } ); - - //fetchFauxEntitySuggestions resolves on next "tick" of event loop - await eventLoopTick(); - - // Step down into the search results, highlighting the first result item - act( () => { - Simulate.keyDown( searchInput, { keyCode: DOWN } ); - } ); + act( () => { + render( <LinkControlConsumer />, container ); + } ); - // TODO: select these by aria relationship to autocomplete rather than arbitary selector. - const searchResultElements = container.querySelectorAll( '[role="listbox"] [role="option"]' ); - const firstSearchSuggestion = first( searchResultElements ); - const secondSearchSuggestion = nth( searchResultElements, 1 ); + // Search Input UI + const searchInput = container.querySelector( + 'input[aria-label="URL"]' + ); + const form = container.querySelector( 'form' ); - let selectedSearchResultElement = container.querySelector( '[role="option"][aria-selected="true"]' ); + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { + target: { value: searchTerm }, + } ); + } ); - // We should have highlighted the first item using the keyboard - expect( selectedSearchResultElement ).toEqual( firstSearchSuggestion ); + //fetchFauxEntitySuggestions resolves on next "tick" of event loop + await eventLoopTick(); - // Only entity searches contain more than 1 suggestion - if ( type === 'entity' ) { - // Check we can go down again using the down arrow + // Step down into the search results, highlighting the first result item act( () => { Simulate.keyDown( searchInput, { keyCode: DOWN } ); } ); - selectedSearchResultElement = container.querySelector( '[role="option"][aria-selected="true"]' ); + // TODO: select these by aria relationship to autocomplete rather than arbitary selector. + const searchResultElements = container.querySelectorAll( + '[role="listbox"] [role="option"]' + ); + const firstSearchSuggestion = first( searchResultElements ); + const secondSearchSuggestion = nth( searchResultElements, 1 ); + + let selectedSearchResultElement = container.querySelector( + '[role="option"][aria-selected="true"]' + ); // We should have highlighted the first item using the keyboard - expect( selectedSearchResultElement ).toEqual( secondSearchSuggestion ); + expect( selectedSearchResultElement ).toEqual( + firstSearchSuggestion + ); - // Check we can go back up via up arrow + // Only entity searches contain more than 1 suggestion + if ( type === 'entity' ) { + // Check we can go down again using the down arrow + act( () => { + Simulate.keyDown( searchInput, { keyCode: DOWN } ); + } ); + + selectedSearchResultElement = container.querySelector( + '[role="option"][aria-selected="true"]' + ); + + // We should have highlighted the first item using the keyboard + expect( selectedSearchResultElement ).toEqual( + secondSearchSuggestion + ); + + // Check we can go back up via up arrow + act( () => { + Simulate.keyDown( searchInput, { keyCode: UP } ); + } ); + + selectedSearchResultElement = container.querySelector( + '[role="option"][aria-selected="true"]' + ); + + // We should be back to highlighting the first search result again + expect( selectedSearchResultElement ).toEqual( + firstSearchSuggestion + ); + } + + // Commit the selected item as the current link act( () => { - Simulate.keyDown( searchInput, { keyCode: UP } ); + Simulate.keyDown( searchInput, { keyCode: ENTER } ); + } ); + act( () => { + Simulate.submit( form ); } ); - selectedSearchResultElement = container.querySelector( '[role="option"][aria-selected="true"]' ); - - // We should be back to highlighting the first search result again - expect( selectedSearchResultElement ).toEqual( firstSearchSuggestion ); - } - - // Commit the selected item as the current link - act( () => { - Simulate.keyDown( searchInput, { keyCode: ENTER } ); - } ); - act( () => { - Simulate.submit( form ); - } ); - - // Check that the suggestion selected via is now shown as selected - const currentLink = container.querySelector( '.block-editor-link-control__search-item.is-current' ); - const currentLinkHTML = currentLink.innerHTML; - const currentLinkAnchor = currentLink.querySelector( `[href="${ selectedLink.url }"]` ); + // Check that the suggestion selected via is now shown as selected + const currentLink = container.querySelector( + '.block-editor-link-control__search-item.is-current' + ); + const currentLinkHTML = currentLink.innerHTML; + const currentLinkAnchor = currentLink.querySelector( + `[href="${ selectedLink.url }"]` + ); - // Make sure focus is retained after submission. - expect( container.contains( document.activeElement ) ).toBe( true ); + // Make sure focus is retained after submission. + expect( container.contains( document.activeElement ) ).toBe( + true + ); - expect( currentLinkHTML ).toEqual( expect.stringContaining( selectedLink.title ) ); - expect( currentLinkHTML ).toEqual( expect.stringContaining( 'Edit' ) ); - expect( currentLinkAnchor ).not.toBeNull(); - } ); + expect( currentLinkHTML ).toEqual( + expect.stringContaining( selectedLink.title ) + ); + expect( currentLinkHTML ).toEqual( + expect.stringContaining( 'Edit' ) + ); + expect( currentLinkAnchor ).not.toBeNull(); + } + ); } ); } ); @@ -623,11 +783,21 @@ describe( 'Addition Settings UI', () => { render( <LinkControlConsumer />, container ); } ); - const newTabSettingLabel = Array.from( container.querySelectorAll( 'label' ) ).find( ( label ) => label.innerHTML && label.innerHTML.includes( expectedSettingText ) ); + const newTabSettingLabel = Array.from( + container.querySelectorAll( 'label' ) + ).find( + ( label ) => + label.innerHTML && + label.innerHTML.includes( expectedSettingText ) + ); expect( newTabSettingLabel ).not.toBeUndefined(); // find() returns "undefined" if not found - const newTabSettingLabelForAttr = newTabSettingLabel.getAttribute( 'for' ); - const newTabSettingInput = container.querySelector( `#${ newTabSettingLabelForAttr }` ); + const newTabSettingLabelForAttr = newTabSettingLabel.getAttribute( + 'for' + ); + const newTabSettingInput = container.querySelector( + `#${ newTabSettingLabelForAttr }` + ); expect( newTabSettingInput ).not.toBeNull(); expect( newTabSettingInput.checked ).toBe( false ); } ); @@ -646,7 +816,9 @@ describe( 'Addition Settings UI', () => { }, ]; - const customSettingsLabelsText = customSettings.map( ( setting ) => setting.title ); + const customSettingsLabelsText = customSettings.map( + ( setting ) => setting.title + ); const LinkControlConsumer = () => { const [ link ] = useState( selectedLink ); @@ -664,20 +836,34 @@ describe( 'Addition Settings UI', () => { } ); // Grab the elements using user perceivable DOM queries - const settingsLegend = Array.from( container.querySelectorAll( 'legend' ) ).find( ( legend ) => legend.innerHTML && legend.innerHTML.includes( 'Currently selected link settings' ) ); + const settingsLegend = Array.from( + container.querySelectorAll( 'legend' ) + ).find( + ( legend ) => + legend.innerHTML && + legend.innerHTML.includes( 'Currently selected link settings' ) + ); const settingsFieldset = settingsLegend.closest( 'fieldset' ); - const settingControlsLabels = Array.from( settingsFieldset.querySelectorAll( 'label' ) ); + const settingControlsLabels = Array.from( + settingsFieldset.querySelectorAll( 'label' ) + ); const settingControlsInputs = settingControlsLabels.map( ( label ) => { - return settingsFieldset.querySelector( `#${ label.getAttribute( 'for' ) }` ); + return settingsFieldset.querySelector( + `#${ label.getAttribute( 'for' ) }` + ); } ); - const settingControlLabelsText = Array.from( settingControlsLabels ).map( ( label ) => label.innerHTML ); + const settingControlLabelsText = Array.from( + settingControlsLabels + ).map( ( label ) => label.innerHTML ); // Check we have the correct number of controls expect( settingControlsLabels ).toHaveLength( 2 ); // Check the labels match - expect( settingControlLabelsText ).toEqual( expect.arrayContaining( customSettingsLabelsText ) ); + expect( settingControlLabelsText ).toEqual( + expect.arrayContaining( customSettingsLabelsText ) + ); // Assert the default "checked" states match the expected expect( settingControlsInputs[ 0 ].checked ).toEqual( false ); diff --git a/packages/block-editor/src/components/media-edit/index.native.js b/packages/block-editor/src/components/media-edit/index.native.js index 33cc95d1987f37..fd10c10e2ef5b1 100644 --- a/packages/block-editor/src/components/media-edit/index.native.js +++ b/packages/block-editor/src/components/media-edit/index.native.js @@ -72,13 +72,16 @@ export class MediaEdit extends React.Component { const mediaOptions = () => ( <Picker hideCancelButton - ref={ ( instance ) => this.picker = instance } + ref={ ( instance ) => ( this.picker = instance ) } options={ this.getMediaOptionsItems() } onChange={ this.onPickerSelect } /> ); - return this.props.render( { open: this.onPickerPresent, mediaOptions } ); + return this.props.render( { + open: this.onPickerPresent, + mediaOptions, + } ); } } diff --git a/packages/block-editor/src/components/media-placeholder/index.js b/packages/block-editor/src/components/media-placeholder/index.js index e22befc7e0ed46..1874c431a83d02 100644 --- a/packages/block-editor/src/components/media-placeholder/index.js +++ b/packages/block-editor/src/components/media-placeholder/index.js @@ -1,13 +1,7 @@ /** * External dependencies */ -import { - every, - get, - isArray, - noop, - startsWith, -} from 'lodash'; +import { every, get, isArray, noop, startsWith } from 'lodash'; import classnames from 'classnames'; /** @@ -78,7 +72,9 @@ export class MediaPlaceholder extends Component { return false; } return every( allowedTypes, ( allowedType ) => { - return allowedType === 'image' || startsWith( allowedType, 'image/' ); + return ( + allowedType === 'image' || startsWith( allowedType, 'image/' ) + ); } ); } @@ -87,7 +83,10 @@ export class MediaPlaceholder extends Component { } componentDidUpdate( prevProps ) { - if ( get( prevProps.value, [ 'src' ], '' ) !== get( this.props.value, [ 'src' ], '' ) ) { + if ( + get( prevProps.value, [ 'src' ], '' ) !== + get( this.props.value, [ 'src' ], '' ) + ) { this.setState( { src: get( this.props.value, [ 'src' ], '' ) } ); } } @@ -166,7 +165,9 @@ export class MediaPlaceholder extends Component { let title = labels.title; if ( ! mediaUpload && ! onSelectURL ) { - instructions = __( 'To edit this block, you need permission to upload media.' ); + instructions = __( + 'To edit this block, you need permission to upload media.' + ); } if ( instructions === undefined || title === undefined ) { @@ -176,14 +177,22 @@ export class MediaPlaceholder extends Component { const isVideo = isOneType && 'video' === allowedTypes[ 0 ]; if ( instructions === undefined && mediaUpload ) { - instructions = __( 'Upload a media file or pick one from your media library.' ); + instructions = __( + 'Upload a media file or pick one from your media library.' + ); if ( isAudio ) { - instructions = __( 'Upload an audio file, pick one from your media library, or add one with a URL.' ); + instructions = __( + 'Upload an audio file, pick one from your media library, or add one with a URL.' + ); } else if ( isImage ) { - instructions = __( 'Upload an image file, pick one from your media library, or add one with a URL.' ); + instructions = __( + 'Upload an image file, pick one from your media library, or add one with a URL.' + ); } else if ( isVideo ) { - instructions = __( 'Upload a video file, pick one from your media library, or add one with a URL.' ); + instructions = __( + 'Upload a video file, pick one from your media library, or add one with a URL.' + ); } } @@ -203,7 +212,9 @@ export class MediaPlaceholder extends Component { const placeholderClassName = classnames( 'block-editor-media-placeholder', className, - { 'is-appender': isAppender } + { + 'is-appender': isAppender, + } ); return ( @@ -239,32 +250,27 @@ export class MediaPlaceholder extends Component { } renderCancelLink() { - const { - onCancel, - } = this.props; - return ( onCancel && - <Button - className="block-editor-media-placeholder__cancel-button" - title={ __( 'Cancel' ) } - isLink - onClick={ onCancel } - > - { __( 'Cancel' ) } - </Button> + const { onCancel } = this.props; + return ( + onCancel && ( + <Button + className="block-editor-media-placeholder__cancel-button" + title={ __( 'Cancel' ) } + isLink + onClick={ onCancel } + > + { __( 'Cancel' ) } + </Button> + ) ); } renderUrlSelectionUI() { - const { - onSelectURL, - } = this.props; + const { onSelectURL } = this.props; if ( ! onSelectURL ) { return null; } - const { - isURLInputVisible, - src, - } = this.state; + const { isURLInputVisible, src } = this.state; return ( <div className="block-editor-media-placeholder__url-input-container"> <Button @@ -307,9 +313,7 @@ export class MediaPlaceholder extends Component { onSelect={ onSelect } allowedTypes={ allowedTypes } value={ - isArray( value ) ? - value.map( ( { id } ) => id ) : - value.id + isArray( value ) ? value.map( ( { id } ) => id ) : value.id } render={ ( { open } ) => { return ( @@ -352,7 +356,10 @@ export class MediaPlaceholder extends Component { { this.renderCancelLink() } </> ); - return this.renderPlaceholder( content, openFileDialog ); + return this.renderPlaceholder( + content, + openFileDialog + ); } } /> </> @@ -387,28 +394,28 @@ export class MediaPlaceholder extends Component { } render() { - const { - disableMediaButtons, - dropZoneUIOnly, - } = this.props; + const { disableMediaButtons, dropZoneUIOnly } = this.props; if ( dropZoneUIOnly || disableMediaButtons ) { if ( dropZoneUIOnly ) { - deprecated( 'wp.blockEditor.MediaPlaceholder dropZoneUIOnly prop', { - alternative: 'disableMediaButtons', - } ); + deprecated( + 'wp.blockEditor.MediaPlaceholder dropZoneUIOnly prop', + { + alternative: 'disableMediaButtons', + } + ); } return ( - <MediaUploadCheck> - { this.renderDropZone() } - </MediaUploadCheck> + <MediaUploadCheck>{ this.renderDropZone() }</MediaUploadCheck> ); } return ( <MediaUploadCheck - fallback={ this.renderPlaceholder( this.renderUrlSelectionUI() ) } + fallback={ this.renderPlaceholder( + this.renderUrlSelectionUI() + ) } > { this.renderMediaUploadChecked() } </MediaUploadCheck> @@ -429,5 +436,5 @@ const applyWithSelect = withSelect( ( select ) => { */ export default compose( applyWithSelect, - withFilters( 'editor.MediaPlaceholder' ), + withFilters( 'editor.MediaPlaceholder' ) )( MediaPlaceholder ); diff --git a/packages/block-editor/src/components/media-placeholder/index.native.js b/packages/block-editor/src/components/media-placeholder/index.native.js index e120c53daf2c01..c1a4def4e0e8c1 100644 --- a/packages/block-editor/src/components/media-placeholder/index.native.js +++ b/packages/block-editor/src/components/media-placeholder/index.native.js @@ -15,12 +15,21 @@ import { } from '@wordpress/block-editor'; import { Dashicon } from '@wordpress/components'; import { withPreferredColorScheme } from '@wordpress/compose'; +import { useRef } from '@wordpress/element'; /** * Internal dependencies */ import styles from './styles.scss'; +// remove duplicates after gallery append +const dedupMedia = ( media ) => + uniqWith( + media, + ( media1, media2 ) => + media1.id === media2.id || media1.url === media2.url + ); + function MediaPlaceholder( props ) { const { addToGallery, @@ -35,11 +44,18 @@ function MediaPlaceholder( props ) { value = [], } = props; - const setMedia = multiple && addToGallery ? - ( selected ) => onSelect( uniqWith( [ ...value, ...selected ], ( media1, media2 ) => { - return media1.id === media2.id || media1.url === media2.url; - } ) ) : - onSelect; + // use ref to keep media array current for callbacks during rerenders + const mediaRef = useRef( value ); + mediaRef.current = value; + + // append and deduplicate media array for gallery use case + const setMedia = + multiple && addToGallery + ? ( selected ) => + onSelect( + dedupMedia( [ ...mediaRef.current, ...selected ] ) + ) + : onSelect; const isOneType = allowedTypes.length === 1; const isImage = isOneType && allowedTypes.includes( MEDIA_TYPE_IMAGE ); @@ -73,16 +89,20 @@ function MediaPlaceholder( props ) { accessibilityHint = __( 'Double tap to select a video' ); } - const emptyStateTitleStyle = getStylesFromColorScheme( styles.emptyStateTitle, styles.emptyStateTitleDark ); - const addMediaButtonStyle = getStylesFromColorScheme( styles.addMediaButton, styles.addMediaButtonDark ); + const emptyStateTitleStyle = getStylesFromColorScheme( + styles.emptyStateTitle, + styles.emptyStateTitleDark + ); + const addMediaButtonStyle = getStylesFromColorScheme( + styles.addMediaButton, + styles.addMediaButtonDark + ); const renderContent = () => { if ( isAppender === undefined || ! isAppender ) { return ( <> - <View style={ styles.modalIcon }> - { icon } - </View> + <View style={ styles.modalIcon }>{ icon }</View> <Text style={ emptyStateTitleStyle }> { placeholderTitle } </Text> @@ -107,8 +127,14 @@ function MediaPlaceholder( props ) { return null; } - const appenderStyle = getStylesFromColorScheme( styles.appender, styles.appenderDark ); - const emptyStateContainerStyle = getStylesFromColorScheme( styles.emptyStateContainer, styles.emptyStateContainerDark ); + const appenderStyle = getStylesFromColorScheme( + styles.appender, + styles.appenderDark + ); + const emptyStateContainerStyle = getStylesFromColorScheme( + styles.emptyStateContainer, + styles.emptyStateContainerDark + ); return ( <View style={ { flex: 1 } }> @@ -129,12 +155,14 @@ function MediaPlaceholder( props ) { onPress={ ( event ) => { props.onFocus( event ); open(); - } }> + } } + > <View style={ [ emptyStateContainerStyle, isAppender && appenderStyle, - ] }> + ] } + > { getMediaOptions() } { renderContent() } </View> diff --git a/packages/block-editor/src/components/media-placeholder/test/index.js b/packages/block-editor/src/components/media-placeholder/test/index.js index 6d7050eb84f062..32496cf65ddd51 100644 --- a/packages/block-editor/src/components/media-placeholder/test/index.js +++ b/packages/block-editor/src/components/media-placeholder/test/index.js @@ -12,8 +12,8 @@ jest.mock( '../../media-upload/check', () => () => null ); describe( 'MediaPlaceholder', () => { it( 'renders successfully when allowedTypes property is not specified', () => { - expect( () => mount( - <MediaPlaceholder hasUploadPermissions={ false } /> - ) ).not.toThrow(); + expect( () => + mount( <MediaPlaceholder hasUploadPermissions={ false } /> ) + ).not.toThrow(); } ); } ); diff --git a/packages/block-editor/src/components/media-replace-flow/index.js b/packages/block-editor/src/components/media-replace-flow/index.js index 8e2c09567d0180..73723a4484f909 100644 --- a/packages/block-editor/src/components/media-replace-flow/index.js +++ b/packages/block-editor/src/components/media-replace-flow/index.js @@ -13,14 +13,7 @@ import { Dropdown, withNotices, } from '@wordpress/components'; -import { - LEFT, - RIGHT, - UP, - DOWN, - BACKSPACE, - ENTER, -} from '@wordpress/keycodes'; +import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; import { useSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; @@ -32,17 +25,15 @@ import MediaUploadCheck from '../media-upload/check'; import LinkEditor from '../url-popover/link-editor'; import LinkViewer from '../url-popover/link-viewer'; -const MediaReplaceFlow = ( - { - mediaURL, - allowedTypes, - accept, - onSelect, - onSelectURL, - onError, - name = __( 'Replace' ), - } -) => { +const MediaReplaceFlow = ( { + mediaURL, + allowedTypes, + accept, + onSelect, + onSelectURL, + onError, + name = __( 'Replace' ), +} ) => { const [ showURLInput, setShowURLInput ] = useState( false ); const [ showEditURLInput, setShowEditURLInput ] = useState( false ); const [ mediaURLValue, setMediaURLValue ] = useState( mediaURL ); @@ -56,7 +47,11 @@ const MediaReplaceFlow = ( }; const stopPropagationRelevantKeys = ( event ) => { - if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( event.keyCode ) > -1 ) { + if ( + [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( + event.keyCode + ) > -1 + ) { // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. event.stopPropagation(); } @@ -104,7 +99,7 @@ const MediaReplaceFlow = ( value={ mediaURLValue } isFullWidthInput={ true } hasInputBorder={ true } - onChangeInputValue={ ( url ) => ( setMediaURLValue( url ) ) } + onChangeInputValue={ ( url ) => setMediaURLValue( url ) } onSubmit={ ( event ) => { event.preventDefault(); selectURL( mediaURLValue ); @@ -118,7 +113,9 @@ const MediaReplaceFlow = ( isFullWidth={ true } className="block-editor-media-replace-flow__link-viewer" url={ mediaURLValue } - onEditLinkClick={ () => ( setShowEditURLInput( ! showEditURLInput ) ) } + onEditLinkClick={ () => + setShowEditURLInput( ! showEditURLInput ) + } /> ); } @@ -146,10 +143,7 @@ const MediaReplaceFlow = ( onSelect={ ( media ) => selectMedia( media ) } allowedTypes={ allowedTypes } render={ ( { open } ) => ( - <MenuItem - icon="admin-media" - onClick={ open } - > + <MenuItem icon="admin-media" onClick={ open }> { __( 'Open Media Library' ) } </MenuItem> ) } @@ -174,23 +168,27 @@ const MediaReplaceFlow = ( } } /> </MediaUploadCheck> - { onSelectURL && <MenuItem - icon="admin-links" - onClick={ () => ( setShowURLInput( ! showURLInput ) ) } - aria-expanded={ showURLInput } - > - <div> { __( 'Insert from URL' ) } </div> - </MenuItem> } + { onSelectURL && ( + <MenuItem + icon="admin-links" + onClick={ () => + setShowURLInput( ! showURLInput ) + } + aria-expanded={ showURLInput } + > + <div> { __( 'Insert from URL' ) } </div> + </MenuItem> + ) } </NavigableMenu> - { showURLInput && <div className="block-editor-media-flow__url-input"> - { urlInputUIContent } - </div> } + { showURLInput && ( + <div className="block-editor-media-flow__url-input"> + { urlInputUIContent } + </div> + ) } </> ) } /> ); }; -export default compose( - withNotices, -)( MediaReplaceFlow ); +export default compose( withNotices )( MediaReplaceFlow ); diff --git a/packages/block-editor/src/components/media-upload-progress/image-size.native.js b/packages/block-editor/src/components/media-upload-progress/image-size.native.js index ade5984c1cadab..3e022777fce2cc 100644 --- a/packages/block-editor/src/components/media-upload-progress/image-size.native.js +++ b/packages/block-editor/src/components/media-upload-progress/image-size.native.js @@ -62,7 +62,10 @@ class ImageSize extends Component { if ( this.image === undefined || this.container === undefined ) { return; } - const { width, height } = calculatePreferedImageSize( this.image, this.container ); + const { width, height } = calculatePreferedImageSize( + this.image, + this.container + ); this.setState( { width, height } ); } diff --git a/packages/block-editor/src/components/media-upload-progress/index.native.js b/packages/block-editor/src/components/media-upload-progress/index.native.js index ca13038a2795ed..63f53c97a1d3a3 100644 --- a/packages/block-editor/src/components/media-upload-progress/index.native.js +++ b/packages/block-editor/src/components/media-upload-progress/index.native.js @@ -3,16 +3,12 @@ */ import React from 'react'; import { View } from 'react-native'; -import { - subscribeMediaUpload, -} from 'react-native-gutenberg-bridge'; +import { subscribeMediaUpload } from 'react-native-gutenberg-bridge'; /** * WordPress dependencies */ -import { - Spinner, -} from '@wordpress/components'; +import { Spinner } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; /** @@ -71,7 +67,11 @@ export class MediaUploadProgress extends React.Component { } updateMediaProgress( payload ) { - this.setState( { progress: payload.progress, isUploadInProgress: true, isUploadFailed: false } ); + this.setState( { + progress: payload.progress, + isUploadInProgress: true, + isUploadFailed: false, + } ); if ( this.props.onUpdateMediaProgress ) { this.props.onUpdateMediaProgress( payload ); } @@ -103,9 +103,11 @@ export class MediaUploadProgress extends React.Component { if ( this.subscriptionParentMediaUpload ) { return; } - this.subscriptionParentMediaUpload = subscribeMediaUpload( ( payload ) => { - this.mediaUpload( payload ); - } ); + this.subscriptionParentMediaUpload = subscribeMediaUpload( + ( payload ) => { + this.mediaUpload( payload ); + } + ); } removeMediaUploadListener() { @@ -119,17 +121,19 @@ export class MediaUploadProgress extends React.Component { const { isUploadInProgress, isUploadFailed } = this.state; const showSpinner = this.state.isUploadInProgress; const progress = this.state.progress * 100; - const retryMessage = __( 'Failed to insert media.\nPlease tap for options.' ); + const retryMessage = __( + 'Failed to insert media.\nPlease tap for options.' + ); return ( <View style={ styles.mediaUploadProgress }> - { showSpinner && + { showSpinner && ( <View style={ styles.progressBar }> <Spinner progress={ progress } /> </View> - } - { coverUrl && - <ImageSize src={ coverUrl } > + ) } + { coverUrl && ( + <ImageSize src={ coverUrl }> { ( sizes ) => { const { imageWidthWithinContainer, @@ -137,30 +141,37 @@ export class MediaUploadProgress extends React.Component { } = sizes; let finalHeight = imageHeightWithinContainer; - if ( height > 0 && height < imageHeightWithinContainer ) { + if ( + height > 0 && + height < imageHeightWithinContainer + ) { finalHeight = height; } let finalWidth = imageWidthWithinContainer; - if ( width > 0 && width < imageWidthWithinContainer ) { + if ( + width > 0 && + width < imageWidthWithinContainer + ) { finalWidth = width; } - return ( this.props.renderContent( { + return this.props.renderContent( { isUploadInProgress, isUploadFailed, finalWidth, finalHeight, imageWidthWithinContainer, retryMessage, - } ) ); + } ); } } </ImageSize> - } - { ! coverUrl && this.props.renderContent( { - isUploadInProgress, - isUploadFailed, - retryMessage, - } ) } + ) } + { ! coverUrl && + this.props.renderContent( { + isUploadInProgress, + isUploadFailed, + retryMessage, + } ) } </View> ); } diff --git a/packages/block-editor/src/components/media-upload-progress/test/index.native.js b/packages/block-editor/src/components/media-upload-progress/test/index.native.js index e7c48e80d05df5..41a542b3703caa 100644 --- a/packages/block-editor/src/components/media-upload-progress/test/index.native.js +++ b/packages/block-editor/src/components/media-upload-progress/test/index.native.js @@ -2,9 +2,7 @@ * External dependencies */ import { shallow } from 'enzyme'; -import { - sendMediaUpload, -} from 'react-native-gutenberg-bridge'; +import { sendMediaUpload } from 'react-native-gutenberg-bridge'; /** * Internal dependencies @@ -42,7 +40,11 @@ describe( 'MediaUploadProgress component', () => { it( 'listens media upload progress', () => { const progress = 10; - const payload = { state: MEDIA_UPLOAD_STATE_UPLOADING, mediaId: MEDIA_ID, progress }; + const payload = { + state: MEDIA_UPLOAD_STATE_UPLOADING, + mediaId: MEDIA_ID, + progress, + }; const onUpdateMediaProgress = jest.fn(); @@ -64,7 +66,11 @@ describe( 'MediaUploadProgress component', () => { } ); it( 'does not get affected by unrelated media uploads', () => { - const payload = { state: MEDIA_UPLOAD_STATE_UPLOADING, mediaId: 1, progress: 20 }; + const payload = { + state: MEDIA_UPLOAD_STATE_UPLOADING, + mediaId: 1, + progress: 20, + }; const onUpdateMediaProgress = jest.fn(); const wrapper = shallow( <MediaUploadProgress @@ -82,14 +88,23 @@ describe( 'MediaUploadProgress component', () => { it( 'listens media upload success', () => { const progress = 10; - const payloadSuccess = { state: MEDIA_UPLOAD_STATE_SUCCEEDED, mediaId: MEDIA_ID }; - const payloadUploading = { state: MEDIA_UPLOAD_STATE_UPLOADING, mediaId: MEDIA_ID, progress }; + const payloadSuccess = { + state: MEDIA_UPLOAD_STATE_SUCCEEDED, + mediaId: MEDIA_ID, + }; + const payloadUploading = { + state: MEDIA_UPLOAD_STATE_UPLOADING, + mediaId: MEDIA_ID, + progress, + }; const onFinishMediaUploadWithSuccess = jest.fn(); const wrapper = shallow( <MediaUploadProgress - onFinishMediaUploadWithSuccess={ onFinishMediaUploadWithSuccess } + onFinishMediaUploadWithSuccess={ + onFinishMediaUploadWithSuccess + } mediaId={ MEDIA_ID } renderContent={ () => {} } /> @@ -103,19 +118,30 @@ describe( 'MediaUploadProgress component', () => { expect( wrapper.instance().state.isUploadInProgress ).toEqual( false ); expect( onFinishMediaUploadWithSuccess ).toHaveBeenCalledTimes( 1 ); - expect( onFinishMediaUploadWithSuccess ).toHaveBeenCalledWith( payloadSuccess ); + expect( onFinishMediaUploadWithSuccess ).toHaveBeenCalledWith( + payloadSuccess + ); } ); it( 'listens media upload fail', () => { const progress = 10; - const payloadFail = { state: MEDIA_UPLOAD_STATE_FAILED, mediaId: MEDIA_ID }; - const payloadUploading = { state: MEDIA_UPLOAD_STATE_UPLOADING, mediaId: MEDIA_ID, progress }; + const payloadFail = { + state: MEDIA_UPLOAD_STATE_FAILED, + mediaId: MEDIA_ID, + }; + const payloadUploading = { + state: MEDIA_UPLOAD_STATE_UPLOADING, + mediaId: MEDIA_ID, + progress, + }; const onFinishMediaUploadWithFailure = jest.fn(); const wrapper = shallow( <MediaUploadProgress - onFinishMediaUploadWithFailure={ onFinishMediaUploadWithFailure } + onFinishMediaUploadWithFailure={ + onFinishMediaUploadWithFailure + } mediaId={ MEDIA_ID } renderContent={ () => {} } /> @@ -130,13 +156,22 @@ describe( 'MediaUploadProgress component', () => { expect( wrapper.instance().state.isUploadInProgress ).toEqual( false ); expect( wrapper.instance().state.isUploadFailed ).toEqual( true ); expect( onFinishMediaUploadWithFailure ).toHaveBeenCalledTimes( 1 ); - expect( onFinishMediaUploadWithFailure ).toHaveBeenCalledWith( payloadFail ); + expect( onFinishMediaUploadWithFailure ).toHaveBeenCalledWith( + payloadFail + ); } ); it( 'listens media upload reset', () => { const progress = 10; - const payloadReset = { state: MEDIA_UPLOAD_STATE_RESET, mediaId: MEDIA_ID }; - const payloadUploading = { state: MEDIA_UPLOAD_STATE_UPLOADING, mediaId: MEDIA_ID, progress }; + const payloadReset = { + state: MEDIA_UPLOAD_STATE_RESET, + mediaId: MEDIA_ID, + }; + const payloadUploading = { + state: MEDIA_UPLOAD_STATE_UPLOADING, + mediaId: MEDIA_ID, + progress, + }; const onMediaUploadStateReset = jest.fn(); diff --git a/packages/block-editor/src/components/media-upload/check.js b/packages/block-editor/src/components/media-upload/check.js index c1fb3398020f4c..7059dfc3f196c1 100644 --- a/packages/block-editor/src/components/media-upload/check.js +++ b/packages/block-editor/src/components/media-upload/check.js @@ -3,7 +3,11 @@ */ import { withSelect } from '@wordpress/data'; -export function MediaUploadCheck( { hasUploadPermissions, fallback = null, children } ) { +export function MediaUploadCheck( { + hasUploadPermissions, + fallback = null, + children, +} ) { return hasUploadPermissions ? children : fallback; } diff --git a/packages/block-editor/src/components/media-upload/index.native.js b/packages/block-editor/src/components/media-upload/index.native.js index 72a337cc921bd1..2a57846847ebe9 100644 --- a/packages/block-editor/src/components/media-upload/index.native.js +++ b/packages/block-editor/src/components/media-upload/index.native.js @@ -52,7 +52,12 @@ const siteLibrarySource = { icon: 'wordpress-alt', }; -const internalSources = [ deviceLibrarySource, cameraImageSource, cameraVideoSource, siteLibrarySource ]; +const internalSources = [ + deviceLibrarySource, + cameraImageSource, + cameraVideoSource, + siteLibrarySource, +]; export class MediaUpload extends React.Component { constructor( props ) { @@ -69,13 +74,15 @@ export class MediaUpload extends React.Component { componentDidMount() { const { allowedTypes = [] } = this.props; getOtherMediaOptions( allowedTypes, ( otherMediaOptions ) => { - const otherMediaOptionsWithIcons = otherMediaOptions.map( ( option ) => { - return { - ...option, - types: allowedTypes, - id: option.value, - }; - } ); + const otherMediaOptionsWithIcons = otherMediaOptions.map( + ( option ) => { + return { + ...option, + types: allowedTypes, + id: option.value, + }; + } + ); this.setState( { otherMediaOptions: otherMediaOptionsWithIcons } ); } ); @@ -96,14 +103,20 @@ export class MediaUpload extends React.Component { } } - return this.getAllSources().filter( ( source ) => { - return allowedTypes.filter( ( allowedType ) => source.types.includes( allowedType ) ).length > 0; - } ).map( ( source ) => { - return { - ...source, - icon: source.icon || this.getChooseFromDeviceIcon(), - }; - } ); + return this.getAllSources() + .filter( ( source ) => { + return ( + allowedTypes.filter( ( allowedType ) => + source.types.includes( allowedType ) + ).length > 0 + ); + } ) + .map( ( source ) => { + return { + ...source, + icon: source.icon || this.getChooseFromDeviceIcon(), + }; + } ); } getChooseFromDeviceIcon() { @@ -128,8 +141,12 @@ export class MediaUpload extends React.Component { onPickerSelect( value ) { const { allowedTypes = [], onSelect, multiple = false } = this.props; - const mediaSource = this.getAllSources().filter( ( source ) => source.value === value ).shift(); - const types = allowedTypes.filter( ( type ) => mediaSource.types.includes( type ) ); + const mediaSource = this.getAllSources() + .filter( ( source ) => source.value === value ) + .shift(); + const types = allowedTypes.filter( ( type ) => + mediaSource.types.includes( type ) + ); requestMediaPicker( mediaSource.id, types, multiple, ( media ) => { if ( ( multiple && media ) || ( media && media.id ) ) { onSelect( media ); @@ -141,13 +158,16 @@ export class MediaUpload extends React.Component { const getMediaOptions = () => ( <Picker hideCancelButton - ref={ ( instance ) => this.picker = instance } + ref={ ( instance ) => ( this.picker = instance ) } options={ this.getMediaOptionsItems() } onChange={ this.onPickerSelect } /> ); - return this.props.render( { open: this.onPickerPresent, getMediaOptions } ); + return this.props.render( { + open: this.onPickerPresent, + getMediaOptions, + } ); } } diff --git a/packages/block-editor/src/components/media-upload/test/index.native.js b/packages/block-editor/src/components/media-upload/test/index.native.js index 337b038cab9499..539f9f4361c5d9 100644 --- a/packages/block-editor/src/components/media-upload/test/index.native.js +++ b/packages/block-editor/src/components/media-upload/test/index.native.js @@ -32,13 +32,16 @@ describe( 'MediaUpload component', () => { it( 'opens media options picker', () => { const wrapper = shallow( - <MediaUpload allowedTypes={ [] } render={ ( { open, getMediaOptions } ) => { - return ( - <TouchableWithoutFeedback onPress={ open }> - { getMediaOptions() } - </TouchableWithoutFeedback> - ); - } } /> + <MediaUpload + allowedTypes={ [] } + render={ ( { open, getMediaOptions } ) => { + return ( + <TouchableWithoutFeedback onPress={ open }> + { getMediaOptions() } + </TouchableWithoutFeedback> + ); + } } + /> ); expect( wrapper.find( 'Picker' ) ).toHaveLength( 1 ); } ); @@ -54,23 +57,35 @@ describe( 'MediaUpload component', () => { { getMediaOptions() } </TouchableWithoutFeedback> ); - } } /> + } } + /> ); - expect( wrapper.find( 'Picker' ).props().options.filter( ( item ) => item.label === expectedOption ) ).toHaveLength( 1 ); + expect( + wrapper + .find( 'Picker' ) + .props() + .options.filter( ( item ) => item.label === expectedOption ) + ).toHaveLength( 1 ); }; expectOptionForMediaType( MEDIA_TYPE_IMAGE, OPTION_TAKE_PHOTO ); expectOptionForMediaType( MEDIA_TYPE_VIDEO, OPTION_TAKE_VIDEO ); } ); - const expectMediaPickerForOption = ( option, allowMultiple, requestFunction ) => { - requestFunction.mockImplementation( ( source, mediaTypes, multiple, callback ) => { - expect( mediaTypes[ 0 ] ).toEqual( MEDIA_TYPE_VIDEO ); - if ( multiple ) { - callback( [ { id: MEDIA_ID, url: MEDIA_URL } ] ); - } else { - callback( { id: MEDIA_ID, url: MEDIA_URL } ); + const expectMediaPickerForOption = ( + option, + allowMultiple, + requestFunction + ) => { + requestFunction.mockImplementation( + ( source, mediaTypes, multiple, callback ) => { + expect( mediaTypes[ 0 ] ).toEqual( MEDIA_TYPE_VIDEO ); + if ( multiple ) { + callback( [ { id: MEDIA_ID, url: MEDIA_URL } ] ); + } else { + callback( { id: MEDIA_ID, url: MEDIA_URL } ); + } } - } ); + ); const onSelect = jest.fn(); @@ -85,7 +100,8 @@ describe( 'MediaUpload component', () => { { getMediaOptions() } </TouchableWithoutFeedback> ); - } } /> + } } + /> ); wrapper.find( 'Picker' ).simulate( 'change', option ); const media = { id: MEDIA_ID, url: MEDIA_URL }; @@ -93,26 +109,48 @@ describe( 'MediaUpload component', () => { expect( requestFunction ).toHaveBeenCalledTimes( 1 ); expect( onSelect ).toHaveBeenCalledTimes( 1 ); - expect( onSelect ).toHaveBeenCalledWith( allowMultiple ? [ media ] : media ); + expect( onSelect ).toHaveBeenCalledWith( + allowMultiple ? [ media ] : media + ); }; it( 'can select media from device library', () => { - expectMediaPickerForOption( mediaSources.deviceLibrary, false, requestMediaPicker ); + expectMediaPickerForOption( + mediaSources.deviceLibrary, + false, + requestMediaPicker + ); } ); it( 'can select media from WP media library', () => { - expectMediaPickerForOption( mediaSources.siteMediaLibrary, false, requestMediaPicker ); + expectMediaPickerForOption( + mediaSources.siteMediaLibrary, + false, + requestMediaPicker + ); } ); it( 'can select media by capturig', () => { - expectMediaPickerForOption( mediaSources.deviceCamera, false, requestMediaPicker ); + expectMediaPickerForOption( + mediaSources.deviceCamera, + false, + requestMediaPicker + ); } ); it( 'can select multiple media from device library', () => { - expectMediaPickerForOption( mediaSources.deviceLibrary, true, requestMediaPicker ); + expectMediaPickerForOption( + mediaSources.deviceLibrary, + true, + requestMediaPicker + ); } ); it( 'can select multiple media from WP media library', () => { - expectMediaPickerForOption( mediaSources.siteMediaLibrary, true, requestMediaPicker ); + expectMediaPickerForOption( + mediaSources.siteMediaLibrary, + true, + requestMediaPicker + ); } ); } ); diff --git a/packages/block-editor/src/components/multi-select-scroll-into-view/index.js b/packages/block-editor/src/components/multi-select-scroll-into-view/index.js index 23a917ca676e2b..3443ee44d2956c 100644 --- a/packages/block-editor/src/components/multi-select-scroll-into-view/index.js +++ b/packages/block-editor/src/components/multi-select-scroll-into-view/index.js @@ -21,10 +21,9 @@ import { getBlockDOMNode } from '../../utils/dom'; */ export default function MultiSelectScrollIntoView() { const selector = ( select ) => { - const { - getBlockSelectionEnd, - isMultiSelecting, - } = select( 'core/block-editor' ); + const { getBlockSelectionEnd, isMultiSelecting } = select( + 'core/block-editor' + ); return { selectionEnd: getBlockSelectionEnd(), diff --git a/packages/block-editor/src/components/multi-selection-inspector/index.js b/packages/block-editor/src/components/multi-selection-inspector/index.js index 15a1c8302e58f2..d501e1247d71ac 100644 --- a/packages/block-editor/src/components/multi-selection-inspector/index.js +++ b/packages/block-editor/src/components/multi-selection-inspector/index.js @@ -5,10 +5,7 @@ import { sprintf, _n } from '@wordpress/i18n'; import { withSelect } from '@wordpress/data'; import { serialize } from '@wordpress/blocks'; import { count as wordCount } from '@wordpress/wordcount'; -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; /** * Internal dependencies @@ -20,21 +17,25 @@ function MultiSelectionInspector( { blocks } ) { return ( <div className="block-editor-multi-selection-inspector__card"> - <BlockIcon icon={ - <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm18-4H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14z" /></SVG> - } showColors /> + <BlockIcon + icon={ + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M3 5H1v16c0 1.1.9 2 2 2h16v-2H3V5zm18-4H7c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 16H7V3h14v14z" /> + </SVG> + } + showColors + /> <div className="block-editor-multi-selection-inspector__card-content"> <div className="block-editor-multi-selection-inspector__card-title"> - { - /* translators: %d: number of blocks */ - sprintf( _n( '%d block', '%d blocks', blocks.length ), blocks.length ) - } + { /* translators: %d: number of blocks */ + sprintf( + _n( '%d block', '%d blocks', blocks.length ), + blocks.length + ) } </div> <div className="block-editor-multi-selection-inspector__card-description"> - { - /* translators: %d: number of words */ - sprintf( _n( '%d word', '%d words', words ), words ) - } + { /* translators: %d: number of words */ + sprintf( _n( '%d word', '%d words', words ), words ) } </div> </div> </div> diff --git a/packages/block-editor/src/components/navigable-toolbar/index.js b/packages/block-editor/src/components/navigable-toolbar/index.js index 5cc3a12bd266bc..c79563c807a64e 100644 --- a/packages/block-editor/src/components/navigable-toolbar/index.js +++ b/packages/block-editor/src/components/navigable-toolbar/index.js @@ -14,11 +14,10 @@ function NavigableToolbar( { children, focusOnMount, ...props } ) { tabbables[ 0 ].focus(); } }, [] ); - useShortcut( - 'core/block-editor/focus-toolbar', - focusToolbar, - { bindGlobal: true, eventName: 'keydown' } - ); + useShortcut( 'core/block-editor/focus-toolbar', focusToolbar, { + bindGlobal: true, + eventName: 'keydown', + } ); useEffect( () => { if ( focusOnMount ) { focusToolbar(); diff --git a/packages/block-editor/src/components/observe-typing/index.js b/packages/block-editor/src/components/observe-typing/index.js index 0e9c176e251f2e..ae7cddfeb80aec 100644 --- a/packages/block-editor/src/components/observe-typing/index.js +++ b/packages/block-editor/src/components/observe-typing/index.js @@ -41,12 +41,11 @@ function isKeyDownEligibleForStartTyping( event ) { return ! shiftKey && includes( KEY_DOWN_ELIGIBLE_KEY_CODES, keyCode ); } -function ObserveTyping( { - children, - setTimeout: setSafeTimeout, -} ) { +function ObserveTyping( { children, setTimeout: setSafeTimeout } ) { const lastMouseMove = useRef(); - const isTyping = useSelect( ( select ) => select( 'core/block-editor' ).isTyping() ); + const isTyping = useSelect( ( select ) => + select( 'core/block-editor' ).isTyping() + ); const { startTyping, stopTyping } = useDispatch( 'core/block-editor' ); useEffect( () => { toggleEventBindings( isTyping ); @@ -61,7 +60,10 @@ function ObserveTyping( { */ function toggleEventBindings( isBound ) { const bindFn = isBound ? 'addEventListener' : 'removeEventListener'; - document[ bindFn ]( 'selectionchange', stopTypingOnSelectionUncollapse ); + document[ bindFn ]( + 'selectionchange', + stopTypingOnSelectionUncollapse + ); document[ bindFn ]( 'mousemove', stopTypingOnMouseMove ); } @@ -95,7 +97,8 @@ function ObserveTyping( { */ function stopTypingOnSelectionUncollapse() { const selection = window.getSelection(); - const isCollapsed = selection.rangeCount > 0 && selection.getRangeAt( 0 ).collapsed; + const isCollapsed = + selection.rangeCount > 0 && selection.getRangeAt( 0 ).collapsed; if ( ! isCollapsed ) { stopTyping(); @@ -124,14 +127,21 @@ function ObserveTyping( { // Abort early if already typing, or key press is incurred outside a // text field (e.g. arrow-ing through toolbar buttons). // Ignore typing in a block toolbar - if ( isTyping || ! isTextField( target ) || target.closest( '.block-editor-block-toolbar' ) ) { + if ( + isTyping || + ! isTextField( target ) || + target.closest( '.block-editor-block-toolbar' ) + ) { return; } // Special-case keydown because certain keys do not emit a keypress // event. Conversely avoid keydown as the canonical event since there // are many keydown which are explicitly not targeted for typing. - if ( type === 'keydown' && ! isKeyDownEligibleForStartTyping( event ) ) { + if ( + type === 'keydown' && + ! isKeyDownEligibleForStartTyping( event ) + ) { return; } @@ -164,7 +174,10 @@ function ObserveTyping( { <div onFocus={ stopTypingOnNonTextField } onKeyPress={ startTypingInTextField } - onKeyDown={ over( [ startTypingInTextField, stopTypingOnEscapeKey ] ) } + onKeyDown={ over( [ + startTypingInTextField, + stopTypingOnEscapeKey, + ] ) } > { children } </div> diff --git a/packages/block-editor/src/components/page-template-picker/button.js b/packages/block-editor/src/components/page-template-picker/button.js index 7d5b03479816d9..a45722101c839b 100644 --- a/packages/block-editor/src/components/page-template-picker/button.js +++ b/packages/block-editor/src/components/page-template-picker/button.js @@ -4,11 +4,7 @@ import { Button } from '@wordpress/components'; const PickerButton = ( props ) => { - const { - icon, - label, - onPress, - } = props; + const { icon, label, onPress } = props; return ( <Button onClick={ onPress }> diff --git a/packages/block-editor/src/components/page-template-picker/button.native.js b/packages/block-editor/src/components/page-template-picker/button.native.js index 897ceb20dd41f9..9c03d10379f65f 100644 --- a/packages/block-editor/src/components/page-template-picker/button.native.js +++ b/packages/block-editor/src/components/page-template-picker/button.native.js @@ -14,14 +14,15 @@ import { withPreferredColorScheme } from '@wordpress/compose'; */ import styles from './styles.scss'; -const PickerButton = ( { - icon, - label, - onPress, - getStylesFromColorScheme, -} ) => { - const butonStyles = getStylesFromColorScheme( styles.button, styles.buttonDark ); - const butonTextStyles = getStylesFromColorScheme( styles.buttonText, styles.buttonTextDark ); +const PickerButton = ( { icon, label, onPress, getStylesFromColorScheme } ) => { + const butonStyles = getStylesFromColorScheme( + styles.button, + styles.buttonDark + ); + const butonTextStyles = getStylesFromColorScheme( + styles.buttonText, + styles.buttonTextDark + ); return ( <TouchableOpacity diff --git a/packages/block-editor/src/components/page-template-picker/container.native.js b/packages/block-editor/src/components/page-template-picker/container.native.js index 7d1aa596a51849..d5f49b27aabc14 100644 --- a/packages/block-editor/src/components/page-template-picker/container.native.js +++ b/packages/block-editor/src/components/page-template-picker/container.native.js @@ -19,7 +19,7 @@ const Container = ( { style, children } ) => { style={ [ styles.container, style ] } > { children } - </ScrollView > + </ScrollView> ); }; diff --git a/packages/block-editor/src/components/page-template-picker/default-templates.js b/packages/block-editor/src/components/page-template-picker/default-templates.js index 771a6ad5e3c06e..60baaf031f7a30 100644 --- a/packages/block-editor/src/components/page-template-picker/default-templates.js +++ b/packages/block-editor/src/components/page-template-picker/default-templates.js @@ -89,9 +89,11 @@ const defaultTemplates = [ }, ]; -const parsedTemplates = memoize( () => defaultTemplates.map( ( template ) => ( { - ...template, - blocks: parse( template.content ), -} ) ) ); +const parsedTemplates = memoize( () => + defaultTemplates.map( ( template ) => ( { + ...template, + blocks: parse( template.content ), + } ) ) +); export default parsedTemplates; diff --git a/packages/block-editor/src/components/page-template-picker/picker.js b/packages/block-editor/src/components/page-template-picker/picker.js index ff8dd16de42770..e708cbf1dec791 100644 --- a/packages/block-editor/src/components/page-template-picker/picker.js +++ b/packages/block-editor/src/components/page-template-picker/picker.js @@ -13,11 +13,13 @@ import Container from './container'; import getDefaultTemplates from './default-templates'; import Preview from './preview'; -const __experimentalPageTemplatePicker = ( { templates = getDefaultTemplates() } ) => { +const __experimentalPageTemplatePicker = ( { + templates = getDefaultTemplates(), +} ) => { const { editPost } = useDispatch( 'core/editor' ); const [ templatePreview, setTemplatePreview ] = useState(); - const onApply = ( ) => { + const onApply = () => { editPost( { title: templatePreview.name, blocks: parse( templatePreview.content ), diff --git a/packages/block-editor/src/components/page-template-picker/preview.native.js b/packages/block-editor/src/components/page-template-picker/preview.native.js index 442c0b2e5e7020..65b19d93ba9269 100644 --- a/packages/block-editor/src/components/page-template-picker/preview.native.js +++ b/packages/block-editor/src/components/page-template-picker/preview.native.js @@ -28,10 +28,7 @@ const BlockPreview = ( { blocks } ) => { }; return ( - <BlockEditorProvider - value={ blocks } - settings={ settings } - > + <BlockEditorProvider value={ blocks } settings={ settings }> <View style={ { flex: 1 } }> <BlockList /> </View> @@ -43,15 +40,14 @@ BlockPreview.displayName = 'BlockPreview'; const Preview = ( props ) => { const { template, onDismiss, onApply } = props; const preferredColorScheme = usePreferredColorScheme(); - const containerBackgroundColor = preferredColorScheme === 'dark' ? 'black' : 'white'; + const containerBackgroundColor = + preferredColorScheme === 'dark' ? 'black' : 'white'; if ( template === undefined ) { return null; } - const leftButton = ( - <ModalHeaderBar.CloseButton onPress={ onDismiss } /> - ); + const leftButton = <ModalHeaderBar.CloseButton onPress={ onDismiss } />; const rightButton = ( <ModalHeaderBar.Button @@ -68,16 +64,16 @@ const Preview = ( props ) => { onRequestClose={ onDismiss } supportedOrientations={ [ 'portrait', 'landscape' ] } > - <SafeAreaView style={ { flex: 1, backgroundColor: containerBackgroundColor } }> + <SafeAreaView + style={ { flex: 1, backgroundColor: containerBackgroundColor } } + > <ModalHeaderBar leftButton={ leftButton } rightButton={ rightButton } title={ template.name } subtitle={ __( 'Template Preview' ) } /> - <BlockPreview - blocks={ template.blocks } - /> + <BlockPreview blocks={ template.blocks } /> </SafeAreaView> </Modal> ); diff --git a/packages/block-editor/src/components/page-template-picker/use-page-template-picker-visible.js b/packages/block-editor/src/components/page-template-picker/use-page-template-picker-visible.js index ed764f2221f1f1..fba563260e86e3 100644 --- a/packages/block-editor/src/components/page-template-picker/use-page-template-picker-visible.js +++ b/packages/block-editor/src/components/page-template-picker/use-page-template-picker-visible.js @@ -6,22 +6,20 @@ import { useSelect } from '@wordpress/data'; const __experimentalUsePageTemplatePickerVisible = () => { return useSelect( ( select ) => { - const { - getCurrentPostType, - } = select( 'core/editor' ); + const { getCurrentPostType } = select( 'core/editor' ); - const { - getBlockOrder, - getBlock, - getSettings, - } = select( 'core/block-editor' ); + const { getBlockOrder, getBlock, getSettings } = select( + 'core/block-editor' + ); - const isPageTemplatesEnabled = getSettings().__experimentalEnablePageTemplates; + const isPageTemplatesEnabled = getSettings() + .__experimentalEnablePageTemplates; const blocks = getBlockOrder(); const isEmptyBlockList = blocks.length === 0; const firstBlock = ! isEmptyBlockList && getBlock( blocks[ 0 ] ); - const isOnlyUnmodifiedDefault = blocks.length === 1 && isUnmodifiedDefaultBlock( firstBlock ); + const isOnlyUnmodifiedDefault = + blocks.length === 1 && isUnmodifiedDefaultBlock( firstBlock ); const isEmptyContent = isEmptyBlockList || isOnlyUnmodifiedDefault; const isPage = getCurrentPostType() === 'page'; diff --git a/packages/block-editor/src/components/panel-color-settings/index.js b/packages/block-editor/src/components/panel-color-settings/index.js index 18e7e48858841c..f346684024fb2e 100644 --- a/packages/block-editor/src/components/panel-color-settings/index.js +++ b/packages/block-editor/src/components/panel-color-settings/index.js @@ -3,10 +3,7 @@ */ import PanelColorGradientSettings from '../colors-gradients/panel-color-gradient-settings'; -const PanelColorSettings = ( { - colorSettings, - ...props -} ) => { +const PanelColorSettings = ( { colorSettings, ...props } ) => { const settings = colorSettings.map( ( { value, onChange, ...otherSettings } ) => ( { ...otherSettings, diff --git a/packages/block-editor/src/components/panel-color-settings/test/index.js b/packages/block-editor/src/components/panel-color-settings/test/index.js index de5818c2bc1416..4a6c6e5baa3e26 100644 --- a/packages/block-editor/src/components/panel-color-settings/test/index.js +++ b/packages/block-editor/src/components/panel-color-settings/test/index.js @@ -14,23 +14,25 @@ describe( 'PanelColorSettings', () => { let root; await act( async () => { - root = create( <PanelColorSettings - title="Test Title" - colors={ [] } - disableCustomColors={ true } - colorSettings={ [ - { - value: '#000', - onChange: noop, - label: 'border color', - }, - { - value: '#111', - onChange: noop, - label: 'background color', - }, - ] } - /> ); + root = create( + <PanelColorSettings + title="Test Title" + colors={ [] } + disableCustomColors={ true } + colorSettings={ [ + { + value: '#000', + onChange: noop, + label: 'border color', + }, + { + value: '#111', + onChange: noop, + label: 'background color', + }, + ] } + /> + ); } ); expect( root.toJSON() ).toBe( null ); @@ -76,11 +78,13 @@ describe( 'PanelColorSettings', () => { value: '#000', onChange: noop, label: 'border color', - colors: [ { - slug: 'red', - name: 'Red', - color: '#ff0000', - } ], + colors: [ + { + slug: 'red', + name: 'Red', + color: '#ff0000', + }, + ], }, { value: '#111', diff --git a/packages/block-editor/src/components/plain-text/index.js b/packages/block-editor/src/components/plain-text/index.js index 1893538fe61ee9..a0a2aae23a2ce7 100644 --- a/packages/block-editor/src/components/plain-text/index.js +++ b/packages/block-editor/src/components/plain-text/index.js @@ -1,4 +1,3 @@ - /** * WordPress dependencies */ diff --git a/packages/block-editor/src/components/plain-text/index.native.js b/packages/block-editor/src/components/plain-text/index.native.js index 56bdf757741824..aeebb66b567d2a 100644 --- a/packages/block-editor/src/components/plain-text/index.native.js +++ b/packages/block-editor/src/components/plain-text/index.native.js @@ -21,7 +21,10 @@ export default class PlainText extends Component { componentDidMount() { // if isSelected is true, we should request the focus on this TextInput - if ( ( this._input.isFocused() === false ) && ( this._input.props.isSelected === true ) ) { + if ( + this._input.isFocused() === false && + this._input.props.isSelected === true + ) { this.focus(); } } @@ -40,14 +43,19 @@ export default class PlainText extends Component { return ( <TextInput { ...this.props } - ref={ ( x ) => this._input = x } + ref={ ( x ) => ( this._input = x ) } onChange={ ( event ) => { this.props.onChange( event.nativeEvent.text ); } } onFocus={ this.props.onFocus } // always assign onFocus as a props onBlur={ this.props.onBlur } // always assign onBlur as a props - fontFamily={ ( this.props.style && this.props.style.fontFamily ) || ( styles[ 'block-editor-plain-text' ].fontFamily ) } - style={ this.props.style || styles[ 'block-editor-plain-text' ] } + fontFamily={ + ( this.props.style && this.props.style.fontFamily ) || + styles[ 'block-editor-plain-text' ].fontFamily + } + style={ + this.props.style || styles[ 'block-editor-plain-text' ] + } /> ); } diff --git a/packages/block-editor/src/components/provider/index.js b/packages/block-editor/src/components/provider/index.js index a9dfbf11430c60..57413c693f1fa6 100644 --- a/packages/block-editor/src/components/provider/index.js +++ b/packages/block-editor/src/components/provider/index.js @@ -105,19 +105,15 @@ class BlockEditorProvider extends Component { let isPersistent = isLastBlockChangePersistent(); this.unsubscribe = registry.subscribe( () => { - const { - onChange = noop, - onInput = noop, - } = this.props; + const { onChange = noop, onInput = noop } = this.props; const newBlocks = getBlocks(); const newIsPersistent = isLastBlockChangePersistent(); if ( - newBlocks !== blocks && ( - this.isSyncingIncomingValue || - __unstableIsLastBlockChangeIgnored() - ) + newBlocks !== blocks && + ( this.isSyncingIncomingValue || + __unstableIsLastBlockChangeIgnored() ) ) { this.isSyncingIncomingValue = null; blocks = newBlocks; @@ -161,11 +157,9 @@ class BlockEditorProvider extends Component { export default compose( [ withRegistryProvider, withDispatch( ( dispatch ) => { - const { - updateSettings, - resetBlocks, - resetSelection, - } = dispatch( 'core/block-editor' ); + const { updateSettings, resetBlocks, resetSelection } = dispatch( + 'core/block-editor' + ); return { updateSettings, diff --git a/packages/block-editor/src/components/provider/index.native.js b/packages/block-editor/src/components/provider/index.native.js index a9dfbf11430c60..57413c693f1fa6 100644 --- a/packages/block-editor/src/components/provider/index.native.js +++ b/packages/block-editor/src/components/provider/index.native.js @@ -105,19 +105,15 @@ class BlockEditorProvider extends Component { let isPersistent = isLastBlockChangePersistent(); this.unsubscribe = registry.subscribe( () => { - const { - onChange = noop, - onInput = noop, - } = this.props; + const { onChange = noop, onInput = noop } = this.props; const newBlocks = getBlocks(); const newIsPersistent = isLastBlockChangePersistent(); if ( - newBlocks !== blocks && ( - this.isSyncingIncomingValue || - __unstableIsLastBlockChangeIgnored() - ) + newBlocks !== blocks && + ( this.isSyncingIncomingValue || + __unstableIsLastBlockChangeIgnored() ) ) { this.isSyncingIncomingValue = null; blocks = newBlocks; @@ -161,11 +157,9 @@ class BlockEditorProvider extends Component { export default compose( [ withRegistryProvider, withDispatch( ( dispatch ) => { - const { - updateSettings, - resetBlocks, - resetSelection, - } = dispatch( 'core/block-editor' ); + const { updateSettings, resetBlocks, resetSelection } = dispatch( + 'core/block-editor' + ); return { updateSettings, diff --git a/packages/block-editor/src/components/provider/with-registry-provider.js b/packages/block-editor/src/components/provider/with-registry-provider.js index cdb3b7fe7b2ded..60109fea59f3c4 100644 --- a/packages/block-editor/src/components/provider/with-registry-provider.js +++ b/packages/block-editor/src/components/provider/with-registry-provider.js @@ -2,7 +2,11 @@ * WordPress dependencies */ import { useState, useEffect } from '@wordpress/element'; -import { withRegistry, createRegistry, RegistryProvider } from '@wordpress/data'; +import { + withRegistry, + createRegistry, + RegistryProvider, +} from '@wordpress/data'; import { createHigherOrderComponent } from '@wordpress/compose'; /** @@ -11,31 +15,44 @@ import { createHigherOrderComponent } from '@wordpress/compose'; import { storeConfig } from '../../store'; import applyMiddlewares from '../../store/middlewares'; -const withRegistryProvider = createHigherOrderComponent( ( WrappedComponent ) => { - return withRegistry( ( { useSubRegistry = true, registry, ...props } ) => { - if ( ! useSubRegistry ) { - return <WrappedComponent registry={ registry } { ...props } />; - } +const withRegistryProvider = createHigherOrderComponent( + ( WrappedComponent ) => { + return withRegistry( + ( { useSubRegistry = true, registry, ...props } ) => { + if ( ! useSubRegistry ) { + return ( + <WrappedComponent registry={ registry } { ...props } /> + ); + } - const [ subRegistry, setSubRegistry ] = useState( null ); - useEffect( () => { - const newRegistry = createRegistry( {}, registry ); - const store = newRegistry.registerStore( 'core/block-editor', storeConfig ); - // This should be removed after the refactoring of the effects to controls. - applyMiddlewares( store ); - setSubRegistry( newRegistry ); - }, [ registry ] ); + const [ subRegistry, setSubRegistry ] = useState( null ); + useEffect( () => { + const newRegistry = createRegistry( {}, registry ); + const store = newRegistry.registerStore( + 'core/block-editor', + storeConfig + ); + // This should be removed after the refactoring of the effects to controls. + applyMiddlewares( store ); + setSubRegistry( newRegistry ); + }, [ registry ] ); - if ( ! subRegistry ) { - return null; - } + if ( ! subRegistry ) { + return null; + } - return ( - <RegistryProvider value={ subRegistry }> - <WrappedComponent registry={ subRegistry } { ...props } /> - </RegistryProvider> + return ( + <RegistryProvider value={ subRegistry }> + <WrappedComponent + registry={ subRegistry } + { ...props } + /> + </RegistryProvider> + ); + } ); - } ); -}, 'withRegistryProvider' ); + }, + 'withRegistryProvider' +); export default withRegistryProvider; diff --git a/packages/block-editor/src/components/responsive-block-control/index.js b/packages/block-editor/src/components/responsive-block-control/index.js index c4f47d1169128f..07b6a6e5fa874f 100644 --- a/packages/block-editor/src/components/responsive-block-control/index.js +++ b/packages/block-editor/src/components/responsive-block-control/index.js @@ -5,9 +5,7 @@ import { __, sprintf } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; -import { - ToggleControl, -} from '@wordpress/components'; +import { ToggleControl } from '@wordpress/components'; /** * Internal dependencies @@ -25,7 +23,9 @@ function ResponsiveBlockControl( props ) { isResponsive = false, defaultLabel = { id: 'all', - label: __( 'All' ), /* translators: 'Label. Used to signify a layout property (eg: margin, padding) will apply uniformly to all screensizes.' */ + label: __( + 'All' + ) /* translators: 'Label. Used to signify a layout property (eg: margin, padding) will apply uniformly to all screensizes.' */, }, viewports = [ { @@ -48,25 +48,42 @@ function ResponsiveBlockControl( props ) { } /* translators: 'Toggle control label. Should the property be the same across all screen sizes or unique per screen size.'. %s property value for the control (eg: margin, padding...etc) */ - const toggleControlLabel = toggleLabel || sprintf( __( 'Use the same %s on all screensizes.', ), property ); + const toggleControlLabel = + toggleLabel || + sprintf( __( 'Use the same %s on all screensizes.' ), property ); /* translators: 'Help text for the responsive mode toggle control.' */ - const toggleHelpText = __( 'Toggle between using the same value for all screen sizes or using a unique value per screen size.' ); + const toggleHelpText = __( + 'Toggle between using the same value for all screen sizes or using a unique value per screen size.' + ); - const defaultControl = renderDefaultControl( <ResponsiveBlockControlLabel property={ property } viewport={ defaultLabel } />, defaultLabel ); + const defaultControl = renderDefaultControl( + <ResponsiveBlockControlLabel + property={ property } + viewport={ defaultLabel } + />, + defaultLabel + ); const defaultResponsiveControls = () => { return viewports.map( ( viewport ) => ( <Fragment key={ viewport.id }> - { renderDefaultControl( <ResponsiveBlockControlLabel property={ property } viewport={ viewport } />, viewport ) } + { renderDefaultControl( + <ResponsiveBlockControlLabel + property={ property } + viewport={ viewport } + />, + viewport + ) } </Fragment> ) ); }; return ( - <fieldset className="block-editor-responsive-block-control"> - <legend className="block-editor-responsive-block-control__title">{ title }</legend> + <legend className="block-editor-responsive-block-control__title"> + { title } + </legend> <div className="block-editor-responsive-block-control__inner"> <ToggleControl @@ -78,17 +95,21 @@ function ResponsiveBlockControl( props ) { /> { ! isResponsive && ( - <div className="block-editor-responsive-block-control__group block-editor-responsive-block-control__group--default" > + <div className="block-editor-responsive-block-control__group block-editor-responsive-block-control__group--default"> { defaultControl } </div> ) } { isResponsive && ( - <div className="block-editor-responsive-block-control__group block-editor-responsive-block-control__group--responsive" hidden={ ! isResponsive }> - { ( renderResponsiveControls ? renderResponsiveControls( viewports ) : defaultResponsiveControls() ) } + <div + className="block-editor-responsive-block-control__group block-editor-responsive-block-control__group--responsive" + hidden={ ! isResponsive } + > + { renderResponsiveControls + ? renderResponsiveControls( viewports ) + : defaultResponsiveControls() } </div> ) } - </div> </fieldset> ); diff --git a/packages/block-editor/src/components/responsive-block-control/label.js b/packages/block-editor/src/components/responsive-block-control/label.js index 4637e8a12d2c78..dba8fe9e310c74 100644 --- a/packages/block-editor/src/components/responsive-block-control/label.js +++ b/packages/block-editor/src/components/responsive-block-control/label.js @@ -5,15 +5,33 @@ import { useInstanceId } from '@wordpress/compose'; import { _x, sprintf } from '@wordpress/i18n'; import { Fragment } from '@wordpress/element'; -export default function ResponsiveBlockControlLabel( { property, viewport, desc } ) { +export default function ResponsiveBlockControlLabel( { + property, + viewport, + desc, +} ) { const instanceId = useInstanceId( ResponsiveBlockControlLabel ); - const accessibleLabel = desc || sprintf( _x( 'Controls the %1$s property for %2$s viewports.', 'Text labelling a interface as controlling a given layout property (eg: margin) for a given screen size.' ), property, viewport.label ); + const accessibleLabel = + desc || + sprintf( + _x( + 'Controls the %1$s property for %2$s viewports.', + 'Text labelling a interface as controlling a given layout property (eg: margin) for a given screen size.' + ), + property, + viewport.label + ); return ( <Fragment> <span aria-describedby={ `rbc-desc-${ instanceId }` }> { viewport.label } </span> - <span className="screen-reader-text" id={ `rbc-desc-${ instanceId }` }>{ accessibleLabel }</span> + <span + className="screen-reader-text" + id={ `rbc-desc-${ instanceId }` } + > + { accessibleLabel } + </span> </Fragment> ); } diff --git a/packages/block-editor/src/components/responsive-block-control/test/index.js b/packages/block-editor/src/components/responsive-block-control/test/index.js index 877bddb0dfb91a..56045c7341d42d 100644 --- a/packages/block-editor/src/components/responsive-block-control/test/index.js +++ b/packages/block-editor/src/components/responsive-block-control/test/index.js @@ -10,9 +10,7 @@ import { uniqueId } from 'lodash'; */ import { Fragment, useState } from '@wordpress/element'; -import { - SelectControl, -} from '@wordpress/components'; +import { SelectControl } from '@wordpress/components'; /** * Internal dependencies @@ -57,13 +55,10 @@ const sizeOptions = [ const renderTestDefaultControlComponent = ( labelComponent, device ) => { return ( <Fragment> - <SelectControl - label={ labelComponent } - options={ sizeOptions } - /> + <SelectControl label={ labelComponent } options={ sizeOptions } /> <p id={ device.id }> - { device.label } is used here for testing purposes to ensure we have access - to details about the device. + { device.label } is used here for testing purposes to ensure we + have access to details about the device. </p> </Fragment> ); @@ -77,23 +72,41 @@ describe( 'Basic rendering', () => { title="Padding" property="padding" renderDefaultControl={ renderTestDefaultControlComponent } - />, container + />, + container ); } ); - const activePropertyLabel = Array.from( container.querySelectorAll( 'legend' ) ).find( ( legend ) => legend.innerHTML === 'Padding' ); + const activePropertyLabel = Array.from( + container.querySelectorAll( 'legend' ) + ).find( ( legend ) => legend.innerHTML === 'Padding' ); - const activeViewportLabel = Array.from( container.querySelectorAll( 'label' ) ).find( ( label ) => label.innerHTML.includes( 'All' ) ); + const activeViewportLabel = Array.from( + container.querySelectorAll( 'label' ) + ).find( ( label ) => label.innerHTML.includes( 'All' ) ); - const defaultControl = container.querySelector( `#${ activeViewportLabel.getAttribute( 'for' ) }` ); + const defaultControl = container.querySelector( + `#${ activeViewportLabel.getAttribute( 'for' ) }` + ); - const toggleLabel = Array.from( container.querySelectorAll( 'label' ) ).filter( ( label ) => label.innerHTML.includes( 'Use the same padding on all screensizes' ) ); + const toggleLabel = Array.from( + container.querySelectorAll( 'label' ) + ).filter( ( label ) => + label.innerHTML.includes( + 'Use the same padding on all screensizes' + ) + ); - const toggleState = container.querySelector( 'input[type="checkbox"]' ).checked; + const toggleState = container.querySelector( 'input[type="checkbox"]' ) + .checked; - const defaultControlGroup = container.querySelector( '.block-editor-responsive-block-control__group--default' ); + const defaultControlGroup = container.querySelector( + '.block-editor-responsive-block-control__group--default' + ); - const responsiveControlGroup = container.querySelector( '.block-editor-responsive-block-control__group--responsive' ); + const responsiveControlGroup = container.querySelector( + '.block-editor-responsive-block-control__group--responsive' + ); expect( container.innerHTML ).not.toBe( '' ); @@ -114,7 +127,8 @@ describe( 'Basic rendering', () => { <ResponsiveBlockControl property="padding" renderDefaultControl={ renderTestDefaultControlComponent } - />, container + />, + container ); } ); @@ -127,7 +141,8 @@ describe( 'Basic rendering', () => { <ResponsiveBlockControl title="Padding" renderDefaultControl={ renderTestDefaultControlComponent } - />, container + />, + container ); } ); @@ -137,10 +152,8 @@ describe( 'Basic rendering', () => { it( 'should not render without valid default control render prop', () => { act( () => { render( - <ResponsiveBlockControl - title="Padding" - property="padding" - />, container + <ResponsiveBlockControl title="Padding" property="padding" />, + container ); } ); @@ -148,7 +161,8 @@ describe( 'Basic rendering', () => { } ); it( 'should render with custom label for toggle control when provided', () => { - const customToggleLabel = 'Utilise a matching padding value on all viewports'; + const customToggleLabel = + 'Utilise a matching padding value on all viewports'; act( () => { render( <ResponsiveBlockControl @@ -156,11 +170,14 @@ describe( 'Basic rendering', () => { property="padding" renderDefaultControl={ renderTestDefaultControlComponent } toggleLabel={ customToggleLabel } - />, container + />, + container ); } ); - const actualToggleLabel = container.querySelector( 'label.components-toggle-control__label' ).innerHTML; + const actualToggleLabel = container.querySelector( + 'label.components-toggle-control__label' + ).innerHTML; expect( actualToggleLabel ).toEqual( customToggleLabel ); } ); @@ -175,11 +192,14 @@ describe( 'Basic rendering', () => { property="padding" renderDefaultControl={ renderTestDefaultControlComponent } defaultLabel={ customDefaultControlGroupLabel } - />, container + />, + container ); } ); - const defaultControlLabel = Array.from( container.querySelectorAll( 'label' ) ).find( ( label ) => label.innerHTML.includes( 'Everything' ) ); + const defaultControlLabel = Array.from( + container.querySelectorAll( 'label' ) + ).find( ( label ) => label.innerHTML.includes( 'Everything' ) ); expect( defaultControlLabel ).not.toBeNull(); } ); @@ -194,12 +214,17 @@ describe( 'Default and Responsive modes', () => { property="padding" isResponsive={ true } renderDefaultControl={ renderTestDefaultControlComponent } - />, container + />, + container ); } ); - const defaultControlGroup = container.querySelector( '.block-editor-responsive-block-control__group--default' ); - const responsiveControlGroup = container.querySelector( '.block-editor-responsive-block-control__group--responsive' ); + const defaultControlGroup = container.querySelector( + '.block-editor-responsive-block-control__group--default' + ); + const responsiveControlGroup = container.querySelector( + '.block-editor-responsive-block-control__group--responsive' + ); expect( defaultControlGroup ).toBeNull(); expect( responsiveControlGroup ).not.toBeNull(); @@ -225,7 +250,9 @@ describe( 'Default and Responsive modes', () => { }, ]; - const mockRenderDefaultControl = jest.fn( renderTestDefaultControlComponent ); + const mockRenderDefaultControl = jest.fn( + renderTestDefaultControlComponent + ); act( () => { render( @@ -235,21 +262,30 @@ describe( 'Default and Responsive modes', () => { isResponsive={ true } renderDefaultControl={ mockRenderDefaultControl } viewports={ customViewportSet } - />, container + />, + container ); } ); const defaultRenderControlCall = 1; // Get array of labels which match those in the custom viewports provided - const responsiveViewportsLabels = Array.from( container.querySelectorAll( 'label' ) ).filter( ( label ) => { + const responsiveViewportsLabels = Array.from( + container.querySelectorAll( 'label' ) + ).filter( ( label ) => { const labelText = label.innerHTML; // Is the label one of those in the custom device set? - return !! customViewportSet.find( ( deviceName ) => labelText.includes( deviceName.label ) ); + return !! customViewportSet.find( ( deviceName ) => + labelText.includes( deviceName.label ) + ); } ); - expect( responsiveViewportsLabels ).toHaveLength( customViewportSet.length ); - expect( mockRenderDefaultControl ).toHaveBeenCalledTimes( customViewportSet.length + defaultRenderControlCall ); + expect( responsiveViewportsLabels ).toHaveLength( + customViewportSet.length + ); + expect( mockRenderDefaultControl ).toHaveBeenCalledTimes( + customViewportSet.length + defaultRenderControlCall + ); } ); it( 'should switch between default and responsive modes when interacting with toggle control', () => { @@ -270,17 +306,27 @@ describe( 'Default and Responsive modes', () => { }; act( () => { - render( - <ResponsiveBlockControlConsumer />, container - ); + render( <ResponsiveBlockControlConsumer />, container ); } ); - let defaultControlGroup = container.querySelector( '.block-editor-responsive-block-control__group--default' ); - let responsiveControlGroup = container.querySelector( '.block-editor-responsive-block-control__group--responsive' ); + let defaultControlGroup = container.querySelector( + '.block-editor-responsive-block-control__group--default' + ); + let responsiveControlGroup = container.querySelector( + '.block-editor-responsive-block-control__group--responsive' + ); // Select elements based on what the user can see - const toggleLabel = Array.from( container.querySelectorAll( 'label' ) ).find( ( label ) => label.innerHTML.includes( 'Use the same padding on all screensizes' ) ); - const toggleInput = container.querySelector( `#${ toggleLabel.getAttribute( 'for' ) }` ); + const toggleLabel = Array.from( + container.querySelectorAll( 'label' ) + ).find( ( label ) => + label.innerHTML.includes( + 'Use the same padding on all screensizes' + ) + ); + const toggleInput = container.querySelector( + `#${ toggleLabel.getAttribute( 'for' ) }` + ); // Initial mode (default) expect( defaultControlGroup ).not.toBeNull(); @@ -291,8 +337,12 @@ describe( 'Default and Responsive modes', () => { Simulate.change( toggleInput, { target: { checked: false } } ); } ); - defaultControlGroup = container.querySelector( '.block-editor-responsive-block-control__group--default' ); - responsiveControlGroup = container.querySelector( '.block-editor-responsive-block-control__group--responsive' ); + defaultControlGroup = container.querySelector( + '.block-editor-responsive-block-control__group--default' + ); + responsiveControlGroup = container.querySelector( + '.block-editor-responsive-block-control__group--responsive' + ); expect( defaultControlGroup ).toBeNull(); expect( responsiveControlGroup ).not.toBeNull(); @@ -302,8 +352,12 @@ describe( 'Default and Responsive modes', () => { Simulate.change( toggleInput, { target: { checked: true } } ); } ); - defaultControlGroup = container.querySelector( '.block-editor-responsive-block-control__group--default' ); - responsiveControlGroup = container.querySelector( '.block-editor-responsive-block-control__group--responsive' ); + defaultControlGroup = container.querySelector( + '.block-editor-responsive-block-control__group--default' + ); + responsiveControlGroup = container.querySelector( + '.block-editor-responsive-block-control__group--responsive' + ); expect( defaultControlGroup ).not.toBeNull(); expect( responsiveControlGroup ).toBeNull(); @@ -316,7 +370,9 @@ describe( 'Default and Responsive modes', () => { return viewports.map( ( { id, label } ) => { return ( <Fragment key={ `${ inputId }-${ id }` }> - <label htmlFor={ `${ inputId }-${ id }` }>Custom Viewport { label }</label> + <label htmlFor={ `${ inputId }-${ id }` }> + Custom Viewport { label } + </label> <input id={ `${ inputId }-${ id }` } defaultValue={ label } @@ -335,12 +391,15 @@ describe( 'Default and Responsive modes', () => { isResponsive={ true } renderDefaultControl={ spyRenderDefaultControl } renderResponsiveControls={ mockRenderResponsiveControls } - />, container + />, + container ); } ); // The user should see "range" controls so we can legitimately query for them here - const customControls = Array.from( container.querySelectorAll( 'input[type="range"]' ) ); + const customControls = Array.from( + container.querySelectorAll( 'input[type="range"]' ) + ); // Also called because default control rendeer function is always called // (for convenience) even though it's not displayed in output. @@ -351,4 +410,3 @@ describe( 'Default and Responsive modes', () => { expect( customControls ).toHaveLength( 3 ); } ); } ); - diff --git a/packages/block-editor/src/components/rich-text/format-toolbar/index.js b/packages/block-editor/src/components/rich-text/format-toolbar/index.js index 56b5cf1d0252ee..1667bc2a06b042 100644 --- a/packages/block-editor/src/components/rich-text/format-toolbar/index.js +++ b/packages/block-editor/src/components/rich-text/format-toolbar/index.js @@ -19,17 +19,25 @@ const FormatToolbar = () => { return ( <div className="block-editor-format-toolbar"> <Toolbar> - { [ 'bold', 'italic', 'link' ].map( ( format ) => - <Slot name={ `RichText.ToolbarControls.${ format }` } key={ format } /> - ) } + { [ 'bold', 'italic', 'link' ].map( ( format ) => ( + <Slot + name={ `RichText.ToolbarControls.${ format }` } + key={ format } + /> + ) ) } <Slot name="RichText.ToolbarControls"> - { ( fills ) => fills.length !== 0 && - <DropdownMenu - icon={ false } - label={ __( 'More rich text controls' ) } - controls={ orderBy( fills.map( ( [ { props } ] ) => props ), 'title' ) } - popoverProps={ POPOVER_PROPS } - /> + { ( fills ) => + fills.length !== 0 && ( + <DropdownMenu + icon={ false } + label={ __( 'More rich text controls' ) } + controls={ orderBy( + fills.map( ( [ { props } ] ) => props ), + 'title' + ) } + popoverProps={ POPOVER_PROPS } + /> + ) } </Slot> </Toolbar> diff --git a/packages/block-editor/src/components/rich-text/format-toolbar/index.native.js b/packages/block-editor/src/components/rich-text/format-toolbar/index.native.js index d39f9b4668eb44..e7be93d4b43100 100644 --- a/packages/block-editor/src/components/rich-text/format-toolbar/index.native.js +++ b/packages/block-editor/src/components/rich-text/format-toolbar/index.native.js @@ -7,9 +7,12 @@ import { Toolbar, Slot } from '@wordpress/components'; const FormatToolbar = () => { return ( <Toolbar> - { [ 'bold', 'italic', 'link' ].map( ( format ) => - <Slot name={ `RichText.ToolbarControls.${ format }` } key={ format } /> - ) } + { [ 'bold', 'italic', 'link' ].map( ( format ) => ( + <Slot + name={ `RichText.ToolbarControls.${ format }` } + key={ format } + /> + ) ) } <Slot name="RichText.ToolbarControls" /> </Toolbar> ); diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index cae2f37ce71392..6cfbbcc51ac998 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -7,10 +7,22 @@ import { omit } from 'lodash'; /** * WordPress dependencies */ -import { RawHTML, Component, useRef, Platform, forwardRef } from '@wordpress/element'; -import { withDispatch, withSelect } from '@wordpress/data'; -import { pasteHandler, children as childrenSource, getBlockTransforms, findTransform, isUnmodifiedDefaultBlock } from '@wordpress/blocks'; -import { withInstanceId, compose } from '@wordpress/compose'; +import { + RawHTML, + Platform, + useRef, + useCallback, + forwardRef, +} from '@wordpress/element'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { + pasteHandler, + children as childrenSource, + getBlockTransforms, + findTransform, + isUnmodifiedDefaultBlock, +} from '@wordpress/blocks'; +import { useInstanceId } from '@wordpress/compose'; import { __experimentalRichText as RichText, __unstableCreateElement, @@ -32,7 +44,7 @@ import { isURL } from '@wordpress/url'; * Internal dependencies */ import Autocomplete from '../autocomplete'; -import { withBlockEditContext } from '../block-edit/context'; +import { useBlockEditContext } from '../block-edit'; import { RemoveBrowserShortcuts } from './remove-browser-shortcuts'; import { filePasteHandler } from './file-paste-handler'; import FormatToolbarContainer from './format-toolbar-container'; @@ -55,151 +67,186 @@ function getMultilineTag( multiline ) { return multiline === true ? 'p' : multiline; } -class RichTextWrapper extends Component { - constructor() { - super( ...arguments ); - this.onEnter = this.onEnter.bind( this ); - this.onSplit = this.onSplit.bind( this ); - this.onPaste = this.onPaste.bind( this ); - this.onDelete = this.onDelete.bind( this ); - this.inputRule = this.inputRule.bind( this ); +function getAllowedFormats( { allowedFormats, formattingControls } ) { + if ( ! allowedFormats && ! formattingControls ) { + return; } - onEnter( { value, onChange, shiftKey } ) { - const { - onReplace, - onSplit, - multiline, - markAutomaticChange, - } = this.props; - const canSplit = onReplace && onSplit; - - if ( onReplace ) { - const transforms = getBlockTransforms( 'from' ) - .filter( ( { type } ) => type === 'enter' ); - const transformation = findTransform( transforms, ( item ) => { - return item.regExp.test( value.text ); - } ); - - if ( transformation ) { - onReplace( [ - transformation.transform( { content: value.text } ), - ] ); - markAutomaticChange(); - } - } - - if ( multiline ) { - if ( shiftKey ) { - onChange( insert( value, '\n' ) ); - } else if ( canSplit && isEmptyLine( value ) ) { - this.onSplit( value ); - } else { - onChange( insertLineSeparator( value ) ); - } - } else if ( shiftKey || ! canSplit ) { - onChange( insert( value, '\n' ) ); - } else { - this.onSplit( value ); - } + if ( allowedFormats ) { + return allowedFormats; } - onDelete( { value, isReverse } ) { - const { onMerge, onRemove } = this.props; + deprecated( 'wp.blockEditor.RichText formattingControls prop', { + alternative: 'allowedFormats', + } ); - if ( onMerge ) { - onMerge( ! isReverse ); - } + return formattingControls.map( ( name ) => `core/${ name }` ); +} - // Only handle remove on Backspace. This serves dual-purpose of being - // an intentional user interaction distinguishing between Backspace and - // Delete to remove the empty field, but also to avoid merge & remove - // causing destruction of two fields (merge, then removed merged). - if ( onRemove && isEmpty( value ) && isReverse ) { - onRemove( ! isReverse ); - } - } +function RichTextWrapper( + { + children, + tagName, + value: originalValue, + onChange: originalOnChange, + isSelected: originalIsSelected, + multiline, + inlineToolbar, + wrapperClassName, + className, + autocompleters, + onReplace, + placeholder, + keepPlaceholderOnFocus, + allowedFormats, + formattingControls, + withoutInteractiveFormatting, + onRemove, + onMerge, + onSplit, + __unstableOnSplitMiddle: onSplitMiddle, + identifier, + // To do: find a better way to implicitly inherit props. + start: startAttr, + reversed, + style, + preserveWhiteSpace, + __unstableEmbedURLOnPaste, + ...props + }, + forwardedRef +) { + const instanceId = useInstanceId( RichTextWrapper ); + + identifier = identifier || instanceId; - onPaste( { value, onChange, html, plainText, files, activeFormats } ) { + const fallbackRef = useRef(); + const ref = forwardedRef || fallbackRef; + const { + clientId, + onCaretVerticalPositionChange, + isSelected: blockIsSelected, + } = useBlockEditContext(); + const selector = ( select ) => { const { - onReplace, - onSplit, - tagName, - canUserUseUnfilteredHTML, - multiline, - __unstableEmbedURLOnPaste, - } = this.props; - - // Only process file if no HTML is present. - // Note: a pasted file may have the URL as plain text. - if ( files && files.length && ! html ) { - const content = pasteHandler( { - HTML: filePasteHandler( files ), - mode: 'BLOCKS', - tagName, - } ); + isCaretWithinFormattedText, + getSelectionStart, + getSelectionEnd, + getSettings, + didAutomaticChange, + __unstableGetBlockWithoutInnerBlocks, + isMultiSelecting, + hasMultiSelection, + } = select( 'core/block-editor' ); - // Allows us to ask for this information when we get a report. - // eslint-disable-next-line no-console - window.console.log( 'Received items:\n\n', files ); + const selectionStart = getSelectionStart(); + const selectionEnd = getSelectionEnd(); + const { + __experimentalCanUserUseUnfilteredHTML, + __experimentalUndo: undo, + } = getSettings(); - if ( onReplace && isEmpty( value ) ) { - onReplace( content ); - } else { - this.onSplit( value, content ); - } + let isSelected; - return; + if ( originalIsSelected === undefined ) { + isSelected = + selectionStart.clientId === clientId && + selectionStart.attributeKey === identifier; + } else if ( originalIsSelected ) { + isSelected = selectionStart.clientId === clientId; } - let mode = onReplace && onSplit ? 'AUTO' : 'INLINE'; - - if ( - __unstableEmbedURLOnPaste && - isEmpty( value ) && - isURL( plainText.trim() ) - ) { - mode = 'BLOCKS'; + let extraProps = {}; + if ( Platform.OS === 'native' ) { + // If the block of this RichText is unmodified then it's a candidate for replacing when adding a new block. + // In order to fix https://github.com/wordpress-mobile/gutenberg-mobile/issues/1126, let's blur on unmount in that case. + // This apparently assumes functionality the BlockHlder actually + const block = + clientId && __unstableGetBlockWithoutInnerBlocks( clientId ); + const shouldBlurOnUnmount = + block && isSelected && isUnmodifiedDefaultBlock( block ); + extraProps = { + shouldBlurOnUnmount, + }; } - const content = pasteHandler( { - HTML: html, - plainText, - mode, - tagName, - canUserUseUnfilteredHTML, - } ); - - if ( typeof content === 'string' ) { - let valueToInsert = create( { html: content } ); - - // If there are active formats, merge them with the pasted formats. - if ( activeFormats.length ) { - let index = valueToInsert.formats.length; - - while ( index-- ) { - valueToInsert.formats[ index ] = [ - ...activeFormats, - ...( valueToInsert.formats[ index ] || [] ), - ]; - } - } + return { + canUserUseUnfilteredHTML: __experimentalCanUserUseUnfilteredHTML, + isCaretWithinFormattedText: isCaretWithinFormattedText(), + selectionStart: isSelected ? selectionStart.offset : undefined, + selectionEnd: isSelected ? selectionEnd.offset : undefined, + isSelected, + didAutomaticChange: didAutomaticChange(), + disabled: isMultiSelecting() || hasMultiSelection(), + undo, + ...extraProps, + }; + }; + // This selector must run on every render so the right selection state is + // retreived from the store on merge. + // To do: fix this somehow. + const { + canUserUseUnfilteredHTML, + isCaretWithinFormattedText, + selectionStart, + selectionEnd, + isSelected, + didAutomaticChange, + disabled, + undo, + shouldBlurOnUnmount, + } = useSelect( selector ); + const { + __unstableMarkLastChangeAsPersistent, + enterFormattedText, + exitFormattedText, + selectionChange, + __unstableMarkAutomaticChange, + } = useDispatch( 'core/block-editor' ); + const multilineTag = getMultilineTag( multiline ); + const adjustedAllowedFormats = getAllowedFormats( { + allowedFormats, + formattingControls, + } ); + const hasFormats = + ! adjustedAllowedFormats || adjustedAllowedFormats.length > 0; + let adjustedValue = originalValue; + let adjustedOnChange = originalOnChange; + + // Handle deprecated format. + if ( Array.isArray( originalValue ) ) { + adjustedValue = childrenSource.toHTML( originalValue ); + adjustedOnChange = ( newValue ) => + originalOnChange( + childrenSource.fromDOM( + __unstableCreateElement( document, newValue ).childNodes + ) + ); + } - // If the content should be multiline, we should process text - // separated by a line break as separate lines. - if ( multiline ) { - valueToInsert = replace( valueToInsert, /\n+/g, LINE_SEPARATOR ); + const onSelectionChange = useCallback( + ( start, end ) => { + selectionChange( clientId, identifier, start, end ); + }, + [ clientId, identifier ] + ); + + const onDelete = useCallback( + ( { value, isReverse } ) => { + if ( onMerge ) { + onMerge( ! isReverse ); } - onChange( insert( value, valueToInsert ) ); - } else if ( content.length > 0 ) { - if ( onReplace && isEmpty( value ) ) { - onReplace( content ); - } else { - this.onSplit( value, content ); + // Only handle remove on Backspace. This serves dual-purpose of being + // an intentional user interaction distinguishing between Backspace and + // Delete to remove the empty field, but also to avoid merge & remove + // causing destruction of two fields (merge, then removed merged). + if ( onRemove && isEmpty( value ) && isReverse ) { + onRemove( ! isReverse ); } - } - } + }, + [ onMerge, onRemove ] + ); /** * Signals to the RichText owner that the block can be replaced with two @@ -210,349 +257,338 @@ class RichTextWrapper extends Component { * @param {Object} record The rich text value to split. * @param {Array} pastedBlocks The pasted blocks to insert, if any. */ - onSplit( record, pastedBlocks = [] ) { - const { - onReplace, - onSplit, - __unstableOnSplitMiddle: onSplitMiddle, - multiline, - } = this.props; - - if ( ! onReplace || ! onSplit ) { - return; - } - - const blocks = []; - const [ before, after ] = split( record ); - const hasPastedBlocks = pastedBlocks.length > 0; - const multilineTag = getMultilineTag( multiline ); - - // Create a block with the content before the caret if there's no pasted - // blocks, or if there are pasted blocks and the value is not empty. - // We do not want a leading empty block on paste, but we do if split - // with e.g. the enter key. - if ( ! hasPastedBlocks || ! isEmpty( before ) ) { - blocks.push( onSplit( toHTMLString( { - value: before, - multilineTag, - } ) ) ); - } - - if ( hasPastedBlocks ) { - blocks.push( ...pastedBlocks ); - } else if ( onSplitMiddle ) { - blocks.push( onSplitMiddle() ); - } - - // If there's pasted blocks, append a block with the content after the - // caret. Otherwise, do append and empty block if there is no - // `onSplitMiddle` prop, but if there is and the content is empty, the - // middle block is enough to set focus in. - if ( hasPastedBlocks || ! onSplitMiddle || ! isEmpty( after ) ) { - blocks.push( onSplit( toHTMLString( { - value: after, - multilineTag, - } ) ) ); - } - - // If there are pasted blocks, set the selection to the last one. - // Otherwise, set the selection to the second block. - const indexToSelect = hasPastedBlocks ? blocks.length - 1 : 1; - - onReplace( blocks, indexToSelect ); - } + const splitValue = useCallback( + ( record, pastedBlocks = [] ) => { + if ( ! onReplace || ! onSplit ) { + return; + } - inputRule( value, valueToFormat ) { - const { onReplace, markAutomaticChange } = this.props; + const blocks = []; + const [ before, after ] = split( record ); + const hasPastedBlocks = pastedBlocks.length > 0; + + // Create a block with the content before the caret if there's no pasted + // blocks, or if there are pasted blocks and the value is not empty. + // We do not want a leading empty block on paste, but we do if split + // with e.g. the enter key. + if ( ! hasPastedBlocks || ! isEmpty( before ) ) { + blocks.push( + onSplit( + toHTMLString( { + value: before, + multilineTag, + } ) + ) + ); + } - if ( ! onReplace ) { - return; - } + if ( hasPastedBlocks ) { + blocks.push( ...pastedBlocks ); + } else if ( onSplitMiddle ) { + blocks.push( onSplitMiddle() ); + } - const { start, text } = value; - const characterBefore = text.slice( start - 1, start ); + // If there's pasted blocks, append a block with the content after the + // caret. Otherwise, do append and empty block if there is no + // `onSplitMiddle` prop, but if there is and the content is empty, the + // middle block is enough to set focus in. + if ( hasPastedBlocks || ! onSplitMiddle || ! isEmpty( after ) ) { + blocks.push( + onSplit( + toHTMLString( { + value: after, + multilineTag, + } ) + ) + ); + } - // The character right before the caret must be a plain space. - if ( characterBefore !== ' ' ) { - return; - } + // If there are pasted blocks, set the selection to the last one. + // Otherwise, set the selection to the second block. + const indexToSelect = hasPastedBlocks ? blocks.length - 1 : 1; + + onReplace( blocks, indexToSelect ); + }, + [ onReplace, onSplit, multilineTag, onSplitMiddle ] + ); + + const onEnter = useCallback( + ( { value, onChange, shiftKey } ) => { + const canSplit = onReplace && onSplit; + + if ( onReplace ) { + const transforms = getBlockTransforms( 'from' ).filter( + ( { type } ) => type === 'enter' + ); + const transformation = findTransform( transforms, ( item ) => { + return item.regExp.test( value.text ); + } ); + + if ( transformation ) { + onReplace( [ + transformation.transform( { content: value.text } ), + ] ); + __unstableMarkAutomaticChange(); + } + } - const trimmedTextBefore = text.slice( 0, start ).trim(); - const prefixTransforms = getBlockTransforms( 'from' ) - .filter( ( { type } ) => type === 'prefix' ); - const transformation = findTransform( prefixTransforms, ( { prefix } ) => { - return trimmedTextBefore === prefix; - } ); + if ( multiline ) { + if ( shiftKey ) { + onChange( insert( value, '\n' ) ); + } else if ( canSplit && isEmptyLine( value ) ) { + splitValue( value ); + } else { + onChange( insertLineSeparator( value ) ); + } + } else if ( shiftKey || ! canSplit ) { + onChange( insert( value, '\n' ) ); + } else { + splitValue( value ); + } + }, + [ + onReplace, + onSplit, + __unstableMarkAutomaticChange, + multiline, + splitValue, + ] + ); + + const onPaste = useCallback( + ( { value, onChange, html, plainText, files, activeFormats } ) => { + // Only process file if no HTML is present. + // Note: a pasted file may have the URL as plain text. + if ( files && files.length && ! html ) { + const content = pasteHandler( { + HTML: filePasteHandler( files ), + mode: 'BLOCKS', + tagName, + } ); + + // Allows us to ask for this information when we get a report. + // eslint-disable-next-line no-console + window.console.log( 'Received items:\n\n', files ); + + if ( onReplace && isEmpty( value ) ) { + onReplace( content ); + } else { + splitValue( value, content ); + } - if ( ! transformation ) { - return; - } + return; + } - const content = valueToFormat( slice( value, start, text.length ) ); - const block = transformation.transform( content ); + let mode = onReplace && onSplit ? 'AUTO' : 'INLINE'; - onReplace( [ block ] ); - markAutomaticChange(); - } + if ( + __unstableEmbedURLOnPaste && + isEmpty( value ) && + isURL( plainText.trim() ) + ) { + mode = 'BLOCKS'; + } - getAllowedFormats() { - const { allowedFormats, formattingControls } = this.props; + const content = pasteHandler( { + HTML: html, + plainText, + mode, + tagName, + canUserUseUnfilteredHTML, + } ); - if ( ! allowedFormats && ! formattingControls ) { - return; - } + if ( typeof content === 'string' ) { + let valueToInsert = create( { html: content } ); - if ( allowedFormats ) { - return allowedFormats; - } + // If there are active formats, merge them with the pasted formats. + if ( activeFormats.length ) { + let index = valueToInsert.formats.length; - deprecated( 'wp.blockEditor.RichText formattingControls prop', { - alternative: 'allowedFormats', - } ); + while ( index-- ) { + valueToInsert.formats[ index ] = [ + ...activeFormats, + ...( valueToInsert.formats[ index ] || [] ), + ]; + } + } - return formattingControls.map( ( name ) => `core/${ name }` ); - } + // If the content should be multiline, we should process text + // separated by a line break as separate lines. + if ( multiline ) { + valueToInsert = replace( + valueToInsert, + /\n+/g, + LINE_SEPARATOR + ); + } - render() { - const { - children, + onChange( insert( value, valueToInsert ) ); + } else if ( content.length > 0 ) { + if ( onReplace && isEmpty( value ) ) { + onReplace( content ); + } else { + splitValue( value, content ); + } + } + }, + [ tagName, - value: originalValue, - onChange: originalOnChange, - selectionStart, - selectionEnd, - onSelectionChange, - multiline, - inlineToolbar, - wrapperClassName, - className, - autocompleters, onReplace, - isCaretWithinFormattedText, - onEnterFormattedText, - onExitFormattedText, - isSelected: originalIsSelected, - onCreateUndoLevel, - markAutomaticChange, - didAutomaticChange, - undo, - placeholder, - keepPlaceholderOnFocus, - // eslint-disable-next-line no-unused-vars - allowedFormats, - withoutInteractiveFormatting, - // eslint-disable-next-line no-unused-vars - onRemove, - // eslint-disable-next-line no-unused-vars - onMerge, - // eslint-disable-next-line no-unused-vars onSplit, - // eslint-disable-next-line no-unused-vars + splitValue, + __unstableEmbedURLOnPaste, canUserUseUnfilteredHTML, - // eslint-disable-next-line no-unused-vars - instanceId, - // To do: find a better way to implicitly inherit props. - start, - reversed, - style, - preserveWhiteSpace, - disabled, - forwardedRef, - ...props - } = this.props; - const multilineTag = getMultilineTag( multiline ); - - const adjustedAllowedFormats = this.getAllowedFormats(); - const hasFormats = ! adjustedAllowedFormats || adjustedAllowedFormats.length > 0; - let adjustedValue = originalValue; - let adjustedOnChange = originalOnChange; - - // Handle deprecated format. - if ( Array.isArray( originalValue ) ) { - adjustedValue = childrenSource.toHTML( originalValue ); - adjustedOnChange = ( newValue ) => originalOnChange( childrenSource.fromDOM( - __unstableCreateElement( document, newValue ).childNodes - ) ); - } - - const content = ( - <RichText - { ...props } - ref={ forwardedRef } - value={ adjustedValue } - onChange={ adjustedOnChange } - selectionStart={ selectionStart } - selectionEnd={ selectionEnd } - onSelectionChange={ onSelectionChange } - tagName={ tagName } - className={ classnames( classes, className, { - 'keep-placeholder-on-focus': keepPlaceholderOnFocus, - } ) } - placeholder={ placeholder } - allowedFormats={ adjustedAllowedFormats } - withoutInteractiveFormatting={ withoutInteractiveFormatting } - onEnter={ this.onEnter } - onDelete={ this.onDelete } - onPaste={ this.onPaste } - __unstableIsSelected={ originalIsSelected } - __unstableInputRule={ this.inputRule } - __unstableMultilineTag={ multilineTag } - __unstableIsCaretWithinFormattedText={ isCaretWithinFormattedText } - __unstableOnEnterFormattedText={ onEnterFormattedText } - __unstableOnExitFormattedText={ onExitFormattedText } - __unstableOnCreateUndoLevel={ onCreateUndoLevel } - __unstableMarkAutomaticChange={ markAutomaticChange } - __unstableDidAutomaticChange={ didAutomaticChange } - __unstableUndo={ undo } - style={ style } - preserveWhiteSpace={ preserveWhiteSpace } - disabled={ disabled } - start={ start } - reversed={ reversed } - > - { ( { isSelected, value, onChange, onFocus, Editable } ) => - <> - { children && children( { value, onChange, onFocus } ) } - { isSelected && hasFormats && ( <FormatToolbarContainer inline={ inlineToolbar } anchorRef={ forwardedRef.current } /> ) } - { isSelected && <RemoveBrowserShortcuts /> } - <Autocomplete - onReplace={ onReplace } - completers={ autocompleters } - record={ value } - onChange={ onChange } - isSelected={ isSelected } - > - { ( { listBoxId, activeId, onKeyDown } ) => - <Editable - aria-autocomplete={ listBoxId ? 'list' : undefined } - aria-owns={ listBoxId } - aria-activedescendant={ activeId } - start={ start } - reversed={ reversed } - onKeyDown={ onKeyDown } - /> - } - </Autocomplete> - </> - } - </RichText> - ); - - if ( ! wrapperClassName ) { - return content; - } + multiline, + ] + ); - deprecated( 'wp.blockEditor.RichText wrapperClassName prop', { - alternative: 'className prop or create your own wrapper div', - } ); + const inputRule = useCallback( + ( value, valueToFormat ) => { + if ( ! onReplace ) { + return; + } - return ( - <div className={ classnames( wrapperClasses, wrapperClassName ) }> - { content } - </div> - ); - } -} + const { start, text } = value; + const characterBefore = text.slice( start - 1, start ); -const RichTextContainer = compose( [ - withInstanceId, - withBlockEditContext( ( { clientId, onCaretVerticalPositionChange, isSelected }, ownProps ) => { - if ( Platform.OS === 'web' ) { - return { clientId }; - } - return { - clientId, - blockIsSelected: ownProps.isSelected !== undefined ? ownProps.isSelected : isSelected, - onCaretVerticalPositionChange, - }; - } ), - withSelect( ( select, { - clientId, - instanceId, - identifier = instanceId, - isSelected, - } ) => { - const { - isCaretWithinFormattedText, - getSelectionStart, - getSelectionEnd, - getSettings, - didAutomaticChange, - __unstableGetBlockWithoutInnerBlocks, - isMultiSelecting, - hasMultiSelection, - } = select( 'core/block-editor' ); + // The character right before the caret must be a plain space. + if ( characterBefore !== ' ' ) { + return; + } - const selectionStart = getSelectionStart(); - const selectionEnd = getSelectionEnd(); - const { - __experimentalCanUserUseUnfilteredHTML, - __experimentalUndo: undo, - } = getSettings(); - if ( isSelected === undefined ) { - isSelected = ( - selectionStart.clientId === clientId && - selectionStart.attributeKey === identifier + const trimmedTextBefore = text.slice( 0, start ).trim(); + const prefixTransforms = getBlockTransforms( 'from' ).filter( + ( { type } ) => type === 'prefix' + ); + const transformation = findTransform( + prefixTransforms, + ( { prefix } ) => { + return trimmedTextBefore === prefix; + } ); - } else if ( isSelected ) { - isSelected = selectionStart.clientId === clientId; - } - let extraProps = {}; - if ( Platform.OS === 'native' ) { - // If the block of this RichText is unmodified then it's a candidate for replacing when adding a new block. - // In order to fix https://github.com/wordpress-mobile/gutenberg-mobile/issues/1126, let's blur on unmount in that case. - // This apparently assumes functionality the BlockHlder actually - const block = clientId && __unstableGetBlockWithoutInnerBlocks( clientId ); - const shouldBlurOnUnmount = block && isSelected && isUnmodifiedDefaultBlock( block ); - extraProps = { - shouldBlurOnUnmount, - }; - } + if ( ! transformation ) { + return; + } - return { - canUserUseUnfilteredHTML: __experimentalCanUserUseUnfilteredHTML, - isCaretWithinFormattedText: isCaretWithinFormattedText(), - selectionStart: isSelected ? selectionStart.offset : undefined, - selectionEnd: isSelected ? selectionEnd.offset : undefined, - isSelected, - didAutomaticChange: didAutomaticChange(), - disabled: isMultiSelecting() || hasMultiSelection(), - undo, - ...extraProps, - }; - } ), - withDispatch( ( dispatch, { - clientId, - instanceId, - identifier = instanceId, - } ) => { - const { - __unstableMarkLastChangeAsPersistent, - enterFormattedText, - exitFormattedText, - selectionChange, - __unstableMarkAutomaticChange, - } = dispatch( 'core/block-editor' ); + const content = valueToFormat( slice( value, start, text.length ) ); + const block = transformation.transform( content ); + + onReplace( [ block ] ); + __unstableMarkAutomaticChange(); + }, + [ onReplace, __unstableMarkAutomaticChange ] + ); + + const content = ( + <RichText + { ...props } + clientId={ clientId } + identifier={ identifier } + ref={ ref } + value={ adjustedValue } + onChange={ adjustedOnChange } + selectionStart={ selectionStart } + selectionEnd={ selectionEnd } + onSelectionChange={ onSelectionChange } + tagName={ tagName } + className={ classnames( classes, className, { + 'keep-placeholder-on-focus': keepPlaceholderOnFocus, + } ) } + placeholder={ placeholder } + allowedFormats={ adjustedAllowedFormats } + withoutInteractiveFormatting={ withoutInteractiveFormatting } + onEnter={ onEnter } + onDelete={ onDelete } + onPaste={ onPaste } + __unstableIsSelected={ isSelected } + __unstableInputRule={ inputRule } + __unstableMultilineTag={ multilineTag } + __unstableIsCaretWithinFormattedText={ isCaretWithinFormattedText } + __unstableOnEnterFormattedText={ enterFormattedText } + __unstableOnExitFormattedText={ exitFormattedText } + __unstableOnCreateUndoLevel={ __unstableMarkLastChangeAsPersistent } + __unstableMarkAutomaticChange={ __unstableMarkAutomaticChange } + __unstableDidAutomaticChange={ didAutomaticChange } + __unstableUndo={ undo } + style={ style } + preserveWhiteSpace={ preserveWhiteSpace } + disabled={ disabled } + start={ startAttr } + reversed={ reversed } + // Native props. + onCaretVerticalPositionChange={ onCaretVerticalPositionChange } + blockIsSelected={ + originalIsSelected !== undefined + ? originalIsSelected + : blockIsSelected + } + shouldBlurOnUnmount={ shouldBlurOnUnmount } + > + { ( { + isSelected: nestedIsSelected, + value, + onChange, + onFocus, + Editable, + } ) => ( + <> + { children && children( { value, onChange, onFocus } ) } + { nestedIsSelected && hasFormats && ( + <FormatToolbarContainer + inline={ inlineToolbar } + anchorRef={ ref.current } + /> + ) } + { nestedIsSelected && <RemoveBrowserShortcuts /> } + <Autocomplete + onReplace={ onReplace } + completers={ autocompleters } + record={ value } + onChange={ onChange } + isSelected={ nestedIsSelected } + > + { ( { listBoxId, activeId, onKeyDown } ) => ( + <Editable + aria-autocomplete={ + listBoxId ? 'list' : undefined + } + aria-owns={ listBoxId } + aria-activedescendant={ activeId } + start={ startAttr } + reversed={ reversed } + onKeyDown={ onKeyDown } + /> + ) } + </Autocomplete> + </> + ) } + </RichText> + ); + + if ( ! wrapperClassName ) { + return content; + } - return { - onCreateUndoLevel: __unstableMarkLastChangeAsPersistent, - onEnterFormattedText: enterFormattedText, - onExitFormattedText: exitFormattedText, - onSelectionChange( start, end ) { - selectionChange( clientId, identifier, start, end ); - }, - markAutomaticChange: __unstableMarkAutomaticChange, - }; - } ), -] )( RichTextWrapper ); + deprecated( 'wp.blockEditor.RichText wrapperClassName prop', { + alternative: 'className prop or create your own wrapper div', + } ); -const ForwardedRichTextContainer = forwardRef( ( props, ref ) => { - const fallbackRef = useRef(); - return <RichTextContainer { ...props } forwardedRef={ ref || fallbackRef } />; -} ); + return ( + <div className={ classnames( wrapperClasses, wrapperClassName ) }> + { content } + </div> + ); +} + +const ForwardedRichTextContainer = forwardRef( RichTextWrapper ); -ForwardedRichTextContainer.Content = ( { value, tagName: Tag, multiline, ...props } ) => { +ForwardedRichTextContainer.Content = ( { + value, + tagName: Tag, + multiline, + ...props +} ) => { // Handle deprecated `children` and `node` sources. if ( Array.isArray( value ) ) { value = childrenSource.toHTML( value ); diff --git a/packages/block-editor/src/components/rich-text/remove-browser-shortcuts.js b/packages/block-editor/src/components/rich-text/remove-browser-shortcuts.js index 9eee7b641a0e61..d6c061283624d0 100644 --- a/packages/block-editor/src/components/rich-text/remove-browser-shortcuts.js +++ b/packages/block-editor/src/components/rich-text/remove-browser-shortcuts.js @@ -30,9 +30,11 @@ const HANDLED_SHORTCUTS = [ const SHORTCUTS_ELEMENT = ( <KeyboardShortcuts bindGlobal - shortcuts={ fromPairs( HANDLED_SHORTCUTS.map( ( shortcut ) => { - return [ shortcut, ( event ) => event.preventDefault() ]; - } ) ) } + shortcuts={ fromPairs( + HANDLED_SHORTCUTS.map( ( shortcut ) => { + return [ shortcut, ( event ) => event.preventDefault() ]; + } ) + ) } /> ); diff --git a/packages/block-editor/src/components/rich-text/toolbar-button.js b/packages/block-editor/src/components/rich-text/toolbar-button.js index 2febd82e717c57..e7ff63189fd92e 100644 --- a/packages/block-editor/src/components/rich-text/toolbar-button.js +++ b/packages/block-editor/src/components/rich-text/toolbar-button.js @@ -4,7 +4,12 @@ import { Fill, ToolbarButton } from '@wordpress/components'; import { displayShortcut } from '@wordpress/keycodes'; -export function RichTextToolbarButton( { name, shortcutType, shortcutCharacter, ...props } ) { +export function RichTextToolbarButton( { + name, + shortcutType, + shortcutCharacter, + ...props +} ) { let shortcut; let fillName = 'RichText.ToolbarControls'; @@ -18,10 +23,7 @@ export function RichTextToolbarButton( { name, shortcutType, shortcutCharacter, return ( <Fill name={ fillName }> - <ToolbarButton - { ...props } - shortcut={ shortcut } - /> + <ToolbarButton { ...props } shortcut={ shortcut } /> </Fill> ); } diff --git a/packages/block-editor/src/components/skip-to-selected-block/index.js b/packages/block-editor/src/components/skip-to-selected-block/index.js index d8e5ea81405358..581f84c4035e22 100644 --- a/packages/block-editor/src/components/skip-to-selected-block/index.js +++ b/packages/block-editor/src/components/skip-to-selected-block/index.js @@ -17,15 +17,22 @@ const SkipToSelectedBlock = ( { selectedBlockClientId } ) => { }; return ( - selectedBlockClientId && - <Button isSecondary className="block-editor-skip-to-selected-block" onClick={ onClick }> - { __( 'Skip to the selected block' ) } - </Button> + selectedBlockClientId && ( + <Button + isSecondary + className="block-editor-skip-to-selected-block" + onClick={ onClick } + > + { __( 'Skip to the selected block' ) } + </Button> + ) ); }; export default withSelect( ( select ) => { return { - selectedBlockClientId: select( 'core/block-editor' ).getBlockSelectionStart(), + selectedBlockClientId: select( + 'core/block-editor' + ).getBlockSelectionStart(), }; } )( SkipToSelectedBlock ); diff --git a/packages/block-editor/src/components/tool-selector/index.js b/packages/block-editor/src/components/tool-selector/index.js index e032f33c3c5765..1b9b7e0e5a0b2b 100644 --- a/packages/block-editor/src/components/tool-selector/index.js +++ b/packages/block-editor/src/components/tool-selector/index.js @@ -13,11 +13,33 @@ import { __ } from '@wordpress/i18n'; import { useSelect, useDispatch } from '@wordpress/data'; import { useViewportMatch } from '@wordpress/compose'; -const editIcon = <SVG xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M14.06 9.02l.92.92L5.92 19H5v-.92l9.06-9.06M17.66 3c-.25 0-.51.1-.7.29l-1.83 1.83 3.75 3.75 1.83-1.83c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.2-.2-.45-.29-.71-.29zm-3.6 3.19L3 17.25V21h3.75L17.81 9.94l-3.75-3.75z" /></SVG>; -const selectIcon = <SVG xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><Path d="M6.5 1v21.5l6-6.5H21L6.5 1zm5.1 13l-3.1 3.4V5.9l7.8 8.1h-4.7z" /></SVG>; +const editIcon = ( + <SVG + xmlns="http://www.w3.org/2000/svg" + width="20" + height="20" + viewBox="0 0 24 24" + > + <Path fill="none" d="M0 0h24v24H0V0z" /> + <Path d="M14.06 9.02l.92.92L5.92 19H5v-.92l9.06-9.06M17.66 3c-.25 0-.51.1-.7.29l-1.83 1.83 3.75 3.75 1.83-1.83c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.2-.2-.45-.29-.71-.29zm-3.6 3.19L3 17.25V21h3.75L17.81 9.94l-3.75-3.75z" /> + </SVG> +); +const selectIcon = ( + <SVG + xmlns="http://www.w3.org/2000/svg" + width="20" + height="20" + viewBox="0 0 24 24" + > + <Path d="M6.5 1v21.5l6-6.5H21L6.5 1zm5.1 13l-3.1 3.4V5.9l7.8 8.1h-4.7z" /> + </SVG> +); function ToolSelector() { - const isNavigationTool = useSelect( ( select ) => select( 'core/block-editor' ).isNavigationMode(), [] ); + const isNavigationTool = useSelect( + ( select ) => select( 'core/block-editor' ).isNavigationMode(), + [] + ); const { setNavigationMode } = useDispatch( 'core/block-editor' ); const isMediumViewport = useViewportMatch( 'medium' ); if ( ! isMediumViewport ) { @@ -40,10 +62,7 @@ function ToolSelector() { ) } renderContent={ () => ( <> - <NavigableMenu - role="menu" - aria-label={ __( 'Tools' ) } - > + <NavigableMenu role="menu" aria-label={ __( 'Tools' ) }> <MenuItemsChoice value={ isNavigationTool ? 'select' : 'edit' } onSelect={ onSwitchMode } @@ -70,7 +89,9 @@ function ToolSelector() { /> </NavigableMenu> <div className="block-editor-tool-selector__help"> - { __( 'Tools offer different interactions for block selection & editing. To select, press Escape, to go back to editing, press Enter.' ) } + { __( + 'Tools offer different interactions for block selection & editing. To select, press Escape, to go back to editing, press Enter.' + ) } </div> </> ) } diff --git a/packages/block-editor/src/components/typewriter/index.js b/packages/block-editor/src/components/typewriter/index.js index 99487f7a31cf22..b9b2e18ea61040 100644 --- a/packages/block-editor/src/components/typewriter/index.js +++ b/packages/block-editor/src/components/typewriter/index.js @@ -18,12 +18,18 @@ class Typewriter extends Component { this.ref = createRef(); this.onKeyDown = this.onKeyDown.bind( this ); - this.addSelectionChangeListener = this.addSelectionChangeListener.bind( this ); - this.computeCaretRectOnSelectionChange = this.computeCaretRectOnSelectionChange.bind( this ); + this.addSelectionChangeListener = this.addSelectionChangeListener.bind( + this + ); + this.computeCaretRectOnSelectionChange = this.computeCaretRectOnSelectionChange.bind( + this + ); this.maintainCaretPosition = this.maintainCaretPosition.bind( this ); this.computeCaretRect = this.computeCaretRect.bind( this ); this.onScrollResize = this.onScrollResize.bind( this ); - this.isSelectionEligibleForScroll = this.isSelectionEligibleForScroll.bind( this ); + this.isSelectionEligibleForScroll = this.isSelectionEligibleForScroll.bind( + this + ); } componentDidMount() { @@ -36,7 +42,10 @@ class Typewriter extends Component { componentWillUnmount() { window.removeEventListener( 'scroll', this.onScrollResize, true ); window.removeEventListener( 'resize', this.onScrollResize, true ); - document.removeEventListener( 'selectionchange', this.computeCaretRectOnSelectionChange ); + document.removeEventListener( + 'selectionchange', + this.computeCaretRectOnSelectionChange + ); if ( this.onScrollResize.rafId ) { window.cancelAnimationFrame( this.onScrollResize.rafId ); @@ -61,7 +70,10 @@ class Typewriter extends Component { * event. Also removes the listener, so it acts as a one-time listener. */ computeCaretRectOnSelectionChange() { - document.removeEventListener( 'selectionchange', this.computeCaretRectOnSelectionChange ); + document.removeEventListener( + 'selectionchange', + this.computeCaretRectOnSelectionChange + ); this.computeCaretRect(); } @@ -145,16 +157,16 @@ class Typewriter extends Component { } const windowScroll = scrollContainer === document.body; - const scrollY = windowScroll ? - window.scrollY : - scrollContainer.scrollTop; - const scrollContainerY = windowScroll ? - 0 : - scrollContainer.getBoundingClientRect().top; - const relativeScrollPosition = windowScroll ? - this.caretRect.top / window.innerHeight : - ( this.caretRect.top - scrollContainerY ) / - ( window.innerHeight - scrollContainerY ); + const scrollY = windowScroll + ? window.scrollY + : scrollContainer.scrollTop; + const scrollContainerY = windowScroll + ? 0 + : scrollContainer.getBoundingClientRect().top; + const relativeScrollPosition = windowScroll + ? this.caretRect.top / window.innerHeight + : ( this.caretRect.top - scrollContainerY ) / + ( window.innerHeight - scrollContainerY ); // If the scroll position is at the start, the active editable element // is the last one, and the caret is positioned within the initial @@ -172,9 +184,9 @@ class Typewriter extends Component { return; } - const scrollContainerHeight = windowScroll ? - window.innerHeight : - scrollContainer.clientHeight; + const scrollContainerHeight = windowScroll + ? window.innerHeight + : scrollContainer.clientHeight; // Abort if the target scroll position would scroll the caret out of // view. @@ -202,7 +214,10 @@ class Typewriter extends Component { * maintained. */ addSelectionChangeListener() { - document.addEventListener( 'selectionchange', this.computeCaretRectOnSelectionChange ); + document.addEventListener( + 'selectionchange', + this.computeCaretRectOnSelectionChange + ); } onKeyDown( event ) { diff --git a/packages/block-editor/src/components/ungroup-button/icon.js b/packages/block-editor/src/components/ungroup-button/icon.js index 4ef9e2694bd6ef..d71219e4696662 100644 --- a/packages/block-editor/src/components/ungroup-button/icon.js +++ b/packages/block-editor/src/components/ungroup-button/icon.js @@ -3,6 +3,24 @@ */ import { SVG, Path } from '@wordpress/components'; -const UngroupSVG = <SVG width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M9 2H15C16.1 2 17 2.9 17 4V7C17 8.1 16.1 9 15 9H9C7.9 9 7 8.1 7 7V4C7 2.9 7.9 2 9 2ZM9 7H15V4H9V7Z" /><Path fillRule="evenodd" clipRule="evenodd" d="M5 11H11C12.1 11 13 11.9 13 13V16C13 17.1 12.1 18 11 18H5C3.9 18 3 17.1 3 16V13C3 11.9 3.9 11 5 11ZM5 16H11V13H5V16Z" /></SVG>; +const UngroupSVG = ( + <SVG + width="20" + height="20" + viewBox="0 0 20 20" + xmlns="http://www.w3.org/2000/svg" + > + <Path + fillRule="evenodd" + clipRule="evenodd" + d="M9 2H15C16.1 2 17 2.9 17 4V7C17 8.1 16.1 9 15 9H9C7.9 9 7 8.1 7 7V4C7 2.9 7.9 2 9 2ZM9 7H15V4H9V7Z" + /> + <Path + fillRule="evenodd" + clipRule="evenodd" + d="M5 11H11C12.1 11 13 11.9 13 13V16C13 17.1 12.1 18 11 18H5C3.9 18 3 17.1 3 16V13C3 11.9 3.9 11 5 11ZM5 16H11V13H5V16Z" + /> + </SVG> +); export default UngroupSVG; diff --git a/packages/block-editor/src/components/ungroup-button/index.native.js b/packages/block-editor/src/components/ungroup-button/index.native.js index df006897210f97..e3056a5104e9e6 100644 --- a/packages/block-editor/src/components/ungroup-button/index.native.js +++ b/packages/block-editor/src/components/ungroup-button/index.native.js @@ -6,10 +6,7 @@ import { noop } from 'lodash'; /** * WordPress dependencies */ -import { - Toolbar, - ToolbarButton, -} from '@wordpress/components'; +import { Toolbar, ToolbarButton } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; @@ -19,10 +16,7 @@ import { compose } from '@wordpress/compose'; */ import UngroupIcon from './icon'; -export function UngroupButton( { - onConvertFromGroup, - isUngroupable = false, -} ) { +export function UngroupButton( { onConvertFromGroup, isUngroupable = false } ) { if ( ! isUngroupable ) { return null; } @@ -39,21 +33,22 @@ export function UngroupButton( { export default compose( [ withSelect( ( select ) => { - const { - getSelectedBlockClientId, - getBlock, - } = select( 'core/block-editor' ); + const { getSelectedBlockClientId, getBlock } = select( + 'core/block-editor' + ); - const { - getGroupingBlockName, - } = select( 'core/blocks' ); + const { getGroupingBlockName } = select( 'core/blocks' ); const selectedId = getSelectedBlockClientId(); const selectedBlock = getBlock( selectedId ); const groupingBlockName = getGroupingBlockName(); - const isUngroupable = selectedBlock && selectedBlock.innerBlocks && !! selectedBlock.innerBlocks.length && selectedBlock.name === groupingBlockName; + const isUngroupable = + selectedBlock && + selectedBlock.innerBlocks && + !! selectedBlock.innerBlocks.length && + selectedBlock.name === groupingBlockName; const innerBlocks = isUngroupable ? selectedBlock.innerBlocks : []; return { @@ -63,9 +58,7 @@ export default compose( [ }; } ), withDispatch( ( dispatch, { clientId, innerBlocks, onToggle = noop } ) => { - const { - replaceBlocks, - } = dispatch( 'core/block-editor' ); + const { replaceBlocks } = dispatch( 'core/block-editor' ); return { onConvertFromGroup() { @@ -73,10 +66,7 @@ export default compose( [ return; } - replaceBlocks( - clientId, - innerBlocks - ); + replaceBlocks( clientId, innerBlocks ); onToggle(); }, diff --git a/packages/block-editor/src/components/url-input/button.js b/packages/block-editor/src/components/url-input/button.js index fbaf3eedacb13d..e262dce201b3ed 100644 --- a/packages/block-editor/src/components/url-input/button.js +++ b/packages/block-editor/src/components/url-input/button.js @@ -43,7 +43,7 @@ class URLInputButton extends Component { className="components-toolbar__control" isPressed={ !! url } /> - { expanded && + { expanded && ( <form className="block-editor-url-input__button-modal" onSubmit={ this.submitLink } @@ -55,7 +55,10 @@ class URLInputButton extends Component { label={ __( 'Close' ) } onClick={ this.toggle } /> - <URLInput value={ url || '' } onChange={ onChange } /> + <URLInput + value={ url || '' } + onChange={ onChange } + /> <Button icon="editor-break" label={ __( 'Submit' ) } @@ -63,7 +66,7 @@ class URLInputButton extends Component { /> </div> </form> - } + ) } </div> ); } diff --git a/packages/block-editor/src/components/url-input/index.js b/packages/block-editor/src/components/url-input/index.js index 3f4d6d4e40e136..2ef7b911877c73 100644 --- a/packages/block-editor/src/components/url-input/index.js +++ b/packages/block-editor/src/components/url-input/index.js @@ -11,7 +11,13 @@ import scrollIntoView from 'dom-scroll-into-view'; import { __, sprintf, _n } from '@wordpress/i18n'; import { Component, createRef } from '@wordpress/element'; import { UP, DOWN, ENTER, TAB } from '@wordpress/keycodes'; -import { BaseControl, Button, Spinner, withSpokenMessages, Popover } from '@wordpress/components'; +import { + BaseControl, + Button, + Spinner, + withSpokenMessages, + Popover, +} from '@wordpress/components'; import { withInstanceId, withSafeTimeout, compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; import { isURL } from '@wordpress/url'; @@ -32,7 +38,10 @@ class URLInput extends Component { this.bindSuggestionNode = this.bindSuggestionNode.bind( this ); this.autocompleteRef = props.autocompleteRef || createRef(); this.inputRef = createRef(); - this.updateSuggestions = throttle( this.updateSuggestions.bind( this ), 200 ); + this.updateSuggestions = throttle( + this.updateSuggestions.bind( this ), + 200 + ); this.suggestionNodes = []; @@ -50,12 +59,20 @@ class URLInput extends Component { // only have to worry about scrolling selected suggestion into view // when already expanded - if ( showSuggestions && selectedSuggestion !== null && ! this.scrollingIntoView ) { + if ( + showSuggestions && + selectedSuggestion !== null && + ! this.scrollingIntoView + ) { this.scrollingIntoView = true; - scrollIntoView( this.suggestionNodes[ selectedSuggestion ], this.autocompleteRef.current, { - onlyScrollIfNeeded: true, - } ); + scrollIntoView( + this.suggestionNodes[ selectedSuggestion ], + this.autocompleteRef.current, + { + onlyScrollIfNeeded: true, + } + ); this.props.setTimeout( () => { this.scrollingIntoView = false; @@ -85,8 +102,16 @@ class URLInput extends Component { shouldShowInitialSuggestions() { const { suggestions } = this.state; - const { __experimentalShowInitialSuggestions = false, value } = this.props; - return ! this.isUpdatingSuggestions && __experimentalShowInitialSuggestions && ! ( value && value.length ) && ! ( suggestions && suggestions.length ); + const { + __experimentalShowInitialSuggestions = false, + value, + } = this.props; + return ( + ! this.isUpdatingSuggestions && + __experimentalShowInitialSuggestions && + ! ( value && value.length ) && + ! ( suggestions && suggestions.length ) + ); } updateSuggestions( value = '' ) { @@ -105,7 +130,10 @@ class URLInput extends Component { // - there are at least 2 characters in the search input (except manual searches where // search input length is not required to trigger a fetch) // - this is a direct entry (eg: a URL) - if ( ! isInitialSuggestions && ( value.length < 2 || ( ! handleURLSuggestions && isURL( value ) ) ) ) { + if ( + ! isInitialSuggestions && + ( value.length < 2 || ( ! handleURLSuggestions && isURL( value ) ) ) + ) { this.setState( { showSuggestions: false, selectedSuggestion: null, @@ -127,37 +155,48 @@ class URLInput extends Component { isInitialSuggestions, } ); - request.then( ( suggestions ) => { - // A fetch Promise doesn't have an abort option. It's mimicked by - // comparing the request reference in on the instance, which is - // reset or deleted on subsequent requests or unmounting. - if ( this.suggestionsRequest !== request ) { - return; - } - - this.setState( { - suggestions, - loading: false, - } ); + request + .then( ( suggestions ) => { + // A fetch Promise doesn't have an abort option. It's mimicked by + // comparing the request reference in on the instance, which is + // reset or deleted on subsequent requests or unmounting. + if ( this.suggestionsRequest !== request ) { + return; + } - if ( !! suggestions.length ) { - this.props.debouncedSpeak( sprintf( _n( - '%d result found, use up and down arrow keys to navigate.', - '%d results found, use up and down arrow keys to navigate.', - suggestions.length - ), suggestions.length ), 'assertive' ); - } else { - this.props.debouncedSpeak( __( 'No results.' ), 'assertive' ); - } - this.isUpdatingSuggestions = false; - } ).catch( () => { - if ( this.suggestionsRequest === request ) { this.setState( { + suggestions, loading: false, } ); + + if ( !! suggestions.length ) { + this.props.debouncedSpeak( + sprintf( + _n( + '%d result found, use up and down arrow keys to navigate.', + '%d results found, use up and down arrow keys to navigate.', + suggestions.length + ), + suggestions.length + ), + 'assertive' + ); + } else { + this.props.debouncedSpeak( + __( 'No results.' ), + 'assertive' + ); + } this.isUpdatingSuggestions = false; - } - } ); + } ) + .catch( () => { + if ( this.suggestionsRequest === request ) { + this.setState( { + loading: false, + } ); + this.isUpdatingSuggestions = false; + } + } ); // Note that this assignment is handled *before* the async search request // as a Promise always resolves on the next tick of the event loop. @@ -174,13 +213,16 @@ class URLInput extends Component { } onKeyDown( event ) { - const { showSuggestions, selectedSuggestion, suggestions, loading } = this.state; + const { + showSuggestions, + selectedSuggestion, + suggestions, + loading, + } = this.state; // If the suggestions are not shown or loading, we shouldn't handle the arrow keys // We shouldn't preventDefault to allow block arrow keys navigation - if ( - ( ! showSuggestions || ! suggestions.length || loading ) - ) { + if ( ! showSuggestions || ! suggestions.length || loading ) { // In the Windows version of Firefox the up and down arrows don't move the caret // within an input field like they do for Mac Firefox/Chrome/Safari. This causes // a form of focus trapping that is disruptive to the user experience. This disruption @@ -202,12 +244,17 @@ class URLInput extends Component { // When DOWN is pressed, if the caret is not at the end of the text, move it to the // last position. case DOWN: { - if ( this.props.value.length !== event.target.selectionStart ) { + if ( + this.props.value.length !== event.target.selectionStart + ) { event.stopPropagation(); event.preventDefault(); // Set the input caret to the last position - event.target.setSelectionRange( this.props.value.length, this.props.value.length ); + event.target.setSelectionRange( + this.props.value.length, + this.props.value.length + ); } break; } @@ -216,13 +263,17 @@ class URLInput extends Component { return; } - const suggestion = this.state.suggestions[ this.state.selectedSuggestion ]; + const suggestion = this.state.suggestions[ + this.state.selectedSuggestion + ]; switch ( event.keyCode ) { case UP: { event.stopPropagation(); event.preventDefault(); - const previousIndex = ! selectedSuggestion ? suggestions.length - 1 : selectedSuggestion - 1; + const previousIndex = ! selectedSuggestion + ? suggestions.length - 1 + : selectedSuggestion - 1; this.setState( { selectedSuggestion: previousIndex, } ); @@ -231,7 +282,11 @@ class URLInput extends Component { case DOWN: { event.stopPropagation(); event.preventDefault(); - const nextIndex = selectedSuggestion === null || ( selectedSuggestion === suggestions.length - 1 ) ? 0 : selectedSuggestion + 1; + const nextIndex = + selectedSuggestion === null || + selectedSuggestion === suggestions.length - 1 + ? 0 + : selectedSuggestion + 1; this.setState( { selectedSuggestion: nextIndex, } ); @@ -269,7 +324,14 @@ class URLInput extends Component { this.inputRef.current.focus(); } - static getDerivedStateFromProps( { value, disableSuggestions, __experimentalShowInitialSuggestions = false }, { showSuggestions } ) { + static getDerivedStateFromProps( + { + value, + disableSuggestions, + __experimentalShowInitialSuggestions = false, + }, + { showSuggestions } + ) { let shouldShowSuggestions = showSuggestions; const hasValue = value && value.length; @@ -353,50 +415,71 @@ class URLInput extends Component { aria-expanded={ showSuggestions } aria-autocomplete="list" aria-owns={ suggestionsListboxId } - aria-activedescendant={ selectedSuggestion !== null ? `${ suggestionOptionIdPrefix }-${ selectedSuggestion }` : undefined } + aria-activedescendant={ + selectedSuggestion !== null + ? `${ suggestionOptionIdPrefix }-${ selectedSuggestion }` + : undefined + } ref={ this.inputRef } /> - { ( loading ) && <Spinner /> } - - { isFunction( renderSuggestions ) && showSuggestions && !! suggestions.length && renderSuggestions( { - suggestions, - selectedSuggestion, - suggestionsListProps, - buildSuggestionItemProps, - isLoading: loading, - handleSuggestionClick: this.handleOnClick, - isInitialSuggestions: __experimentalShowInitialSuggestions && ! ( value && value.length ), - } ) } - - { ! isFunction( renderSuggestions ) && showSuggestions && !! suggestions.length && - <Popover - position="bottom" - noArrow - focusOnMount={ false } - > - <div - { ...suggestionsListProps } - className={ classnames( - 'block-editor-url-input__suggestions', - `${ className }__suggestions` - ) } + { loading && <Spinner /> } + + { isFunction( renderSuggestions ) && + showSuggestions && + !! suggestions.length && + renderSuggestions( { + suggestions, + selectedSuggestion, + suggestionsListProps, + buildSuggestionItemProps, + isLoading: loading, + handleSuggestionClick: this.handleOnClick, + isInitialSuggestions: + __experimentalShowInitialSuggestions && + ! ( value && value.length ), + } ) } + + { ! isFunction( renderSuggestions ) && + showSuggestions && + !! suggestions.length && ( + <Popover + position="bottom" + noArrow + focusOnMount={ false } > - { suggestions.map( ( suggestion, index ) => ( - <Button - { ...buildSuggestionItemProps( suggestion, index ) } - key={ suggestion.id } - className={ classnames( 'block-editor-url-input__suggestion', { - 'is-selected': index === selectedSuggestion, - } ) } - onClick={ () => this.handleOnClick( suggestion ) } - > - { suggestion.title } - </Button> - ) ) } - </div> - </Popover> - } + <div + { ...suggestionsListProps } + className={ classnames( + 'block-editor-url-input__suggestions', + `${ className }__suggestions` + ) } + > + { suggestions.map( ( suggestion, index ) => ( + <Button + { ...buildSuggestionItemProps( + suggestion, + index + ) } + key={ suggestion.id } + className={ classnames( + 'block-editor-url-input__suggestion', + { + 'is-selected': + index === + selectedSuggestion, + } + ) } + onClick={ () => + this.handleOnClick( suggestion ) + } + > + { suggestion.title } + </Button> + ) ) } + </div> + </Popover> + ) } </BaseControl> ); /* eslint-enable jsx-a11y/no-autofocus */ @@ -418,7 +501,8 @@ export default compose( } const { getSettings } = select( 'core/block-editor' ); return { - __experimentalFetchLinkSuggestions: getSettings().__experimentalFetchLinkSuggestions, + __experimentalFetchLinkSuggestions: getSettings() + .__experimentalFetchLinkSuggestions, }; } ) )( URLInput ); diff --git a/packages/block-editor/src/components/url-input/test/button.js b/packages/block-editor/src/components/url-input/test/button.js index f9898fd41ea6a4..04601dfea51c4c 100644 --- a/packages/block-editor/src/components/url-input/test/button.js +++ b/packages/block-editor/src/components/url-input/test/button.js @@ -14,19 +14,28 @@ import URLInputButton from '../button'; import '../../../store'; describe( 'URLInputButton', () => { - const clickEditLink = ( wrapper ) => wrapper.find( 'ForwardRef(Button).components-toolbar__control' ).simulate( 'click' ); + const clickEditLink = ( wrapper ) => + wrapper + .find( 'ForwardRef(Button).components-toolbar__control' ) + .simulate( 'click' ); it( 'should have a valid class name in the wrapper tag', () => { const wrapper = shallow( <URLInputButton /> ); - expect( wrapper.hasClass( 'block-editor-url-input__button' ) ).toBe( true ); + expect( wrapper.hasClass( 'block-editor-url-input__button' ) ).toBe( + true + ); } ); it( 'should have isPressed props set to false when url prop not defined', () => { const wrapper = shallow( <URLInputButton /> ); - expect( wrapper.find( 'ForwardRef(Button)' ).prop( 'isPressed' ) ).toBe( false ); + expect( wrapper.find( 'ForwardRef(Button)' ).prop( 'isPressed' ) ).toBe( + false + ); } ); it( 'should have isPressed prop set to true if url prop defined', () => { const wrapper = shallow( <URLInputButton url="https://example.com" /> ); - expect( wrapper.find( 'ForwardRef(Button)' ).prop( 'isPressed' ) ).toBe( true ); + expect( wrapper.find( 'ForwardRef(Button)' ).prop( 'isPressed' ) ).toBe( + true + ); } ); it( 'should have hidden form by default', () => { const wrapper = shallow( <URLInputButton /> ); @@ -63,19 +72,20 @@ describe( 'URLInputButton', () => { } ); it( 'should close the form when user submits it', () => { const wrapper = TestUtils.renderIntoDocument( <URLInputButton /> ); - const buttonElement = () => TestUtils.scryRenderedDOMComponentsWithClass( - wrapper, - 'components-toolbar__control' - ); - const formElement = () => TestUtils.scryRenderedDOMComponentsWithTag( - wrapper, - 'form' - ); + const buttonElement = () => + TestUtils.scryRenderedDOMComponentsWithClass( + wrapper, + 'components-toolbar__control' + ); + const formElement = () => + TestUtils.scryRenderedDOMComponentsWithTag( wrapper, 'form' ); TestUtils.Simulate.click( buttonElement().shift() ); expect( wrapper.state.expanded ).toBe( true ); TestUtils.Simulate.submit( formElement().shift() ); expect( wrapper.state.expanded ).toBe( false ); - // eslint-disable-next-line react/no-find-dom-node - ReactDOM.unmountComponentAtNode( ReactDOM.findDOMNode( wrapper ).parentNode ); + ReactDOM.unmountComponentAtNode( + // eslint-disable-next-line react/no-find-dom-node + ReactDOM.findDOMNode( wrapper ).parentNode + ); } ); } ); diff --git a/packages/block-editor/src/components/url-popover/image-url-input-ui.js b/packages/block-editor/src/components/url-popover/image-url-input-ui.js index f8fabb072b38c8..5263dbe57c74c6 100644 --- a/packages/block-editor/src/components/url-popover/image-url-input-ui.js +++ b/packages/block-editor/src/components/url-popover/image-url-input-ui.js @@ -17,14 +17,7 @@ import { SVG, Path, } from '@wordpress/components'; -import { - LEFT, - RIGHT, - UP, - DOWN, - BACKSPACE, - ENTER, -} from '@wordpress/keycodes'; +import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; /** * Internal dependencies @@ -37,7 +30,13 @@ const LINK_DESTINATION_MEDIA = 'media'; const LINK_DESTINATION_ATTACHMENT = 'attachment'; const NEW_TAB_REL = [ 'noreferrer', 'noopener' ]; -const icon = <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0,0h24v24H0V0z" fill="none" /><Path d="m19 5v14h-14v-14h14m0-2h-14c-1.1 0-2 0.9-2 2v14c0 1.1 0.9 2 2 2h14c1.1 0 2-0.9 2-2v-14c0-1.1-0.9-2-2-2z" /><Path d="m14.14 11.86l-3 3.87-2.14-2.59-3 3.86h12l-3.86-5.14z" /></SVG>; +const icon = ( + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <Path d="M0,0h24v24H0V0z" fill="none" /> + <Path d="m19 5v14h-14v-14h14m0-2h-14c-1.1 0-2 0.9-2 2v14c0 1.1 0.9 2 2 2h14c1.1 0 2-0.9 2-2v-14c0-1.1-0.9-2-2-2z" /> + <Path d="m14.14 11.86l-3 3.87-2.14-2.59-3 3.86h12l-3.86-5.14z" /> + </SVG> +); const ImageURLInputUI = ( { linkDestination, @@ -65,14 +64,19 @@ const ImageURLInputUI = ( { }; const stopPropagationRelevantKeys = ( event ) => { - if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( event.keyCode ) > -1 ) { + if ( + [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( + event.keyCode + ) > -1 + ) { // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. event.stopPropagation(); } }; const startEditLink = useCallback( () => { - if ( linkDestination === LINK_DESTINATION_MEDIA || + if ( + linkDestination === LINK_DESTINATION_MEDIA || linkDestination === LINK_DESTINATION_ATTACHMENT ) { setUrlInput( '' ); @@ -137,7 +141,10 @@ const ImageURLInputUI = ( { // LinkContainer. Detect clicks on autocomplete suggestions using a ref here, and // return to avoid the popover being closed. const autocompleteElement = autocompleteRef.current; - if ( autocompleteElement && autocompleteElement.contains( event.target ) ) { + if ( + autocompleteElement && + autocompleteElement.contains( event.target ) + ) { return; } setIsOpen( false ); @@ -176,7 +183,12 @@ const ImageURLInputUI = ( { linkDestination: LINK_DESTINATION_ATTACHMENT, title: __( 'Attachment Page' ), url: mediaType === 'image' ? mediaLink : undefined, - icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0 0h24v24H0V0z" fill="none" /><Path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zM6 20V4h7v5h5v11H6z" /></SVG>, + icon: ( + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <Path d="M0 0h24v24H0V0z" fill="none" /> + <Path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zM6 20V4h7v5h5v11H6z" /> + </SVG> + ), }, ]; }; @@ -190,8 +202,7 @@ const ImageURLInputUI = ( { linkDestinationInput = ( find( linkDestinations, ( destination ) => { return destination.url === value; - } ) || - { linkDestination: LINK_DESTINATION_CUSTOM } + } ) || { linkDestination: LINK_DESTINATION_CUSTOM } ).linkDestination; } onChangeUrl( { @@ -218,7 +229,8 @@ const ImageURLInputUI = ( { <ToggleControl label={ __( 'Open in New Tab' ) } onChange={ onSetNewTab } - checked={ linkTarget === '_blank' } /> + checked={ linkTarget === '_blank' } + /> <TextControl label={ __( 'Link Rel' ) } value={ removeNewTabRel( rel ) || '' } @@ -238,7 +250,10 @@ const ImageURLInputUI = ( { const linkEditorValue = urlInput !== null ? urlInput : url; - const urlLabel = ( find( getLinkDestinations(), [ 'linkDestination', linkDestination ] ) || {} ).title; + const urlLabel = ( + find( getLinkDestinations(), [ 'linkDestination', linkDestination ] ) || + {} + ).title; return ( <> @@ -254,10 +269,10 @@ const ImageURLInputUI = ( { onFocusOutside={ onFocusOutside() } onClose={ closeLinkUI } renderSettings={ () => advancedOptions } - additionalControls={ ! linkEditorValue && ( - <NavigableMenu> - { - map( getLinkDestinations(), ( link ) => ( + additionalControls={ + ! linkEditorValue && ( + <NavigableMenu> + { map( getLinkDestinations(), ( link ) => ( <MenuItem key={ link.linkDestination } icon={ link.icon } @@ -269,10 +284,10 @@ const ImageURLInputUI = ( { > { link.title } </MenuItem> - ) ) - } - </NavigableMenu> - ) } + ) ) } + </NavigableMenu> + ) + } > { ( ! url || isEditingLink ) && ( <URLPopover.LinkEditor @@ -285,7 +300,7 @@ const ImageURLInputUI = ( { autocompleteRef={ autocompleteRef } /> ) } - { ( url && ! isEditingLink ) && ( + { url && ! isEditingLink && ( <> <URLPopover.LinkViewer className="block-editor-format-toolbar__link-container-content" @@ -307,6 +322,4 @@ const ImageURLInputUI = ( { ); }; -export { - ImageURLInputUI as __experimentalImageURLInputUI, -}; +export { ImageURLInputUI as __experimentalImageURLInputUI }; diff --git a/packages/block-editor/src/components/url-popover/index.js b/packages/block-editor/src/components/url-popover/index.js index d133ae6a86cf73..d35f3bda683ecf 100644 --- a/packages/block-editor/src/components/url-popover/index.js +++ b/packages/block-editor/src/components/url-popover/index.js @@ -3,10 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; -import { - Button, - Popover, -} from '@wordpress/components'; +import { Button, Popover } from '@wordpress/components'; /** * Internal dependencies @@ -18,7 +15,9 @@ class URLPopover extends Component { constructor() { super( ...arguments ); - this.toggleSettingsVisibility = this.toggleSettingsVisibility.bind( this ); + this.toggleSettingsVisibility = this.toggleSettingsVisibility.bind( + this + ); this.state = { isSettingsExpanded: false, @@ -41,9 +40,7 @@ class URLPopover extends Component { ...popoverProps } = this.props; - const { - isSettingsExpanded, - } = this.state; + const { isSettingsExpanded } = this.state; const showSettings = !! renderSettings && isSettingsExpanded; @@ -74,9 +71,7 @@ class URLPopover extends Component { ) } </div> { additionalControls && ! showSettings && ( - <div - className="block-editor-url-popover__additional-controls" - > + <div className="block-editor-url-popover__additional-controls"> { additionalControls } </div> ) } diff --git a/packages/block-editor/src/components/url-popover/link-viewer.js b/packages/block-editor/src/components/url-popover/link-viewer.js index bdf657ae424398..570c773124e50d 100644 --- a/packages/block-editor/src/components/url-popover/link-viewer.js +++ b/packages/block-editor/src/components/url-popover/link-viewer.js @@ -7,10 +7,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { - ExternalLink, - Button, -} from '@wordpress/components'; +import { ExternalLink, Button } from '@wordpress/components'; import { safeDecodeURI, filterURLForDisplay } from '@wordpress/url'; function LinkViewerUrl( { url, urlLabel, className } ) { @@ -24,10 +21,7 @@ function LinkViewerUrl( { url, urlLabel, className } ) { } return ( - <ExternalLink - className={ linkClassName } - href={ url } - > + <ExternalLink className={ linkClassName } href={ url }> { urlLabel || filterURLForDisplay( safeDecodeURI( url ) ) } </ExternalLink> ); @@ -49,8 +43,18 @@ export default function LinkViewer( { ) } { ...props } > - <LinkViewerUrl url={ url } urlLabel={ urlLabel } className={ linkClassName } /> - { onEditLinkClick && <Button icon="edit" label={ __( 'Edit' ) } onClick={ onEditLinkClick } /> } + <LinkViewerUrl + url={ url } + urlLabel={ urlLabel } + className={ linkClassName } + /> + { onEditLinkClick && ( + <Button + icon="edit" + label={ __( 'Edit' ) } + onClick={ onEditLinkClick } + /> + ) } </div> ); } diff --git a/packages/block-editor/src/components/url-popover/test/index.js b/packages/block-editor/src/components/url-popover/test/index.js index e541343151034f..af157935104356 100644 --- a/packages/block-editor/src/components/url-popover/test/index.js +++ b/packages/block-editor/src/components/url-popover/test/index.js @@ -11,11 +11,7 @@ import URLPopover from '../'; describe( 'URLPopover', () => { it( 'matches the snapshot in its default state', () => { const wrapper = shallow( - <URLPopover - renderSettings={ () => ( - <div>Settings</div> - ) } - > + <URLPopover renderSettings={ () => <div>Settings</div> }> <div>Editor</div> </URLPopover> ); @@ -25,16 +21,14 @@ describe( 'URLPopover', () => { it( 'matches the snapshot when the settings are toggled open', () => { const wrapper = shallow( - <URLPopover - renderSettings={ () => ( - <div>Settings</div> - ) } - > + <URLPopover renderSettings={ () => <div>Settings</div> }> <div>Editor</div> </URLPopover> ); - const toggleButton = wrapper.find( '.block-editor-url-popover__settings-toggle' ); + const toggleButton = wrapper.find( + '.block-editor-url-popover__settings-toggle' + ); expect( toggleButton ).toHaveLength( 1 ); toggleButton.simulate( 'click' ); diff --git a/packages/block-editor/src/components/video-player/gridicon-play.native.js b/packages/block-editor/src/components/video-player/gridicon-play.native.js index 6f14d04f16a299..037cdfd36272c7 100644 --- a/packages/block-editor/src/components/video-player/gridicon-play.native.js +++ b/packages/block-editor/src/components/video-player/gridicon-play.native.js @@ -3,5 +3,11 @@ */ import { Path, SVG } from '@wordpress/components'; -export default <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm-2 14.5v-9l6 4.5z" fill="white" /></SVG>; - +export default ( + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <Path + d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm-2 14.5v-9l6 4.5z" + fill="white" + /> + </SVG> +); diff --git a/packages/block-editor/src/components/video-player/index.native.js b/packages/block-editor/src/components/video-player/index.native.js index 73c2038a0b457c..a409c1d4ad99fc 100644 --- a/packages/block-editor/src/components/video-player/index.native.js +++ b/packages/block-editor/src/components/video-player/index.native.js @@ -54,17 +54,33 @@ class Video extends Component { // Tries opening the URL outside of the app openURL( url ) { - Linking.canOpenURL( url ).then( ( supported ) => { - if ( ! supported ) { - Alert.alert( __( 'Problem opening the video' ), __( 'No application can handle this request. Please install a Web browser.' ) ); - window.console.warn( 'No application found that can open the video with URL: ' + url ); - } else { - return Linking.openURL( url ); - } - } ).catch( ( err ) => { - Alert.alert( __( 'Problem opening the video' ), __( 'An unknown error occurred. Please try again.' ) ); - window.console.error( 'An error occurred while opening the video URL: ' + url, err ); - } ); + Linking.canOpenURL( url ) + .then( ( supported ) => { + if ( ! supported ) { + Alert.alert( + __( 'Problem opening the video' ), + __( + 'No application can handle this request. Please install a Web browser.' + ) + ); + window.console.warn( + 'No application found that can open the video with URL: ' + + url + ); + } else { + return Linking.openURL( url ); + } + } ) + .catch( ( err ) => { + Alert.alert( + __( 'Problem opening the video' ), + __( 'An unknown error occurred. Please try again.' ) + ); + window.console.error( + 'An error occurred while opening the video URL: ' + url, + err + ); + } ); } render() { @@ -93,14 +109,22 @@ class Video extends Component { this.setState( { isFullScreen: false } ); } } /> - { showPlayButton && - // If we add the play icon as a subview to VideoPlayer then react-native-video decides to show control buttons - // even if we set controls={ false }, so we are adding our play button as a sibling overlay view. - <TouchableOpacity disabled={ ! isSelected } onPress={ this.onPressPlay } style={ [ style, styles.overlayContainer ] }> - <View style={ styles.blackOverlay } /> - <Icon icon={ PlayIcon } style={ styles.playIcon } size={ styles.playIcon.size } /> - </TouchableOpacity> - } + { showPlayButton && ( + // If we add the play icon as a subview to VideoPlayer then react-native-video decides to show control buttons + // even if we set controls={ false }, so we are adding our play button as a sibling overlay view. + <TouchableOpacity + disabled={ ! isSelected } + onPress={ this.onPressPlay } + style={ [ style, styles.overlayContainer ] } + > + <View style={ styles.blackOverlay } /> + <Icon + icon={ PlayIcon } + style={ styles.playIcon } + size={ styles.playIcon.size } + /> + </TouchableOpacity> + ) } </View> ); } diff --git a/packages/block-editor/src/components/warning/index.js b/packages/block-editor/src/components/warning/index.js index 1806501fff4e79..9c5765ff9cf5e0 100644 --- a/packages/block-editor/src/components/warning/index.js +++ b/packages/block-editor/src/components/warning/index.js @@ -19,7 +19,10 @@ function Warning( { className, actions, children, secondaryActions } ) { { Children.count( actions ) > 0 && ( <div className="block-editor-warning__actions"> { Children.map( actions, ( action, i ) => ( - <span key={ i } className="block-editor-warning__action"> + <span + key={ i } + className="block-editor-warning__action" + > { action } </span> ) ) } @@ -41,11 +44,11 @@ function Warning( { className, actions, children, secondaryActions } ) { ) } renderContent={ () => ( <MenuGroup> - { secondaryActions.map( ( item, pos ) => + { secondaryActions.map( ( item, pos ) => ( <MenuItem onClick={ item.onClick } key={ pos }> { item.title } </MenuItem> - ) } + ) ) } </MenuGroup> ) } /> diff --git a/packages/block-editor/src/components/warning/index.native.js b/packages/block-editor/src/components/warning/index.native.js index dc7e83d16509af..cda7f13a68405d 100644 --- a/packages/block-editor/src/components/warning/index.native.js +++ b/packages/block-editor/src/components/warning/index.native.js @@ -15,15 +15,32 @@ import { normalizeIconObject } from '@wordpress/blocks'; */ import styles from './style.scss'; -function Warning( { title, message, icon, iconClass, preferredColorScheme, getStylesFromColorScheme, ...viewProps } ) { +function Warning( { + title, + message, + icon, + iconClass, + preferredColorScheme, + getStylesFromColorScheme, + ...viewProps +} ) { icon = icon && normalizeIconObject( icon ); const internalIconClass = 'warning-icon' + '-' + preferredColorScheme; - const titleStyle = getStylesFromColorScheme( styles.title, styles.titleDark ); - const messageStyle = getStylesFromColorScheme( styles.message, styles.messageDark ); + const titleStyle = getStylesFromColorScheme( + styles.title, + styles.titleDark + ); + const messageStyle = getStylesFromColorScheme( + styles.message, + styles.messageDark + ); return ( <View - style={ getStylesFromColorScheme( styles.container, styles.containerDark ) } + style={ getStylesFromColorScheme( + styles.container, + styles.containerDark + ) } { ...viewProps } > { icon && ( @@ -34,12 +51,8 @@ function Warning( { title, message, icon, iconClass, preferredColorScheme, getSt /> </View> ) } - { title && ( - <Text style={ titleStyle }>{ title }</Text> - ) } - { message && ( - <Text style={ messageStyle }>{ message }</Text> - ) } + { title && <Text style={ titleStyle }>{ title }</Text> } + { message && <Text style={ messageStyle }>{ message }</Text> } </View> ); } diff --git a/packages/block-editor/src/components/warning/test/index.js b/packages/block-editor/src/components/warning/test/index.js index f1a10c4e6fbc3f..b8fcdbee4e4900 100644 --- a/packages/block-editor/src/components/warning/test/index.js +++ b/packages/block-editor/src/components/warning/test/index.js @@ -19,23 +19,35 @@ describe( 'Warning', () => { const wrapper = shallow( <Warning /> ); expect( wrapper.hasClass( 'block-editor-warning' ) ).toBe( true ); - expect( wrapper.find( '.block-editor-warning__actions' ) ).toHaveLength( 0 ); - expect( wrapper.find( '.block-editor-warning__hidden' ) ).toHaveLength( 0 ); + expect( wrapper.find( '.block-editor-warning__actions' ) ).toHaveLength( + 0 + ); + expect( wrapper.find( '.block-editor-warning__hidden' ) ).toHaveLength( + 0 + ); } ); it( 'should show child error message element', () => { - const wrapper = shallow( <Warning actions={ <button /> }>Message</Warning> ); + const wrapper = shallow( + <Warning actions={ <button /> }>Message</Warning> + ); const actions = wrapper.find( '.block-editor-warning__actions' ); const action = actions.childAt( 0 ); expect( actions ).toHaveLength( 1 ); - expect( action.hasClass( 'block-editor-warning__action' ) ).toBe( true ); + expect( action.hasClass( 'block-editor-warning__action' ) ).toBe( + true + ); expect( action.childAt( 0 ).type() ).toBe( 'button' ); } ); it( 'should show hidden actions', () => { - const wrapper = shallow( <Warning secondaryActions={ [ { title: 'test', onClick: null } ] }>Message</Warning> ); + const wrapper = shallow( + <Warning secondaryActions={ [ { title: 'test', onClick: null } ] }> + Message + </Warning> + ); const actions = wrapper.find( '.block-editor-warning__secondary' ); diff --git a/packages/block-editor/src/components/writing-flow/focus-capture.js b/packages/block-editor/src/components/writing-flow/focus-capture.js index 56bd1bc70b180a..8440a3097e0428 100644 --- a/packages/block-editor/src/components/writing-flow/focus-capture.js +++ b/packages/block-editor/src/components/writing-flow/focus-capture.js @@ -30,74 +30,79 @@ import { getBlockDOMNode } from '../../utils/dom'; * * @return {WPElement} The focus capture element. */ -const FocusCapture = forwardRef( ( { - selectedClientId, - isReverse, - containerRef, - noCapture, - hasMultiSelection, - multiSelectionContainer, -}, ref ) => { - const isNavigationMode = useSelect( ( select ) => - select( 'core/block-editor' ).isNavigationMode() - ); - const { setNavigationMode } = useDispatch( 'core/block-editor' ); +const FocusCapture = forwardRef( + ( + { + selectedClientId, + isReverse, + containerRef, + noCapture, + hasMultiSelection, + multiSelectionContainer, + }, + ref + ) => { + const isNavigationMode = useSelect( ( select ) => + select( 'core/block-editor' ).isNavigationMode() + ); + const { setNavigationMode } = useDispatch( 'core/block-editor' ); - function onFocus() { - // Do not capture incoming focus if set by us in WritingFlow. - if ( noCapture.current ) { - noCapture.current = null; - return; - } - - // When focus coming in from out of the block list, and no block is - // selected, enable Navigation mode and select the first or last block - // depending on the direction. - if ( ! selectedClientId ) { - if ( hasMultiSelection ) { - multiSelectionContainer.current.focus(); + function onFocus() { + // Do not capture incoming focus if set by us in WritingFlow. + if ( noCapture.current ) { + noCapture.current = null; return; } - setNavigationMode( true ); + // When focus coming in from out of the block list, and no block is + // selected, enable Navigation mode and select the first or last block + // depending on the direction. + if ( ! selectedClientId ) { + if ( hasMultiSelection ) { + multiSelectionContainer.current.focus(); + return; + } + + setNavigationMode( true ); - const tabbables = focus.tabbable.find( containerRef.current ); + const tabbables = focus.tabbable.find( containerRef.current ); - if ( tabbables.length ) { - if ( isReverse ) { - last( tabbables ).focus(); - } else { - first( tabbables ).focus(); + if ( tabbables.length ) { + if ( isReverse ) { + last( tabbables ).focus(); + } else { + first( tabbables ).focus(); + } } - } - return; - } + return; + } - // If there is a selected block, move focus to the first or last - // tabbable element depending on the direction. - const wrapper = getBlockDOMNode( selectedClientId ); + // If there is a selected block, move focus to the first or last + // tabbable element depending on the direction. + const wrapper = getBlockDOMNode( selectedClientId ); - if ( isReverse ) { - const tabbables = focus.tabbable.find( wrapper ); - const lastTabbable = last( tabbables ) || wrapper; - lastTabbable.focus(); - } else { - wrapper.focus(); + if ( isReverse ) { + const tabbables = focus.tabbable.find( wrapper ); + const lastTabbable = last( tabbables ) || wrapper; + lastTabbable.focus(); + } else { + wrapper.focus(); + } } - } - return ( - <div - ref={ ref } - // Don't allow tabbing to this element in Navigation mode. - tabIndex={ ! isNavigationMode ? '0' : undefined } - onFocus={ onFocus } - // Needs to be positioned within the viewport, so focus to this - // element does not scroll the page. - style={ { position: 'fixed' } } - /> - ); -} ); + return ( + <div + ref={ ref } + // Don't allow tabbing to this element in Navigation mode. + tabIndex={ ! isNavigationMode ? '0' : undefined } + onFocus={ onFocus } + // Needs to be positioned within the viewport, so focus to this + // element does not scroll the page. + style={ { position: 'fixed' } } + /> + ); + } +); export default FocusCapture; diff --git a/packages/block-editor/src/components/writing-flow/index.js b/packages/block-editor/src/components/writing-flow/index.js index 1e3da8cda48e1f..91b0dcfe274ecb 100644 --- a/packages/block-editor/src/components/writing-flow/index.js +++ b/packages/block-editor/src/components/writing-flow/index.js @@ -18,7 +18,15 @@ import { placeCaretAtVerticalEdge, isEntirelySelected, } from '@wordpress/dom'; -import { UP, DOWN, LEFT, RIGHT, TAB, isKeyboardEvent, ESCAPE } from '@wordpress/keycodes'; +import { + UP, + DOWN, + LEFT, + RIGHT, + TAB, + isKeyboardEvent, + ESCAPE, +} from '@wordpress/keycodes'; import { useSelect, useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; @@ -65,7 +73,7 @@ const isTabbableTextField = overEvery( [ * @return {boolean} Whether element should consider edge navigation. */ export function isNavigationCandidate( element, keyCode, hasModifier ) { - const isVertical = ( keyCode === UP || keyCode === DOWN ); + const isVertical = keyCode === UP || keyCode === DOWN; // Currently, all elements support unmodified vertical navigation. if ( isVertical && ! hasModifier ) { @@ -100,7 +108,9 @@ export function getClosestTabbable( target, isReverse, containerElement ) { // Consider as candidates those focusables after the current target. // It's assumed this can only be reached if the target is focusable // (on its keydown event), so no need to verify it exists in the set. - focusableNodes = focusableNodes.slice( focusableNodes.indexOf( target ) + 1 ); + focusableNodes = focusableNodes.slice( + focusableNodes.indexOf( target ) + 1 + ); function isTabCandidate( node, i, array ) { // Not a candidate if the node is not tabbable. @@ -132,7 +142,11 @@ export function getClosestTabbable( target, isReverse, containerElement ) { // In case of block focus stop, check to see if there's a better // text field candidate within. - for ( let offset = 1, nextNode; ( nextNode = array[ i + offset ] ); offset++ ) { + for ( + let offset = 1, nextNode; + ( nextNode = array[ i + offset ] ); + offset++ + ) { // Abort if no longer testing descendents of focus stop. if ( ! node.contains( nextNode ) ) { break; @@ -176,8 +190,12 @@ function selector( select ) { return { selectedBlockClientId, selectionStartClientId, - selectionBeforeEndClientId: getPreviousBlockClientId( selectionEndClientId || selectedBlockClientId ), - selectionAfterEndClientId: getNextBlockClientId( selectionEndClientId || selectedBlockClientId ), + selectionBeforeEndClientId: getPreviousBlockClientId( + selectionEndClientId || selectedBlockClientId + ), + selectionAfterEndClientId: getNextBlockClientId( + selectionEndClientId || selectedBlockClientId + ), selectedFirstClientId: getFirstMultiSelectedBlockClientId(), selectedLastClientId: getLastMultiSelectedBlockClientId(), hasMultiSelection: hasMultiSelection(), @@ -238,7 +256,10 @@ export default function WritingFlow( { children } ) { if ( isNavigationMode && selectedBlockClientId && - isInsideRootBlock( getBlockDOMNode( selectedBlockClientId ), event.target ) + isInsideRootBlock( + getBlockDOMNode( selectedBlockClientId ), + event.target + ) ) { setNavigationMode( false ); } @@ -258,11 +279,11 @@ export default function WritingFlow( { children } ) { multiSelect( blockSelectionStart, clientId ); event.preventDefault(); } - // Allow user to escape out of a multi-selection to a singular - // selection of a block via click. This is handled here since - // focus handling excludes blocks when there is multiselection, - // as focus can be incurred by starting a multiselection (focus - // moved to first block's multi-controls). + // Allow user to escape out of a multi-selection to a singular + // selection of a block via click. This is handled here since + // focus handling excludes blocks when there is multiselection, + // as focus can be incurred by starting a multiselection (focus + // moved to first block's multi-controls). } else if ( hasMultiSelection ) { selectBlock( clientId ); } @@ -271,9 +292,9 @@ export default function WritingFlow( { children } ) { } function expandSelection( isReverse ) { - const nextSelectionEndClientId = isReverse ? - selectionBeforeEndClientId : - selectionAfterEndClientId; + const nextSelectionEndClientId = isReverse + ? selectionBeforeEndClientId + : selectionAfterEndClientId; if ( nextSelectionEndClientId ) { multiSelect( @@ -284,7 +305,9 @@ export default function WritingFlow( { children } ) { } function moveSelection( isReverse ) { - const focusedBlockClientId = isReverse ? selectedFirstClientId : selectedLastClientId; + const focusedBlockClientId = isReverse + ? selectedFirstClientId + : selectedLastClientId; if ( focusedBlockClientId ) { selectBlock( focusedBlockClientId ); @@ -303,7 +326,11 @@ export default function WritingFlow( { children } ) { * @return {boolean} Whether field is at edge for tab transition. */ function isTabbableEdge( target, isReverse ) { - const closestTabbable = getClosestTabbable( target, isReverse, container.current ); + const closestTabbable = getClosestTabbable( + target, + isReverse, + container.current + ); return ! closestTabbable || ! isInSameBlock( target, closestTabbable ); } @@ -320,14 +347,17 @@ export default function WritingFlow( { children } ) { const isVertical = isUp || isDown; const isNav = isHorizontal || isVertical; const isShift = event.shiftKey; - const hasModifier = isShift || event.ctrlKey || event.altKey || event.metaKey; + const hasModifier = + isShift || event.ctrlKey || event.altKey || event.metaKey; const isNavEdge = isVertical ? isVerticalEdge : isHorizontalEdge; // In navigation mode, tab and arrows navigate from block to block. if ( isNavigationMode ) { const navigateUp = ( isTab && isShift ) || isUp; const navigateDown = ( isTab && ! isShift ) || isDown; - const focusedBlockUid = navigateUp ? selectionBeforeEndClientId : selectionAfterEndClientId; + const focusedBlockUid = navigateUp + ? selectionBeforeEndClientId + : selectionAfterEndClientId; if ( navigateDown || navigateUp ) { if ( focusedBlockUid ) { @@ -387,7 +417,11 @@ export default function WritingFlow( { children } ) { } else if ( isEscape ) { setNavigationMode( true ); } - } else if ( hasMultiSelection && isTab && target === multiSelectionContainer.current ) { + } else if ( + hasMultiSelection && + isTab && + target === multiSelectionContainer.current + ) { // See comment above. noCapture.current = true; @@ -427,7 +461,11 @@ export default function WritingFlow( { children } ) { // have been set by the browser earlier in this call stack. We // need check the previous result, otherwise all blocks will be // selected right away. - if ( target.isContentEditable ? entirelySelected.current : isEntirelySelected( target ) ) { + if ( + target.isContentEditable + ? entirelySelected.current + : isEntirelySelected( target ) + ) { multiSelect( first( blocks ), last( blocks ) ); event.preventDefault(); } @@ -455,20 +493,16 @@ export default function WritingFlow( { children } ) { // In the case of RTL scripts, right means previous and left means next, // which is the exact reverse of LTR. const { direction } = getComputedStyle( target ); - const isReverseDir = direction === 'rtl' ? ( ! isReverse ) : isReverse; + const isReverseDir = direction === 'rtl' ? ! isReverse : isReverse; if ( isShift ) { if ( - ( - // Ensure that there is a target block. - ( isReverse && selectionBeforeEndClientId ) || - ( ! isReverse && selectionAfterEndClientId ) - ) && ( - hasMultiSelection || ( - isTabbableEdge( target, isReverse ) && - isNavEdge( target, isReverse ) - ) - ) + // Ensure that there is a target block. + ( ( isReverse && selectionBeforeEndClientId ) || + ( ! isReverse && selectionAfterEndClientId ) ) && + ( hasMultiSelection || + ( isTabbableEdge( target, isReverse ) && + isNavEdge( target, isReverse ) ) ) ) { // Shift key is down, and there is multi selection or we're at // the end of the current block. @@ -480,14 +514,30 @@ export default function WritingFlow( { children } ) { moveSelection( isReverse ); event.preventDefault(); } else if ( isVertical && isVerticalEdge( target, isReverse ) ) { - const closestTabbable = getClosestTabbable( target, isReverse, container.current ); + const closestTabbable = getClosestTabbable( + target, + isReverse, + container.current + ); if ( closestTabbable ) { - placeCaretAtVerticalEdge( closestTabbable, isReverse, verticalRect.current ); + placeCaretAtVerticalEdge( + closestTabbable, + isReverse, + verticalRect.current + ); event.preventDefault(); } - } else if ( isHorizontal && getSelection().isCollapsed && isHorizontalEdge( target, isReverseDir ) ) { - const closestTabbable = getClosestTabbable( target, isReverseDir, container.current ); + } else if ( + isHorizontal && + getSelection().isCollapsed && + isHorizontalEdge( target, isReverseDir ) + ) { + const closestTabbable = getClosestTabbable( + target, + isReverseDir, + container.current + ); placeCaretAtHorizontalEdge( closestTabbable, isReverseDir ); event.preventDefault(); } @@ -532,7 +582,11 @@ export default function WritingFlow( { children } ) { <div ref={ multiSelectionContainer } tabIndex={ hasMultiSelection ? '0' : undefined } - aria-label={ hasMultiSelection ? __( 'Multiple selected blocks' ) : undefined } + aria-label={ + hasMultiSelection + ? __( 'Multiple selected blocks' ) + : undefined + } // Needs to be positioned within the viewport, so focus to this // element does not scroll the page. style={ { position: 'fixed' } } diff --git a/packages/block-editor/src/components/writing-flow/test/index.js b/packages/block-editor/src/components/writing-flow/test/index.js index 88ff30f107a72e..54cde2bde376aa 100644 --- a/packages/block-editor/src/components/writing-flow/test/index.js +++ b/packages/block-editor/src/components/writing-flow/test/index.js @@ -19,7 +19,11 @@ describe( 'isNavigationCandidate', () => { it( 'should return true if vertically navigating input without modifier', () => { [ UP, DOWN ].forEach( ( keyCode ) => { - const result = isNavigationCandidate( elements.input, keyCode, false ); + const result = isNavigationCandidate( + elements.input, + keyCode, + false + ); expect( result ).toBe( true ); } ); @@ -27,7 +31,11 @@ describe( 'isNavigationCandidate', () => { it( 'should return false if vertically navigating input with modifier', () => { [ UP, DOWN ].forEach( ( keyCode ) => { - const result = isNavigationCandidate( elements.input, keyCode, true ); + const result = isNavigationCandidate( + elements.input, + keyCode, + true + ); expect( result ).toBe( false ); } ); @@ -35,7 +43,11 @@ describe( 'isNavigationCandidate', () => { it( 'should return false if horizontally navigating input', () => { [ LEFT, RIGHT ].forEach( ( keyCode ) => { - const result = isNavigationCandidate( elements.input, keyCode, false ); + const result = isNavigationCandidate( + elements.input, + keyCode, + false + ); expect( result ).toBe( false ); } ); @@ -43,7 +55,11 @@ describe( 'isNavigationCandidate', () => { it( 'should return true if horizontally navigating non-input', () => { [ LEFT, RIGHT ].forEach( ( keyCode ) => { - const result = isNavigationCandidate( elements.contentEditable, keyCode, false ); + const result = isNavigationCandidate( + elements.contentEditable, + keyCode, + false + ); expect( result ).toBe( true ); } ); diff --git a/packages/block-editor/src/hooks/align.js b/packages/block-editor/src/hooks/align.js index bd04ecd554a36e..cfded941725ecc 100644 --- a/packages/block-editor/src/hooks/align.js +++ b/packages/block-editor/src/hooks/align.js @@ -9,7 +9,11 @@ import { assign, get, has, includes, without } from 'lodash'; */ import { createHigherOrderComponent } from '@wordpress/compose'; import { addFilter } from '@wordpress/hooks'; -import { getBlockSupport, getBlockType, hasBlockSupport } from '@wordpress/blocks'; +import { + getBlockSupport, + getBlockType, + hasBlockSupport, +} from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; /** @@ -47,7 +51,11 @@ const WIDE_ALIGNMENTS = [ 'wide', 'full' ]; * * @return {string[]} Valid alignments. */ -export function getValidAlignments( blockAlign, hasWideBlockSupport = true, hasWideEnabled = true ) { +export function getValidAlignments( + blockAlign, + hasWideBlockSupport = true, + hasWideEnabled = true +) { let validAlignments; if ( Array.isArray( blockAlign ) ) { validAlignments = blockAlign; @@ -99,42 +107,44 @@ export function addAttribute( settings ) { * @return {Function} Wrapped component */ export const withToolbarControls = createHigherOrderComponent( - ( BlockEdit ) => ( - ( props ) => { - const { name: blockName } = props; - // Compute valid alignments without taking into account, - // if the theme supports wide alignments or not. - // BlockAlignmentToolbar takes into account the theme support. - const validAlignments = getValidAlignments( - getBlockSupport( blockName, 'align' ), - hasBlockSupport( blockName, 'alignWide', true ), - ); - - const updateAlignment = ( nextAlign ) => { - if ( ! nextAlign ) { - const blockType = getBlockType( props.name ); - const blockDefaultAlign = get( blockType, [ 'attributes', 'align', 'default' ] ); - if ( blockDefaultAlign ) { - nextAlign = ''; - } + ( BlockEdit ) => ( props ) => { + const { name: blockName } = props; + // Compute valid alignments without taking into account, + // if the theme supports wide alignments or not. + // BlockAlignmentToolbar takes into account the theme support. + const validAlignments = getValidAlignments( + getBlockSupport( blockName, 'align' ), + hasBlockSupport( blockName, 'alignWide', true ) + ); + + const updateAlignment = ( nextAlign ) => { + if ( ! nextAlign ) { + const blockType = getBlockType( props.name ); + const blockDefaultAlign = get( blockType, [ + 'attributes', + 'align', + 'default', + ] ); + if ( blockDefaultAlign ) { + nextAlign = ''; } - props.setAttributes( { align: nextAlign } ); - }; - - return [ - validAlignments.length > 0 && props.isSelected && ( - <BlockControls key="align-controls"> - <BlockAlignmentToolbar - value={ props.attributes.align } - onChange={ updateAlignment } - controls={ validAlignments } - /> - </BlockControls> - ), - <BlockEdit key="edit" { ...props } />, - ]; - } - ), + } + props.setAttributes( { align: nextAlign } ); + }; + + return [ + validAlignments.length > 0 && props.isSelected && ( + <BlockControls key="align-controls"> + <BlockAlignmentToolbar + value={ props.attributes.align } + onChange={ updateAlignment } + controls={ validAlignments } + /> + </BlockControls> + ), + <BlockEdit key="edit" { ...props } />, + ]; + }, 'withToolbarControls' ); @@ -144,33 +154,36 @@ export const withToolbarControls = createHigherOrderComponent( * @param {Function} BlockListBlock Original component * @return {Function} Wrapped component */ -export const withDataAlign = createHigherOrderComponent( ( BlockListBlock ) => ( props ) => { - const { name, attributes } = props; - const { align } = attributes; - const hasWideEnabled = useSelect( - ( select ) => !! select( 'core/block-editor' ).getSettings().alignWide, - [] - ); +export const withDataAlign = createHigherOrderComponent( + ( BlockListBlock ) => ( props ) => { + const { name, attributes } = props; + const { align } = attributes; + const hasWideEnabled = useSelect( + ( select ) => + !! select( 'core/block-editor' ).getSettings().alignWide, + [] + ); + + // If an alignment is not assigned, there's no need to go through the + // effort to validate or assign its value. + if ( align === undefined ) { + return <BlockListBlock { ...props } />; + } - // If an alignment is not assigned, there's no need to go through the - // effort to validate or assign its value. - if ( align === undefined ) { - return <BlockListBlock { ...props } />; - } + const validAlignments = getValidAlignments( + getBlockSupport( name, 'align' ), + hasBlockSupport( name, 'alignWide', true ), + hasWideEnabled + ); - const validAlignments = getValidAlignments( - getBlockSupport( name, 'align' ), - hasBlockSupport( name, 'alignWide', true ), - hasWideEnabled - ); + let wrapperProps = props.wrapperProps; + if ( includes( validAlignments, align ) ) { + wrapperProps = { ...wrapperProps, 'data-align': align }; + } - let wrapperProps = props.wrapperProps; - if ( includes( validAlignments, align ) ) { - wrapperProps = { ...wrapperProps, 'data-align': align }; + return <BlockListBlock { ...props } wrapperProps={ wrapperProps } />; } - - return <BlockListBlock { ...props } wrapperProps={ wrapperProps } />; -} ); +); /** * Override props assigned to save component to inject alignment class name if @@ -199,8 +212,23 @@ export function addAssignedAlign( props, blockType, attributes ) { return props; } -addFilter( 'blocks.registerBlockType', 'core/align/addAttribute', addAttribute ); -addFilter( 'editor.BlockListBlock', 'core/editor/align/with-data-align', withDataAlign ); -addFilter( 'editor.BlockEdit', 'core/editor/align/with-toolbar-controls', withToolbarControls ); -addFilter( 'blocks.getSaveContent.extraProps', 'core/align/addAssignedAlign', addAssignedAlign ); - +addFilter( + 'blocks.registerBlockType', + 'core/align/addAttribute', + addAttribute +); +addFilter( + 'editor.BlockListBlock', + 'core/editor/align/with-data-align', + withDataAlign +); +addFilter( + 'editor.BlockEdit', + 'core/editor/align/with-toolbar-controls', + withToolbarControls +); +addFilter( + 'blocks.getSaveContent.extraProps', + 'core/align/addAssignedAlign', + addAssignedAlign +); diff --git a/packages/block-editor/src/hooks/anchor.js b/packages/block-editor/src/hooks/anchor.js index 5f8f1ca25879f5..f9aeab08ab95e9 100644 --- a/packages/block-editor/src/hooks/anchor.js +++ b/packages/block-editor/src/hooks/anchor.js @@ -60,42 +60,55 @@ export function addAttribute( settings ) { * * @return {WPComponent} Wrapped component. */ -export const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => { - return ( props ) => { - const hasAnchor = hasBlockSupport( props.name, 'anchor' ); +export const withInspectorControl = createHigherOrderComponent( + ( BlockEdit ) => { + return ( props ) => { + const hasAnchor = hasBlockSupport( props.name, 'anchor' ); - if ( hasAnchor && props.isSelected ) { - return ( - <> - <BlockEdit { ...props } /> - <InspectorAdvancedControls> - <TextControl - className="html-anchor-control" - label={ __( 'HTML Anchor' ) } - help={ ( - <> - { __( 'Enter a word or two — without spaces — to make a unique web address just for this heading, called an “anchor.” Then, you’ll be able to link directly to this section of your page.' ) } + if ( hasAnchor && props.isSelected ) { + return ( + <> + <BlockEdit { ...props } /> + <InspectorAdvancedControls> + <TextControl + className="html-anchor-control" + label={ __( 'HTML Anchor' ) } + help={ + <> + { __( + 'Enter a word or two — without spaces — to make a unique web address just for this heading, called an “anchor.” Then, you’ll be able to link directly to this section of your page.' + ) } - <ExternalLink href={ 'https://wordpress.org/support/article/page-jumps/' }> - { __( 'Learn more about anchors' ) } - </ExternalLink> - </> - ) } - value={ props.attributes.anchor || '' } - onChange={ ( nextValue ) => { - nextValue = nextValue.replace( ANCHOR_REGEX, '-' ); - props.setAttributes( { - anchor: nextValue, - } ); - } } /> - </InspectorAdvancedControls> - </> - ); - } + <ExternalLink + href={ + 'https://wordpress.org/support/article/page-jumps/' + } + > + { __( 'Learn more about anchors' ) } + </ExternalLink> + </> + } + value={ props.attributes.anchor || '' } + onChange={ ( nextValue ) => { + nextValue = nextValue.replace( + ANCHOR_REGEX, + '-' + ); + props.setAttributes( { + anchor: nextValue, + } ); + } } + /> + </InspectorAdvancedControls> + </> + ); + } - return <BlockEdit { ...props } />; - }; -}, 'withInspectorControl' ); + return <BlockEdit { ...props } />; + }; + }, + 'withInspectorControl' +); /** * Override props assigned to save component to inject anchor ID, if block @@ -117,5 +130,13 @@ export function addSaveProps( extraProps, blockType, attributes ) { } addFilter( 'blocks.registerBlockType', 'core/anchor/attribute', addAttribute ); -addFilter( 'editor.BlockEdit', 'core/editor/anchor/with-inspector-control', withInspectorControl ); -addFilter( 'blocks.getSaveContent.extraProps', 'core/anchor/save-props', addSaveProps ); +addFilter( + 'editor.BlockEdit', + 'core/editor/anchor/with-inspector-control', + withInspectorControl +); +addFilter( + 'blocks.getSaveContent.extraProps', + 'core/anchor/save-props', + addSaveProps +); diff --git a/packages/block-editor/src/hooks/custom-class-name.js b/packages/block-editor/src/hooks/custom-class-name.js index dfbceb627389cc..df461c178072d1 100644 --- a/packages/block-editor/src/hooks/custom-class-name.js +++ b/packages/block-editor/src/hooks/custom-class-name.js @@ -51,32 +51,44 @@ export function addAttribute( settings ) { * * @return {WPComponent} Wrapped component. */ -export const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => { - return ( props ) => { - const hasCustomClassName = hasBlockSupport( props.name, 'customClassName', true ); - if ( hasCustomClassName && props.isSelected ) { - return ( - <> - <BlockEdit { ...props } /> - <InspectorAdvancedControls> - <TextControl - label={ __( 'Additional CSS Class(es)' ) } - value={ props.attributes.className || '' } - onChange={ ( nextValue ) => { - props.setAttributes( { - className: nextValue !== '' ? nextValue : undefined, - } ); - } } - help={ __( 'Separate multiple classes with spaces.' ) } - /> - </InspectorAdvancedControls> - </> +export const withInspectorControl = createHigherOrderComponent( + ( BlockEdit ) => { + return ( props ) => { + const hasCustomClassName = hasBlockSupport( + props.name, + 'customClassName', + true ); - } + if ( hasCustomClassName && props.isSelected ) { + return ( + <> + <BlockEdit { ...props } /> + <InspectorAdvancedControls> + <TextControl + label={ __( 'Additional CSS Class(es)' ) } + value={ props.attributes.className || '' } + onChange={ ( nextValue ) => { + props.setAttributes( { + className: + nextValue !== '' + ? nextValue + : undefined, + } ); + } } + help={ __( + 'Separate multiple classes with spaces.' + ) } + /> + </InspectorAdvancedControls> + </> + ); + } - return <BlockEdit { ...props } />; - }; -}, 'withInspectorControl' ); + return <BlockEdit { ...props } />; + }; + }, + 'withInspectorControl' +); /** * Override props assigned to save component to inject anchor ID, if block @@ -90,8 +102,14 @@ export const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => * @return {Object} Filtered props applied to save element. */ export function addSaveProps( extraProps, blockType, attributes ) { - if ( hasBlockSupport( blockType, 'customClassName', true ) && attributes.className ) { - extraProps.className = classnames( extraProps.className, attributes.className ); + if ( + hasBlockSupport( blockType, 'customClassName', true ) && + attributes.className + ) { + extraProps.className = classnames( + extraProps.className, + attributes.className + ); } return extraProps; @@ -136,7 +154,9 @@ export function addParsedDifference( blockAttributes, blockType, innerHTML ) { // attributes, with the exception of `className`. This will determine // the default set of classes. From there, any difference in innerHTML // can be considered as custom classes. - const attributesSansClassName = omit( blockAttributes, [ 'className' ] ); + const attributesSansClassName = omit( blockAttributes, [ + 'className', + ] ); const serialized = getSaveContent( blockType, attributesSansClassName ); const defaultClasses = getHTMLRootElementClasses( serialized ); const actualClasses = getHTMLRootElementClasses( innerHTML ); @@ -152,7 +172,23 @@ export function addParsedDifference( blockAttributes, blockType, innerHTML ) { return blockAttributes; } -addFilter( 'blocks.registerBlockType', 'core/custom-class-name/attribute', addAttribute ); -addFilter( 'editor.BlockEdit', 'core/editor/custom-class-name/with-inspector-control', withInspectorControl ); -addFilter( 'blocks.getSaveContent.extraProps', 'core/custom-class-name/save-props', addSaveProps ); -addFilter( 'blocks.getBlockAttributes', 'core/custom-class-name/addParsedDifference', addParsedDifference ); +addFilter( + 'blocks.registerBlockType', + 'core/custom-class-name/attribute', + addAttribute +); +addFilter( + 'editor.BlockEdit', + 'core/editor/custom-class-name/with-inspector-control', + withInspectorControl +); +addFilter( + 'blocks.getSaveContent.extraProps', + 'core/custom-class-name/save-props', + addSaveProps +); +addFilter( + 'blocks.getBlockAttributes', + 'core/custom-class-name/addParsedDifference', + addParsedDifference +); diff --git a/packages/block-editor/src/hooks/custom-class-name.native.js b/packages/block-editor/src/hooks/custom-class-name.native.js index c59d604b903050..4edfd96db65c28 100644 --- a/packages/block-editor/src/hooks/custom-class-name.native.js +++ b/packages/block-editor/src/hooks/custom-class-name.native.js @@ -47,8 +47,14 @@ export function addAttribute( settings ) { * @return {Object} Filtered props applied to save element. */ export function addSaveProps( extraProps, blockType, attributes ) { - if ( hasBlockSupport( blockType, 'customClassName', true ) && attributes.className ) { - extraProps.className = classnames( extraProps.className, attributes.className ); + if ( + hasBlockSupport( blockType, 'customClassName', true ) && + attributes.className + ) { + extraProps.className = classnames( + extraProps.className, + attributes.className + ); } return extraProps; @@ -112,6 +118,18 @@ export function addParsedDifference( blockAttributes, blockType, innerHTML ) { return blockAttributes; } -addFilter( 'blocks.registerBlockType', 'core/custom-class-name/attribute', addAttribute ); -addFilter( 'blocks.getSaveContent.extraProps', 'core/custom-class-name/save-props', addSaveProps ); -addFilter( 'blocks.getBlockAttributes', 'core/custom-class-name/addParsedDifference', addParsedDifference ); +addFilter( + 'blocks.registerBlockType', + 'core/custom-class-name/attribute', + addAttribute +); +addFilter( + 'blocks.getSaveContent.extraProps', + 'core/custom-class-name/save-props', + addSaveProps +); +addFilter( + 'blocks.getBlockAttributes', + 'core/custom-class-name/addParsedDifference', + addParsedDifference +); diff --git a/packages/block-editor/src/hooks/generated-class-name.js b/packages/block-editor/src/hooks/generated-class-name.js index c5f7d1b49502cc..8a10c9b9e891d5 100644 --- a/packages/block-editor/src/hooks/generated-class-name.js +++ b/packages/block-editor/src/hooks/generated-class-name.js @@ -29,7 +29,9 @@ export function addGeneratedClassName( extraProps, blockType ) { extraProps.className = uniq( [ getBlockDefaultClassName( blockType.name ), ...extraProps.className.split( ' ' ), - ] ).join( ' ' ).trim(); + ] ) + .join( ' ' ) + .trim(); } else { // There is no string in the className variable, // so we just dump the default name in there @@ -39,4 +41,8 @@ export function addGeneratedClassName( extraProps, blockType ) { return extraProps; } -addFilter( 'blocks.getSaveContent.extraProps', 'core/generated-class-name/save-props', addGeneratedClassName ); +addFilter( + 'blocks.getSaveContent.extraProps', + 'core/generated-class-name/save-props', + addGeneratedClassName +); diff --git a/packages/block-editor/src/hooks/test/align.js b/packages/block-editor/src/hooks/test/align.js index 1e348229ad675c..9242cff172eabb 100644 --- a/packages/block-editor/src/hooks/test/align.js +++ b/packages/block-editor/src/hooks/test/align.js @@ -39,7 +39,10 @@ describe( 'align', () => { } ); describe( 'addAttribute()', () => { - const filterRegisterBlockType = applyFilters.bind( null, 'blocks.registerBlockType' ); + const filterRegisterBlockType = applyFilters.bind( + null, + 'blocks.registerBlockType' + ); it( 'should do nothing if the block settings does not define align support', () => { const settings = filterRegisterBlockType( blockSettings ); @@ -65,54 +68,75 @@ describe( 'align', () => { } ); it( 'should return all custom aligns set', () => { - expect( - getValidAlignments( [ 'left', 'right' ] ) - ).toEqual( - [ 'left', 'right' ] - ); + expect( getValidAlignments( [ 'left', 'right' ] ) ).toEqual( [ + 'left', + 'right', + ] ); } ); it( 'should return all aligns if block defines align support as true', () => { - expect( - getValidAlignments( true ) - ).toEqual( - [ 'left', 'center', 'right', 'wide', 'full' ] - ); + expect( getValidAlignments( true ) ).toEqual( [ + 'left', + 'center', + 'right', + 'wide', + 'full', + ] ); } ); it( 'should return all aligns except wide if wide align explicitly false on the block', () => { - expect( - getValidAlignments( true, false, true ) - ).toEqual( [ 'left', 'center', 'right' ] ); - - expect( - getValidAlignments( true, false, false ) - ).toEqual( [ 'left', 'center', 'right' ] ); + expect( getValidAlignments( true, false, true ) ).toEqual( [ + 'left', + 'center', + 'right', + ] ); + + expect( getValidAlignments( true, false, false ) ).toEqual( [ + 'left', + 'center', + 'right', + ] ); } ); it( 'should return all aligns except wide if wide align is not supported by the theme', () => { - expect( - getValidAlignments( true, true, false ) - ).toEqual( [ 'left', 'center', 'right' ] ); - - expect( - getValidAlignments( true, false, false ) - ).toEqual( [ 'left', 'center', 'right' ] ); + expect( getValidAlignments( true, true, false ) ).toEqual( [ + 'left', + 'center', + 'right', + ] ); + + expect( getValidAlignments( true, false, false ) ).toEqual( [ + 'left', + 'center', + 'right', + ] ); } ); it( 'should not remove wide aligns if they are not supported by the block and were set using an array in supports align', () => { expect( - getValidAlignments( [ 'left', 'right', 'wide', 'full' ], false, true ) + getValidAlignments( + [ 'left', 'right', 'wide', 'full' ], + false, + true + ) ).toEqual( [ 'left', 'right', 'wide', 'full' ] ); } ); it( 'should remove wide aligns if they are not supported by the theme and were set using an array in supports align', () => { expect( - getValidAlignments( [ 'left', 'right', 'wide', 'full' ], true, false ) + getValidAlignments( + [ 'left', 'right', 'wide', 'full' ], + true, + false + ) ).toEqual( [ 'left', 'right' ] ); expect( - getValidAlignments( [ 'left', 'right', 'wide', 'full' ], false, false ) + getValidAlignments( + [ 'left', 'right', 'wide', 'full' ], + false, + false + ) ).toEqual( [ 'left', 'right' ] ); } ); } ); @@ -121,9 +145,9 @@ describe( 'align', () => { it( 'should do nothing if no valid alignments', () => { registerBlockType( 'core/foo', blockSettings ); - const EnhancedComponent = withToolbarControls( ( { wrapperProps } ) => ( - <div { ...wrapperProps } /> - ) ); + const EnhancedComponent = withToolbarControls( + ( { wrapperProps } ) => <div { ...wrapperProps } /> + ); const wrapper = renderer.create( <EnhancedComponent @@ -145,9 +169,9 @@ describe( 'align', () => { }, } ); - const EnhancedComponent = withToolbarControls( ( { wrapperProps } ) => ( - <div { ...wrapperProps } /> - ) ); + const EnhancedComponent = withToolbarControls( + ( { wrapperProps } ) => <div { ...wrapperProps } /> + ); const wrapper = renderer.create( <EnhancedComponent @@ -177,7 +201,10 @@ describe( 'align', () => { let wrapper; act( () => { wrapper = renderer.create( - <BlockEditorProvider settings={ { alignWide: true } } value={ [] }> + <BlockEditorProvider + settings={ { alignWide: true } } + value={ [] } + > <EnhancedComponent attributes={ { align: 'wide', @@ -208,7 +235,10 @@ describe( 'align', () => { let wrapper; act( () => { wrapper = renderer.create( - <BlockEditorProvider settings={ { alignWide: false } } value={ [] }> + <BlockEditorProvider + settings={ { alignWide: false } } + value={ [] } + > <EnhancedComponent name="core/foo" attributes={ { @@ -237,7 +267,10 @@ describe( 'align', () => { let wrapper; act( () => { wrapper = renderer.create( - <BlockEditorProvider settings={ { alignWide: true } } value={ [] }> + <BlockEditorProvider + settings={ { alignWide: true } } + value={ [] } + > <EnhancedComponent name="core/foo" attributes={ { @@ -255,11 +288,15 @@ describe( 'align', () => { it( 'should do nothing if block does not support align', () => { registerBlockType( 'core/foo', blockSettings ); - const props = addAssignedAlign( { - className: 'foo', - }, 'core/foo', { - align: 'wide', - } ); + const props = addAssignedAlign( + { + className: 'foo', + }, + 'core/foo', + { + align: 'wide', + } + ); expect( props ).toEqual( { className: 'foo', @@ -274,11 +311,15 @@ describe( 'align', () => { }, } ); - const props = addAssignedAlign( { - className: 'foo', - }, 'core/foo', { - align: 'wide', - } ); + const props = addAssignedAlign( + { + className: 'foo', + }, + 'core/foo', + { + align: 'wide', + } + ); expect( props ).toEqual( { className: 'alignwide foo', diff --git a/packages/block-editor/src/hooks/test/anchor.js b/packages/block-editor/src/hooks/test/anchor.js index d55a54fff332e2..bb4d496ff477b2 100644 --- a/packages/block-editor/src/hooks/test/anchor.js +++ b/packages/block-editor/src/hooks/test/anchor.js @@ -21,7 +21,10 @@ describe( 'anchor', () => { }; describe( 'addAttribute()', () => { - const registerBlockType = applyFilters.bind( null, 'blocks.registerBlockType' ); + const registerBlockType = applyFilters.bind( + null, + 'blocks.registerBlockType' + ); it( 'should do nothing if the block settings do not define anchor support', () => { const settings = registerBlockType( blockSettings ); @@ -54,40 +57,58 @@ describe( 'anchor', () => { }, } ); - expect( settings.attributes.anchor ).toEqual( { type: 'string', default: 'testAnchor' } ); + expect( settings.attributes.anchor ).toEqual( { + type: 'string', + default: 'testAnchor', + } ); } ); } ); describe( 'addSaveProps', () => { - const getSaveContentExtraProps = applyFilters.bind( null, 'blocks.getSaveContent.extraProps' ); + const getSaveContentExtraProps = applyFilters.bind( + null, + 'blocks.getSaveContent.extraProps' + ); it( 'should do nothing if the block settings do not define anchor support', () => { const attributes = { anchor: 'foo' }; - const extraProps = getSaveContentExtraProps( {}, blockSettings, attributes ); + const extraProps = getSaveContentExtraProps( + {}, + blockSettings, + attributes + ); expect( extraProps ).not.toHaveProperty( 'id' ); } ); it( 'should inject anchor attribute ID', () => { const attributes = { anchor: 'foo' }; - const extraProps = getSaveContentExtraProps( {}, { - ...blockSettings, - supports: { - anchor: true, + const extraProps = getSaveContentExtraProps( + {}, + { + ...blockSettings, + supports: { + anchor: true, + }, }, - }, attributes ); + attributes + ); expect( extraProps.id ).toBe( 'foo' ); } ); it( 'should remove an anchor attribute ID when feild is cleared', () => { const attributes = { anchor: '' }; - const extraProps = getSaveContentExtraProps( {}, { - ...blockSettings, - supports: { - anchor: true, + const extraProps = getSaveContentExtraProps( + {}, + { + ...blockSettings, + supports: { + anchor: true, + }, }, - }, attributes ); + attributes + ); expect( extraProps.id ).toBe( null ); } ); diff --git a/packages/block-editor/src/hooks/test/custom-class-name.js b/packages/block-editor/src/hooks/test/custom-class-name.js index 6b2c84b886139d..8429a657859bde 100644 --- a/packages/block-editor/src/hooks/test/custom-class-name.js +++ b/packages/block-editor/src/hooks/test/custom-class-name.js @@ -22,7 +22,10 @@ describe( 'custom className', () => { }; describe( 'addAttribute()', () => { - const addAttribute = applyFilters.bind( null, 'blocks.registerBlockType' ); + const addAttribute = applyFilters.bind( + null, + 'blocks.registerBlockType' + ); it( 'should do nothing if the block settings disable custom className support', () => { const settings = addAttribute( { @@ -43,23 +46,34 @@ describe( 'custom className', () => { } ); describe( 'addSaveProps', () => { - const addSaveProps = applyFilters.bind( null, 'blocks.getSaveContent.extraProps' ); + const addSaveProps = applyFilters.bind( + null, + 'blocks.getSaveContent.extraProps' + ); it( 'should do nothing if the block settings do not define custom className support', () => { const attributes = { className: 'foo' }; - const extraProps = addSaveProps( {}, { - ...blockSettings, - supports: { - customClassName: false, + const extraProps = addSaveProps( + {}, + { + ...blockSettings, + supports: { + customClassName: false, + }, }, - }, attributes ); + attributes + ); expect( extraProps ).not.toHaveProperty( 'className' ); } ); it( 'should inject the custom className', () => { const attributes = { className: 'bar' }; - const extraProps = addSaveProps( { className: 'foo' }, blockSettings, attributes ); + const extraProps = addSaveProps( + { className: 'foo' }, + blockSettings, + attributes + ); expect( extraProps.className ).toBe( 'foo bar' ); } ); @@ -73,14 +87,19 @@ describe( 'custom className', () => { } ); it( 'return an array of parsed classes from inner HTML', () => { - const classes = getHTMLRootElementClasses( '<div class=" foo bar "></div>' ); + const classes = getHTMLRootElementClasses( + '<div class=" foo bar "></div>' + ); expect( classes ).toEqual( [ 'foo', 'bar' ] ); } ); } ); describe( 'addParsedDifference', () => { - const addParsedDifference = applyFilters.bind( null, 'blocks.getBlockAttributes' ); + const addParsedDifference = applyFilters.bind( + null, + 'blocks.getBlockAttributes' + ); it( 'should do nothing if the block settings do not define custom className support', () => { const attributes = addParsedDifference( @@ -161,7 +180,7 @@ describe( 'custom className', () => { const attributes = addParsedDifference( { className: 'custom1' }, dynamicBlockSettings, - null, + null ); expect( attributes.className ).toBe( 'custom1' ); diff --git a/packages/block-editor/src/hooks/test/generated-class-name.js b/packages/block-editor/src/hooks/test/generated-class-name.js index 397ec9ab333cef..1bf447c48ce12c 100644 --- a/packages/block-editor/src/hooks/test/generated-class-name.js +++ b/packages/block-editor/src/hooks/test/generated-class-name.js @@ -22,30 +22,45 @@ describe( 'generated className', () => { }; describe( 'addSaveProps', () => { - const addSaveProps = applyFilters.bind( null, 'blocks.getSaveContent.extraProps' ); + const addSaveProps = applyFilters.bind( + null, + 'blocks.getSaveContent.extraProps' + ); it( 'should do nothing if the block settings do not define generated className support', () => { const attributes = { className: 'foo' }; - const extraProps = addSaveProps( {}, { - ...blockSettings, - supports: { - className: false, + const extraProps = addSaveProps( + {}, + { + ...blockSettings, + supports: { + className: false, + }, }, - }, attributes ); + attributes + ); expect( extraProps ).not.toHaveProperty( 'className' ); } ); it( 'should inject the generated className', () => { const attributes = { className: 'bar' }; - const extraProps = addSaveProps( { className: 'foo' }, blockSettings, attributes ); + const extraProps = addSaveProps( + { className: 'foo' }, + blockSettings, + attributes + ); expect( extraProps.className ).toBe( 'wp-block-chicken-ribs foo' ); } ); it( 'should not inject duplicates into className', () => { const attributes = { className: 'bar' }; - const extraProps = addSaveProps( { className: 'foo wp-block-chicken-ribs' }, blockSettings, attributes ); + const extraProps = addSaveProps( + { className: 'foo wp-block-chicken-ribs' }, + blockSettings, + attributes + ); expect( extraProps.className ).toBe( 'wp-block-chicken-ribs foo' ); } ); diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index b6a6552e1f019b..a9162f4b6d7728 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -6,7 +6,12 @@ import { castArray, first, get, includes, last, some } from 'lodash'; /** * WordPress dependencies */ -import { getDefaultBlockName, createBlock, hasBlockSupport, cloneBlock } from '@wordpress/blocks'; +import { + getDefaultBlockName, + createBlock, + hasBlockSupport, + cloneBlock, +} from '@wordpress/blocks'; import { speak } from '@wordpress/a11y'; import { __ } from '@wordpress/i18n'; @@ -22,10 +27,7 @@ import { select } from './controls'; * replacement, etc). */ function* ensureDefaultBlock() { - const count = yield select( - 'core/block-editor', - 'getBlockCount', - ); + const count = yield select( 'core/block-editor', 'getBlockCount' ); // To avoid a focus loss when removing the last block, assure there is // always a default block if the last of the blocks have been removed. @@ -269,7 +271,8 @@ function getBlocksWithDefaultStylesApplied( blocks, blockEditorSettings ) { ...block, attributes: { ...attributes, - className: `${ ( className || '' ) } is-style-${ blockStyle }`.trim(), + className: `${ className || + '' } is-style-${ blockStyle }`.trim(), }, }; } ); @@ -290,10 +293,7 @@ export function* replaceBlocks( clientIds, blocks, indexToSelect ) { clientIds = castArray( clientIds ); blocks = getBlocksWithDefaultStylesApplied( castArray( blocks ), - yield select( - 'core/block-editor', - 'getSettings', - ) + yield select( 'core/block-editor', 'getSettings' ) ); const rootClientId = yield select( 'core/block-editor', @@ -368,7 +368,12 @@ export const moveBlocksUp = createOnMove( 'MOVE_BLOCKS_UP' ); * * @yield {Object} Action object. */ -export function* moveBlockToPosition( clientId, fromRootClientId = '', toRootClientId = '', index ) { +export function* moveBlockToPosition( + clientId, + fromRootClientId = '', + toRootClientId = '', + index +) { const templateLock = yield select( 'core/block-editor', 'getTemplateLock', @@ -434,14 +439,9 @@ export function insertBlock( block, index, rootClientId, - updateSelection = true, + updateSelection = true ) { - return insertBlocks( - [ block ], - index, - rootClientId, - updateSelection - ); + return insertBlocks( [ block ], index, rootClientId, updateSelection ); } /** @@ -463,10 +463,7 @@ export function* insertBlocks( ) { blocks = getBlocksWithDefaultStylesApplied( castArray( blocks ), - yield select( - 'core/block-editor', - 'getSettings', - ) + yield select( 'core/block-editor', 'getSettings' ) ); const allowedBlocks = []; for ( const block of blocks ) { @@ -575,8 +572,16 @@ export function* removeBlocks( clientIds, selectPrevious = true ) { } clientIds = castArray( clientIds ); - const rootClientId = yield select( 'core/block-editor', 'getBlockRootClientId', clientIds[ 0 ] ); - const isLocked = yield select( 'core/block-editor', 'getTemplateLock', rootClientId ); + const rootClientId = yield select( + 'core/block-editor', + 'getBlockRootClientId', + clientIds[ 0 ] + ); + const isLocked = yield select( + 'core/block-editor', + 'getTemplateLock', + rootClientId + ); if ( isLocked ) { return; } @@ -619,7 +624,11 @@ export function removeBlock( clientId, selectPrevious ) { * * @return {Object} Action object. */ -export function replaceInnerBlocks( rootClientId, blocks, updateSelection = true ) { +export function replaceInnerBlocks( + rootClientId, + blocks, + updateSelection = true +) { return { type: 'REPLACE_INNER_BLOCKS', rootClientId, @@ -721,7 +730,12 @@ export function exitFormattedText() { * * @return {Object} Action object. */ -export function selectionChange( clientId, attributeKey, startOffset, endOffset ) { +export function selectionChange( + clientId, + attributeKey, + startOffset, + endOffset +) { return { type: 'SELECTION_CHANGE', clientId, @@ -839,16 +853,24 @@ export function __unstableMarkAutomaticChange() { * * @param {string} isNavigationMode Enable/Disable navigation mode. */ -export function * setNavigationMode( isNavigationMode = true ) { +export function* setNavigationMode( isNavigationMode = true ) { yield { type: 'SET_NAVIGATION_MODE', isNavigationMode, }; if ( isNavigationMode ) { - speak( __( 'You are currently in navigation mode. Navigate blocks using the Tab key. To exit navigation mode and edit the selected block, press Enter.' ) ); + speak( + __( + 'You are currently in navigation mode. Navigate blocks using the Tab key. To exit navigation mode and edit the selected block, press Enter.' + ) + ); } else { - speak( __( 'You are currently in edit mode. To return to the navigation mode, press Escape.' ) ); + speak( + __( + 'You are currently in edit mode. To return to the navigation mode, press Escape.' + ) + ); } } @@ -857,19 +879,32 @@ export function * setNavigationMode( isNavigationMode = true ) { * * @param {string[]} clientIds */ -export function * duplicateBlocks( clientIds ) { +export function* duplicateBlocks( clientIds ) { if ( ! clientIds && ! clientIds.length ) { return; } - const blocks = yield select( 'core/block-editor', 'getBlocksByClientId', clientIds ); - const rootClientId = yield select( 'core/block-editor', 'getBlockRootClientId', clientIds[ 0 ] ); + const blocks = yield select( + 'core/block-editor', + 'getBlocksByClientId', + clientIds + ); + const rootClientId = yield select( + 'core/block-editor', + 'getBlockRootClientId', + clientIds[ 0 ] + ); // Return early if blocks don't exist. if ( some( blocks, ( block ) => ! block ) ) { return; } const blockNames = blocks.map( ( block ) => block.name ); // Return early if blocks don't support multipe usage. - if ( some( blockNames, ( blockName ) => ! hasBlockSupport( blockName, 'multiple', true ) ) ) { + if ( + some( + blockNames, + ( blockName ) => ! hasBlockSupport( blockName, 'multiple', true ) + ) + ) { return; } @@ -880,11 +915,7 @@ export function * duplicateBlocks( clientIds ) { rootClientId ); const clonedBlocks = blocks.map( ( block ) => cloneBlock( block ) ); - yield insertBlocks( - clonedBlocks, - lastSelectedIndex + 1, - rootClientId - ); + yield insertBlocks( clonedBlocks, lastSelectedIndex + 1, rootClientId ); if ( clonedBlocks.length > 1 ) { yield multiSelect( first( clonedBlocks ).clientId, @@ -898,17 +929,30 @@ export function * duplicateBlocks( clientIds ) { * * @param {string} clientId */ -export function * insertBeforeBlock( clientId ) { +export function* insertBeforeBlock( clientId ) { if ( ! clientId ) { return; } - const rootClientId = yield select( 'core/block-editor', 'getBlockRootClientId', clientId ); - const isLocked = yield select( 'core/block-editor', 'getTemplateLock', rootClientId ); + const rootClientId = yield select( + 'core/block-editor', + 'getBlockRootClientId', + clientId + ); + const isLocked = yield select( + 'core/block-editor', + 'getTemplateLock', + rootClientId + ); if ( isLocked ) { return; } - const firstSelectedIndex = yield select( 'core/block-editor', 'getBlockIndex', clientId, rootClientId ); + const firstSelectedIndex = yield select( + 'core/block-editor', + 'getBlockIndex', + clientId, + rootClientId + ); yield insertDefaultBlock( {}, rootClientId, firstSelectedIndex ); } @@ -917,16 +961,29 @@ export function * insertBeforeBlock( clientId ) { * * @param {string} clientId */ -export function * insertAfterBlock( clientId ) { +export function* insertAfterBlock( clientId ) { if ( ! clientId ) { return; } - const rootClientId = yield select( 'core/block-editor', 'getBlockRootClientId', clientId ); - const isLocked = yield select( 'core/block-editor', 'getTemplateLock', rootClientId ); + const rootClientId = yield select( + 'core/block-editor', + 'getBlockRootClientId', + clientId + ); + const isLocked = yield select( + 'core/block-editor', + 'getTemplateLock', + rootClientId + ); if ( isLocked ) { return; } - const firstSelectedIndex = yield select( 'core/block-editor', 'getBlockIndex', clientId, rootClientId ); + const firstSelectedIndex = yield select( + 'core/block-editor', + 'getBlockIndex', + clientId, + rootClientId + ); yield insertDefaultBlock( {}, rootClientId, firstSelectedIndex + 1 ); } diff --git a/packages/block-editor/src/store/array.js b/packages/block-editor/src/store/array.js index 176d8936450afa..3a2977eb152aec 100644 --- a/packages/block-editor/src/store/array.js +++ b/packages/block-editor/src/store/array.js @@ -36,6 +36,6 @@ export function moveTo( array, from, to, count = 1 ) { return insertAt( withoutMovedElements, array.slice( from, from + count ), - to, + to ); } diff --git a/packages/block-editor/src/store/controls.js b/packages/block-editor/src/store/controls.js index 5012ab244c21c8..3a64e045f69666 100644 --- a/packages/block-editor/src/store/controls.js +++ b/packages/block-editor/src/store/controls.js @@ -22,9 +22,11 @@ export function select( storeName, selectorName, ...args ) { } const controls = { - SELECT: createRegistryControl( ( registry ) => ( { storeName, selectorName, args } ) => { - return registry.select( storeName )[ selectorName ]( ...args ); - } ), + SELECT: createRegistryControl( + ( registry ) => ( { storeName, selectorName, args } ) => { + return registry.select( storeName )[ selectorName ]( ...args ); + } + ), }; export default controls; diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js index dca689127ad93d..a0ab78569c5083 100644 --- a/packages/block-editor/src/store/defaults.js +++ b/packages/block-editor/src/store/defaults.js @@ -44,10 +44,7 @@ export const SETTINGS_DEFAULTS = { slug: 'pale-pink', color: '#f78da7', }, - { name: __( 'Vivid red' ), - slug: 'vivid-red', - color: '#cf2e2e', - }, + { name: __( 'Vivid red' ), slug: 'vivid-red', color: '#cf2e2e' }, { name: __( 'Luminous vivid orange' ), slug: 'luminous-vivid-orange', @@ -160,62 +157,74 @@ export const SETTINGS_DEFAULTS = { gradients: [ { name: __( 'Vivid cyan blue to vivid purple' ), - gradient: 'linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)', + gradient: + 'linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)', slug: 'vivid-cyan-blue-to-vivid-purple', }, { name: __( 'Light green cyan to vivid green cyan' ), - gradient: 'linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%)', + gradient: + 'linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%)', slug: 'light-green-cyan-to-vivid-green-cyan', }, { name: __( 'Luminous vivid amber to luminous vivid orange' ), - gradient: 'linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%)', + gradient: + 'linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%)', slug: 'luminous-vivid-amber-to-luminous-vivid-orange', }, { name: __( 'Luminous vivid orange to vivid red' ), - gradient: 'linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%)', + gradient: + 'linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%)', slug: 'luminous-vivid-orange-to-vivid-red', }, { name: __( 'Very light gray to cyan bluish gray' ), - gradient: 'linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%)', + gradient: + 'linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%)', slug: 'very-light-gray-to-cyan-bluish-gray', }, { name: __( 'Cool to warm spectrum' ), - gradient: 'linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%)', + gradient: + 'linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%)', slug: 'cool-to-warm-spectrum', }, { name: __( 'Blush light purple' ), - gradient: 'linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%)', + gradient: + 'linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%)', slug: 'blush-light-purple', }, { name: __( 'Blush bordeaux' ), - gradient: 'linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%)', + gradient: + 'linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%)', slug: 'blush-bordeaux', }, { name: __( 'Luminous dusk' ), - gradient: 'linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)', + gradient: + 'linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)', slug: 'luminous-dusk', }, { name: __( 'Pale ocean' ), - gradient: 'linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%)', + gradient: + 'linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%)', slug: 'pale-ocean', }, { name: __( 'Electric grass' ), - gradient: 'linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%)', + gradient: + 'linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%)', slug: 'electric-grass', }, { name: __( 'Midnight' ), - gradient: 'linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%)', + gradient: + 'linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%)', slug: 'midnight', }, ], diff --git a/packages/block-editor/src/store/effects.js b/packages/block-editor/src/store/effects.js index fbfaee23cc715d..b01e37b1e76dd8 100644 --- a/packages/block-editor/src/store/effects.js +++ b/packages/block-editor/src/store/effects.js @@ -55,11 +55,10 @@ export function validateBlocksToTemplate( action, store ) { // Unlocked templates are considered always valid because they act // as default values only. - const isBlocksValidToTemplate = ( + const isBlocksValidToTemplate = ! template || templateLock !== 'all' || - doBlocksMatchTemplate( action.blocks, template ) - ); + doBlocksMatchTemplate( action.blocks, template ); // Update if validity has changed. if ( isBlocksValidToTemplate !== isValidTemplate( state ) ) { @@ -84,9 +83,11 @@ export default { const blockB = getBlock( state, clientIdB ); const blockBType = getBlockType( blockB.name ); const { clientId, attributeKey, offset } = getSelectionStart( state ); - const selectedBlockType = clientId === clientIdA ? blockAType : blockBType; - const attributeDefinition = selectedBlockType.attributes[ attributeKey ]; - const canRestoreTextSelection = ( + const selectedBlockType = + clientId === clientIdA ? blockAType : blockBType; + const attributeDefinition = + selectedBlockType.attributes[ attributeKey ]; + const canRestoreTextSelection = ( clientId === clientIdA || clientId === clientIdB ) && attributeKey !== undefined && offset !== undefined && @@ -94,8 +95,7 @@ export default { // is not a defined block attribute key. This can be the case if the // fallback intance ID is used to store selection (and no RichText // identifier is set), or when the identifier is wrong. - !! attributeDefinition - ); + !! attributeDefinition; if ( ! attributeDefinition ) { if ( typeof attributeKey === 'number' ) { @@ -125,12 +125,17 @@ export default { __unstableMultilineWrapperTags: multilineWrapperTags, __unstablePreserveWhiteSpace: preserveWhiteSpace, } = attributeDefinition; - const value = insert( create( { - html, - multilineTag, - multilineWrapperTags, - preserveWhiteSpace, - } ), START_OF_SELECTED_AREA, offset, offset ); + const value = insert( + create( { + html, + multilineTag, + multilineWrapperTags, + preserveWhiteSpace, + } ), + START_OF_SELECTED_AREA, + offset, + offset + ); selectedBlock.attributes[ attributeKey ] = toHTMLString( { value, @@ -141,9 +146,10 @@ export default { // We can only merge blocks with similar types // thus, we transform the block to merge first - const blocksWithTheSameType = blockA.name === blockB.name ? - [ cloneB ] : - switchToBlockType( cloneB, blockA.name ); + const blocksWithTheSameType = + blockA.name === blockB.name + ? [ cloneB ] + : switchToBlockType( cloneB, blockA.name ); // If the block types can not match, do nothing if ( ! blocksWithTheSameType || ! blocksWithTheSameType.length ) { @@ -157,8 +163,11 @@ export default { ); if ( canRestoreTextSelection ) { - const newAttributeKey = findKey( updatedAttributes, ( v ) => - typeof v === 'string' && v.indexOf( START_OF_SELECTED_AREA ) !== -1 + const newAttributeKey = findKey( + updatedAttributes, + ( v ) => + typeof v === 'string' && + v.indexOf( START_OF_SELECTED_AREA ) !== -1 ); const convertedHtml = updatedAttributes[ newAttributeKey ]; const { @@ -172,7 +181,9 @@ export default { multilineWrapperTags, preserveWhiteSpace, } ); - const newOffset = convertedValue.text.indexOf( START_OF_SELECTED_AREA ); + const newOffset = convertedValue.text.indexOf( + START_OF_SELECTED_AREA + ); const newValue = remove( convertedValue, newOffset, newOffset + 1 ); const newHtml = toHTMLString( { value: newValue, @@ -182,42 +193,53 @@ export default { updatedAttributes[ newAttributeKey ] = newHtml; - dispatch( selectionChange( - blockA.clientId, - newAttributeKey, - newOffset, - newOffset - ) ); + dispatch( + selectionChange( + blockA.clientId, + newAttributeKey, + newOffset, + newOffset + ) + ); } - dispatch( replaceBlocks( - [ blockA.clientId, blockB.clientId ], - [ - { - ...blockA, - attributes: { - ...blockA.attributes, - ...updatedAttributes, + dispatch( + replaceBlocks( + [ blockA.clientId, blockB.clientId ], + [ + { + ...blockA, + attributes: { + ...blockA.attributes, + ...updatedAttributes, + }, }, - }, - ...blocksWithTheSameType.slice( 1 ), - ] - ) ); + ...blocksWithTheSameType.slice( 1 ), + ] + ) + ); }, - RESET_BLOCKS: [ - validateBlocksToTemplate, - ], + RESET_BLOCKS: [ validateBlocksToTemplate ], MULTI_SELECT: ( action, { getState } ) => { const blockCount = getSelectedBlockCount( getState() ); /* translators: %s: number of selected blocks */ - speak( sprintf( _n( '%s block selected.', '%s blocks selected.', blockCount ), blockCount ), 'assertive' ); + speak( + sprintf( + _n( '%s block selected.', '%s blocks selected.', blockCount ), + blockCount + ), + 'assertive' + ); }, SYNCHRONIZE_TEMPLATE( action, { getState } ) { const state = getState(); const blocks = getBlocks( state ); const template = getTemplate( state ); - const updatedBlockList = synchronizeBlocksWithTemplate( blocks, template ); + const updatedBlockList = synchronizeBlocksWithTemplate( + blocks, + template + ); return resetBlocks( updatedBlockList ); }, diff --git a/packages/block-editor/src/store/middlewares.js b/packages/block-editor/src/store/middlewares.js index 6381132bb81e08..0f4c5aef8df703 100644 --- a/packages/block-editor/src/store/middlewares.js +++ b/packages/block-editor/src/store/middlewares.js @@ -18,15 +18,12 @@ import effects from './effects'; * @return {Object} Update Store Object. */ function applyMiddlewares( store ) { - const middlewares = [ - refx( effects ), - multi, - ]; + const middlewares = [ refx( effects ), multi ]; let enhancedDispatch = () => { throw new Error( 'Dispatching while constructing your middleware is not allowed. ' + - 'Other middleware would not be applied to this dispatch.' + 'Other middleware would not be applied to this dispatch.' ); }; let chain = []; diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index d7b3799a61a0d4..45a5dc63cea04d 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -27,10 +27,7 @@ import { isReusableBlock } from '@wordpress/blocks'; /** * Internal dependencies */ -import { - PREFERENCES_DEFAULTS, - SETTINGS_DEFAULTS, -} from './defaults'; +import { PREFERENCES_DEFAULTS, SETTINGS_DEFAULTS } from './defaults'; import { insertAt, moveTo } from './array'; /** @@ -67,11 +64,15 @@ function mapBlockOrder( blocks, rootClientId = '' ) { * @return {Object} Block order map object. */ function mapBlockParents( blocks, rootClientId = '' ) { - return blocks.reduce( ( result, block ) => Object.assign( - result, - { [ block.clientId ]: rootClientId }, - mapBlockParents( block.innerBlocks, block.clientId ) - ), {} ); + return blocks.reduce( + ( result, block ) => + Object.assign( + result, + { [ block.clientId ]: rootClientId }, + mapBlockParents( block.innerBlocks, block.clientId ) + ), + {} + ); } /** @@ -138,11 +139,15 @@ function getFlattenedBlockAttributes( blocks ) { * @return {Array} List of descendant client IDs. */ function getNestedBlockClientIds( blocksOrder, rootClientId = '' ) { - return reduce( blocksOrder[ rootClientId ], ( result, clientId ) => [ - ...result, - clientId, - ...getNestedBlockClientIds( blocksOrder, clientId ), - ], [] ); + return reduce( + blocksOrder[ rootClientId ], + ( result, clientId ) => [ + ...result, + clientId, + ...getNestedBlockClientIds( blocksOrder, clientId ), + ], + [] + ); } /** @@ -249,7 +254,10 @@ const withBlockCache = ( reducer ) => ( state = {}, action ) => { switch ( action.type ) { case 'RESET_BLOCKS': - newState.cache = mapValues( flattenBlocks( action.blocks ), () => ( {} ) ); + newState.cache = mapValues( + flattenBlocks( action.blocks ), + () => ( {} ) + ); break; case 'RECEIVE_BLOCKS': case 'INSERT_BLOCKS': { @@ -260,7 +268,7 @@ const withBlockCache = ( reducer ) => ( state = {}, action ) => { newState.cache = { ...newState.cache, ...fillKeysWithEmptyObject( - getBlocksWithParentsClientIds( updatedBlockUids ), + getBlocksWithParentsClientIds( updatedBlockUids ) ), }; break; @@ -270,24 +278,31 @@ const withBlockCache = ( reducer ) => ( state = {}, action ) => { newState.cache = { ...newState.cache, ...fillKeysWithEmptyObject( - getBlocksWithParentsClientIds( [ action.clientId ] ), + getBlocksWithParentsClientIds( [ action.clientId ] ) ), }; break; case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': - const parentClientIds = fillKeysWithEmptyObject( getBlocksWithParentsClientIds( action.replacedClientIds ) ); + const parentClientIds = fillKeysWithEmptyObject( + getBlocksWithParentsClientIds( action.replacedClientIds ) + ); newState.cache = { ...omit( newState.cache, action.replacedClientIds ), ...omit( parentClientIds, action.replacedClientIds ), - ...fillKeysWithEmptyObject( keys( flattenBlocks( action.blocks ) ) ), + ...fillKeysWithEmptyObject( + keys( flattenBlocks( action.blocks ) ) + ), }; break; case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': newState.cache = { ...omit( newState.cache, action.removedClientIds ), ...fillKeysWithEmptyObject( - difference( getBlocksWithParentsClientIds( action.clientIds ), action.clientIds ), + difference( + getBlocksWithParentsClientIds( action.clientIds ), + action.clientIds + ) ), }; break; @@ -322,9 +337,14 @@ const withBlockCache = ( reducer ) => ( state = {}, action ) => { break; } case 'SAVE_REUSABLE_BLOCK_SUCCESS': { - const updatedBlockUids = keys( omitBy( newState.attributes, ( attributes, clientId ) => { - return newState.byClientId[ clientId ].name !== 'core/block' || attributes.ref !== action.updatedId; - } ) ); + const updatedBlockUids = keys( + omitBy( newState.attributes, ( attributes, clientId ) => { + return ( + newState.byClientId[ clientId ].name !== 'core/block' || + attributes.ref !== action.updatedId + ); + } ) + ); newState.cache = { ...newState.cache, @@ -355,14 +375,21 @@ function withPersistentBlockChange( reducer ) { return ( state, action ) => { let nextState = reducer( state, action ); - const isExplicitPersistentChange = action.type === 'MARK_LAST_CHANGE_AS_PERSISTENT' || markNextChangeAsNotPersistent; + const isExplicitPersistentChange = + action.type === 'MARK_LAST_CHANGE_AS_PERSISTENT' || + markNextChangeAsNotPersistent; // Defer to previous state value (or default) unless changing or // explicitly marking as persistent. if ( state === nextState && ! isExplicitPersistentChange ) { - markNextChangeAsNotPersistent = action.type === 'MARK_NEXT_CHANGE_AS_NOT_PERSISTENT'; - - const nextIsPersistentChange = get( state, [ 'isPersistentChange' ], true ); + markNextChangeAsNotPersistent = + action.type === 'MARK_NEXT_CHANGE_AS_NOT_PERSISTENT'; + + const nextIsPersistentChange = get( + state, + [ 'isPersistentChange' ], + true + ); if ( state.isPersistentChange === nextIsPersistentChange ) { return state; } @@ -375,16 +402,17 @@ function withPersistentBlockChange( reducer ) { nextState = { ...nextState, - isPersistentChange: isExplicitPersistentChange ? - ! markNextChangeAsNotPersistent : - ! isUpdatingSameBlockAttribute( action, lastAction ), + isPersistentChange: isExplicitPersistentChange + ? ! markNextChangeAsNotPersistent + : ! isUpdatingSameBlockAttribute( action, lastAction ), }; // In comparing against the previous action, consider only those which // would have qualified as one which would have been ignored or not // have resulted in a changed state. lastAction = action; - markNextChangeAsNotPersistent = action.type === 'MARK_NEXT_CHANGE_AS_NOT_PERSISTENT'; + markNextChangeAsNotPersistent = + action.type === 'MARK_NEXT_CHANGE_AS_NOT_PERSISTENT'; return nextState; }; @@ -406,9 +434,7 @@ function withIgnoredBlockChange( reducer ) { * * @type {Set} */ - const IGNORED_ACTION_TYPES = new Set( [ - 'RECEIVE_BLOCKS', - ] ); + const IGNORED_ACTION_TYPES = new Set( [ 'RECEIVE_BLOCKS' ] ); return ( state, action ) => { const nextState = reducer( state, action ); @@ -562,17 +588,20 @@ const withSaveReusableBlock = ( reducer ) => ( state, action ) => { state = { ...state }; - state.attributes = mapValues( state.attributes, ( attributes, clientId ) => { - const { name } = state.byClientId[ clientId ]; - if ( name === 'core/block' && attributes.ref === id ) { - return { - ...attributes, - ref: updatedId, - }; - } + state.attributes = mapValues( + state.attributes, + ( attributes, clientId ) => { + const { name } = state.byClientId[ clientId ]; + if ( name === 'core/block' && attributes.ref === id ) { + return { + ...attributes, + ref: updatedId, + }; + } - return attributes; - } ); + return attributes; + } + ); } return reducer( state, action ); @@ -594,7 +623,7 @@ export const blocks = flow( withReplaceInnerBlocks, // needs to be after withInnerBlocksRemoveCascade withBlockReset, withPersistentBlockChange, - withIgnoredBlockChange, + withIgnoredBlockChange )( { byClientId( state = {}, action ) { switch ( action.type ) { @@ -659,7 +688,10 @@ export const blocks = flow( case 'UPDATE_BLOCK': // Ignore updates if block isn't known or there are no attribute changes. - if ( ! state[ action.clientId ] || ! action.updates.attributes ) { + if ( + ! state[ action.clientId ] || + ! action.updates.attributes + ) { return state; } @@ -678,14 +710,21 @@ export const blocks = flow( } // Consider as updates only changed values - const nextAttributes = reduce( action.attributes, ( result, value, key ) => { - if ( value !== result[ key ] ) { - result = getMutateSafeObject( state[ action.clientId ], result ); - result[ key ] = value; - } - - return result; - }, state[ action.clientId ] ); + const nextAttributes = reduce( + action.attributes, + ( result, value, key ) => { + if ( value !== result[ key ] ) { + result = getMutateSafeObject( + state[ action.clientId ], + result + ); + result[ key ] = value; + } + + return result; + }, + state[ action.clientId ] + ); // Skip update if nothing has been changed. The reference will // match the original block if `reduce` had no changed values. @@ -730,18 +769,29 @@ export const blocks = flow( case 'INSERT_BLOCKS': { const { rootClientId = '' } = action; const subState = state[ rootClientId ] || []; - const mappedBlocks = mapBlockOrder( action.blocks, rootClientId ); + const mappedBlocks = mapBlockOrder( + action.blocks, + rootClientId + ); const { index = subState.length } = action; return { ...state, ...mappedBlocks, - [ rootClientId ]: insertAt( subState, mappedBlocks[ rootClientId ], index ), + [ rootClientId ]: insertAt( + subState, + mappedBlocks[ rootClientId ], + index + ), }; } case 'MOVE_BLOCK_TO_POSITION': { - const { fromRootClientId = '', toRootClientId = '', clientId } = action; + const { + fromRootClientId = '', + toRootClientId = '', + clientId, + } = action; const { index = state[ toRootClientId ].length } = action; // Moving inside the same parent block @@ -750,15 +800,26 @@ export const blocks = flow( const fromIndex = subState.indexOf( clientId ); return { ...state, - [ toRootClientId ]: moveTo( state[ toRootClientId ], fromIndex, index ), + [ toRootClientId ]: moveTo( + state[ toRootClientId ], + fromIndex, + index + ), }; } // Moving from a parent block to another return { ...state, - [ fromRootClientId ]: without( state[ fromRootClientId ], clientId ), - [ toRootClientId ]: insertAt( state[ toRootClientId ], clientId, index ), + [ fromRootClientId ]: without( + state[ fromRootClientId ], + clientId + ), + [ toRootClientId ]: insertAt( + state[ toRootClientId ], + clientId, + index + ), }; } @@ -767,7 +828,10 @@ export const blocks = flow( const firstClientId = first( clientIds ); const subState = state[ rootClientId ]; - if ( ! subState.length || firstClientId === first( subState ) ) { + if ( + ! subState.length || + firstClientId === first( subState ) + ) { return state; } @@ -775,7 +839,12 @@ export const blocks = flow( return { ...state, - [ rootClientId ]: moveTo( subState, firstIndex, firstIndex - 1, clientIds.length ), + [ rootClientId ]: moveTo( + subState, + firstIndex, + firstIndex - 1, + clientIds.length + ), }; } @@ -793,7 +862,12 @@ export const blocks = flow( return { ...state, - [ rootClientId ]: moveTo( subState, firstIndex, firstIndex + 1, clientIds.length ), + [ rootClientId ]: moveTo( + subState, + firstIndex, + firstIndex + 1, + clientIds.length + ), }; } @@ -806,27 +880,35 @@ export const blocks = flow( const mappedBlocks = mapBlockOrder( action.blocks ); return flow( [ - ( nextState ) => omit( nextState, action.replacedClientIds ), + ( nextState ) => + omit( nextState, action.replacedClientIds ), ( nextState ) => ( { ...nextState, ...omit( mappedBlocks, '' ), } ), - ( nextState ) => mapValues( nextState, ( subState ) => ( - reduce( subState, ( result, clientId ) => { - if ( clientId === clientIds[ 0 ] ) { - return [ - ...result, - ...mappedBlocks[ '' ], - ]; - } - - if ( clientIds.indexOf( clientId ) === -1 ) { - result.push( clientId ); - } - - return result; - }, [] ) - ) ), + ( nextState ) => + mapValues( nextState, ( subState ) => + reduce( + subState, + ( result, clientId ) => { + if ( clientId === clientIds[ 0 ] ) { + return [ + ...result, + ...mappedBlocks[ '' ], + ]; + } + + if ( + clientIds.indexOf( clientId ) === -1 + ) { + result.push( clientId ); + } + + return result; + }, + [] + ) + ), ] )( state ); } @@ -836,9 +918,10 @@ export const blocks = flow( ( nextState ) => omit( nextState, action.removedClientIds ), // Remove deleted blocks from other blocks' orderings - ( nextState ) => mapValues( nextState, ( subState ) => ( - without( subState, ...action.removedClientIds ) - ) ), + ( nextState ) => + mapValues( nextState, ( subState ) => + without( subState, ...action.removedClientIds ) + ), ] )( state ); } @@ -861,7 +944,10 @@ export const blocks = flow( case 'INSERT_BLOCKS': return { ...state, - ...mapBlockParents( action.blocks, action.rootClientId || '' ), + ...mapBlockParents( + action.blocks, + action.rootClientId || '' + ), }; case 'MOVE_BLOCK_TO_POSITION': { @@ -874,7 +960,10 @@ export const blocks = flow( case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN': return { ...omit( state, action.replacedClientIds ), - ...mapBlockParents( action.blocks, state[ action.clientIds[ 0 ] ] ), + ...mapBlockParents( + action.blocks, + state[ action.clientIds[ 0 ] ] + ), }; case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN': @@ -992,7 +1081,8 @@ function selection( state = {}, action ) { return state; } - const indexToSelect = action.indexToSelect || action.blocks.length - 1; + const indexToSelect = + action.indexToSelect || action.blocks.length - 1; const blockToSelect = action.blocks[ indexToSelect ]; if ( ! blockToSelect ) { @@ -1124,7 +1214,10 @@ export function blocksMode( state = {}, action ) { const { clientId } = action; return { ...state, - [ clientId ]: state[ clientId ] && state[ clientId ] === 'html' ? 'visual' : 'html', + [ clientId ]: + state[ clientId ] && state[ clientId ] === 'html' + ? 'visual' + : 'html', }; } @@ -1220,7 +1313,9 @@ export function preferences( state = PREFERENCES_DEFAULTS, action ) { ...prevState.insertUsage, [ id ]: { time: action.time, - count: prevState.insertUsage[ id ] ? prevState.insertUsage[ id ].count + 1 : 1, + count: prevState.insertUsage[ id ] + ? prevState.insertUsage[ id ].count + 1 + : 1, insert, }, }, @@ -1244,7 +1339,7 @@ export const blockListSettings = ( state = {}, action ) => { switch ( action.type ) { // Even if the replaced blocks have the same client ID, our logic // should correct the state. - case 'REPLACE_BLOCKS' : + case 'REPLACE_BLOCKS': case 'REMOVE_BLOCKS': { return omit( state, action.clientIds ); } diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 6c197332652ac5..f6c1f3052530ab 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -66,7 +66,14 @@ export const INSERTER_UTILITY_NONE = 0; const MILLISECONDS_PER_HOUR = 3600 * 1000; const MILLISECONDS_PER_DAY = 24 * 3600 * 1000; const MILLISECONDS_PER_WEEK = 7 * 24 * 3600 * 1000; -const templateIcon = <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Rect x="0" fill="none" width="24" height="24" /><G><Path d="M19 3H5c-1.105 0-2 .895-2 2v14c0 1.105.895 2 2 2h14c1.105 0 2-.895 2-2V5c0-1.105-.895-2-2-2zM6 6h5v5H6V6zm4.5 13C9.12 19 8 17.88 8 16.5S9.12 14 10.5 14s2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5zm3-6l3-5 3 5h-6z" /></G></SVG>; +const templateIcon = ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Rect x="0" fill="none" width="24" height="24" /> + <G> + <Path d="M19 3H5c-1.105 0-2 .895-2 2v14c0 1.105.895 2 2 2h14c1.105 0 2-.895 2-2V5c0-1.105-.895-2-2-2zM6 6h5v5H6V6zm4.5 13C9.12 19 8 17.88 8 16.5S9.12 14 10.5 14s2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5zm3-6l3-5 3 5h-6z" /> + </G> + </SVG> +); /** * Shared reference to an empty array for cases where it is important to avoid @@ -190,9 +197,8 @@ export const __unstableGetBlockWithoutInnerBlocks = createSelector( */ export const getBlocks = createSelector( ( state, rootClientId ) => { - return map( - getBlockOrder( state, rootClientId ), - ( clientId ) => getBlock( state, clientId ) + return map( getBlockOrder( state, rootClientId ), ( clientId ) => + getBlock( state, clientId ) ); }, ( state ) => [ @@ -211,10 +217,14 @@ export const getBlocks = createSelector( * * @return {Array} ids of descendants. */ -export const getClientIdsOfDescendants = ( state, clientIds ) => flatMap( clientIds, ( clientId ) => { - const descendants = getBlockOrder( state, clientId ); - return [ ...descendants, ...getClientIdsOfDescendants( state, descendants ) ]; -} ); +export const getClientIdsOfDescendants = ( state, clientIds ) => + flatMap( clientIds, ( clientId ) => { + const descendants = getBlockOrder( state, clientId ); + return [ + ...descendants, + ...getClientIdsOfDescendants( state, descendants ), + ]; + } ); /** * Returns an array containing the clientIds of the top-level blocks @@ -227,11 +237,12 @@ export const getClientIdsOfDescendants = ( state, clientIds ) => flatMap( client export const getClientIdsWithDescendants = createSelector( ( state ) => { const topLevelIds = getBlockOrder( state ); - return [ ...topLevelIds, ...getClientIdsOfDescendants( state, topLevelIds ) ]; + return [ + ...topLevelIds, + ...getClientIdsOfDescendants( state, topLevelIds ), + ]; }, - ( state ) => [ - state.blocks.order, - ] + ( state ) => [ state.blocks.order ] ); /** @@ -249,15 +260,16 @@ export const getGlobalBlockCount = createSelector( if ( ! blockName ) { return clientIds.length; } - return reduce( clientIds, ( accumulator, clientId ) => { - const block = state.blocks.byClientId[ clientId ]; - return block.name === blockName ? accumulator + 1 : accumulator; - }, 0 ); + return reduce( + clientIds, + ( accumulator, clientId ) => { + const block = state.blocks.byClientId[ clientId ]; + return block.name === blockName ? accumulator + 1 : accumulator; + }, + 0 + ); }, - ( state ) => [ - state.blocks.order, - state.blocks.byClientId, - ] + ( state ) => [ state.blocks.order, state.blocks.byClientId ] ); /** @@ -270,10 +282,10 @@ export const getGlobalBlockCount = createSelector( * @return {WPBlock[]} Block objects. */ export const getBlocksByClientId = createSelector( - ( state, clientIds ) => map( - castArray( clientIds ), - ( clientId ) => getBlock( state, clientId ) - ), + ( state, clientIds ) => + map( castArray( clientIds ), ( clientId ) => + getBlock( state, clientId ) + ), ( state ) => [ state.blocks.byClientId, state.blocks.order, @@ -351,7 +363,8 @@ export function getBlockSelectionEnd( state ) { * @return {number} Number of blocks selected in the post. */ export function getSelectedBlockCount( state ) { - const multiSelectedBlockCount = getMultiSelectedBlockClientIds( state ).length; + const multiSelectedBlockCount = getMultiSelectedBlockClientIds( state ) + .length; if ( multiSelectedBlockCount ) { return multiSelectedBlockCount; @@ -417,9 +430,9 @@ export function getSelectedBlock( state ) { * @return {?string} Root client ID, if exists */ export function getBlockRootClientId( state, clientId ) { - return state.blocks.parents[ clientId ] !== undefined ? - state.blocks.parents[ clientId ] : - null; + return state.blocks.parents[ clientId ] !== undefined + ? state.blocks.parents[ clientId ] + : null; } /** @@ -442,9 +455,7 @@ export const getBlockParents = createSelector( return ascending ? parents : parents.reverse(); }, - ( state ) => [ - state.blocks.parents, - ] + ( state ) => [ state.blocks.parents ] ); /** @@ -476,7 +487,10 @@ export function getBlockHierarchyRootClientId( state, clientId ) { export function getLowestCommonAncestorWithSelectedBlock( state, clientId ) { const selectedId = getSelectedBlockClientId( state ); const clientParents = [ ...getBlockParents( state, clientId ), clientId ]; - const selectedParents = [ ...getBlockParents( state, selectedId ), selectedId ]; + const selectedParents = [ + ...getBlockParents( state, selectedId ), + selectedId, + ]; let lowestCommonAncestor; @@ -536,7 +550,7 @@ export function getAdjacentBlockClientId( state, startClientId, modifier = 1 ) { const { order } = state.blocks; const orderSet = order[ rootClientId ]; const index = orderSet.indexOf( startClientId ); - const nextIndex = ( index + ( 1 * modifier ) ); + const nextIndex = index + 1 * modifier; // Block was first in set and we're attempting to get previous. if ( nextIndex < 0 ) { @@ -619,7 +633,10 @@ export const getSelectedBlockClientIds = createSelector( // Retrieve root client ID to aid in retrieving relevant nested block // order, being careful to allow the falsey empty string top-level root // by explicitly testing against null. - const rootClientId = getBlockRootClientId( state, selectionStart.clientId ); + const rootClientId = getBlockRootClientId( + state, + selectionStart.clientId + ); if ( rootClientId === null ) { return EMPTY_ARRAY; } @@ -638,7 +655,7 @@ export const getSelectedBlockClientIds = createSelector( state.blocks.order, state.selectionStart.clientId, state.selectionEnd.clientId, - ], + ] ); /** @@ -669,12 +686,16 @@ export function getMultiSelectedBlockClientIds( state ) { */ export const getMultiSelectedBlocks = createSelector( ( state ) => { - const multiSelectedBlockClientIds = getMultiSelectedBlockClientIds( state ); + const multiSelectedBlockClientIds = getMultiSelectedBlockClientIds( + state + ); if ( ! multiSelectedBlockClientIds.length ) { return EMPTY_ARRAY; } - return multiSelectedBlockClientIds.map( ( clientId ) => getBlock( state, clientId ) ); + return multiSelectedBlockClientIds.map( ( clientId ) => + getBlock( state, clientId ) + ); }, ( state ) => [ ...getSelectedBlockClientIds.getDependants( state ), @@ -759,7 +780,7 @@ export const isAncestorMultiSelected = createSelector( state.blocks.order, state.selectionStart.clientId, state.selectionEnd.clientId, - ], + ] ); /** * Returns the client ID of the block which begins the multi-selection set, or @@ -864,11 +885,10 @@ export function isBlockSelected( state, clientId ) { export function hasSelectedInnerBlock( state, clientId, deep = false ) { return some( getBlockOrder( state, clientId ), - ( innerClientId ) => ( + ( innerClientId ) => isBlockSelected( state, innerClientId ) || isBlockMultiSelected( state, innerClientId ) || ( deep && hasSelectedInnerBlock( state, innerClientId, deep ) ) - ) ); } @@ -1070,7 +1090,11 @@ export function getTemplateLock( state, rootClientId ) { * * @return {boolean} Whether the given block type is allowed to be inserted. */ -const canInsertBlockTypeUnmemoized = ( state, blockName, rootClientId = null ) => { +const canInsertBlockTypeUnmemoized = ( + state, + blockName, + rootClientId = null +) => { const checkAllowList = ( list, item, defaultResult = null ) => { if ( isBoolean( list ) ) { return list; @@ -1094,7 +1118,11 @@ const canInsertBlockTypeUnmemoized = ( state, blockName, rootClientId = null ) = const { allowedBlockTypes } = getSettings( state ); - const isBlockAllowedInEditor = checkAllowList( allowedBlockTypes, blockName, true ); + const isBlockAllowedInEditor = checkAllowList( + allowedBlockTypes, + blockName, + true + ); if ( ! isBlockAllowedInEditor ) { return false; } @@ -1105,12 +1133,20 @@ const canInsertBlockTypeUnmemoized = ( state, blockName, rootClientId = null ) = } const parentBlockListSettings = getBlockListSettings( state, rootClientId ); - const parentAllowedBlocks = get( parentBlockListSettings, [ 'allowedBlocks' ] ); - const hasParentAllowedBlock = checkAllowList( parentAllowedBlocks, blockName ); + const parentAllowedBlocks = get( parentBlockListSettings, [ + 'allowedBlocks', + ] ); + const hasParentAllowedBlock = checkAllowList( + parentAllowedBlocks, + blockName + ); const blockAllowedParentBlocks = blockType.parent; const parentName = getBlockName( state, rootClientId ); - const hasBlockAllowedParent = checkAllowList( blockAllowedParentBlocks, parentName ); + const hasBlockAllowedParent = checkAllowList( + blockAllowedParentBlocks, + parentName + ); if ( hasParentAllowedBlock !== null && hasBlockAllowedParent !== null ) { return hasParentAllowedBlock || hasBlockAllowedParent; @@ -1139,7 +1175,7 @@ export const canInsertBlockType = createSelector( state.blocks.byClientId[ rootClientId ], state.settings.allowedBlockTypes, state.settings.templateLock, - ], + ] ); /** @@ -1250,12 +1286,20 @@ export const getInserterItems = createSelector( let isDisabled = false; if ( ! hasBlockSupport( blockType.name, 'multiple', true ) ) { - isDisabled = some( getBlocksByClientId( state, getClientIdsWithDescendants( state ) ), { name: blockType.name } ); + isDisabled = some( + getBlocksByClientId( + state, + getClientIdsWithDescendants( state ) + ), + { + name: blockType.name, + } + ); } const isContextual = isArray( blockType.parent ); const { time, count = 0 } = getInsertUsage( state, id ) || {}; - const inserterPatterns = blockType.patterns.filter( + const inserterVariations = blockType.variations.filter( ( { scope } ) => ! scope || scope.includes( 'inserter' ) ); @@ -1268,10 +1312,14 @@ export const getInserterItems = createSelector( icon: blockType.icon, category: blockType.category, keywords: blockType.keywords, - patterns: inserterPatterns, + variations: inserterVariations, example: blockType.example, isDisabled, - utility: calculateUtility( blockType.category, count, isContextual ), + utility: calculateUtility( + blockType.category, + count, + isContextual + ), frecency: calculateFrecency( time, count ), }; }; @@ -1279,10 +1327,15 @@ export const getInserterItems = createSelector( const buildReusableBlockInserterItem = ( reusableBlock ) => { const id = `core/block/${ reusableBlock.id }`; - const referencedBlocks = __experimentalGetParsedReusableBlock( state, reusableBlock.id ); + const referencedBlocks = __experimentalGetParsedReusableBlock( + state, + reusableBlock.id + ); let referencedBlockType; if ( referencedBlocks.length === 1 ) { - referencedBlockType = getBlockType( referencedBlocks[ 0 ].name ); + referencedBlockType = getBlockType( + referencedBlocks[ 0 ].name + ); } const { time, count = 0 } = getInsertUsage( state, id ) || {}; @@ -1294,7 +1347,9 @@ export const getInserterItems = createSelector( name: 'core/block', initialAttributes: { ref: reusableBlock.id }, title: reusableBlock.title, - icon: referencedBlockType ? referencedBlockType.icon : templateIcon, + icon: referencedBlockType + ? referencedBlockType.icon + : templateIcon, category: 'reusable', keywords: [], isDisabled: false, @@ -1304,12 +1359,18 @@ export const getInserterItems = createSelector( }; const blockTypeInserterItems = getBlockTypes() - .filter( ( blockType ) => canIncludeBlockTypeInInserter( state, blockType, rootClientId ) ) + .filter( ( blockType ) => + canIncludeBlockTypeInInserter( state, blockType, rootClientId ) + ) .map( buildBlockTypeInserterItem ); - const reusableBlockInserterItems = canInsertBlockTypeUnmemoized( state, 'core/block', rootClientId ) ? - getReusableBlocks( state ).map( buildReusableBlockInserterItem ) : - []; + const reusableBlockInserterItems = canInsertBlockTypeUnmemoized( + state, + 'core/block', + rootClientId + ) + ? getReusableBlocks( state ).map( buildReusableBlockInserterItem ) + : []; return orderBy( [ ...blockTypeInserterItems, ...reusableBlockInserterItems ], @@ -1326,7 +1387,7 @@ export const getInserterItems = createSelector( state.settings.templateLock, getReusableBlocks( state ), getBlockTypes(), - ], + ] ); /** @@ -1339,17 +1400,15 @@ export const getInserterItems = createSelector( */ export const hasInserterItems = createSelector( ( state, rootClientId = null ) => { - const hasBlockType = some( - getBlockTypes(), - ( blockType ) => canIncludeBlockTypeInInserter( state, blockType, rootClientId ) + const hasBlockType = some( getBlockTypes(), ( blockType ) => + canIncludeBlockTypeInInserter( state, blockType, rootClientId ) ); if ( hasBlockType ) { return true; } - const hasReusableBlock = ( + const hasReusableBlock = canInsertBlockTypeUnmemoized( state, 'core/block', rootClientId ) && - getReusableBlocks( state ).length > 0 - ); + getReusableBlocks( state ).length > 0; return hasReusableBlock; }, @@ -1360,7 +1419,7 @@ export const hasInserterItems = createSelector( state.settings.templateLock, getReusableBlocks( state ), getBlockTypes(), - ], + ] ); /** @@ -1377,9 +1436,8 @@ export const __experimentalGetAllowedBlocks = createSelector( return; } - return filter( - getBlockTypes(), - ( blockType ) => canIncludeBlockTypeInInserter( state, blockType, rootClientId ) + return filter( getBlockTypes(), ( blockType ) => + canIncludeBlockTypeInInserter( state, blockType, rootClientId ) ); }, ( state, rootClientId ) => [ @@ -1437,11 +1495,11 @@ export function isLastBlockChangePersistent( state ) { */ export const __experimentalGetBlockListSettingsForBlocks = createSelector( ( state, clientIds ) => { - return filter( state.blockListSettings, ( value, key ) => clientIds.includes( key ) ); + return filter( state.blockListSettings, ( value, key ) => + clientIds.includes( key ) + ); }, - ( state ) => [ - state.blockListSettings, - ], + ( state ) => [ state.blockListSettings ] ); /** @@ -1464,9 +1522,7 @@ export const __experimentalGetParsedReusableBlock = createSelector( return parse( reusableBlock.content ); }, - ( state ) => [ - getReusableBlocks( state ), - ], + ( state ) => [ getReusableBlocks( state ) ] ); /** @@ -1508,7 +1564,11 @@ export function __experimentalGetLastBlockAttributeChanges( state ) { * @return {Array} Reusable blocks */ function getReusableBlocks( state ) { - return get( state, [ 'settings', '__experimentalReusableBlocks' ], EMPTY_ARRAY ); + return get( + state, + [ 'settings', '__experimentalReusableBlocks' ], + EMPTY_ARRAY + ); } /** diff --git a/packages/block-editor/src/store/test/actions.js b/packages/block-editor/src/store/test/actions.js index d6e99912904dfd..3cad986cd639be 100644 --- a/packages/block-editor/src/store/test/actions.js +++ b/packages/block-editor/src/store/test/actions.js @@ -129,45 +129,35 @@ describe( 'actions', () => { // Skip getSettings select. replaceBlockGenerator.next(); - expect( - replaceBlockGenerator.next().value, - ).toEqual( { + expect( replaceBlockGenerator.next().value ).toEqual( { args: [ 'chicken' ], selectorName: 'getBlockRootClientId', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - replaceBlockGenerator.next().value, - ).toEqual( { + expect( replaceBlockGenerator.next().value ).toEqual( { args: [ 'core/test-block', undefined ], selectorName: 'canInsertBlockType', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - replaceBlockGenerator.next( true ).value, - ).toEqual( { + expect( replaceBlockGenerator.next( true ).value ).toEqual( { type: 'REPLACE_BLOCKS', clientIds: [ 'chicken' ], blocks: [ block ], time: expect.any( Number ), } ); - expect( - replaceBlockGenerator.next().value, - ).toEqual( { + expect( replaceBlockGenerator.next().value ).toEqual( { args: [], selectorName: 'getBlockCount', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - replaceBlockGenerator.next( 1 ), - ).toEqual( { + expect( replaceBlockGenerator.next( 1 ) ).toEqual( { value: undefined, done: true, } ); @@ -176,122 +166,112 @@ describe( 'actions', () => { describe( 'replaceBlocks', () => { it( 'should not yield the REPLACE_BLOCKS action if the replacement is not possible', () => { - const blocks = [ { - clientId: 'ribs', - name: 'core/test-ribs', - }, { - clientId: 'chicken', - name: 'core/test-chicken', - } ]; + const blocks = [ + { + clientId: 'ribs', + name: 'core/test-ribs', + }, + { + clientId: 'chicken', + name: 'core/test-chicken', + }, + ]; - const replaceBlockGenerator = replaceBlocks( [ 'chicken' ], blocks ); + const replaceBlockGenerator = replaceBlocks( + [ 'chicken' ], + blocks + ); - expect( - replaceBlockGenerator.next().value, - ).toEqual( { + expect( replaceBlockGenerator.next().value ).toEqual( { args: [], selectorName: 'getSettings', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - replaceBlockGenerator.next().value, - ).toEqual( { + expect( replaceBlockGenerator.next().value ).toEqual( { args: [ 'chicken' ], selectorName: 'getBlockRootClientId', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - replaceBlockGenerator.next().value, - ).toEqual( { + expect( replaceBlockGenerator.next().value ).toEqual( { args: [ 'core/test-ribs', undefined ], selectorName: 'canInsertBlockType', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - replaceBlockGenerator.next( true ).value, - ).toEqual( { + expect( replaceBlockGenerator.next( true ).value ).toEqual( { args: [ 'core/test-chicken', undefined ], selectorName: 'canInsertBlockType', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - replaceBlockGenerator.next( false ), - ).toEqual( { + expect( replaceBlockGenerator.next( false ) ).toEqual( { value: undefined, done: true, } ); } ); it( 'should yield the REPLACE_BLOCKS action if the all the replacement blocks can be inserted in the parent block', () => { - const blocks = [ { - clientId: 'ribs', - name: 'core/test-ribs', - }, { - clientId: 'chicken', - name: 'core/test-chicken', - } ]; + const blocks = [ + { + clientId: 'ribs', + name: 'core/test-ribs', + }, + { + clientId: 'chicken', + name: 'core/test-chicken', + }, + ]; - const replaceBlockGenerator = replaceBlocks( [ 'chicken' ], blocks ); + const replaceBlockGenerator = replaceBlocks( + [ 'chicken' ], + blocks + ); // Skip getSettings select. replaceBlockGenerator.next(); - expect( - replaceBlockGenerator.next().value, - ).toEqual( { + expect( replaceBlockGenerator.next().value ).toEqual( { args: [ 'chicken' ], selectorName: 'getBlockRootClientId', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - replaceBlockGenerator.next().value, - ).toEqual( { + expect( replaceBlockGenerator.next().value ).toEqual( { args: [ 'core/test-ribs', undefined ], selectorName: 'canInsertBlockType', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - replaceBlockGenerator.next( true ).value, - ).toEqual( { + expect( replaceBlockGenerator.next( true ).value ).toEqual( { args: [ 'core/test-chicken', undefined ], selectorName: 'canInsertBlockType', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - replaceBlockGenerator.next( true ).value, - ).toEqual( { + expect( replaceBlockGenerator.next( true ).value ).toEqual( { type: 'REPLACE_BLOCKS', clientIds: [ 'chicken' ], blocks, time: expect.any( Number ), } ); - expect( - replaceBlockGenerator.next().value, - ).toEqual( { + expect( replaceBlockGenerator.next().value ).toEqual( { args: [], selectorName: 'getBlockCount', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - replaceBlockGenerator.next( 1 ), - ).toEqual( { + expect( replaceBlockGenerator.next( 1 ) ).toEqual( { value: undefined, done: true, } ); @@ -306,23 +286,24 @@ describe( 'actions', () => { }; const index = 5; - const insertBlockGenerator = insertBlock( block, index, 'testclientid', true ); + const insertBlockGenerator = insertBlock( + block, + index, + 'testclientid', + true + ); // Skip getSettings select. insertBlockGenerator.next(); - expect( - insertBlockGenerator.next().value - ).toEqual( { + expect( insertBlockGenerator.next().value ).toEqual( { args: [ 'core/test-block', 'testclientid' ], selectorName: 'canInsertBlockType', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - insertBlockGenerator.next( true ), - ).toEqual( { + expect( insertBlockGenerator.next( true ) ).toEqual( { done: true, value: { type: 'INSERT_BLOCKS', @@ -350,17 +331,16 @@ describe( 'actions', () => { clientId: 'chicken-ribs', name: 'core/test-chicken-ribs', }; - const blocks = [ - ribsBlock, - chickenBlock, - chickenRibsBlock, - ]; + const blocks = [ ribsBlock, chickenBlock, chickenRibsBlock ]; - const insertBlocksGenerator = insertBlocks( blocks, 5, 'testrootid', false ); + const insertBlocksGenerator = insertBlocks( + blocks, + 5, + 'testrootid', + false + ); - expect( - insertBlocksGenerator.next().value, - ).toEqual( { + expect( insertBlocksGenerator.next().value ).toEqual( { args: [], selectorName: 'getSettings', storeName: 'core/block-editor', @@ -375,7 +355,7 @@ describe( 'actions', () => { 'core/test-chicken-ribs': 'colorful', }, }, - } ).value, + } ).value ).toEqual( { args: [ 'core/test-ribs', 'testrootid' ], selectorName: 'canInsertBlockType', @@ -383,34 +363,34 @@ describe( 'actions', () => { type: 'SELECT', } ); - expect( - insertBlocksGenerator.next( true ).value - ).toEqual( { + expect( insertBlocksGenerator.next( true ).value ).toEqual( { args: [ 'core/test-chicken', 'testrootid' ], selectorName: 'canInsertBlockType', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - insertBlocksGenerator.next( true ).value, - ).toEqual( { + expect( insertBlocksGenerator.next( true ).value ).toEqual( { args: [ 'core/test-chicken-ribs', 'testrootid' ], selectorName: 'canInsertBlockType', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - insertBlocksGenerator.next( true ), - ).toEqual( { + expect( insertBlocksGenerator.next( true ) ).toEqual( { done: true, value: { type: 'INSERT_BLOCKS', blocks: [ - { ...ribsBlock, attributes: { className: 'is-style-squared' } }, + { + ...ribsBlock, + attributes: { className: 'is-style-squared' }, + }, chickenBlock, - { ...chickenRibsBlock, attributes: { className: 'is-style-colorful' } }, + { + ...chickenRibsBlock, + attributes: { className: 'is-style-colorful' }, + }, ], index: 5, rootClientId: 'testrootid', @@ -428,15 +408,16 @@ describe( 'actions', () => { className: 'is-style-colorful', }, }; - const blocks = [ - ribsWithStyleBlock, - ]; + const blocks = [ ribsWithStyleBlock ]; - const insertBlocksGenerator = insertBlocks( blocks, 5, 'testrootid', false ); + const insertBlocksGenerator = insertBlocks( + blocks, + 5, + 'testrootid', + false + ); - expect( - insertBlocksGenerator.next().value, - ).toEqual( { + expect( insertBlocksGenerator.next().value ).toEqual( { args: [], selectorName: 'getSettings', storeName: 'core/block-editor', @@ -450,7 +431,7 @@ describe( 'actions', () => { 'core/test-ribs': 'squared', }, }, - } ).value, + } ).value ).toEqual( { args: [ 'core/test-ribs', 'testrootid' ], selectorName: 'canInsertBlockType', @@ -458,14 +439,15 @@ describe( 'actions', () => { type: 'SELECT', } ); - expect( - insertBlocksGenerator.next( true ), - ).toEqual( { + expect( insertBlocksGenerator.next( true ) ).toEqual( { done: true, value: { type: 'INSERT_BLOCKS', blocks: [ - { ...ribsWithStyleBlock, attributes: { className: 'is-style-colorful' } }, + { + ...ribsWithStyleBlock, + attributes: { className: 'is-style-colorful' }, + }, ], index: 5, rootClientId: 'testrootid', @@ -487,47 +469,40 @@ describe( 'actions', () => { clientId: 'chicken-ribs', name: 'core/test-chicken-ribs', }; - const blocks = [ - ribsBlock, - chickenBlock, - chickenRibsBlock, - ]; + const blocks = [ ribsBlock, chickenBlock, chickenRibsBlock ]; - const insertBlocksGenerator = insertBlocks( blocks, 5, 'testrootid', false ); + const insertBlocksGenerator = insertBlocks( + blocks, + 5, + 'testrootid', + false + ); // Skip getSettings select. insertBlocksGenerator.next(); - expect( - insertBlocksGenerator.next().value - ).toEqual( { + expect( insertBlocksGenerator.next().value ).toEqual( { args: [ 'core/test-ribs', 'testrootid' ], selectorName: 'canInsertBlockType', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - insertBlocksGenerator.next( true ).value - ).toEqual( { + expect( insertBlocksGenerator.next( true ).value ).toEqual( { args: [ 'core/test-chicken', 'testrootid' ], selectorName: 'canInsertBlockType', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - insertBlocksGenerator.next( false ).value, - ).toEqual( { + expect( insertBlocksGenerator.next( false ).value ).toEqual( { args: [ 'core/test-chicken-ribs', 'testrootid' ], selectorName: 'canInsertBlockType', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - insertBlocksGenerator.next( true ), - ).toEqual( { + expect( insertBlocksGenerator.next( true ) ).toEqual( { done: true, value: { type: 'INSERT_BLOCKS', @@ -549,37 +524,33 @@ describe( 'actions', () => { clientId: 'chicken', name: 'core/test-chicken', }; - const blocks = [ - ribsBlock, - chickenBlock, - ]; + const blocks = [ ribsBlock, chickenBlock ]; - const insertBlocksGenerator = insertBlocks( blocks, 5, 'testrootid', false ); + const insertBlocksGenerator = insertBlocks( + blocks, + 5, + 'testrootid', + false + ); // Skip getSettings select. insertBlocksGenerator.next(); - expect( - insertBlocksGenerator.next().value - ).toEqual( { + expect( insertBlocksGenerator.next().value ).toEqual( { args: [ 'core/test-ribs', 'testrootid' ], selectorName: 'canInsertBlockType', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - insertBlocksGenerator.next( false ).value, - ).toEqual( { + expect( insertBlocksGenerator.next( false ).value ).toEqual( { args: [ 'core/test-chicken', 'testrootid' ], selectorName: 'canInsertBlockType', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - insertBlocksGenerator.next( false ), - ).toEqual( { + expect( insertBlocksGenerator.next( false ) ).toEqual( { done: true, value: undefined, } ); @@ -606,7 +577,9 @@ describe( 'actions', () => { it( 'should return MERGE_BLOCKS action', () => { const firstBlockClientId = 'blockA'; const secondBlockClientId = 'blockB'; - expect( mergeBlocks( firstBlockClientId, secondBlockClientId ) ).toEqual( { + expect( + mergeBlocks( firstBlockClientId, secondBlockClientId ) + ).toEqual( { type: 'MERGE_BLOCKS', blocks: [ firstBlockClientId, secondBlockClientId ], } ); @@ -628,10 +601,7 @@ describe( 'actions', () => { type: 'REMOVE_BLOCKS', clientIds, }, - select( - 'core/block-editor', - 'getBlockCount', - ), + select( 'core/block-editor', 'getBlockCount' ), ] ); } ); } ); @@ -645,9 +615,7 @@ describe( 'actions', () => { 5 ); - expect( - moveBlockToPositionGenerator.next().value - ).toEqual( { + expect( moveBlockToPositionGenerator.next().value ).toEqual( { args: [ 'ribs' ], selectorName: 'getTemplateLock', storeName: 'core/block-editor', @@ -664,9 +632,7 @@ describe( 'actions', () => { index: 5, } ); - expect( - moveBlockToPositionGenerator.next().done - ).toBe( true ); + expect( moveBlockToPositionGenerator.next().done ).toBe( true ); } ); it( 'should not yield MOVE_BLOCK_TO_POSITION action if locking is all', () => { @@ -677,18 +643,14 @@ describe( 'actions', () => { 5 ); - expect( - moveBlockToPositionGenerator.next().value - ).toEqual( { + expect( moveBlockToPositionGenerator.next().value ).toEqual( { args: [ 'ribs' ], selectorName: 'getTemplateLock', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - moveBlockToPositionGenerator.next( 'all' ) - ).toEqual( { + expect( moveBlockToPositionGenerator.next( 'all' ) ).toEqual( { done: true, value: undefined, } ); @@ -702,42 +664,35 @@ describe( 'actions', () => { 5 ); - expect( - moveBlockToPositionGenerator.next().value - ).toEqual( { + expect( moveBlockToPositionGenerator.next().value ).toEqual( { args: [ 'ribs' ], selectorName: 'getTemplateLock', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - moveBlockToPositionGenerator.next( 'insert' ) - ).toEqual( { + expect( moveBlockToPositionGenerator.next( 'insert' ) ).toEqual( { done: true, value: undefined, } ); } ); it( 'should yield MOVE_BLOCK_TO_POSITION action if there is not locking in the original root block and block can be inserted in the destination', () => { - const moveBlockToPositionGenerator = moveBlockToPosition( 'chicken', + const moveBlockToPositionGenerator = moveBlockToPosition( + 'chicken', 'ribs', 'chicken-ribs', 5 ); - expect( - moveBlockToPositionGenerator.next().value - ).toEqual( { + expect( moveBlockToPositionGenerator.next().value ).toEqual( { args: [ 'ribs' ], selectorName: 'getTemplateLock', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - moveBlockToPositionGenerator.next().value - ).toEqual( { + expect( moveBlockToPositionGenerator.next().value ).toEqual( { args: [ 'chicken' ], selectorName: 'getBlockName', storeName: 'core/block-editor', @@ -745,7 +700,8 @@ describe( 'actions', () => { } ); expect( - moveBlockToPositionGenerator.next( 'myblock/chicken-block' ).value + moveBlockToPositionGenerator.next( 'myblock/chicken-block' ) + .value ).toEqual( { args: [ 'myblock/chicken-block', 'chicken-ribs' ], selectorName: 'canInsertBlockType', @@ -753,9 +709,7 @@ describe( 'actions', () => { type: 'SELECT', } ); - expect( - moveBlockToPositionGenerator.next( true ).value - ).toEqual( { + expect( moveBlockToPositionGenerator.next( true ).value ).toEqual( { type: 'MOVE_BLOCK_TO_POSITION', fromRootClientId: 'ribs', toRootClientId: 'chicken-ribs', @@ -763,33 +717,28 @@ describe( 'actions', () => { index: 5, } ); - expect( - moveBlockToPositionGenerator.next() - ).toEqual( { + expect( moveBlockToPositionGenerator.next() ).toEqual( { done: true, value: undefined, } ); } ); it( 'should not yield MOVE_BLOCK_TO_POSITION action if there is not locking in the original root block and block can be inserted in the destination', () => { - const moveBlockToPositionGenerator = moveBlockToPosition( 'chicken', + const moveBlockToPositionGenerator = moveBlockToPosition( + 'chicken', 'ribs', 'chicken-ribs', 5 ); - expect( - moveBlockToPositionGenerator.next().value - ).toEqual( { + expect( moveBlockToPositionGenerator.next().value ).toEqual( { args: [ 'ribs' ], selectorName: 'getTemplateLock', storeName: 'core/block-editor', type: 'SELECT', } ); - expect( - moveBlockToPositionGenerator.next().value - ).toEqual( { + expect( moveBlockToPositionGenerator.next().value ).toEqual( { args: [ 'chicken' ], selectorName: 'getBlockName', storeName: 'core/block-editor', @@ -797,7 +746,8 @@ describe( 'actions', () => { } ); expect( - moveBlockToPositionGenerator.next( 'myblock/chicken-block' ).value + moveBlockToPositionGenerator.next( 'myblock/chicken-block' ) + .value ).toEqual( { args: [ 'myblock/chicken-block', 'chicken-ribs' ], selectorName: 'canInsertBlockType', @@ -805,9 +755,7 @@ describe( 'actions', () => { type: 'SELECT', } ); - expect( - moveBlockToPositionGenerator.next( false ) - ).toEqual( { + expect( moveBlockToPositionGenerator.next( false ) ).toEqual( { done: true, value: undefined, } ); @@ -828,10 +776,7 @@ describe( 'actions', () => { type: 'REMOVE_BLOCKS', clientIds: [ clientId ], }, - select( - 'core/block-editor', - 'getBlockCount', - ), + select( 'core/block-editor', 'getBlockCount' ), ] ); } ); @@ -847,10 +792,7 @@ describe( 'actions', () => { type: 'REMOVE_BLOCKS', clientIds: [ clientId ], }, - select( - 'core/block-editor', - 'getBlockCount', - ), + select( 'core/block-editor', 'getBlockCount' ), ] ); } ); } ); @@ -930,7 +872,9 @@ describe( 'actions', () => { } ); it( 'should return the UPDATE_BLOCK_LIST_SETTINGS action with the passed settings', () => { - expect( updateBlockListSettings( 'chicken', { chicken: 'ribs' } ) ).toEqual( { + expect( + updateBlockListSettings( 'chicken', { chicken: 'ribs' } ) + ).toEqual( { type: 'UPDATE_BLOCK_LIST_SETTINGS', clientId: 'chicken', settings: { chicken: 'ribs' }, diff --git a/packages/block-editor/src/store/test/array.js b/packages/block-editor/src/store/test/array.js index 5edcad46ad80f5..f88b6f858725d6 100644 --- a/packages/block-editor/src/store/test/array.js +++ b/packages/block-editor/src/store/test/array.js @@ -7,39 +7,46 @@ describe( 'array', () => { describe( 'insertAt', () => { it( 'should insert a unique item at a given position', () => { const array = [ 'a', 'b', 'd' ]; - expect( insertAt( array, 'c', 2 ) ).toEqual( - [ 'a', 'b', 'c', 'd' ] - ); + expect( insertAt( array, 'c', 2 ) ).toEqual( [ + 'a', + 'b', + 'c', + 'd', + ] ); } ); it( 'should insert multiple items at a given position', () => { const array = [ 'a', 'b', 'e' ]; - expect( insertAt( array, [ 'c', 'd' ], 2 ) ).toEqual( - [ 'a', 'b', 'c', 'd', 'e' ] - ); + expect( insertAt( array, [ 'c', 'd' ], 2 ) ).toEqual( [ + 'a', + 'b', + 'c', + 'd', + 'e', + ] ); } ); } ); describe( 'moveTo', () => { it( 'should move an item to a given position', () => { const array = [ 'a', 'b', 'd', 'c' ]; - expect( moveTo( array, 3, 2 ) ).toEqual( - [ 'a', 'b', 'c', 'd' ] - ); + expect( moveTo( array, 3, 2 ) ).toEqual( [ 'a', 'b', 'c', 'd' ] ); } ); it( 'should move an item upwards to a given position', () => { const array = [ 'b', 'a', 'c', 'd' ]; - expect( moveTo( array, 0, 1 ) ).toEqual( - [ 'a', 'b', 'c', 'd' ] - ); + expect( moveTo( array, 0, 1 ) ).toEqual( [ 'a', 'b', 'c', 'd' ] ); } ); it( 'should move multiple items to a given position', () => { const array = [ 'a', 'c', 'd', 'b', 'e' ]; - expect( moveTo( array, 1, 2, 2 ) ).toEqual( - [ 'a', 'b', 'c', 'd', 'e' ] - ); + expect( moveTo( array, 1, 2, 2 ) ).toEqual( [ + 'a', + 'b', + 'c', + 'd', + 'e', + ] ); } ); } ); } ); diff --git a/packages/block-editor/src/store/test/effects.js b/packages/block-editor/src/store/test/effects.js index fbb93ddfd7f13f..9b8702bb3342c5 100644 --- a/packages/block-editor/src/store/test/effects.js +++ b/packages/block-editor/src/store/test/effects.js @@ -70,7 +70,10 @@ describe( 'effects', () => { const dispatch = jest.fn(); const getState = () => ( {} ); - handler( mergeBlocks( blockA.clientId, blockB.clientId ), { dispatch, getState } ); + handler( mergeBlocks( blockA.clientId, blockB.clientId ), { + dispatch, + getState, + } ); expect( dispatch ).toHaveBeenCalledTimes( 1 ); expect( dispatch ).toHaveBeenCalledWith( selectBlock( 'chicken' ) ); @@ -83,7 +86,10 @@ describe( 'effects', () => { }, merge( attributes, attributesToMerge ) { return { - content: attributes.content + ' ' + attributesToMerge.content, + content: + attributes.content + + ' ' + + attributesToMerge.content, }; }, save: noop, @@ -113,26 +119,34 @@ describe( 'effects', () => { offset: 0, }, } ); - handler( mergeBlocks( blockA.clientId, blockB.clientId ), { dispatch, getState } ); + handler( mergeBlocks( blockA.clientId, blockB.clientId ), { + dispatch, + getState, + } ); expect( dispatch ).toHaveBeenCalledTimes( 2 ); - expect( dispatch ).toHaveBeenCalledWith( selectionChange( - blockA.clientId, - 'content', - 'chicken'.length + 1, - 'chicken'.length + 1, - ) ); + expect( dispatch ).toHaveBeenCalledWith( + selectionChange( + blockA.clientId, + 'content', + 'chicken'.length + 1, + 'chicken'.length + 1 + ) + ); const lastCall = dispatch.mock.calls[ 1 ]; expect( lastCall ).toHaveLength( 1 ); const [ lastCallArgument ] = lastCall; - const expectedGenerator = replaceBlocks( [ 'chicken', 'ribs' ], [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: { content: 'chicken ribs' }, - } ] ); - expect( - Array.from( lastCallArgument ) - ).toEqual( + const expectedGenerator = replaceBlocks( + [ 'chicken', 'ribs' ], + [ + { + clientId: 'chicken', + name: 'core/test-block', + attributes: { content: 'chicken ribs' }, + }, + ] + ); + expect( Array.from( lastCallArgument ) ).toEqual( Array.from( expectedGenerator ) ); } ); @@ -144,7 +158,10 @@ describe( 'effects', () => { }, merge( attributes, attributesToMerge ) { return { - content: attributes.content + ' ' + attributesToMerge.content, + content: + attributes.content + + ' ' + + attributesToMerge.content, }; }, save: noop, @@ -175,7 +192,10 @@ describe( 'effects', () => { offset: 0, }, } ); - handler( mergeBlocks( blockA.clientId, blockB.clientId ), { dispatch, getState } ); + handler( mergeBlocks( blockA.clientId, blockB.clientId ), { + dispatch, + getState, + } ); expect( dispatch ).not.toHaveBeenCalled(); } ); @@ -189,7 +209,10 @@ describe( 'effects', () => { }, merge( attributes, attributesToMerge ) { return { - content: attributes.content + ' ' + attributesToMerge.content, + content: + attributes.content + + ' ' + + attributesToMerge.content, }; }, save: noop, @@ -203,15 +226,17 @@ describe( 'effects', () => { }, }, transforms: { - to: [ { - type: 'block', - blocks: [ 'core/test-block' ], - transform: ( { content2 } ) => { - return createBlock( 'core/test-block', { - content: content2, - } ); + to: [ + { + type: 'block', + blocks: [ 'core/test-block' ], + transform: ( { content2 } ) => { + return createBlock( 'core/test-block', { + content: content2, + } ); + }, }, - } ], + ], }, save: noop, category: 'common', @@ -240,26 +265,34 @@ describe( 'effects', () => { offset: 0, }, } ); - handler( mergeBlocks( blockA.clientId, blockB.clientId ), { dispatch, getState } ); + handler( mergeBlocks( blockA.clientId, blockB.clientId ), { + dispatch, + getState, + } ); expect( dispatch ).toHaveBeenCalledTimes( 2 ); - expect( dispatch ).toHaveBeenCalledWith( selectionChange( - blockA.clientId, - 'content', - 'chicken'.length + 1, - 'chicken'.length + 1, - ) ); - const expectedGenerator = replaceBlocks( [ 'chicken', 'ribs' ], [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: { content: 'chicken ribs' }, - } ] ); + expect( dispatch ).toHaveBeenCalledWith( + selectionChange( + blockA.clientId, + 'content', + 'chicken'.length + 1, + 'chicken'.length + 1 + ) + ); + const expectedGenerator = replaceBlocks( + [ 'chicken', 'ribs' ], + [ + { + clientId: 'chicken', + name: 'core/test-block', + attributes: { content: 'chicken ribs' }, + }, + ] + ); const lastCall = dispatch.mock.calls[ 1 ]; expect( lastCall ).toHaveLength( 1 ); const [ lastCallArgument ] = lastCall; - expect( - Array.from( lastCallArgument ) - ).toEqual( + expect( Array.from( lastCallArgument ) ).toEqual( Array.from( expectedGenerator ) ); } ); @@ -285,53 +318,57 @@ describe( 'effects', () => { } ); it( 'should return undefined if no template assigned', () => { - const result = validateBlocksToTemplate( resetBlocks( [ - createBlock( 'core/test-block' ), - ] ), store ); + const result = validateBlocksToTemplate( + resetBlocks( [ createBlock( 'core/test-block' ) ] ), + store + ); expect( result ).toBe( undefined ); } ); it( 'should return undefined if invalid but unlocked', () => { - store.dispatch( updateSettings( { - template: [ - [ 'core/foo', {} ], - ], - } ) ); + store.dispatch( + updateSettings( { + template: [ [ 'core/foo', {} ] ], + } ) + ); - const result = validateBlocksToTemplate( resetBlocks( [ - createBlock( 'core/test-block' ), - ] ), store ); + const result = validateBlocksToTemplate( + resetBlocks( [ createBlock( 'core/test-block' ) ] ), + store + ); expect( result ).toBe( undefined ); } ); it( 'should return undefined if locked and valid', () => { - store.dispatch( updateSettings( { - template: [ - [ 'core/test-block' ], - ], - templateLock: 'all', - } ) ); + store.dispatch( + updateSettings( { + template: [ [ 'core/test-block' ] ], + templateLock: 'all', + } ) + ); - const result = validateBlocksToTemplate( resetBlocks( [ - createBlock( 'core/test-block' ), - ] ), store ); + const result = validateBlocksToTemplate( + resetBlocks( [ createBlock( 'core/test-block' ) ] ), + store + ); expect( result ).toBe( undefined ); } ); it( 'should return validity set action if invalid on default state', () => { - store.dispatch( updateSettings( { - template: [ - [ 'core/foo' ], - ], - templateLock: 'all', - } ) ); + store.dispatch( + updateSettings( { + template: [ [ 'core/foo' ] ], + templateLock: 'all', + } ) + ); - const result = validateBlocksToTemplate( resetBlocks( [ - createBlock( 'core/test-block' ), - ] ), store ); + const result = validateBlocksToTemplate( + resetBlocks( [ createBlock( 'core/test-block' ) ] ), + store + ); expect( result ).toEqual( setTemplateValidity( false ) ); } ); diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js index 606b54510e9bdc..8610a1b8a3f31c 100644 --- a/packages/block-editor/src/store/test/reducer.js +++ b/packages/block-editor/src/store/test/reducer.js @@ -62,7 +62,9 @@ describe( 'state', () => { clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', }; - expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( false ); + expect( + isUpdatingSameBlockAttribute( action, previousAction ) + ).toBe( false ); } ); it( 'should return false if last action was not updating block attributes', () => { @@ -78,7 +80,9 @@ describe( 'state', () => { clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', }; - expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( false ); + expect( + isUpdatingSameBlockAttribute( action, previousAction ) + ).toBe( false ); } ); it( 'should return false if not updating the same block', () => { @@ -97,7 +101,9 @@ describe( 'state', () => { }, }; - expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( false ); + expect( + isUpdatingSameBlockAttribute( action, previousAction ) + ).toBe( false ); } ); it( 'should return false if not updating the same block attributes', () => { @@ -116,7 +122,9 @@ describe( 'state', () => { }, }; - expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( false ); + expect( + isUpdatingSameBlockAttribute( action, previousAction ) + ).toBe( false ); } ); it( 'should return false if no previous action', () => { @@ -129,7 +137,9 @@ describe( 'state', () => { }; const previousAction = undefined; - expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( false ); + expect( + isUpdatingSameBlockAttribute( action, previousAction ) + ).toBe( false ); } ); it( 'should return true if updating the same block attributes', () => { @@ -148,7 +158,9 @@ describe( 'state', () => { }, }; - expect( isUpdatingSameBlockAttribute( action, previousAction ) ).toBe( true ); + expect( + isUpdatingSameBlockAttribute( action, previousAction ) + ).toBe( true ); } ); } ); @@ -280,7 +292,9 @@ describe( 'state', () => { [ newChildBlockId ]: {}, }, } ); - expect( state.cache.chicken ).not.toBe( existingState.cache.chicken ); + expect( state.cache.chicken ).not.toBe( + existingState.cache.chicken + ); } ); it( 'can insert a child block', () => { @@ -358,7 +372,9 @@ describe( 'state', () => { [ newChildBlockId ]: {}, }, } ); - expect( state.cache.chicken ).not.toBe( existingState.cache.chicken ); + expect( state.cache.chicken ).not.toBe( + existingState.cache.chicken + ); } ); it( 'can replace multiple child blocks', () => { @@ -474,7 +490,11 @@ describe( 'state', () => { }, order: { '': [ 'chicken' ], - chicken: [ newChildBlockId1, newChildBlockId2, newChildBlockId3 ], + chicken: [ + newChildBlockId1, + newChildBlockId2, + newChildBlockId3, + ], [ newChildBlockId1 ]: [], [ newChildBlockId2 ]: [], [ newChildBlockId3 ]: [], @@ -583,7 +603,9 @@ describe( 'state', () => { } ); // the cache key of the parent should be updated - expect( existingState.cache.chicken ).not.toBe( state.cache.chicken ); + expect( existingState.cache.chicken ).not.toBe( + state.cache.chicken + ); } ); } ); @@ -602,17 +624,16 @@ describe( 'state', () => { } ); it( 'should key by reset blocks clientId', () => { - [ - undefined, - blocks( undefined, {} ), - ].forEach( ( original ) => { + [ undefined, blocks( undefined, {} ) ].forEach( ( original ) => { const state = blocks( original, { type: 'RESET_BLOCKS', blocks: [ { clientId: 'bananas', innerBlocks: [] } ], } ); expect( Object.keys( state.byClientId ) ).toHaveLength( 1 ); - expect( values( state.byClientId )[ 0 ].clientId ).toBe( 'bananas' ); + expect( values( state.byClientId )[ 0 ].clientId ).toBe( + 'bananas' + ); expect( state.order ).toEqual( { '': [ 'bananas' ], bananas: [], @@ -627,10 +648,14 @@ describe( 'state', () => { const original = blocks( undefined, {} ); const state = blocks( original, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'bananas', - innerBlocks: [ { clientId: 'apples', innerBlocks: [] } ], - } ], + blocks: [ + { + clientId: 'bananas', + innerBlocks: [ + { clientId: 'apples', innerBlocks: [] }, + ], + }, + ], } ); expect( Object.keys( state.byClientId ) ).toHaveLength( 2 ); @@ -648,20 +673,24 @@ describe( 'state', () => { it( 'should insert block', () => { const original = blocks( undefined, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + ], } ); const state = blocks( original, { type: 'INSERT_BLOCKS', - blocks: [ { - clientId: 'ribs', - name: 'core/freeform', - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'ribs', + name: 'core/freeform', + innerBlocks: [], + }, + ], } ); expect( Object.keys( state.byClientId ) ).toHaveLength( 2 ); @@ -682,25 +711,31 @@ describe( 'state', () => { it( 'should replace the block', () => { const original = blocks( undefined, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + ], } ); const state = blocks( original, { type: 'REPLACE_BLOCKS', clientIds: [ 'chicken' ], - blocks: [ { - clientId: 'wings', - name: 'core/freeform', - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'wings', + name: 'core/freeform', + innerBlocks: [], + }, + ], } ); expect( Object.keys( state.byClientId ) ).toHaveLength( 1 ); - expect( values( state.byClientId )[ 0 ].name ).toBe( 'core/freeform' ); + expect( values( state.byClientId )[ 0 ].name ).toBe( + 'core/freeform' + ); expect( values( state.byClientId )[ 0 ].clientId ).toBe( 'wings' ); expect( state.order ).toEqual( { '': [ 'wings' ], @@ -716,28 +751,32 @@ describe( 'state', () => { it( 'should replace the block and remove references to its inner blocks', () => { const original = blocks( undefined, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [ - { - clientId: 'child', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, - ], - } ], + blocks: [ + { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [ + { + clientId: 'child', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + ], + }, + ], } ); const state = blocks( original, { type: 'REPLACE_BLOCKS', clientIds: [ 'chicken' ], - blocks: [ { - clientId: 'wings', - name: 'core/freeform', - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'wings', + name: 'core/freeform', + innerBlocks: [], + }, + ], } ); expect( Object.keys( state.byClientId ) ).toHaveLength( 1 ); @@ -755,14 +794,17 @@ describe( 'state', () => { it( 'should replace the nested block', () => { const nestedBlock = createBlock( 'core/test-block' ); - const wrapperBlock = createBlock( 'core/test-block', {}, [ nestedBlock ] ); + const wrapperBlock = createBlock( 'core/test-block', {}, [ + nestedBlock, + ] ); const replacementBlock = createBlock( 'core/test-block' ); const original = blocks( undefined, { type: 'RESET_BLOCKS', blocks: [ wrapperBlock ], } ); - const originalWrapperBlockCacheKey = original.cache[ wrapperBlock.clientId ]; + const originalWrapperBlockCacheKey = + original.cache[ wrapperBlock.clientId ]; const state = blocks( original, { type: 'REPLACE_BLOCKS', @@ -770,9 +812,12 @@ describe( 'state', () => { blocks: [ replacementBlock ], } ); - const newWrapperBlockCacheKey = state.cache[ wrapperBlock.clientId ]; + const newWrapperBlockCacheKey = + state.cache[ wrapperBlock.clientId ]; - expect( newWrapperBlockCacheKey ).not.toBe( originalWrapperBlockCacheKey ); + expect( newWrapperBlockCacheKey ).not.toBe( + originalWrapperBlockCacheKey + ); expect( state.order ).toEqual( { '': [ wrapperBlock.clientId ], @@ -794,27 +839,37 @@ describe( 'state', () => { it( 'should replace the block even if the new block clientId is the same', () => { const originalState = blocks( undefined, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + ], } ); const replacedState = blocks( originalState, { type: 'REPLACE_BLOCKS', clientIds: [ 'chicken' ], - blocks: [ { - clientId: 'chicken', - name: 'core/freeform', - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'chicken', + name: 'core/freeform', + innerBlocks: [], + }, + ], } ); expect( Object.keys( replacedState.byClientId ) ).toHaveLength( 1 ); - expect( values( originalState.byClientId )[ 0 ].name ).toBe( 'core/test-block' ); - expect( values( replacedState.byClientId )[ 0 ].name ).toBe( 'core/freeform' ); - expect( values( replacedState.byClientId )[ 0 ].clientId ).toBe( 'chicken' ); + expect( values( originalState.byClientId )[ 0 ].name ).toBe( + 'core/test-block' + ); + expect( values( replacedState.byClientId )[ 0 ].name ).toBe( + 'core/freeform' + ); + expect( values( replacedState.byClientId )[ 0 ].clientId ).toBe( + 'chicken' + ); expect( replacedState.order ).toEqual( { '': [ 'chicken' ], chicken: [], @@ -822,7 +877,9 @@ describe( 'state', () => { expect( replacedState.cache ).toEqual( { chicken: {}, } ); - expect( originalState.cache.chicken ).not.toBe( replacedState.cache.chicken ); + expect( originalState.cache.chicken ).not.toBe( + replacedState.cache.chicken + ); const nestedBlock = { clientId: 'chicken', @@ -830,7 +887,9 @@ describe( 'state', () => { attributes: {}, innerBlocks: [], }; - const wrapperBlock = createBlock( 'core/test-block', {}, [ nestedBlock ] ); + const wrapperBlock = createBlock( 'core/test-block', {}, [ + nestedBlock, + ] ); const replacementNestedBlock = { clientId: 'chicken', name: 'core/freeform', @@ -855,20 +914,26 @@ describe( 'state', () => { [ replacementNestedBlock.clientId ]: [], } ); - expect( originalNestedState.byClientId.chicken.name ).toBe( 'core/test-block' ); - expect( replacedNestedState.byClientId.chicken.name ).toBe( 'core/freeform' ); + expect( originalNestedState.byClientId.chicken.name ).toBe( + 'core/test-block' + ); + expect( replacedNestedState.byClientId.chicken.name ).toBe( + 'core/freeform' + ); } ); it( 'should update the block', () => { const original = blocks( undefined, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - isValid: false, - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + isValid: false, + innerBlocks: [], + }, + ], } ); const state = blocks( deepFreeze( original ), { type: 'UPDATE_BLOCK', @@ -898,15 +963,17 @@ describe( 'state', () => { it( 'should update the reusable block reference if the temporary id is swapped', () => { const original = blocks( undefined, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/block', - attributes: { - ref: 'random-clientId', + blocks: [ + { + clientId: 'chicken', + name: 'core/block', + attributes: { + ref: 'random-clientId', + }, + isValid: false, + innerBlocks: [], }, - isValid: false, - innerBlocks: [], - } ], + ], } ); const state = blocks( deepFreeze( original ), { @@ -933,17 +1000,20 @@ describe( 'state', () => { it( 'should move the block up', () => { const original = blocks( undefined, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + ], } ); const state = blocks( original, { type: 'MOVE_BLOCKS_UP', @@ -958,7 +1028,10 @@ describe( 'state', () => { it( 'should move the nested block up', () => { const movedBlock = createBlock( 'core/test-block' ); const siblingBlock = createBlock( 'core/test-block' ); - const wrapperBlock = createBlock( 'core/test-block', {}, [ siblingBlock, movedBlock ] ); + const wrapperBlock = createBlock( 'core/test-block', {}, [ + siblingBlock, + movedBlock, + ] ); const original = blocks( undefined, { type: 'RESET_BLOCKS', blocks: [ wrapperBlock ], @@ -971,48 +1044,69 @@ describe( 'state', () => { expect( state.order ).toEqual( { '': [ wrapperBlock.clientId ], - [ wrapperBlock.clientId ]: [ movedBlock.clientId, siblingBlock.clientId ], + [ wrapperBlock.clientId ]: [ + movedBlock.clientId, + siblingBlock.clientId, + ], [ movedBlock.clientId ]: [], [ siblingBlock.clientId ]: [], } ); - expect( state.cache[ wrapperBlock.clientId ] ).not.toBe( original.cache[ wrapperBlock.clientId ] ); - expect( state.cache[ movedBlock.clientId ] ).toBe( original.cache[ movedBlock.clientId ] ); - expect( state.cache[ siblingBlock.clientId ] ).toBe( original.cache[ siblingBlock.clientId ] ); + expect( state.cache[ wrapperBlock.clientId ] ).not.toBe( + original.cache[ wrapperBlock.clientId ] + ); + expect( state.cache[ movedBlock.clientId ] ).toBe( + original.cache[ movedBlock.clientId ] + ); + expect( state.cache[ siblingBlock.clientId ] ).toBe( + original.cache[ siblingBlock.clientId ] + ); } ); it( 'should move multiple blocks up', () => { const original = blocks( undefined, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'veggies', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 'veggies', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + ], } ); const state = blocks( original, { type: 'MOVE_BLOCKS_UP', clientIds: [ 'ribs', 'veggies' ], } ); - expect( state.order[ '' ] ).toEqual( [ 'ribs', 'veggies', 'chicken' ] ); + expect( state.order[ '' ] ).toEqual( [ + 'ribs', + 'veggies', + 'chicken', + ] ); } ); it( 'should move multiple nested blocks up', () => { const movedBlockA = createBlock( 'core/test-block' ); const movedBlockB = createBlock( 'core/test-block' ); const siblingBlock = createBlock( 'core/test-block' ); - const wrapperBlock = createBlock( 'core/test-block', {}, [ siblingBlock, movedBlockA, movedBlockB ] ); + const wrapperBlock = createBlock( 'core/test-block', {}, [ + siblingBlock, + movedBlockA, + movedBlockB, + ] ); const original = blocks( undefined, { type: 'RESET_BLOCKS', blocks: [ wrapperBlock ], @@ -1025,7 +1119,11 @@ describe( 'state', () => { expect( state.order ).toEqual( { '': [ wrapperBlock.clientId ], - [ wrapperBlock.clientId ]: [ movedBlockA.clientId, movedBlockB.clientId, siblingBlock.clientId ], + [ wrapperBlock.clientId ]: [ + movedBlockA.clientId, + movedBlockB.clientId, + siblingBlock.clientId, + ], [ movedBlockA.clientId ]: [], [ movedBlockB.clientId ]: [], [ siblingBlock.clientId ]: [], @@ -1035,17 +1133,20 @@ describe( 'state', () => { it( 'should not move the first block up', () => { const original = blocks( undefined, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + ], } ); const state = blocks( original, { type: 'MOVE_BLOCKS_UP', @@ -1058,17 +1159,20 @@ describe( 'state', () => { it( 'should move the block down', () => { const original = blocks( undefined, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + ], } ); const state = blocks( original, { type: 'MOVE_BLOCKS_DOWN', @@ -1081,7 +1185,10 @@ describe( 'state', () => { it( 'should move the nested block down', () => { const movedBlock = createBlock( 'core/test-block' ); const siblingBlock = createBlock( 'core/test-block' ); - const wrapperBlock = createBlock( 'core/test-block', {}, [ movedBlock, siblingBlock ] ); + const wrapperBlock = createBlock( 'core/test-block', {}, [ + movedBlock, + siblingBlock, + ] ); const original = blocks( undefined, { type: 'RESET_BLOCKS', blocks: [ wrapperBlock ], @@ -1094,7 +1201,10 @@ describe( 'state', () => { expect( state.order ).toEqual( { '': [ wrapperBlock.clientId ], - [ wrapperBlock.clientId ]: [ siblingBlock.clientId, movedBlock.clientId ], + [ wrapperBlock.clientId ]: [ + siblingBlock.clientId, + movedBlock.clientId, + ], [ movedBlock.clientId ]: [], [ siblingBlock.clientId ]: [], } ); @@ -1103,36 +1213,48 @@ describe( 'state', () => { it( 'should move multiple blocks down', () => { const original = blocks( undefined, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'veggies', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 'veggies', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + ], } ); const state = blocks( original, { type: 'MOVE_BLOCKS_DOWN', clientIds: [ 'chicken', 'ribs' ], } ); - expect( state.order[ '' ] ).toEqual( [ 'veggies', 'chicken', 'ribs' ] ); + expect( state.order[ '' ] ).toEqual( [ + 'veggies', + 'chicken', + 'ribs', + ] ); } ); it( 'should move multiple nested blocks down', () => { const movedBlockA = createBlock( 'core/test-block' ); const movedBlockB = createBlock( 'core/test-block' ); const siblingBlock = createBlock( 'core/test-block' ); - const wrapperBlock = createBlock( 'core/test-block', {}, [ movedBlockA, movedBlockB, siblingBlock ] ); + const wrapperBlock = createBlock( 'core/test-block', {}, [ + movedBlockA, + movedBlockB, + siblingBlock, + ] ); const original = blocks( undefined, { type: 'RESET_BLOCKS', blocks: [ wrapperBlock ], @@ -1145,7 +1267,11 @@ describe( 'state', () => { expect( state.order ).toEqual( { '': [ wrapperBlock.clientId ], - [ wrapperBlock.clientId ]: [ siblingBlock.clientId, movedBlockA.clientId, movedBlockB.clientId ], + [ wrapperBlock.clientId ]: [ + siblingBlock.clientId, + movedBlockA.clientId, + movedBlockB.clientId, + ], [ movedBlockA.clientId ]: [], [ movedBlockB.clientId ]: [], [ siblingBlock.clientId ]: [], @@ -1155,17 +1281,20 @@ describe( 'state', () => { it( 'should not move the last block down', () => { const original = blocks( undefined, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + ], } ); const state = blocks( original, { type: 'MOVE_BLOCKS_DOWN', @@ -1178,17 +1307,20 @@ describe( 'state', () => { it( 'should remove the block', () => { const original = blocks( undefined, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + ], } ); const state = blocks( original, { type: 'REMOVE_BLOCKS', @@ -1217,22 +1349,26 @@ describe( 'state', () => { it( 'should remove multiple blocks', () => { const original = blocks( undefined, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'veggies', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 'veggies', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + ], } ); const state = blocks( original, { type: 'REMOVE_BLOCKS', @@ -1283,52 +1419,65 @@ describe( 'state', () => { it( 'should insert at the specified index', () => { const original = blocks( undefined, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'kumquat', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'loquat', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'kumquat', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 'loquat', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + ], } ); const state = blocks( original, { type: 'INSERT_BLOCKS', index: 1, - blocks: [ { - clientId: 'persimmon', - name: 'core/freeform', - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'persimmon', + name: 'core/freeform', + innerBlocks: [], + }, + ], } ); expect( Object.keys( state.byClientId ) ).toHaveLength( 3 ); - expect( state.order[ '' ] ).toEqual( [ 'kumquat', 'persimmon', 'loquat' ] ); + expect( state.order[ '' ] ).toEqual( [ + 'kumquat', + 'persimmon', + 'loquat', + ] ); } ); it( 'should move block to lower index', () => { const original = blocks( undefined, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'veggies', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 'veggies', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + ], } ); const state = blocks( original, { type: 'MOVE_BLOCK_TO_POSITION', @@ -1336,28 +1485,36 @@ describe( 'state', () => { index: 0, } ); - expect( state.order[ '' ] ).toEqual( [ 'ribs', 'chicken', 'veggies' ] ); + expect( state.order[ '' ] ).toEqual( [ + 'ribs', + 'chicken', + 'veggies', + ] ); } ); it( 'should move block to higher index', () => { const original = blocks( undefined, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'veggies', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 'veggies', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + ], } ); const state = blocks( original, { type: 'MOVE_BLOCK_TO_POSITION', @@ -1365,28 +1522,36 @@ describe( 'state', () => { index: 2, } ); - expect( state.order[ '' ] ).toEqual( [ 'chicken', 'veggies', 'ribs' ] ); + expect( state.order[ '' ] ).toEqual( [ + 'chicken', + 'veggies', + 'ribs', + ] ); } ); it( 'should not move block if passed same index', () => { const original = blocks( undefined, { type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'chicken', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'ribs', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - }, { - clientId: 'veggies', - name: 'core/test-block', - attributes: {}, - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'chicken', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 'ribs', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 'veggies', + name: 'core/test-block', + attributes: {}, + innerBlocks: [], + }, + ], } ); const state = blocks( original, { type: 'MOVE_BLOCK_TO_POSITION', @@ -1394,7 +1559,11 @@ describe( 'state', () => { index: 1, } ); - expect( state.order[ '' ] ).toEqual( [ 'chicken', 'ribs', 'veggies' ] ); + expect( state.order[ '' ] ).toEqual( [ + 'chicken', + 'ribs', + 'veggies', + ] ); } ); describe( 'blocks', () => { @@ -1425,7 +1594,9 @@ describe( 'state', () => { ], }, ]; - const original = deepFreeze( actions.reduce( blocks, undefined ) ); + const original = deepFreeze( + actions.reduce( blocks, undefined ) + ); const state = blocks( original, { type: 'RESET_BLOCKS', @@ -1452,10 +1623,12 @@ describe( 'state', () => { describe( 'byClientId', () => { it( 'should ignore updates to non-existent block', () => { - const original = deepFreeze( blocks( undefined, { - type: 'RESET_BLOCKS', - blocks: [], - } ) ); + const original = deepFreeze( + blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [], + } ) + ); const state = blocks( original, { type: 'UPDATE_BLOCK_ATTRIBUTES', clientId: 'kumquat', @@ -1468,16 +1641,20 @@ describe( 'state', () => { } ); it( 'should return with same reference if no changes in updates', () => { - const original = deepFreeze( blocks( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'kumquat', - attributes: { - updated: true, - }, - innerBlocks: [], - } ], - } ) ); + const original = deepFreeze( + blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ + { + clientId: 'kumquat', + attributes: { + updated: true, + }, + innerBlocks: [], + }, + ], + } ) + ); const state = blocks( original, { type: 'UPDATE_BLOCK_ATTRIBUTES', clientId: 'kumquat', @@ -1492,14 +1669,18 @@ describe( 'state', () => { describe( 'attributes', () => { it( 'should return with attribute block updates', () => { - const original = deepFreeze( blocks( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'kumquat', - attributes: {}, - innerBlocks: [], - } ], - } ) ); + const original = deepFreeze( + blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ + { + clientId: 'kumquat', + attributes: {}, + innerBlocks: [], + }, + ], + } ) + ); const state = blocks( original, { type: 'UPDATE_BLOCK_ATTRIBUTES', clientId: 'kumquat', @@ -1512,16 +1693,20 @@ describe( 'state', () => { } ); it( 'should accumulate attribute block updates', () => { - const original = deepFreeze( blocks( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'kumquat', - attributes: { - updated: true, - }, - innerBlocks: [], - } ], - } ) ); + const original = deepFreeze( + blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ + { + clientId: 'kumquat', + attributes: { + updated: true, + }, + innerBlocks: [], + }, + ], + } ) + ); const state = blocks( original, { type: 'UPDATE_BLOCK_ATTRIBUTES', clientId: 'kumquat', @@ -1537,10 +1722,12 @@ describe( 'state', () => { } ); it( 'should ignore updates to non-existent block', () => { - const original = deepFreeze( blocks( undefined, { - type: 'RESET_BLOCKS', - blocks: [], - } ) ); + const original = deepFreeze( + blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [], + } ) + ); const state = blocks( original, { type: 'UPDATE_BLOCK_ATTRIBUTES', clientId: 'kumquat', @@ -1553,16 +1740,20 @@ describe( 'state', () => { } ); it( 'should return with same reference if no changes in updates', () => { - const original = deepFreeze( blocks( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'kumquat', - attributes: { - updated: true, - }, - innerBlocks: [], - } ], - } ) ); + const original = deepFreeze( + blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ + { + clientId: 'kumquat', + attributes: { + updated: true, + }, + innerBlocks: [], + }, + ], + } ) + ); const state = blocks( original, { type: 'UPDATE_BLOCK_ATTRIBUTES', clientId: 'kumquat', @@ -1583,10 +1774,12 @@ describe( 'state', () => { } ); it( 'should consider any non-exempt block change as persistent', () => { - const original = deepFreeze( blocks( undefined, { - type: 'RESET_BLOCKS', - blocks: [], - } ) ); + const original = deepFreeze( + blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [], + } ) + ); const state = blocks( original, { type: 'UPDATE_BLOCK_ATTRIBUTES', @@ -1600,14 +1793,18 @@ describe( 'state', () => { } ); it( 'should consider any non-exempt block change as persistent across unchanging actions', () => { - let original = deepFreeze( blocks( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'kumquat', - attributes: {}, - innerBlocks: [], - } ], - } ) ); + let original = deepFreeze( + blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ + { + clientId: 'kumquat', + attributes: {}, + innerBlocks: [], + }, + ], + } ) + ); original = blocks( original, { type: 'NOOP', } ); @@ -1630,14 +1827,18 @@ describe( 'state', () => { } ); it( 'should consider same block attribute update as exempt', () => { - let original = deepFreeze( blocks( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'kumquat', - attributes: {}, - innerBlocks: [], - } ], - } ) ); + let original = deepFreeze( + blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ + { + clientId: 'kumquat', + attributes: {}, + innerBlocks: [], + }, + ], + } ) + ); original = blocks( original, { type: 'UPDATE_BLOCK_ATTRIBUTES', clientId: 'kumquat', @@ -1658,14 +1859,18 @@ describe( 'state', () => { } ); it( 'should flag an explicitly marked persistent change', () => { - let original = deepFreeze( blocks( undefined, { - type: 'RESET_BLOCKS', - blocks: [ { - clientId: 'kumquat', - attributes: {}, - innerBlocks: [], - } ], - } ) ); + let original = deepFreeze( + blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [ + { + clientId: 'kumquat', + attributes: {}, + innerBlocks: [], + }, + ], + } ) + ); original = blocks( original, { type: 'UPDATE_BLOCK_ATTRIBUTES', clientId: 'kumquat', @@ -1689,10 +1894,12 @@ describe( 'state', () => { } ); it( 'should retain reference for same state, same persistence', () => { - const original = deepFreeze( blocks( undefined, { - type: 'RESET_BLOCKS', - blocks: [], - } ) ); + const original = deepFreeze( + blocks( undefined, { + type: 'RESET_BLOCKS', + blocks: [], + } ) + ); const state = blocks( original, { type: '__INERT__', @@ -1704,14 +1911,18 @@ describe( 'state', () => { describe( 'isIgnoredChange', () => { it( 'should consider received blocks as ignored change', () => { - const resetState = blocks( undefined, { type: 'random action' } ); + const resetState = blocks( undefined, { + type: 'random action', + } ); const state = blocks( resetState, { type: 'RECEIVE_BLOCKS', - blocks: [ { - clientId: 'kumquat', - attributes: {}, - innerBlocks: [], - } ], + blocks: [ + { + clientId: 'kumquat', + attributes: {}, + innerBlocks: [], + }, + ], } ); expect( state.isIgnoredChange ).toBe( true ); @@ -1984,10 +2195,7 @@ describe( 'state', () => { const action = { type: 'REPLACE_BLOCKS', clientIds: [ 'wings' ], - blocks: [ - { clientId: 'chicken' }, - { clientId: 'wings' }, - ], + blocks: [ { clientId: 'chicken' }, { clientId: 'wings' } ], }; const state1 = selectionStart( original, action ); const state2 = selectionEnd( original, action ); @@ -2001,10 +2209,7 @@ describe( 'state', () => { const action = { type: 'REPLACE_BLOCKS', clientIds: [ 'chicken' ], - blocks: [ - { clientId: 'chicken' }, - { clientId: 'wings' }, - ], + blocks: [ { clientId: 'chicken' }, { clientId: 'wings' } ], }; const state1 = selectionStart( original, action ); const state2 = selectionEnd( original, action ); @@ -2114,10 +2319,12 @@ describe( 'state', () => { it( 'should record recently used blocks', () => { const state = preferences( deepFreeze( { insertUsage: {} } ), { type: 'INSERT_BLOCKS', - blocks: [ { - clientId: 'bacon', - name: 'core-embed/twitter', - } ], + blocks: [ + { + clientId: 'bacon', + name: 'core-embed/twitter', + }, + ], time: 123456, } ); @@ -2131,26 +2338,32 @@ describe( 'state', () => { }, } ); - const twoRecentBlocks = preferences( deepFreeze( { - insertUsage: { - 'core-embed/twitter': { - time: 123456, - count: 1, - insert: { name: 'core-embed/twitter' }, + const twoRecentBlocks = preferences( + deepFreeze( { + insertUsage: { + 'core-embed/twitter': { + time: 123456, + count: 1, + insert: { name: 'core-embed/twitter' }, + }, }, - }, - } ), { - type: 'INSERT_BLOCKS', - blocks: [ { - clientId: 'eggs', - name: 'core-embed/twitter', - }, { - clientId: 'bacon', - name: 'core/block', - attributes: { ref: 123 }, - } ], - time: 123457, - } ); + } ), + { + type: 'INSERT_BLOCKS', + blocks: [ + { + clientId: 'eggs', + name: 'core-embed/twitter', + }, + { + clientId: 'bacon', + name: 'core/block', + attributes: { ref: 123 }, + }, + ], + time: 123457, + } + ); expect( twoRecentBlocks ).toEqual( { insertUsage: { @@ -2185,7 +2398,10 @@ describe( 'state', () => { type: 'TOGGLE_BLOCK_MODE', clientId: 'chicken', }; - const value = blocksMode( deepFreeze( { chicken: 'html' } ), action ); + const value = blocksMode( + deepFreeze( { chicken: 'html' } ), + action + ); expect( value ).toEqual( { chicken: 'visual' } ); } ); diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index afc2d7b81657e5..d6f4cfaac20e17 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -159,7 +159,10 @@ describe( 'selectors', () => { }, }; - const name = getBlockName( state, 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ); + const name = getBlockName( + state, + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' + ); expect( name ).toBe( null ); } ); @@ -186,7 +189,10 @@ describe( 'selectors', () => { }, }; - const name = getBlockName( state, 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' ); + const name = getBlockName( + state, + 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' + ); expect( name ).toBe( 'core/paragraph' ); } ); @@ -268,12 +274,14 @@ describe( 'selectors', () => { clientId: 123, name: 'core/paragraph', attributes: {}, - innerBlocks: [ { - clientId: 456, - name: 'core/paragraph', - attributes: {}, - innerBlocks: [], - } ], + innerBlocks: [ + { + clientId: 456, + name: 'core/paragraph', + attributes: {}, + innerBlocks: [], + }, + ], } ); } ); } ); @@ -305,8 +313,18 @@ describe( 'selectors', () => { }; expect( getBlocks( state ) ).toEqual( [ - { clientId: 123, name: 'core/paragraph', attributes: {}, innerBlocks: [] }, - { clientId: 23, name: 'core/heading', attributes: {}, innerBlocks: [] }, + { + clientId: 123, + name: 'core/paragraph', + attributes: {}, + innerBlocks: [], + }, + { + clientId: 23, + name: 'core/heading', + attributes: {}, + innerBlocks: [], + }, ] ); } ); } ); @@ -317,20 +335,38 @@ describe( 'selectors', () => { blocks: { byClientId: { 'uuid-2': { clientId: 'uuid-2', name: 'core/image' }, - 'uuid-4': { clientId: 'uuid-4', name: 'core/paragraph' }, - 'uuid-6': { clientId: 'uuid-6', name: 'core/paragraph' }, + 'uuid-4': { + clientId: 'uuid-4', + name: 'core/paragraph', + }, + 'uuid-6': { + clientId: 'uuid-6', + name: 'core/paragraph', + }, 'uuid-8': { clientId: 'uuid-8', name: 'core/block' }, - 'uuid-10': { clientId: 'uuid-10', name: 'core/columns' }, + 'uuid-10': { + clientId: 'uuid-10', + name: 'core/columns', + }, 'uuid-12': { clientId: 'uuid-12', name: 'core/column' }, 'uuid-14': { clientId: 'uuid-14', name: 'core/column' }, 'uuid-16': { clientId: 'uuid-16', name: 'core/quote' }, 'uuid-18': { clientId: 'uuid-18', name: 'core/block' }, - 'uuid-20': { clientId: 'uuid-20', name: 'core/gallery' }, + 'uuid-20': { + clientId: 'uuid-20', + name: 'core/gallery', + }, 'uuid-22': { clientId: 'uuid-22', name: 'core/block' }, - 'uuid-24': { clientId: 'uuid-24', name: 'core/columns' }, + 'uuid-24': { + clientId: 'uuid-24', + name: 'core/columns', + }, 'uuid-26': { clientId: 'uuid-26', name: 'core/column' }, 'uuid-28': { clientId: 'uuid-28', name: 'core/column' }, - 'uuid-30': { clientId: 'uuid-30', name: 'core/paragraph' }, + 'uuid-30': { + clientId: 'uuid-30', + name: 'core/paragraph', + }, }, attributes: { 'uuid-2': {}, @@ -351,19 +387,19 @@ describe( 'selectors', () => { }, order: { '': [ 'uuid-6', 'uuid-8', 'uuid-10', 'uuid-22' ], - 'uuid-2': [ ], - 'uuid-4': [ ], - 'uuid-6': [ ], - 'uuid-8': [ ], + 'uuid-2': [], + 'uuid-4': [], + 'uuid-6': [], + 'uuid-8': [], 'uuid-10': [ 'uuid-12', 'uuid-14' ], 'uuid-12': [ 'uuid-16' ], 'uuid-14': [ 'uuid-18' ], - 'uuid-16': [ ], + 'uuid-16': [], 'uuid-18': [ 'uuid-24' ], - 'uuid-20': [ ], - 'uuid-22': [ ], + 'uuid-20': [], + 'uuid-22': [], 'uuid-24': [ 'uuid-26', 'uuid-28' ], - 'uuid-26': [ ], + 'uuid-26': [], 'uuid-28': [ 'uuid-30' ], }, parents: { @@ -382,7 +418,9 @@ describe( 'selectors', () => { }, }, }; - expect( getClientIdsOfDescendants( state, [ 'uuid-10' ] ) ).toEqual( [ + expect( + getClientIdsOfDescendants( state, [ 'uuid-10' ] ) + ).toEqual( [ 'uuid-12', 'uuid-14', 'uuid-16', @@ -401,20 +439,38 @@ describe( 'selectors', () => { blocks: { byClientId: { 'uuid-2': { clientId: 'uuid-2', name: 'core/image' }, - 'uuid-4': { clientId: 'uuid-4', name: 'core/paragraph' }, - 'uuid-6': { clientId: 'uuid-6', name: 'core/paragraph' }, + 'uuid-4': { + clientId: 'uuid-4', + name: 'core/paragraph', + }, + 'uuid-6': { + clientId: 'uuid-6', + name: 'core/paragraph', + }, 'uuid-8': { clientId: 'uuid-8', name: 'core/block' }, - 'uuid-10': { clientId: 'uuid-10', name: 'core/columns' }, + 'uuid-10': { + clientId: 'uuid-10', + name: 'core/columns', + }, 'uuid-12': { clientId: 'uuid-12', name: 'core/column' }, 'uuid-14': { clientId: 'uuid-14', name: 'core/column' }, 'uuid-16': { clientId: 'uuid-16', name: 'core/quote' }, 'uuid-18': { clientId: 'uuid-18', name: 'core/block' }, - 'uuid-20': { clientId: 'uuid-20', name: 'core/gallery' }, + 'uuid-20': { + clientId: 'uuid-20', + name: 'core/gallery', + }, 'uuid-22': { clientId: 'uuid-22', name: 'core/block' }, - 'uuid-24': { clientId: 'uuid-24', name: 'core/columns' }, + 'uuid-24': { + clientId: 'uuid-24', + name: 'core/columns', + }, 'uuid-26': { clientId: 'uuid-26', name: 'core/column' }, 'uuid-28': { clientId: 'uuid-28', name: 'core/column' }, - 'uuid-30': { clientId: 'uuid-30', name: 'core/paragraph' }, + 'uuid-30': { + clientId: 'uuid-30', + name: 'core/paragraph', + }, }, attributes: { 'uuid-2': {}, @@ -435,19 +491,19 @@ describe( 'selectors', () => { }, order: { '': [ 'uuid-6', 'uuid-8', 'uuid-10', 'uuid-22' ], - 'uuid-2': [ ], - 'uuid-4': [ ], - 'uuid-6': [ ], - 'uuid-8': [ ], + 'uuid-2': [], + 'uuid-4': [], + 'uuid-6': [], + 'uuid-8': [], 'uuid-10': [ 'uuid-12', 'uuid-14' ], 'uuid-12': [ 'uuid-16' ], 'uuid-14': [ 'uuid-18' ], - 'uuid-16': [ ], + 'uuid-16': [], 'uuid-18': [ 'uuid-24' ], - 'uuid-20': [ ], - 'uuid-22': [ ], + 'uuid-20': [], + 'uuid-22': [], 'uuid-24': [ 'uuid-26', 'uuid-28' ], - 'uuid-26': [ ], + 'uuid-26': [], 'uuid-28': [ 'uuid-30' ], }, parents: { @@ -545,8 +601,12 @@ describe( 'selectors', () => { it( 'should return false if multi-selection', () => { const state = { - selectionStart: { clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' }, - selectionEnd: { clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189' }, + selectionStart: { + clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + }, + selectionEnd: { + clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', + }, }; expect( hasSelectedBlock( state ) ).toBe( false ); @@ -554,8 +614,12 @@ describe( 'selectors', () => { it( 'should return true if singular selection', () => { const state = { - selectionStart: { clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' }, - selectionEnd: { clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' }, + selectionStart: { + clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + }, + selectionEnd: { + clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + }, }; expect( hasSelectedBlock( state ) ).toBe( true ); @@ -603,7 +667,9 @@ describe( 'selectors', () => { }, }; expect( getGlobalBlockCount( emptyState ) ).toBe( 0 ); - expect( getGlobalBlockCount( emptyState, 'core/heading' ) ).toBe( 0 ); + expect( getGlobalBlockCount( emptyState, 'core/heading' ) ).toBe( + 0 + ); } ); } ); @@ -947,7 +1013,11 @@ describe( 'selectors', () => { selectionEnd: { clientId: 4 }, }; - expect( getMultiSelectedBlockClientIds( state ) ).toEqual( [ 4, 3, 2 ] ); + expect( getMultiSelectedBlockClientIds( state ) ).toEqual( [ + 4, + 3, + 2, + ] ); } ); it( 'should return selected block clientIds if there is multi selection (nested context)', () => { @@ -973,7 +1043,11 @@ describe( 'selectors', () => { selectionEnd: { clientId: 9 }, }; - expect( getMultiSelectedBlockClientIds( state ) ).toEqual( [ 9, 8, 7 ] ); + expect( getMultiSelectedBlockClientIds( state ) ).toEqual( [ + 9, + 8, + 7, + ] ); } ); } ); @@ -990,9 +1064,9 @@ describe( 'selectors', () => { selectionEnd: {}, }; - expect( + expect( getMultiSelectedBlocks( state ) ).toBe( getMultiSelectedBlocks( state ) - ).toBe( getMultiSelectedBlocks( state ) ); + ); } ); } ); @@ -1142,7 +1216,9 @@ describe( 'selectors', () => { }, }; - expect( getPreviousBlockClientId( state, 56, '123' ) ).toEqual( 456 ); + expect( getPreviousBlockClientId( state, 56, '123' ) ).toEqual( + 456 + ); } ); it( 'should return null for the first block', () => { @@ -1460,8 +1536,12 @@ describe( 'selectors', () => { it( 'should return false if singular selection', () => { const state = { - selectionStart: { clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' }, - selectionEnd: { clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' }, + selectionStart: { + clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + }, + selectionEnd: { + clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + }, }; expect( hasMultiSelection( state ) ).toBe( false ); @@ -1469,8 +1549,12 @@ describe( 'selectors', () => { it( 'should return true if multi-selection', () => { const state = { - selectionStart: { clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1' }, - selectionEnd: { clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189' }, + selectionStart: { + clientId: 'afd1cb17-2c08-4e7a-91be-007ba7ddc3a1', + }, + selectionEnd: { + clientId: '9db792c6-a25a-495d-adbd-97d56a4c4189', + }, }; expect( hasMultiSelection( state ) ).toBe( true ); @@ -1811,7 +1895,9 @@ describe( 'selectors', () => { allowedBlockTypes: [], }, }; - expect( canInsertBlockType( state, 'core/test-block-a' ) ).toBe( false ); + expect( canInsertBlockType( state, 'core/test-block-a' ) ).toBe( + false + ); } ); it( 'should allow blocks that are allowed by the editor', () => { @@ -1825,7 +1911,9 @@ describe( 'selectors', () => { allowedBlockTypes: [ 'core/test-block-a' ], }, }; - expect( canInsertBlockType( state, 'core/test-block-a' ) ).toBe( true ); + expect( canInsertBlockType( state, 'core/test-block-a' ) ).toBe( + true + ); } ); it( 'should deny blocks when the editor has a template lock', () => { @@ -1839,7 +1927,9 @@ describe( 'selectors', () => { templateLock: 'all', }, }; - expect( canInsertBlockType( state, 'core/test-block-a' ) ).toBe( false ); + expect( canInsertBlockType( state, 'core/test-block-a' ) ).toBe( + false + ); } ); it( 'should deny blocks that restrict parent from being inserted into the root', () => { @@ -1851,7 +1941,9 @@ describe( 'selectors', () => { blockListSettings: {}, settings: {}, }; - expect( canInsertBlockType( state, 'core/test-block-c' ) ).toBe( false ); + expect( canInsertBlockType( state, 'core/test-block-c' ) ).toBe( + false + ); } ); it( 'should deny blocks that restrict parent from being inserted into a restricted parent', () => { @@ -1867,7 +1959,9 @@ describe( 'selectors', () => { blockListSettings: {}, settings: {}, }; - expect( canInsertBlockType( state, 'core/test-block-c', 'block1' ) ).toBe( false ); + expect( + canInsertBlockType( state, 'core/test-block-c', 'block1' ) + ).toBe( false ); } ); it( 'should allow blocks that restrict parent to be inserted into an allowed parent', () => { @@ -1883,7 +1977,9 @@ describe( 'selectors', () => { blockListSettings: {}, settings: {}, }; - expect( canInsertBlockType( state, 'core/test-block-c', 'block1' ) ).toBe( true ); + expect( + canInsertBlockType( state, 'core/test-block-c', 'block1' ) + ).toBe( true ); } ); it( 'should deny restricted blocks from being inserted into a block that restricts allowedBlocks', () => { @@ -1903,7 +1999,9 @@ describe( 'selectors', () => { }, settings: {}, }; - expect( canInsertBlockType( state, 'core/test-block-b', 'block1' ) ).toBe( false ); + expect( + canInsertBlockType( state, 'core/test-block-b', 'block1' ) + ).toBe( false ); } ); it( 'should allow allowed blocks to be inserted into a block that restricts allowedBlocks', () => { @@ -1923,7 +2021,9 @@ describe( 'selectors', () => { }, settings: {}, }; - expect( canInsertBlockType( state, 'core/test-block-b', 'block1' ) ).toBe( true ); + expect( + canInsertBlockType( state, 'core/test-block-b', 'block1' ) + ).toBe( true ); } ); it( 'should prioritise parent over allowedBlocks', () => { @@ -1943,7 +2043,9 @@ describe( 'selectors', () => { }, settings: {}, }; - expect( canInsertBlockType( state, 'core/test-block-c', 'block1' ) ).toBe( true ); + expect( + canInsertBlockType( state, 'core/test-block-c', 'block1' ) + ).toBe( true ); } ); it( 'should deny blocks that restrict parent to core/post-content when not in editor root', () => { @@ -1959,7 +2061,9 @@ describe( 'selectors', () => { blockListSettings: {}, settings: {}, }; - expect( canInsertBlockType( state, 'core/post-content-child', 'block1' ) ).toBe( false ); + expect( + canInsertBlockType( state, 'core/post-content-child', 'block1' ) + ).toBe( false ); } ); it( 'should allow blocks that restrict parent to core/post-content when in editor root', () => { @@ -1971,7 +2075,9 @@ describe( 'selectors', () => { blockListSettings: {}, settings: {}, }; - expect( canInsertBlockType( state, 'core/post-content-child' ) ).toBe( true ); + expect( + canInsertBlockType( state, 'core/post-content-child' ) + ).toBe( true ); } ); } ); @@ -2004,7 +2110,9 @@ describe( 'selectors', () => { blockListSettings: {}, }; const items = getInserterItems( state ); - const testBlockAItem = items.find( ( item ) => item.id === 'core/test-block-a' ); + const testBlockAItem = items.find( + ( item ) => item.id === 'core/test-block-a' + ); expect( testBlockAItem ).toEqual( { id: 'core/test-block-a', name: 'core/test-block-a', @@ -2015,12 +2123,14 @@ describe( 'selectors', () => { }, category: 'formatting', keywords: [ 'testing' ], - patterns: [], + variations: [], isDisabled: false, utility: 0, frecency: 0, } ); - const reusableBlockItem = items.find( ( item ) => item.id === 'core/block/1' ); + const reusableBlockItem = items.find( + ( item ) => item.id === 'core/block/1' + ); expect( reusableBlockItem ).toEqual( { id: 'core/block/1', name: 'core/block', @@ -2072,7 +2182,9 @@ describe( 'selectors', () => { }, blockListSettings: {}, }; - const itemIDs = getInserterItems( state ).map( ( item ) => item.id ); + const itemIDs = getInserterItems( state ).map( + ( item ) => item.id + ); expect( itemIDs ).toEqual( [ 'core/post-content-child', 'core/block/2', @@ -2140,7 +2252,10 @@ describe( 'selectors', () => { }; const firstBlockFirstCall = getInserterItems( state, 'block3' ); - const firstBlockSecondCall = getInserterItems( stateSecondBlockRestricted, 'block3' ); + const firstBlockSecondCall = getInserterItems( + stateSecondBlockRestricted, + 'block3' + ); expect( firstBlockFirstCall ).toBe( firstBlockSecondCall ); expect( firstBlockFirstCall.map( ( item ) => item.id ) ).toEqual( [ 'core/test-block-b', @@ -2151,7 +2266,10 @@ describe( 'selectors', () => { ] ); const secondBlockFirstCall = getInserterItems( state, 'block4' ); - const secondBlockSecondCall = getInserterItems( stateSecondBlockRestricted, 'block4' ); + const secondBlockSecondCall = getInserterItems( + stateSecondBlockRestricted, + 'block4' + ); expect( secondBlockFirstCall.map( ( item ) => item.id ) ).toEqual( [ 'core/test-block-b', 'core/test-freeform', @@ -2159,16 +2277,19 @@ describe( 'selectors', () => { 'core/block/1', 'core/block/2', ] ); - expect( secondBlockSecondCall.map( ( item ) => item.id ) ).toEqual( [ - 'core/test-block-b', - ] ); + expect( + secondBlockSecondCall.map( ( item ) => item.id ) + ).toEqual( [ 'core/test-block-b' ] ); } ); it( 'should set isDisabled when a block with `multiple: false` has been used', () => { const state = { blocks: { byClientId: { - block1: { clientId: 'block1', name: 'core/test-block-b' }, + block1: { + clientId: 'block1', + name: 'core/test-block-b', + }, }, attributes: { block1: { attribute: {} }, @@ -2187,7 +2308,9 @@ describe( 'selectors', () => { settings: {}, }; const items = getInserterItems( state ); - const testBlockBItem = items.find( ( item ) => item.id === 'core/test-block-b' ); + const testBlockBItem = items.find( + ( item ) => item.id === 'core/test-block-b' + ); expect( testBlockBItem.isDisabled ).toBe( true ); } ); @@ -2207,7 +2330,9 @@ describe( 'selectors', () => { settings: {}, }; const items = getInserterItems( state ); - const testBlockBItem = items.find( ( item ) => item.id === 'core/test-block-b' ); + const testBlockBItem = items.find( + ( item ) => item.id === 'core/test-block-b' + ); expect( testBlockBItem.utility ).toBe( INSERTER_UTILITY_LOW ); } ); @@ -2229,8 +2354,12 @@ describe( 'selectors', () => { settings: {}, }; const items = getInserterItems( state ); - const reusableBlock2Item = items.find( ( item ) => item.id === 'core/test-block-b' ); - expect( reusableBlock2Item.utility ).toBe( INSERTER_UTILITY_MEDIUM ); + const reusableBlock2Item = items.find( + ( item ) => item.id === 'core/test-block-b' + ); + expect( reusableBlock2Item.utility ).toBe( + INSERTER_UTILITY_MEDIUM + ); expect( reusableBlock2Item.frecency ).toBe( 2.5 ); } ); @@ -2260,7 +2389,9 @@ describe( 'selectors', () => { settings: {}, }; const items = getInserterItems( state, 'block1' ); - const testBlockCItem = items.find( ( item ) => item.id === 'core/test-block-c' ); + const testBlockCItem = items.find( + ( item ) => item.id === 'core/test-block-c' + ); expect( testBlockCItem.utility ).toBe( INSERTER_UTILITY_HIGH ); } ); } ); @@ -2366,7 +2497,9 @@ describe( 'selectors', () => { blockListSettings: {}, }; - expect( getBlockListSettings( state, 'chicken' ) ).toBe( undefined ); + expect( getBlockListSettings( state, 'chicken' ) ).toBe( + undefined + ); } ); } ); @@ -2391,9 +2524,17 @@ describe( 'selectors', () => { }, }; - const targetBlocksClientIds = [ 'test-1-dummy-clientId', 'test-3-dummy-clientId' ]; + const targetBlocksClientIds = [ + 'test-1-dummy-clientId', + 'test-3-dummy-clientId', + ]; - expect( __experimentalGetBlockListSettingsForBlocks( state, targetBlocksClientIds ) ).toEqual( [ + expect( + __experimentalGetBlockListSettingsForBlocks( + state, + targetBlocksClientIds + ) + ).toEqual( [ { setting1: false, }, @@ -2418,9 +2559,17 @@ describe( 'selectors', () => { }, }; - const targetBlocksClientIds = [ 'test-1-dummy-clientId', 'test-3-dummy-clientId' ]; + const targetBlocksClientIds = [ + 'test-1-dummy-clientId', + 'test-3-dummy-clientId', + ]; - expect( __experimentalGetBlockListSettingsForBlocks( state, targetBlocksClientIds ) ).toEqual( [] ); + expect( + __experimentalGetBlockListSettingsForBlocks( + state, + targetBlocksClientIds + ) + ).toEqual( [] ); } ); } ); @@ -2465,7 +2614,9 @@ describe( 'selectors', () => { selectionEnd: {}, }; - expect( getLowestCommonAncestorWithSelectedBlock( state, 'd' ) ).not.toBeDefined(); + expect( + getLowestCommonAncestorWithSelectedBlock( state, 'd' ) + ).not.toBeDefined(); } ); it( 'should not be defined if selected block has no parent', () => { @@ -2475,7 +2626,9 @@ describe( 'selectors', () => { selectionEnd: { clientId: 'b' }, }; - expect( getLowestCommonAncestorWithSelectedBlock( state, 'b' ) ).toBe( 'b' ); + expect( + getLowestCommonAncestorWithSelectedBlock( state, 'b' ) + ).toBe( 'b' ); } ); it( 'should not be defined if selected block has no common parent with given block', () => { @@ -2485,7 +2638,9 @@ describe( 'selectors', () => { selectionEnd: { clientId: 'd' }, }; - expect( getLowestCommonAncestorWithSelectedBlock( state, 'f' ) ).not.toBeDefined(); + expect( + getLowestCommonAncestorWithSelectedBlock( state, 'f' ) + ).not.toBeDefined(); } ); it( 'should return block id if selected block is ancestor of given block', () => { @@ -2495,7 +2650,9 @@ describe( 'selectors', () => { selectionEnd: { clientId: 'c' }, }; - expect( getLowestCommonAncestorWithSelectedBlock( state, 'a' ) ).toBe( 'a' ); + expect( + getLowestCommonAncestorWithSelectedBlock( state, 'a' ) + ).toBe( 'a' ); } ); it( 'should return block id if selected block is nested child of given block', () => { @@ -2505,7 +2662,9 @@ describe( 'selectors', () => { selectionEnd: { clientId: 'e' }, }; - expect( getLowestCommonAncestorWithSelectedBlock( state, 'a' ) ).toBe( 'a' ); + expect( + getLowestCommonAncestorWithSelectedBlock( state, 'a' ) + ).toBe( 'a' ); } ); it( 'should return block id if selected block has common parent with given block', () => { @@ -2515,7 +2674,9 @@ describe( 'selectors', () => { selectionEnd: { clientId: 'e' }, }; - expect( getLowestCommonAncestorWithSelectedBlock( state, 'c' ) ).toBe( 'a' ); + expect( + getLowestCommonAncestorWithSelectedBlock( state, 'c' ) + ).toBe( 'a' ); } ); } ); } ); diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss index b1e2406d94cc00..d170399ed8af61 100644 --- a/packages/block-editor/src/style.scss +++ b/packages/block-editor/src/style.scss @@ -8,13 +8,13 @@ @import "./components/block-mobile-toolbar/style.scss"; @import "./components/block-mover/style.scss"; @import "./components/block-navigation/style.scss"; -@import "./components/block-pattern-picker/style.scss"; @import "./components/block-preview/style.scss"; @import "./components/block-settings-menu/style.scss"; @import "./components/block-styles/style.scss"; @import "./components/block-switcher/style.scss"; @import "./components/block-toolbar/style.scss"; @import "./components/block-types-list/style.scss"; +@import "./components/block-variation-picker/style.scss"; @import "./components/button-block-appender/style.scss"; @import "./components/colors-gradients/style.scss"; @import "./components/contrast-checker/style.scss"; diff --git a/packages/block-editor/src/utils/dom.js b/packages/block-editor/src/utils/dom.js index 9f693a6bba2752..3fb25425678ab8 100644 --- a/packages/block-editor/src/utils/dom.js +++ b/packages/block-editor/src/utils/dom.js @@ -43,7 +43,10 @@ export function isBlockFocusStop( element ) { * @return {boolean} Whether elements are in the same block. */ export function isInSameBlock( a, b ) { - return a.closest( '.block-editor-block-list__block' ) === b.closest( '.block-editor-block-list__block' ); + return ( + a.closest( '.block-editor-block-list__block' ) === + b.closest( '.block-editor-block-list__block' ) + ); } /** @@ -55,9 +58,12 @@ export function isInSameBlock( a, b ) { * @return {boolean} Whether element is in the block Element but not its children. */ export function isInsideRootBlock( blockElement, element ) { - const innerBlocksContainer = blockElement.querySelector( '.block-editor-block-list__layout' ); - return blockElement.contains( element ) && ( - ! innerBlocksContainer || ! innerBlocksContainer.contains( element ) + const innerBlocksContainer = blockElement.querySelector( + '.block-editor-block-list__layout' + ); + return ( + blockElement.contains( element ) && + ( ! innerBlocksContainer || ! innerBlocksContainer.contains( element ) ) ); } diff --git a/packages/block-editor/src/utils/get-paste-event-data.js b/packages/block-editor/src/utils/get-paste-event-data.js index 20211ec336d601..7b798887c76b0e 100644 --- a/packages/block-editor/src/utils/get-paste-event-data.js +++ b/packages/block-editor/src/utils/get-paste-event-data.js @@ -56,7 +56,9 @@ export function getPasteEventData( { clipboardData } ) { } } ); - files = files.filter( ( { type } ) => /^image\/(?:jpe?g|png|gif)$/.test( type ) ); + files = files.filter( ( { type } ) => + /^image\/(?:jpe?g|png|gif)$/.test( type ) + ); // Only process files if no HTML is present. // A pasted file may have the URL as plain text. diff --git a/packages/block-editor/src/utils/test/dom.js b/packages/block-editor/src/utils/test/dom.js index 69e740f629f48d..b755f74bcb899f 100644 --- a/packages/block-editor/src/utils/test/dom.js +++ b/packages/block-editor/src/utils/test/dom.js @@ -6,13 +6,12 @@ import { hasInnerBlocksContext } from '../dom'; describe( 'hasInnerBlocksContext()', () => { it( 'should return false for a block node which has no inner blocks', () => { const wrapper = document.createElement( 'div' ); - wrapper.innerHTML = ( + wrapper.innerHTML = '<div class="block-editor-block-list__block" data-type="core/paragraph" tabindex="0">' + ' <div class="block-editor-block-list__block-edit" aria-label="Block: Paragraph">' + ' <p contenteditable="true">This is a test.</p>' + ' </div>' + - '</div>' - ); + '</div>'; const blockNode = wrapper.firstChild; expect( hasInnerBlocksContext( blockNode ) ).toBe( false ); @@ -20,7 +19,7 @@ describe( 'hasInnerBlocksContext()', () => { it( 'should return true for a block node which contains inner blocks', () => { const wrapper = document.createElement( 'div' ); - wrapper.innerHTML = ( + wrapper.innerHTML = '<div class="block-editor-block-list__block" data-type="core/columns" tabindex="0">' + ' <div class="block-editor-block-list__block-edit" aria-label="Block: Columns (beta)">' + ' <div class="wp-block-columns has-2-columns">' + @@ -28,8 +27,7 @@ describe( 'hasInnerBlocksContext()', () => { ' <div class="block-editor-block-list__layout"></div>' + ' </div>' + ' </div>' + - '</div>' - ); + '</div>'; const blockNode = wrapper.firstChild; expect( hasInnerBlocksContext( blockNode ) ).toBe( true ); diff --git a/packages/block-editor/src/utils/transform-styles/ast/parse.js b/packages/block-editor/src/utils/transform-styles/ast/parse.js index 11e8d22a378475..d3746488fc1fe2 100644 --- a/packages/block-editor/src/utils/transform-styles/ast/parse.js +++ b/packages/block-editor/src/utils/transform-styles/ast/parse.js @@ -67,7 +67,9 @@ export default function( css, options ) { const errorsList = []; function error( msg ) { - const err = new Error( options.source + ':' + lineno + ':' + column + ': ' + msg ); + const err = new Error( + options.source + ':' + lineno + ':' + column + ': ' + msg + ); err.reason = msg; err.filename = options.source; err.line = lineno; @@ -123,7 +125,11 @@ export default function( css, options ) { const accumulator = []; whitespace(); comments( accumulator ); - while ( css.length && css.charAt( 0 ) !== '}' && ( node = atrule() || rule() ) ) { + while ( + css.length && + css.charAt( 0 ) !== '}' && + ( node = atrule() || rule() ) + ) { if ( node !== false ) { accumulator.push( node ); comments( accumulator ); @@ -163,7 +169,7 @@ export default function( css, options ) { let c; accumulator = accumulator || []; // eslint-disable-next-line no-cond-assign - while ( c = comment() ) { + while ( ( c = comment() ) ) { if ( c !== false ) { accumulator.push( c ); } @@ -182,7 +188,10 @@ export default function( css, options ) { } let i = 2; - while ( '' !== css.charAt( i ) && ( '*' !== css.charAt( i ) || '/' !== css.charAt( i + 1 ) ) ) { + while ( + '' !== css.charAt( i ) && + ( '*' !== css.charAt( i ) || '/' !== css.charAt( i + 1 ) ) + ) { ++i; } i += 2; @@ -244,7 +253,9 @@ export default function( css, options ) { } // val - const val = match( /^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/ ); + const val = match( + /^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)/ + ); const ret = pos( { type: 'declaration', @@ -273,7 +284,7 @@ export default function( css, options ) { // declarations let decl; // eslint-disable-next-line no-cond-assign - while ( decl = declaration() ) { + while ( ( decl = declaration() ) ) { if ( decl !== false ) { decls.push( decl ); comments( decls ); @@ -296,7 +307,7 @@ export default function( css, options ) { const pos = position(); // eslint-disable-next-line no-cond-assign - while ( m = match( /^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/ ) ) { + while ( ( m = match( /^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/ ) ) ) { vals.push( m[ 1 ] ); match( /^,\s*/ ); } @@ -339,7 +350,7 @@ export default function( css, options ) { let frame; let frames = comments(); // eslint-disable-next-line no-cond-assign - while ( frame = keyframe() ) { + while ( ( frame = keyframe() ) ) { frames.push( frame ); frames = frames.concat( comments() ); } @@ -483,7 +494,7 @@ export default function( css, options ) { // declarations let decl; // eslint-disable-next-line no-cond-assign - while ( decl = declaration() ) { + while ( ( decl = declaration() ) ) { decls.push( decl ); decls = decls.concat( comments() ); } @@ -550,7 +561,7 @@ export default function( css, options ) { // declarations let decl; // eslint-disable-next-line no-cond-assign - while ( decl = declaration() ) { + while ( ( decl = declaration() ) ) { decls.push( decl ); decls = decls.concat( comments() ); } @@ -610,17 +621,19 @@ export default function( css, options ) { return; } - return atkeyframes() || - atmedia() || - atcustommedia() || - atsupports() || - atimport() || - atcharset() || - atnamespace() || - atdocument() || - atpage() || - athost() || - atfontface(); + return ( + atkeyframes() || + atmedia() || + atcustommedia() || + atsupports() || + atimport() || + atcharset() || + atnamespace() || + atdocument() || + atpage() || + athost() || + atfontface() + ); } /** diff --git a/packages/block-editor/src/utils/transform-styles/ast/stringify/compress.js b/packages/block-editor/src/utils/transform-styles/ast/stringify/compress.js index 6b3f0468c5ad65..7c9cf56ce38631 100644 --- a/packages/block-editor/src/utils/transform-styles/ast/stringify/compress.js +++ b/packages/block-editor/src/utils/transform-styles/ast/stringify/compress.js @@ -36,9 +36,7 @@ inherits( Compiler, Base ); */ Compiler.prototype.compile = function( node ) { - return node.stylesheet - .rules.map( this.visit, this ) - .join( '' ); + return node.stylesheet.rules.map( this.visit, this ).join( '' ); }; /** @@ -62,10 +60,12 @@ Compiler.prototype.import = function( node ) { */ Compiler.prototype.media = function( node ) { - return this.emit( '@media ' + node.media, node.position ) + - this.emit( '{' ) + - this.mapVisit( node.rules ) + - this.emit( '}' ); + return ( + this.emit( '@media ' + node.media, node.position ) + + this.emit( '{' ) + + this.mapVisit( node.rules ) + + this.emit( '}' ) + ); }; /** @@ -75,10 +75,12 @@ Compiler.prototype.media = function( node ) { Compiler.prototype.document = function( node ) { const doc = '@' + ( node.vendor || '' ) + 'document ' + node.document; - return this.emit( doc, node.position ) + - this.emit( '{' ) + - this.mapVisit( node.rules ) + - this.emit( '}' ); + return ( + this.emit( doc, node.position ) + + this.emit( '{' ) + + this.mapVisit( node.rules ) + + this.emit( '}' ) + ); }; /** @@ -102,10 +104,12 @@ Compiler.prototype.namespace = function( node ) { */ Compiler.prototype.supports = function( node ) { - return this.emit( '@supports ' + node.supports, node.position ) + - this.emit( '{' ) + - this.mapVisit( node.rules ) + - this.emit( '}' ); + return ( + this.emit( '@supports ' + node.supports, node.position ) + + this.emit( '{' ) + + this.mapVisit( node.rules ) + + this.emit( '}' ) + ); }; /** @@ -113,13 +117,15 @@ Compiler.prototype.supports = function( node ) { */ Compiler.prototype.keyframes = function( node ) { - return this.emit( '@' + - ( node.vendor || '' ) + - 'keyframes ' + - node.name, node.position ) + - this.emit( '{' ) + - this.mapVisit( node.keyframes ) + - this.emit( '}' ); + return ( + this.emit( + '@' + ( node.vendor || '' ) + 'keyframes ' + node.name, + node.position + ) + + this.emit( '{' ) + + this.mapVisit( node.keyframes ) + + this.emit( '}' ) + ); }; /** @@ -129,10 +135,12 @@ Compiler.prototype.keyframes = function( node ) { Compiler.prototype.keyframe = function( node ) { const decls = node.declarations; - return this.emit( node.values.join( ',' ), node.position ) + - this.emit( '{' ) + - this.mapVisit( decls ) + - this.emit( '}' ); + return ( + this.emit( node.values.join( ',' ), node.position ) + + this.emit( '{' ) + + this.mapVisit( decls ) + + this.emit( '}' ) + ); }; /** @@ -140,14 +148,14 @@ Compiler.prototype.keyframe = function( node ) { */ Compiler.prototype.page = function( node ) { - const sel = node.selectors.length ? - node.selectors.join( ', ' ) : - ''; + const sel = node.selectors.length ? node.selectors.join( ', ' ) : ''; - return this.emit( '@page ' + sel, node.position ) + - this.emit( '{' ) + - this.mapVisit( node.declarations ) + - this.emit( '}' ); + return ( + this.emit( '@page ' + sel, node.position ) + + this.emit( '{' ) + + this.mapVisit( node.declarations ) + + this.emit( '}' ) + ); }; /** @@ -155,10 +163,12 @@ Compiler.prototype.page = function( node ) { */ Compiler.prototype[ 'font-face' ] = function( node ) { - return this.emit( '@font-face', node.position ) + - this.emit( '{' ) + - this.mapVisit( node.declarations ) + - this.emit( '}' ); + return ( + this.emit( '@font-face', node.position ) + + this.emit( '{' ) + + this.mapVisit( node.declarations ) + + this.emit( '}' ) + ); }; /** @@ -166,10 +176,12 @@ Compiler.prototype[ 'font-face' ] = function( node ) { */ Compiler.prototype.host = function( node ) { - return this.emit( '@host', node.position ) + - this.emit( '{' ) + - this.mapVisit( node.rules ) + - this.emit( '}' ); + return ( + this.emit( '@host', node.position ) + + this.emit( '{' ) + + this.mapVisit( node.rules ) + + this.emit( '}' ) + ); }; /** @@ -177,7 +189,10 @@ Compiler.prototype.host = function( node ) { */ Compiler.prototype[ 'custom-media' ] = function( node ) { - return this.emit( '@custom-media ' + node.name + ' ' + node.media + ';', node.position ); + return this.emit( + '@custom-media ' + node.name + ' ' + node.media + ';', + node.position + ); }; /** @@ -190,10 +205,12 @@ Compiler.prototype.rule = function( node ) { return ''; } - return this.emit( node.selectors.join( ',' ), node.position ) + - this.emit( '{' ) + - this.mapVisit( decls ) + - this.emit( '}' ); + return ( + this.emit( node.selectors.join( ',' ), node.position ) + + this.emit( '{' ) + + this.mapVisit( decls ) + + this.emit( '}' ) + ); }; /** @@ -201,5 +218,8 @@ Compiler.prototype.rule = function( node ) { */ Compiler.prototype.declaration = function( node ) { - return this.emit( node.property + ':' + node.value, node.position ) + this.emit( ';' ); + return ( + this.emit( node.property + ':' + node.value, node.position ) + + this.emit( ';' ) + ); }; diff --git a/packages/block-editor/src/utils/transform-styles/ast/stringify/identity.js b/packages/block-editor/src/utils/transform-styles/ast/stringify/identity.js index cc15526e14946e..ceb711a8dbc71a 100644 --- a/packages/block-editor/src/utils/transform-styles/ast/stringify/identity.js +++ b/packages/block-editor/src/utils/transform-styles/ast/stringify/identity.js @@ -56,7 +56,10 @@ Compiler.prototype.stylesheet = function( node ) { */ Compiler.prototype.comment = function( node ) { - return this.emit( this.indent() + '/*' + node.comment + '*/', node.position ); + return this.emit( + this.indent() + '/*' + node.comment + '*/', + node.position + ); }; /** @@ -74,14 +77,9 @@ Compiler.prototype.import = function( node ) { Compiler.prototype.media = function( node ) { return ( this.emit( '@media ' + node.media, node.position ) + - this.emit( - ' {\n' + this.indent( 1 ) - ) + + this.emit( ' {\n' + this.indent( 1 ) ) + this.mapVisit( node.rules, '\n\n' ) + - this.emit( - this.indent( -1 ) + - '\n}' - ) + this.emit( this.indent( -1 ) + '\n}' ) ); }; @@ -94,16 +92,9 @@ Compiler.prototype.document = function( node ) { return ( this.emit( doc, node.position ) + - this.emit( - ' ' + - ' {\n' + - this.indent( 1 ) - ) + + this.emit( ' ' + ' {\n' + this.indent( 1 ) ) + this.mapVisit( node.rules, '\n\n' ) + - this.emit( - this.indent( -1 ) + - '\n}' - ) + this.emit( this.indent( -1 ) + '\n}' ) ); }; @@ -130,15 +121,9 @@ Compiler.prototype.namespace = function( node ) { Compiler.prototype.supports = function( node ) { return ( this.emit( '@supports ' + node.supports, node.position ) + - this.emit( - ' {\n' + - this.indent( 1 ) - ) + + this.emit( ' {\n' + this.indent( 1 ) ) + this.mapVisit( node.rules, '\n\n' ) + - this.emit( - this.indent( -1 ) + - '\n}' - ) + this.emit( this.indent( -1 ) + '\n}' ) ); }; @@ -148,16 +133,13 @@ Compiler.prototype.supports = function( node ) { Compiler.prototype.keyframes = function( node ) { return ( - this.emit( '@' + ( node.vendor || '' ) + 'keyframes ' + node.name, node.position ) + this.emit( - ' {\n' + - this.indent( 1 ) + '@' + ( node.vendor || '' ) + 'keyframes ' + node.name, + node.position ) + + this.emit( ' {\n' + this.indent( 1 ) ) + this.mapVisit( node.keyframes, '\n' ) + - this.emit( - this.indent( -1 ) + - '}' - ) + this.emit( this.indent( -1 ) + '}' ) ); }; @@ -171,16 +153,9 @@ Compiler.prototype.keyframe = function( node ) { return ( this.emit( this.indent() ) + this.emit( node.values.join( ', ' ), node.position ) + - this.emit( - ' {\n' + - this.indent( 1 ) - ) + + this.emit( ' {\n' + this.indent( 1 ) ) + this.mapVisit( decls, '\n' ) + - this.emit( - this.indent( -1 ) + - '\n' + - this.indent() + '}\n' - ) + this.emit( this.indent( -1 ) + '\n' + this.indent() + '}\n' ) ); }; @@ -189,16 +164,16 @@ Compiler.prototype.keyframe = function( node ) { */ Compiler.prototype.page = function( node ) { - const sel = node.selectors.length ? - node.selectors.join( ', ' ) + ' ' : - ''; - - return this.emit( '@page ' + sel, node.position ) + - this.emit( '{\n' ) + - this.emit( this.indent( 1 ) ) + - this.mapVisit( node.declarations, '\n' ) + - this.emit( this.indent( -1 ) ) + - this.emit( '\n}' ); + const sel = node.selectors.length ? node.selectors.join( ', ' ) + ' ' : ''; + + return ( + this.emit( '@page ' + sel, node.position ) + + this.emit( '{\n' ) + + this.emit( this.indent( 1 ) ) + + this.mapVisit( node.declarations, '\n' ) + + this.emit( this.indent( -1 ) ) + + this.emit( '\n}' ) + ); }; /** @@ -206,12 +181,14 @@ Compiler.prototype.page = function( node ) { */ Compiler.prototype[ 'font-face' ] = function( node ) { - return this.emit( '@font-face ', node.position ) + - this.emit( '{\n' ) + - this.emit( this.indent( 1 ) ) + - this.mapVisit( node.declarations, '\n' ) + - this.emit( this.indent( -1 ) ) + - this.emit( '\n}' ); + return ( + this.emit( '@font-face ', node.position ) + + this.emit( '{\n' ) + + this.emit( this.indent( 1 ) ) + + this.mapVisit( node.declarations, '\n' ) + + this.emit( this.indent( -1 ) ) + + this.emit( '\n}' ) + ); }; /** @@ -221,15 +198,9 @@ Compiler.prototype[ 'font-face' ] = function( node ) { Compiler.prototype.host = function( node ) { return ( this.emit( '@host', node.position ) + - this.emit( - ' {\n' + - this.indent( 1 ) - ) + + this.emit( ' {\n' + this.indent( 1 ) ) + this.mapVisit( node.rules, '\n\n' ) + - this.emit( - this.indent( -1 ) + - '\n}' - ) + this.emit( this.indent( -1 ) + '\n}' ) ); }; @@ -238,7 +209,10 @@ Compiler.prototype.host = function( node ) { */ Compiler.prototype[ 'custom-media' ] = function( node ) { - return this.emit( '@custom-media ' + node.name + ' ' + node.media + ';', node.position ); + return this.emit( + '@custom-media ' + node.name + ' ' + node.media + ';', + node.position + ); }; /** @@ -252,14 +226,21 @@ Compiler.prototype.rule = function( node ) { return ''; } - return this.emit( node.selectors.map( function( s ) { - return indent + s; - } ).join( ',\n' ), node.position ) + - this.emit( ' {\n' ) + - this.emit( this.indent( 1 ) ) + - this.mapVisit( decls, '\n' ) + - this.emit( this.indent( -1 ) ) + - this.emit( '\n' + this.indent() + '}' ); + return ( + this.emit( + node.selectors + .map( function( s ) { + return indent + s; + } ) + .join( ',\n' ), + node.position + ) + + this.emit( ' {\n' ) + + this.emit( this.indent( 1 ) ) + + this.mapVisit( decls, '\n' ) + + this.emit( this.indent( -1 ) ) + + this.emit( '\n' + this.indent() + '}' ) + ); }; /** @@ -267,9 +248,11 @@ Compiler.prototype.rule = function( node ) { */ Compiler.prototype.declaration = function( node ) { - return this.emit( this.indent() ) + - this.emit( node.property + ': ' + node.value, node.position ) + - this.emit( ';' ); + return ( + this.emit( this.indent() ) + + this.emit( node.property + ': ' + node.value, node.position ) + + this.emit( ';' ) + ); }; /** diff --git a/packages/block-editor/src/utils/transform-styles/ast/stringify/index.js b/packages/block-editor/src/utils/transform-styles/ast/stringify/index.js index 2dcc4d4475f02b..a326ce1e54b139 100644 --- a/packages/block-editor/src/utils/transform-styles/ast/stringify/index.js +++ b/packages/block-editor/src/utils/transform-styles/ast/stringify/index.js @@ -23,9 +23,9 @@ import Identity from './identity'; export default function( node, options ) { options = options || {}; - const compiler = options.compress ? - new Compressed( options ) : - new Identity( options ); + const compiler = options.compress + ? new Compressed( options ) + : new Identity( options ); const code = compiler.compile( node ); return code; diff --git a/packages/block-editor/src/utils/transform-styles/test/traverse.js b/packages/block-editor/src/utils/transform-styles/test/traverse.js index 50d6d8da95bc3f..bb1be2635fe535 100644 --- a/packages/block-editor/src/utils/transform-styles/test/traverse.js +++ b/packages/block-editor/src/utils/transform-styles/test/traverse.js @@ -10,7 +10,9 @@ describe( 'CSS traverse', () => { if ( node.type === 'rule' ) { return { ...node, - selectors: node.selectors.map( ( selector ) => 'namespace ' + selector ), + selectors: node.selectors.map( + ( selector ) => 'namespace ' + selector + ), }; } diff --git a/packages/block-editor/src/utils/transform-styles/transforms/url-rewrite.js b/packages/block-editor/src/utils/transform-styles/transforms/url-rewrite.js index 4516fffa2196dc..4e0d07e4083d74 100644 --- a/packages/block-editor/src/utils/transform-styles/transforms/url-rewrite.js +++ b/packages/block-editor/src/utils/transform-styles/transforms/url-rewrite.js @@ -34,7 +34,10 @@ function isAbsolutePath( filePath ) { */ function isValidURL( meta ) { // ignore hashes or data uris - if ( meta.value.indexOf( 'data:' ) === 0 || meta.value.indexOf( '#' ) === 0 ) { + if ( + meta.value.indexOf( 'data:' ) === 0 || + meta.value.indexOf( '#' ) === 0 + ) { return false; } diff --git a/packages/block-editor/src/utils/transform-styles/transforms/wrap.js b/packages/block-editor/src/utils/transform-styles/transforms/wrap.js index d7fe709138fa83..e9cfafac5740fb 100644 --- a/packages/block-editor/src/utils/transform-styles/transforms/wrap.js +++ b/packages/block-editor/src/utils/transform-styles/transforms/wrap.js @@ -15,9 +15,11 @@ const wrap = ( namespace, ignore = [] ) => ( node ) => { } // Anything other than a root tag is always prefixed. - {if ( ! selector.match( IS_ROOT_TAG ) ) { - return namespace + ' ' + selector; - }} + { + if ( ! selector.match( IS_ROOT_TAG ) ) { + return namespace + ' ' + selector; + } + } // HTML and Body elements cannot be contained within our container so lets extract their styles. return selector.replace( /^(body|html|:root)/, namespace ); diff --git a/packages/block-library/package.json b/packages/block-library/package.json index fe1bb28e1f96e9..1a99a38df771fb 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -37,6 +37,8 @@ "@wordpress/data": "file:../data", "@wordpress/date": "file:../date", "@wordpress/deprecated": "file:../deprecated", + "@wordpress/dom": "file:../dom", + "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", "@wordpress/escape-html": "file:../escape-html", "@wordpress/i18n": "file:../i18n", diff --git a/packages/block-library/src/archives/edit.js b/packages/block-library/src/archives/edit.js index 324080d8ea18a0..8223111736c633 100644 --- a/packages/block-library/src/archives/edit.js +++ b/packages/block-library/src/archives/edit.js @@ -1,11 +1,7 @@ /** * WordPress dependencies */ -import { - PanelBody, - ToggleControl, - Disabled, -} from '@wordpress/components'; +import { PanelBody, ToggleControl, Disabled } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { InspectorControls } from '@wordpress/block-editor'; import ServerSideRender from '@wordpress/server-side-render'; @@ -20,17 +16,28 @@ export default function ArchivesEdit( { attributes, setAttributes } ) { <ToggleControl label={ __( 'Display as Dropdown' ) } checked={ displayAsDropdown } - onChange={ () => setAttributes( { displayAsDropdown: ! displayAsDropdown } ) } + onChange={ () => + setAttributes( { + displayAsDropdown: ! displayAsDropdown, + } ) + } /> <ToggleControl label={ __( 'Show Post Counts' ) } checked={ showPostCounts } - onChange={ () => setAttributes( { showPostCounts: ! showPostCounts } ) } + onChange={ () => + setAttributes( { + showPostCounts: ! showPostCounts, + } ) + } /> </PanelBody> </InspectorControls> <Disabled> - <ServerSideRender block="core/archives" attributes={ attributes } /> + <ServerSideRender + block="core/archives" + attributes={ attributes } + /> </Disabled> </> ); diff --git a/packages/block-library/src/audio/deprecated.js b/packages/block-library/src/audio/deprecated.js index 733591bde00608..075a173db44521 100644 --- a/packages/block-library/src/audio/deprecated.js +++ b/packages/block-library/src/audio/deprecated.js @@ -47,8 +47,19 @@ export default [ return ( <figure> - <audio controls="controls" src={ src } autoPlay={ autoplay } loop={ loop } preload={ preload } /> - { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> } + <audio + controls="controls" + src={ src } + autoPlay={ autoplay } + loop={ loop } + preload={ preload } + /> + { ! RichText.isEmpty( caption ) && ( + <RichText.Content + tagName="figcaption" + value={ caption } + /> + ) } </figure> ); }, diff --git a/packages/block-library/src/audio/edit.js b/packages/block-library/src/audio/edit.js index 49aa2ddc9f167a..d9970ecd93dbf4 100644 --- a/packages/block-library/src/audio/edit.js +++ b/packages/block-library/src/audio/edit.js @@ -80,9 +80,9 @@ class AudioEdit extends Component { // the editing UI. if ( newSrc !== src ) { // Check if there's an embed block that handles this URL. - const embedBlock = createUpgradedEmbedBlock( - { attributes: { url: newSrc } } - ); + const embedBlock = createUpgradedEmbedBlock( { + attributes: { url: newSrc }, + } ); if ( undefined !== embedBlock ) { this.props.onReplace( embedBlock ); return; @@ -98,7 +98,11 @@ class AudioEdit extends Component { } getAutoplayHelp( checked ) { - return checked ? __( 'Note: Autoplaying audio may cause usability issues for some visitors.' ) : null; + return checked + ? __( + 'Note: Autoplaying audio may cause usability issues for some visitors.' + ) + : null; } render() { @@ -160,7 +164,12 @@ class AudioEdit extends Component { label={ __( 'Preload' ) } value={ undefined !== preload ? preload : 'none' } // `undefined` is required for the preload attribute to be unset. - onChange={ ( value ) => setAttributes( { preload: ( 'none' !== value ) ? value : undefined } ) } + onChange={ ( value ) => + setAttributes( { + preload: + 'none' !== value ? value : undefined, + } ) + } options={ [ { value: 'auto', label: __( 'Auto' ) }, { value: 'metadata', label: __( 'Metadata' ) }, @@ -182,7 +191,9 @@ class AudioEdit extends Component { tagName="figcaption" placeholder={ __( 'Write caption…' ) } value={ caption } - onChange={ ( value ) => setAttributes( { caption: value } ) } + onChange={ ( value ) => + setAttributes( { caption: value } ) + } inlineToolbar /> ) } diff --git a/packages/block-library/src/audio/index.js b/packages/block-library/src/audio/index.js index 9e7e414f93c176..2946d5eca61bcd 100644 --- a/packages/block-library/src/audio/index.js +++ b/packages/block-library/src/audio/index.js @@ -20,7 +20,12 @@ export { metadata, name }; export const settings = { title: __( 'Audio' ), description: __( 'Embed a simple audio player.' ), - keywords: [ __( 'music' ), __( 'sound' ), __( 'podcast' ), __( 'recording' ) ], + keywords: [ + __( 'music' ), + __( 'sound' ), + __( 'podcast' ), + __( 'recording' ), + ], icon, transforms, deprecated, diff --git a/packages/block-library/src/audio/save.js b/packages/block-library/src/audio/save.js index c8370c6b7d66e1..f88eb0bba52e0c 100644 --- a/packages/block-library/src/audio/save.js +++ b/packages/block-library/src/audio/save.js @@ -6,10 +6,20 @@ import { RichText } from '@wordpress/block-editor'; export default function save( { attributes } ) { const { autoplay, caption, loop, preload, src } = attributes; - return src && ( - <figure> - <audio controls="controls" src={ src } autoPlay={ autoplay } loop={ loop } preload={ preload } /> - { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> } - </figure> + return ( + src && ( + <figure> + <audio + controls="controls" + src={ src } + autoPlay={ autoplay } + loop={ loop } + preload={ preload } + /> + { ! RichText.isEmpty( caption ) && ( + <RichText.Content tagName="figcaption" value={ caption } /> + ) } + </figure> + ) ); } diff --git a/packages/block-library/src/audio/transforms.js b/packages/block-library/src/audio/transforms.js index 01396eda20dff0..3a61af6807cc51 100644 --- a/packages/block-library/src/audio/transforms.js +++ b/packages/block-library/src/audio/transforms.js @@ -9,7 +9,10 @@ const transforms = { { type: 'files', isMatch( files ) { - return files.length === 1 && files[ 0 ].type.indexOf( 'audio/' ) === 0; + return ( + files.length === 1 && + files[ 0 ].type.indexOf( 'audio/' ) === 0 + ); }, transform( files ) { const file = files[ 0 ]; diff --git a/packages/block-library/src/block/edit-panel/index.js b/packages/block-library/src/block/edit-panel/index.js index ee2097a038b8ae..4205167208cccf 100644 --- a/packages/block-library/src/block/edit-panel/index.js +++ b/packages/block-library/src/block/edit-panel/index.js @@ -31,7 +31,11 @@ class ReusableBlockEditPanel extends Component { this.titleField.current.select(); } // Move focus back to the Edit button after pressing the Escape key or Save. - if ( ( prevProps.isEditing || prevProps.isSaving ) && ! this.props.isEditing && ! this.props.isSaving ) { + if ( + ( prevProps.isEditing || prevProps.isSaving ) && + ! this.props.isEditing && + ! this.props.isSaving + ) { this.editButton.current.focus(); } } @@ -53,11 +57,18 @@ class ReusableBlockEditPanel extends Component { } render() { - const { isEditing, title, isSaving, isEditDisabled, onEdit, instanceId } = this.props; + const { + isEditing, + title, + isSaving, + isEditDisabled, + onEdit, + instanceId, + } = this.props; return ( <> - { ( ! isEditing && ! isSaving ) && ( + { ! isEditing && ! isSaving && ( <div className="reusable-block-edit-panel"> <b className="reusable-block-edit-panel__info"> { title } @@ -74,7 +85,10 @@ class ReusableBlockEditPanel extends Component { </div> ) } { ( isEditing || isSaving ) && ( - <form className="reusable-block-edit-panel" onSubmit={ this.handleFormSubmit }> + <form + className="reusable-block-edit-panel" + onSubmit={ this.handleFormSubmit } + > <label htmlFor={ `reusable-block-edit-panel__title-${ instanceId }` } className="reusable-block-edit-panel__label" diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 76f6e47cf13624..296fb704dbfa7e 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -8,10 +8,7 @@ import { partial } from 'lodash'; */ import { Component } from '@wordpress/element'; import { Placeholder, Spinner, Disabled } from '@wordpress/components'; -import { - withSelect, - withDispatch, -} from '@wordpress/data'; +import { withSelect, withDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { BlockEditorProvider, @@ -60,7 +57,10 @@ class ReusableBlockEdit extends Component { } componentDidUpdate( prevProps ) { - if ( prevProps.reusableBlock !== this.props.reusableBlock && this.state.title === null ) { + if ( + prevProps.reusableBlock !== this.props.reusableBlock && + this.state.title === null + ) { this.setState( { title: this.props.reusableBlock.title, blocks: parse( this.props.reusableBlock.content ), @@ -104,15 +104,30 @@ class ReusableBlockEdit extends Component { } render() { - const { isSelected, reusableBlock, isFetching, isSaving, canUpdateBlock, settings } = this.props; + const { + isSelected, + reusableBlock, + isFetching, + isSaving, + canUpdateBlock, + settings, + } = this.props; const { isEditing, title, blocks } = this.state; if ( ! reusableBlock && isFetching ) { - return <Placeholder><Spinner /></Placeholder>; + return ( + <Placeholder> + <Spinner /> + </Placeholder> + ); } if ( ! reusableBlock ) { - return <Placeholder>{ __( 'Block has been deleted or is unavailable.' ) }</Placeholder>; + return ( + <Placeholder> + { __( 'Block has been deleted or is unavailable.' ) } + </Placeholder> + ); } let element = ( @@ -160,10 +175,9 @@ export default compose( [ __experimentalIsSavingReusableBlock: isSavingReusableBlock, } = select( 'core/editor' ); const { canUser } = select( 'core' ); - const { - __experimentalGetParsedReusableBlock, - getSettings, - } = select( 'core/block-editor' ); + const { __experimentalGetParsedReusableBlock, getSettings } = select( + 'core/block-editor' + ); const { ref } = ownProps.attributes; const reusableBlock = getReusableBlock( ref ); @@ -171,8 +185,13 @@ export default compose( [ reusableBlock, isFetching: isFetchingReusableBlock( ref ), isSaving: isSavingReusableBlock( ref ), - blocks: reusableBlock ? __experimentalGetParsedReusableBlock( reusableBlock.id ) : null, - canUpdateBlock: !! reusableBlock && ! reusableBlock.isTemporary && !! canUser( 'update', 'blocks', ref ), + blocks: reusableBlock + ? __experimentalGetParsedReusableBlock( reusableBlock.id ) + : null, + canUpdateBlock: + !! reusableBlock && + ! reusableBlock.isTemporary && + !! canUser( 'update', 'blocks', ref ), settings: getSettings(), }; } ), diff --git a/packages/block-library/src/block/index.js b/packages/block-library/src/block/index.js index f66fca566f09a7..585333c696a6f5 100644 --- a/packages/block-library/src/block/index.js +++ b/packages/block-library/src/block/index.js @@ -13,7 +13,9 @@ export const name = 'core/block'; export const settings = { title: __( 'Reusable Block' ), category: 'reusable', - description: __( 'Create content, and save it for you and other contributors to reuse across your site. Update the block, and the changes apply everywhere it’s used.' ), + description: __( + 'Create content, and save it for you and other contributors to reuse across your site. Update the block, and the changes apply everywhere it’s used.' + ), supports: { customClassName: false, html: false, diff --git a/packages/block-library/src/button/deprecated.js b/packages/block-library/src/button/deprecated.js index 5e1951c60ec64c..18045aa562c00a 100644 --- a/packages/block-library/src/button/deprecated.js +++ b/packages/block-library/src/button/deprecated.js @@ -7,17 +7,23 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { - RichText, - getColorClassName, -} from '@wordpress/block-editor'; +import { RichText, getColorClassName } from '@wordpress/block-editor'; const colorsMigration = ( attributes ) => { - return omit( { - ...attributes, - customTextColor: attributes.textColor && '#' === attributes.textColor[ 0 ] ? attributes.textColor : undefined, - customBackgroundColor: attributes.color && '#' === attributes.color[ 0 ] ? attributes.color : undefined, - }, [ 'color', 'textColor' ] ); + return omit( + { + ...attributes, + customTextColor: + attributes.textColor && '#' === attributes.textColor[ 0 ] + ? attributes.textColor + : undefined, + customBackgroundColor: + attributes.color && '#' === attributes.color[ 0 ] + ? attributes.color + : undefined, + }, + [ 'color', 'textColor' ] + ); }; const blockAttributes = { @@ -77,12 +83,17 @@ const deprecated = [ }, }, isEligible( attribute ) { - return attribute.className && attribute.className.includes( 'is-style-squared' ); + return ( + attribute.className && + attribute.className.includes( 'is-style-squared' ) + ); }, migrate( attributes ) { let newClassName = attributes.className; if ( newClassName ) { - newClassName = newClassName.replace( /is-style-squared[\s]?/, '' ).trim(); + newClassName = newClassName + .replace( /is-style-squared[\s]?/, '' ) + .trim(); } return { ...attributes, @@ -104,7 +115,10 @@ const deprecated = [ } = attributes; const textClass = getColorClassName( 'color', textColor ); - const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + const backgroundClass = getColorClassName( + 'background-color', + backgroundColor + ); const buttonClasses = classnames( 'wp-block-button__link', { 'has-text-color': textColor || customTextColor, @@ -114,7 +128,9 @@ const deprecated = [ } ); const buttonStyle = { - backgroundColor: backgroundClass ? undefined : customBackgroundColor, + backgroundColor: backgroundClass + ? undefined + : customBackgroundColor, color: textClass ? undefined : customTextColor, }; @@ -166,7 +182,10 @@ const deprecated = [ } = attributes; const textClass = getColorClassName( 'color', textColor ); - const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + const backgroundClass = getColorClassName( + 'background-color', + backgroundColor + ); const buttonClasses = classnames( 'wp-block-button__link', { 'has-text-color': textColor || customTextColor, @@ -176,7 +195,9 @@ const deprecated = [ } ); const buttonStyle = { - backgroundColor: backgroundClass ? undefined : customBackgroundColor, + backgroundColor: backgroundClass + ? undefined + : customBackgroundColor, color: textClass ? undefined : customTextColor, }; @@ -252,7 +273,10 @@ const deprecated = [ const { url, text, title, align, color, textColor } = attributes; return ( - <div className={ `align${ align }` } style={ { backgroundColor: color } }> + <div + className={ `align${ align }` } + style={ { backgroundColor: color } } + > <RichText.Content tagName="a" href={ url } diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index b67c34581c7f0c..69277fd52c9766 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -9,9 +9,7 @@ import { escape } from 'lodash'; */ import { __ } from '@wordpress/i18n'; import { useCallback, useState } from '@wordpress/element'; -import { - compose, -} from '@wordpress/compose'; +import { compose } from '@wordpress/compose'; import { KeyboardShortcuts, PanelBody, @@ -33,10 +31,7 @@ import { withColors, __experimentalLinkControl as LinkControl, } from '@wordpress/block-editor'; -import { - rawShortcut, - displayShortcut, -} from '@wordpress/keycodes'; +import { rawShortcut, displayShortcut } from '@wordpress/keycodes'; const { getComputedStyle } = window; @@ -45,10 +40,19 @@ const applyFallbackStyles = withFallbackStyles( ( node, ownProps ) => { const backgroundColorValue = backgroundColor && backgroundColor.color; const textColorValue = textColor && textColor.color; //avoid the use of querySelector if textColor color is known and verify if node is available. - const textNode = ! textColorValue && node ? node.querySelector( '[contenteditable="true"]' ) : null; + const textNode = + ! textColorValue && node + ? node.querySelector( '[contenteditable="true"]' ) + : null; return { - fallbackBackgroundColor: backgroundColorValue || ! node ? undefined : getComputedStyle( node ).backgroundColor, - fallbackTextColor: textColorValue || ! textNode ? undefined : getComputedStyle( textNode ).color, + fallbackBackgroundColor: + backgroundColorValue || ! node + ? undefined + : getComputedStyle( node ).backgroundColor, + fallbackTextColor: + textColorValue || ! textNode + ? undefined + : getComputedStyle( textNode ).color, }; } ); @@ -79,7 +83,14 @@ function BorderPanel( { borderRadius = '', setAttributes } ) { ); } -function URLPicker( { isSelected, url, title, setAttributes, opensInNewTab, onToggleOpenInNewTab } ) { +function URLPicker( { + isSelected, + url, + title, + setAttributes, + opensInNewTab, + onToggleOpenInNewTab, +} ) { const [ isURLPickerOpen, setIsURLPickerOpen ] = useState( false ); const openLinkControl = () => { setIsURLPickerOpen( true ); @@ -92,7 +103,11 @@ function URLPicker( { isSelected, url, title, setAttributes, opensInNewTab, onTo <LinkControl className="wp-block-navigation-link__inline-link-input" value={ { url, title, opensInNewTab } } - onChange={ ( { title: newTitle = '', url: newURL = '', opensInNewTab: newOpensInNewTab } ) => { + onChange={ ( { + title: newTitle = '', + url: newURL = '', + opensInNewTab: newOpensInNewTab, + } ) => { setAttributes( { title: escape( newTitle ), url: newURL, @@ -190,23 +205,23 @@ function ButtonEdit( { value={ text } onChange={ ( value ) => setAttributes( { text: value } ) } withoutInteractiveFormatting - className={ classnames( - 'wp-block-button__link', { - 'has-background': backgroundColor.color || gradientValue, - [ backgroundColor.class ]: ! gradientValue && backgroundColor.class, - 'has-text-color': textColor.color, - [ textColor.class ]: textColor.class, - [ gradientClass ]: gradientClass, - 'no-border-radius': borderRadius === 0, - } - ) } + className={ classnames( 'wp-block-button__link', { + 'has-background': backgroundColor.color || gradientValue, + [ backgroundColor.class ]: + ! gradientValue && backgroundColor.class, + 'has-text-color': textColor.color, + [ textColor.class ]: textColor.class, + [ gradientClass ]: gradientClass, + 'no-border-radius': borderRadius === 0, + } ) } style={ { - ...( ! backgroundColor.color && gradientValue ? - { background: gradientValue } : - { backgroundColor: backgroundColor.color } - ), + ...( ! backgroundColor.color && gradientValue + ? { background: gradientValue } + : { backgroundColor: backgroundColor.color } ), color: textColor.color, - borderRadius: borderRadius ? borderRadius + 'px' : undefined, + borderRadius: borderRadius + ? borderRadius + 'px' + : undefined, } } /> <URLPicker diff --git a/packages/block-library/src/button/index.js b/packages/block-library/src/button/index.js index ec32d4989ad022..c74cf4ed4beda2 100644 --- a/packages/block-library/src/button/index.js +++ b/packages/block-library/src/button/index.js @@ -18,7 +18,9 @@ export { metadata, name }; export const settings = { title: __( 'Button' ), - description: __( 'Prompt visitors to take action with a button-style link.' ), + description: __( + 'Prompt visitors to take action with a button-style link.' + ), icon, keywords: [ __( 'link' ) ], example: { diff --git a/packages/block-library/src/button/save.js b/packages/block-library/src/button/save.js index 59eaa9f201217e..c171dfa39ef34b 100644 --- a/packages/block-library/src/button/save.js +++ b/packages/block-library/src/button/save.js @@ -29,13 +29,19 @@ export default function save( { attributes } ) { } = attributes; const textClass = getColorClassName( 'color', textColor ); - const backgroundClass = ! customGradient && getColorClassName( 'background-color', backgroundColor ); + const backgroundClass = + ! customGradient && + getColorClassName( 'background-color', backgroundColor ); const gradientClass = __experimentalGetGradientClass( gradient ); const buttonClasses = classnames( 'wp-block-button__link', { 'has-text-color': textColor || customTextColor, [ textClass ]: textClass, - 'has-background': backgroundColor || customBackgroundColor || customGradient || gradient, + 'has-background': + backgroundColor || + customBackgroundColor || + customGradient || + gradient, [ backgroundClass ]: backgroundClass, 'no-border-radius': borderRadius === 0, [ gradientClass ]: gradientClass, @@ -43,7 +49,10 @@ export default function save( { attributes } ) { const buttonStyle = { background: customGradient ? customGradient : undefined, - backgroundColor: backgroundClass || customGradient || gradient ? undefined : customBackgroundColor, + backgroundColor: + backgroundClass || customGradient || gradient + ? undefined + : customBackgroundColor, color: textClass ? undefined : customTextColor, borderRadius: borderRadius ? borderRadius + 'px' : undefined, }; diff --git a/packages/block-library/src/button/style.scss b/packages/block-library/src/button/style.scss index f2ad6bf77b130c..64e5174f287a3e 100644 --- a/packages/block-library/src/button/style.scss +++ b/packages/block-library/src/button/style.scss @@ -36,6 +36,10 @@ $blocks-button__height: 56px; } } +.wp-gs .wp-block-button__link:not(.has-background) { + background-color: var(--wp-block-core-button--color--background, var(--wp-color--primary, $dark-gray-700)); +} + .is-style-squared .wp-block-button__link { border-radius: 0; } diff --git a/packages/block-library/src/buttons/icon.js b/packages/block-library/src/buttons/icon.js index 6e18a60a648fe3..73208af2f03f84 100644 --- a/packages/block-library/src/buttons/icon.js +++ b/packages/block-library/src/buttons/icon.js @@ -4,5 +4,10 @@ import { G, Path, SVG } from '@wordpress/components'; export default ( - <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M19 6H5c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H5V8h14v8z" /></G></SVG> + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <Path fill="none" d="M0 0h24v24H0V0z" /> + <G> + <Path d="M19 6H5c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 10H5V8h14v8z" /> + </G> + </SVG> ); diff --git a/packages/block-library/src/buttons/index.js b/packages/block-library/src/buttons/index.js index 719c0949152660..211c07f4e43ad0 100644 --- a/packages/block-library/src/buttons/index.js +++ b/packages/block-library/src/buttons/index.js @@ -17,7 +17,9 @@ export { metadata, name }; export const settings = { title: __( 'Buttons' ), - description: __( 'Prompt visitors to take action with a group of button-style links.' ), + description: __( + 'Prompt visitors to take action with a group of button-style links.' + ), icon, keywords: [ __( 'link' ) ], supports: { diff --git a/packages/block-library/src/calendar/edit.js b/packages/block-library/src/calendar/edit.js index 243387c6dec399..00eb9b8a480699 100644 --- a/packages/block-library/src/calendar/edit.js +++ b/packages/block-library/src/calendar/edit.js @@ -7,9 +7,7 @@ import memoize from 'memize'; /** * WordPress dependencies */ -import { - Disabled, -} from '@wordpress/components'; +import { Disabled } from '@wordpress/components'; import { Component } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; import ServerSideRender from '@wordpress/server-side-render'; @@ -17,13 +15,14 @@ import ServerSideRender from '@wordpress/server-side-render'; class CalendarEdit extends Component { constructor() { super( ...arguments ); - this.getYearMonth = memoize( - this.getYearMonth.bind( this ), - { maxSize: 1 } - ); + this.getYearMonth = memoize( this.getYearMonth.bind( this ), { + maxSize: 1, + } ); this.getServerSideAttributes = memoize( this.getServerSideAttributes.bind( this ), - { maxSize: 1 } + { + maxSize: 1, + } ); } @@ -65,16 +64,13 @@ export default withSelect( ( select ) => { if ( ! coreEditorSelect ) { return; } - const { - getEditedPostAttribute, - } = coreEditorSelect; + const { getEditedPostAttribute } = coreEditorSelect; const postType = getEditedPostAttribute( 'type' ); // Dates are used to overwrite year and month used on the calendar. // This overwrite should only happen for 'post' post types. // For other post types the calendar always displays the current month. return { - date: postType === 'post' ? - getEditedPostAttribute( 'date' ) : - undefined, + date: + postType === 'post' ? getEditedPostAttribute( 'date' ) : undefined, }; } )( CalendarEdit ); diff --git a/packages/block-library/src/calendar/icon.js b/packages/block-library/src/calendar/icon.js index 023a1902171d04..c89c33a333ea53 100644 --- a/packages/block-library/src/calendar/icon.js +++ b/packages/block-library/src/calendar/icon.js @@ -4,5 +4,10 @@ import { G, Path, SVG } from '@wordpress/components'; export default ( - <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M7 11h2v2H7v-2zm14-5v14c0 1.1-.9 2-2 2H5c-1.11 0-2-.9-2-2l.01-14c0-1.1.88-2 1.99-2h1V2h2v2h8V2h2v2h1c1.1 0 2 .9 2 2zM5 8h14V6H5v2zm14 12V10H5v10h14zm-4-7h2v-2h-2v2zm-4 0h2v-2h-2v2z" /></G></SVG> + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <Path fill="none" d="M0 0h24v24H0V0z" /> + <G> + <Path d="M7 11h2v2H7v-2zm14-5v14c0 1.1-.9 2-2 2H5c-1.11 0-2-.9-2-2l.01-14c0-1.1.88-2 1.99-2h1V2h2v2h8V2h2v2h1c1.1 0 2 .9 2 2zM5 8h14V6H5v2zm14 12V10H5v10h14zm-4-7h2v-2h-2v2zm-4 0h2v-2h-2v2z" /> + </G> + </SVG> ); diff --git a/packages/block-library/src/categories/edit.js b/packages/block-library/src/categories/edit.js index 09e63af5d21ae8..3df75ffddffe84 100644 --- a/packages/block-library/src/categories/edit.js +++ b/packages/block-library/src/categories/edit.js @@ -6,7 +6,12 @@ import { times, unescape } from 'lodash'; /** * WordPress dependencies */ -import { PanelBody, Placeholder, Spinner, ToggleControl } from '@wordpress/components'; +import { + PanelBody, + Placeholder, + Spinner, + ToggleControl, +} from '@wordpress/components'; import { compose, withInstanceId } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; import { InspectorControls } from '@wordpress/block-editor'; @@ -17,7 +22,9 @@ class CategoriesEdit extends Component { constructor() { super( ...arguments ); - this.toggleDisplayAsDropdown = this.toggleDisplayAsDropdown.bind( this ); + this.toggleDisplayAsDropdown = this.toggleDisplayAsDropdown.bind( + this + ); this.toggleShowPostCounts = this.toggleShowPostCounts.bind( this ); this.toggleShowHierarchy = this.toggleShowHierarchy.bind( this ); } @@ -53,7 +60,9 @@ class CategoriesEdit extends Component { return categories; } - return categories.filter( ( category ) => category.parent === parentId ); + return categories.filter( + ( category ) => category.parent === parentId + ); } getCategoryListClassName( level ) { @@ -75,7 +84,9 @@ class CategoriesEdit extends Component { return ( <ul className={ this.getCategoryListClassName( 0 ) }> - { categories.map( ( category ) => this.renderCategoryListItem( category, 0 ) ) } + { categories.map( ( category ) => + this.renderCategoryListItem( category, 0 ) + ) } </ul> ); } @@ -86,21 +97,32 @@ class CategoriesEdit extends Component { return ( <li key={ category.id }> - <a href={ category.link } target="_blank" rel="noreferrer noopener">{ this.renderCategoryName( category ) }</a> - { showPostCounts && + <a + href={ category.link } + target="_blank" + rel="noreferrer noopener" + > + { this.renderCategoryName( category ) } + </a> + { showPostCounts && ( <span className="wp-block-categories__post-count"> - { ' ' }({ category.count }) + { ' ' } + ({ category.count }) </span> - } - - { - showHierarchy && - !! childCategories.length && ( - <ul className={ this.getCategoryListClassName( level + 1 ) }> - { childCategories.map( ( childCategory ) => this.renderCategoryListItem( childCategory, level + 1 ) ) } - </ul> - ) - } + ) } + + { showHierarchy && !! childCategories.length && ( + <ul + className={ this.getCategoryListClassName( level + 1 ) } + > + { childCategories.map( ( childCategory ) => + this.renderCategoryListItem( + childCategory, + level + 1 + ) + ) } + </ul> + ) } </li> ); } @@ -116,8 +138,13 @@ class CategoriesEdit extends Component { <label htmlFor={ selectId } className="screen-reader-text"> { __( 'Categories' ) } </label> - <select id={ selectId } className="wp-block-categories__dropdown"> - { categories.map( ( category ) => this.renderCategoryDropdownItem( category, 0 ) ) } + <select + id={ selectId } + className="wp-block-categories__dropdown" + > + { categories.map( ( category ) => + this.renderCategoryDropdownItem( category, 0 ) + ) } </select> </> ); @@ -131,16 +158,13 @@ class CategoriesEdit extends Component { <option key={ category.id }> { times( level * 3, () => '\xa0' ) } { this.renderCategoryName( category ) } - { - !! showPostCounts ? - ` (${ category.count })` : - '' - } + { !! showPostCounts ? ` (${ category.count })` : '' } </option>, showHierarchy && - !! childCategories.length && ( - childCategories.map( ( childCategory ) => this.renderCategoryDropdownItem( childCategory, level + 1 ) ) - ), + !! childCategories.length && + childCategories.map( ( childCategory ) => + this.renderCategoryDropdownItem( childCategory, level + 1 ) + ), ]; } @@ -174,10 +198,7 @@ class CategoriesEdit extends Component { return ( <> { inspectorControls } - <Placeholder - icon="admin-post" - label={ __( 'Categories' ) } - > + <Placeholder icon="admin-post" label={ __( 'Categories' ) }> <Spinner /> </Placeholder> </> @@ -188,11 +209,9 @@ class CategoriesEdit extends Component { <> { inspectorControls } <div className={ this.props.className }> - { - displayAsDropdown ? - this.renderCategoryDropdown() : - this.renderCategoryList() - } + { displayAsDropdown + ? this.renderCategoryDropdown() + : this.renderCategoryList() } </div> </> ); @@ -206,7 +225,11 @@ export default compose( return { categories: getEntityRecords( 'taxonomy', 'category', query ), - isRequesting: isResolving( 'core', 'getEntityRecords', [ 'taxonomy', 'category', query ] ), + isRequesting: isResolving( 'core', 'getEntityRecords', [ + 'taxonomy', + 'category', + query, + ] ), }; } ), withInstanceId diff --git a/packages/block-library/src/classic/edit.js b/packages/block-library/src/classic/edit.js index 2ce77409e8e0f3..7ea8b295c8aefc 100644 --- a/packages/block-library/src/classic/edit.js +++ b/packages/block-library/src/classic/edit.js @@ -52,7 +52,10 @@ export default class ClassicEdit extends Component { } componentDidUpdate( prevProps ) { - const { clientId, attributes: { content } } = this.props; + const { + clientId, + attributes: { content }, + } = this.props; const editor = window.tinymce.get( `editor-${ clientId }` ); @@ -76,7 +79,10 @@ export default class ClassicEdit extends Component { } onSetup( editor ) { - const { attributes: { content }, setAttributes } = this.props; + const { + attributes: { content }, + setAttributes, + } = this.props; const { ref } = this; let bookmark; @@ -107,7 +113,10 @@ export default class ClassicEdit extends Component { } ); editor.on( 'keydown', ( event ) => { - if ( ( event.keyCode === BACKSPACE || event.keyCode === DELETE ) && isTmceEmpty( editor ) ) { + if ( + ( event.keyCode === BACKSPACE || event.keyCode === DELETE ) && + isTmceEmpty( editor ) + ) { // delete the block this.props.onReplace( [] ); event.preventDefault(); @@ -139,7 +148,10 @@ export default class ClassicEdit extends Component { // Show the second, third, etc. toolbars when the `kitchensink` button is removed by a plugin. editor.on( 'init', function() { - if ( editor.settings.toolbar1 && editor.settings.toolbar1.indexOf( 'kitchensink' ) === -1 ) { + if ( + editor.settings.toolbar1 && + editor.settings.toolbar1.indexOf( 'kitchensink' ) === -1 + ) { editor.dom.addClass( ref, 'has-advanced-toolbar' ); } } ); @@ -189,7 +201,7 @@ export default class ClassicEdit extends Component { <div key="toolbar" id={ `toolbar-${ clientId }` } - ref={ ( ref ) => this.ref = ref } + ref={ ( ref ) => ( this.ref = ref ) } className="block-library-classic__toolbar" onClick={ this.focus } data-placeholder={ __( 'Classic' ) } diff --git a/packages/block-library/src/code/edit.native.js b/packages/block-library/src/code/edit.native.js index 319ce46d9762ce..b89cd3b775b03a 100644 --- a/packages/block-library/src/code/edit.native.js +++ b/packages/block-library/src/code/edit.native.js @@ -22,9 +22,21 @@ import styles from './theme.scss'; // Note: styling is applied directly to the (nested) PlainText component. Web-side components // apply it to the container 'div' but we don't have a proper proposal for cascading styling yet. export function CodeEdit( props ) { - const { attributes, setAttributes, onFocus, onBlur, getStylesFromColorScheme } = props; - const codeStyle = getStylesFromColorScheme( styles.blockCode, styles.blockCodeDark ); - const placeholderStyle = getStylesFromColorScheme( styles.placeholder, styles.placeholderDark ); + const { + attributes, + setAttributes, + onFocus, + onBlur, + getStylesFromColorScheme, + } = props; + const codeStyle = getStylesFromColorScheme( + styles.blockCode, + styles.blockCodeDark + ); + const placeholderStyle = getStylesFromColorScheme( + styles.placeholder, + styles.placeholderDark + ); return ( <View> diff --git a/packages/block-library/src/code/icon.js b/packages/block-library/src/code/icon.js index 35e809693a36ba..9a43a87b75b602 100644 --- a/packages/block-library/src/code/icon.js +++ b/packages/block-library/src/code/icon.js @@ -4,5 +4,8 @@ import { Path, SVG } from '@wordpress/components'; export default ( - <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0,0h24v24H0V0z" fill="none" /><Path d="M9.4,16.6L4.8,12l4.6-4.6L8,6l-6,6l6,6L9.4,16.6z M14.6,16.6l4.6-4.6l-4.6-4.6L16,6l6,6l-6,6L14.6,16.6z" /></SVG> + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <Path d="M0,0h24v24H0V0z" fill="none" /> + <Path d="M9.4,16.6L4.8,12l4.6-4.6L8,6l-6,6l6,6L9.4,16.6z M14.6,16.6l4.6-4.6l-4.6-4.6L16,6l6,6l-6,6L14.6,16.6z" /> + </SVG> ); diff --git a/packages/block-library/src/code/index.js b/packages/block-library/src/code/index.js index 7ab1b268a9a997..65506cfcdfc5d7 100644 --- a/packages/block-library/src/code/index.js +++ b/packages/block-library/src/code/index.js @@ -18,12 +18,16 @@ export { metadata, name }; export const settings = { title: __( 'Code' ), - description: __( 'Display code snippets that respect your spacing and tabs.' ), + description: __( + 'Display code snippets that respect your spacing and tabs.' + ), icon, example: { attributes: { // translators: Preserve \n markers for line breaks - content: __( '// A "block" is the abstract term used\n// to describe units of markup that\n// when composed together, form the\n// content or layout of a page.\nregisterBlockType( name, settings );' ), + content: __( + '// A "block" is the abstract term used\n// to describe units of markup that\n// when composed together, form the\n// content or layout of a page.\nregisterBlockType( name, settings );' + ), }, }, supports: { diff --git a/packages/block-library/src/code/save.js b/packages/block-library/src/code/save.js index ece9eef96dae3d..06ee8a2026bd74 100644 --- a/packages/block-library/src/code/save.js +++ b/packages/block-library/src/code/save.js @@ -4,5 +4,9 @@ import { escape } from './utils'; export default function save( { attributes } ) { - return <pre><code>{ escape( attributes.content ) }</code></pre>; + return ( + <pre> + <code>{ escape( attributes.content ) }</code> + </pre> + ); } diff --git a/packages/block-library/src/code/test/edit.native.js b/packages/block-library/src/code/test/edit.native.js index 62e9fa9adb48cc..cc62a778b3718f 100644 --- a/packages/block-library/src/code/test/edit.native.js +++ b/packages/block-library/src/code/test/edit.native.js @@ -11,13 +11,17 @@ import { TextInput } from 'react-native'; describe( 'Code', () => { it( 'renders without crashing', () => { - const component = renderer.create( <Code attributes={ { content: '' } } /> ); + const component = renderer.create( + <Code attributes={ { content: '' } } /> + ); const rendered = component.toJSON(); expect( rendered ).toBeTruthy(); } ); it( 'renders given text without crashing', () => { - const component = renderer.create( <Code attributes={ { content: 'sample text' } } /> ); + const component = renderer.create( + <Code attributes={ { content: 'sample text' } } /> + ); const testInstance = component.root; const textInput = testInstance.findByType( TextInput ); expect( textInput ).toBeTruthy(); diff --git a/packages/block-library/src/code/transforms.js b/packages/block-library/src/code/transforms.js index 180a95bc9b4c54..585df87c0f7fa1 100644 --- a/packages/block-library/src/code/transforms.js +++ b/packages/block-library/src/code/transforms.js @@ -12,11 +12,10 @@ const transforms = { }, { type: 'raw', - isMatch: ( node ) => ( + isMatch: ( node ) => node.nodeName === 'PRE' && node.children.length === 1 && - node.firstChild.nodeName === 'CODE' - ), + node.firstChild.nodeName === 'CODE', schema: { pre: { children: { diff --git a/packages/block-library/src/code/utils.js b/packages/block-library/src/code/utils.js index 5178e3d0135fca..30609b1f0ee740 100644 --- a/packages/block-library/src/code/utils.js +++ b/packages/block-library/src/code/utils.js @@ -54,5 +54,8 @@ function escapeOpeningSquareBrackets( content ) { * their HTML entity counterpart (i.e. & => &amp;) */ function escapeProtocolInIsolatedUrls( content ) { - return content.replace( /^(\s*https?:)\/\/([^\s<>"]+\s*)$/m, '$1&#47;&#47;$2' ); + return content.replace( + /^(\s*https?:)\/\/([^\s<>"]+\s*)$/m, + '$1&#47;&#47;$2' + ); } diff --git a/packages/block-library/src/column/edit.js b/packages/block-library/src/column/edit.js index 9ae7bd3b265027..f200ba2bd02ec1 100644 --- a/packages/block-library/src/column/edit.js +++ b/packages/block-library/src/column/edit.js @@ -38,13 +38,9 @@ function ColumnEdit( { } ) { const { verticalAlignment, width } = attributes; - const classes = classnames( - className, - 'block-core-columns', - { - [ `is-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, - } - ); + const classes = classnames( className, 'block-core-columns', { + [ `is-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, + } ); return ( <div className={ classes }> @@ -69,11 +65,11 @@ function ColumnEdit( { </InspectorControls> <InnerBlocks templateLock={ false } - renderAppender={ ( - hasChildBlocks ? - undefined : - () => <InnerBlocks.ButtonBlockAppender /> - ) } + renderAppender={ + hasChildBlocks + ? undefined + : () => <InnerBlocks.ButtonBlockAppender /> + } /> </div> ); @@ -92,20 +88,30 @@ export default compose( return { updateAlignment( verticalAlignment ) { const { clientId, setAttributes } = ownProps; - const { updateBlockAttributes } = dispatch( 'core/block-editor' ); - const { getBlockRootClientId } = registry.select( 'core/block-editor' ); + const { updateBlockAttributes } = dispatch( + 'core/block-editor' + ); + const { getBlockRootClientId } = registry.select( + 'core/block-editor' + ); // Update own alignment. setAttributes( { verticalAlignment } ); // Reset Parent Columns Block const rootClientId = getBlockRootClientId( clientId ); - updateBlockAttributes( rootClientId, { verticalAlignment: null } ); + updateBlockAttributes( rootClientId, { + verticalAlignment: null, + } ); }, updateWidth( width ) { const { clientId } = ownProps; - const { updateBlockAttributes } = dispatch( 'core/block-editor' ); - const { getBlockRootClientId, getBlocks } = registry.select( 'core/block-editor' ); + const { updateBlockAttributes } = dispatch( + 'core/block-editor' + ); + const { getBlockRootClientId, getBlocks } = registry.select( + 'core/block-editor' + ); // Constrain or expand siblings to account for gain or loss of // total columns area. @@ -114,12 +120,14 @@ export default compose( // The occupied width is calculated as the sum of the new width // and the total width of blocks _not_ in the adjacent set. - const occupiedWidth = width + getTotalColumnsWidth( - difference( columns, [ - find( columns, { clientId } ), - ...adjacentColumns, - ] ) - ); + const occupiedWidth = + width + + getTotalColumnsWidth( + difference( columns, [ + find( columns, { clientId } ), + ...adjacentColumns, + ] ) + ); // Compute _all_ next column widths, in case the updated column // is in the middle of a set of columns which don't yet have @@ -128,12 +136,21 @@ export default compose( const nextColumnWidths = { ...getColumnWidths( columns, columns.length ), [ clientId ]: toWidthPrecision( width ), - ...getRedistributedColumnWidths( adjacentColumns, 100 - occupiedWidth, columns.length ), + ...getRedistributedColumnWidths( + adjacentColumns, + 100 - occupiedWidth, + columns.length + ), }; - forEach( nextColumnWidths, ( nextColumnWidth, columnClientId ) => { - updateBlockAttributes( columnClientId, { width: nextColumnWidth } ); - } ); + forEach( + nextColumnWidths, + ( nextColumnWidth, columnClientId ) => { + updateBlockAttributes( columnClientId, { + width: nextColumnWidth, + } ); + } + ); }, }; } ) diff --git a/packages/block-library/src/column/index.js b/packages/block-library/src/column/index.js index 0672a032cac6f3..611a160ed8962d 100644 --- a/packages/block-library/src/column/index.js +++ b/packages/block-library/src/column/index.js @@ -38,4 +38,3 @@ export const settings = { edit, save, }; - diff --git a/packages/block-library/src/columns/deprecated.js b/packages/block-library/src/columns/deprecated.js index a344f72eaf69f3..3d08e3d74c2183 100644 --- a/packages/block-library/src/columns/deprecated.js +++ b/packages/block-library/src/columns/deprecated.js @@ -8,9 +8,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { createBlock } from '@wordpress/blocks'; -import { - InnerBlocks, -} from '@wordpress/block-editor'; +import { InnerBlocks } from '@wordpress/block-editor'; /** * Given an HTML string for a deprecated columns inner block, returns the @@ -32,7 +30,9 @@ function getDeprecatedLayoutColumn( originalContent ) { doc.body.innerHTML = originalContent; for ( const classListItem of doc.body.firstChild.classList ) { - if ( ( columnMatch = classListItem.match( /^layout-column-(\d+)$/ ) ) ) { + if ( + ( columnMatch = classListItem.match( /^layout-column-(\d+)$/ ) ) + ) { return Number( columnMatch[ 1 ] ) - 1; } } @@ -51,9 +51,9 @@ export default [ // Columns block and a deprecation is the unlikely case due to // its subsequent migration, optimize for the `false` condition // by performing a naive, inaccurate pass at inner blocks. - const isFastPassEligible = innerBlocks.some( ( innerBlock ) => ( + const isFastPassEligible = innerBlocks.some( ( innerBlock ) => /layout-column-\d+/.test( innerBlock.originalContent ) - ) ); + ); if ( ! isFastPassEligible ) { return false; @@ -61,9 +61,11 @@ export default [ // Only if the fast pass is considered eligible is the more // accurate, durable, slower condition performed. - return innerBlocks.some( ( innerBlock ) => ( - getDeprecatedLayoutColumn( innerBlock.originalContent ) !== undefined - ) ); + return innerBlocks.some( + ( innerBlock ) => + getDeprecatedLayoutColumn( innerBlock.originalContent ) !== + undefined + ); }, migrate( attributes, innerBlocks ) { const columns = innerBlocks.reduce( ( accumulator, innerBlock ) => { @@ -83,14 +85,11 @@ export default [ return accumulator; }, [] ); - const migratedInnerBlocks = columns.map( ( columnBlocks ) => ( + const migratedInnerBlocks = columns.map( ( columnBlocks ) => createBlock( 'core/column', {}, columnBlocks ) - ) ); + ); - return [ - omit( attributes, [ 'columns' ] ), - migratedInnerBlocks, - ]; + return [ omit( attributes, [ 'columns' ] ), migratedInnerBlocks ]; }, save( { attributes } ) { const { columns } = attributes; diff --git a/packages/block-library/src/columns/edit.js b/packages/block-library/src/columns/edit.js index fe89dd1c707d01..98c4aac327c6c4 100644 --- a/packages/block-library/src/columns/edit.js +++ b/packages/block-library/src/columns/edit.js @@ -8,23 +8,16 @@ import { dropRight, get, map, times } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { - PanelBody, - RangeControl, -} from '@wordpress/components'; +import { PanelBody, RangeControl } from '@wordpress/components'; import { InspectorControls, InnerBlocks, BlockControls, BlockVerticalAlignmentToolbar, - __experimentalBlockPatternPicker, + __experimentalBlockVariationPicker, __experimentalUseColors, } from '@wordpress/block-editor'; -import { - withDispatch, - useDispatch, - useSelect, -} from '@wordpress/data'; +import { withDispatch, useDispatch, useSelect } from '@wordpress/data'; import { createBlock } from '@wordpress/blocks'; /** @@ -57,20 +50,21 @@ function ColumnsEditContainer( { } ) { const { verticalAlignment } = attributes; - const { count } = useSelect( ( select ) => { - return { - count: select( 'core/block-editor' ).getBlockCount( clientId ), - }; - }, [ clientId ] ); + const { count } = useSelect( + ( select ) => { + return { + count: select( 'core/block-editor' ).getBlockCount( clientId ), + }; + }, + [ clientId ] + ); const { BackgroundColor, InspectorControlsColorPanel, - } = __experimentalUseColors( - [ - { name: 'backgroundColor', className: 'has-background' }, - ] - ); + } = __experimentalUseColors( [ + { name: 'backgroundColor', className: 'has-background' }, + ] ); const classes = classnames( className, { [ `are-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, @@ -100,147 +94,177 @@ function ColumnsEditContainer( { <div className={ classes }> <InnerBlocks templateLock="all" - allowedBlocks={ ALLOWED_BLOCKS } /> + allowedBlocks={ ALLOWED_BLOCKS } + /> </div> </BackgroundColor> </> ); } -const ColumnsEditContainerWrapper = withDispatch( ( dispatch, ownProps, registry ) => ( { - /** - * Update all child Column blocks with a new vertical alignment setting - * based on whatever alignment is passed in. This allows change to parent - * to overide anything set on a individual column basis. - * - * @param {string} verticalAlignment the vertical alignment setting - */ - updateAlignment( verticalAlignment ) { - const { clientId, setAttributes } = ownProps; - const { updateBlockAttributes } = dispatch( 'core/block-editor' ); - const { getBlockOrder } = registry.select( 'core/block-editor' ); - - // Update own alignment. - setAttributes( { verticalAlignment } ); - - // Update all child Column Blocks to match - const innerBlockClientIds = getBlockOrder( clientId ); - innerBlockClientIds.forEach( ( innerBlockClientId ) => { - updateBlockAttributes( innerBlockClientId, { - verticalAlignment, +const ColumnsEditContainerWrapper = withDispatch( + ( dispatch, ownProps, registry ) => ( { + /** + * Update all child Column blocks with a new vertical alignment setting + * based on whatever alignment is passed in. This allows change to parent + * to overide anything set on a individual column basis. + * + * @param {string} verticalAlignment the vertical alignment setting + */ + updateAlignment( verticalAlignment ) { + const { clientId, setAttributes } = ownProps; + const { updateBlockAttributes } = dispatch( 'core/block-editor' ); + const { getBlockOrder } = registry.select( 'core/block-editor' ); + + // Update own alignment. + setAttributes( { verticalAlignment } ); + + // Update all child Column Blocks to match + const innerBlockClientIds = getBlockOrder( clientId ); + innerBlockClientIds.forEach( ( innerBlockClientId ) => { + updateBlockAttributes( innerBlockClientId, { + verticalAlignment, + } ); } ); - } ); - }, - - /** - * Updates the column count, including necessary revisions to child Column - * blocks to grant required or redistribute available space. - * - * @param {number} previousColumns Previous column count. - * @param {number} newColumns New column count. - */ - updateColumns( previousColumns, newColumns ) { - const { clientId } = ownProps; - const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); - const { getBlocks } = registry.select( 'core/block-editor' ); - - let innerBlocks = getBlocks( clientId ); - const hasExplicitWidths = hasExplicitColumnWidths( innerBlocks ); - - // Redistribute available width for existing inner blocks. - const isAddingColumn = newColumns > previousColumns; - - if ( isAddingColumn && hasExplicitWidths ) { - // If adding a new column, assign width to the new column equal to - // as if it were `1 / columns` of the total available space. - const newColumnWidth = toWidthPrecision( 100 / newColumns ); - - // Redistribute in consideration of pending block insertion as - // constraining the available working width. - const widths = getRedistributedColumnWidths( innerBlocks, 100 - newColumnWidth ); - - innerBlocks = [ - ...getMappedColumnWidths( innerBlocks, widths ), - ...times( newColumns - previousColumns, () => { - return createBlock( 'core/column', { - width: newColumnWidth, - } ); - } ), - ]; - } else if ( isAddingColumn ) { - innerBlocks = [ - ...innerBlocks, - ...times( newColumns - previousColumns, () => { - return createBlock( 'core/column' ); - } ), - ]; - } else { - // The removed column will be the last of the inner blocks. - innerBlocks = dropRight( innerBlocks, previousColumns - newColumns ); - - if ( hasExplicitWidths ) { - // Redistribute as if block is already removed. - const widths = getRedistributedColumnWidths( innerBlocks, 100 ); - - innerBlocks = getMappedColumnWidths( innerBlocks, widths ); + }, + + /** + * Updates the column count, including necessary revisions to child Column + * blocks to grant required or redistribute available space. + * + * @param {number} previousColumns Previous column count. + * @param {number} newColumns New column count. + */ + updateColumns( previousColumns, newColumns ) { + const { clientId } = ownProps; + const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); + const { getBlocks } = registry.select( 'core/block-editor' ); + + let innerBlocks = getBlocks( clientId ); + const hasExplicitWidths = hasExplicitColumnWidths( innerBlocks ); + + // Redistribute available width for existing inner blocks. + const isAddingColumn = newColumns > previousColumns; + + if ( isAddingColumn && hasExplicitWidths ) { + // If adding a new column, assign width to the new column equal to + // as if it were `1 / columns` of the total available space. + const newColumnWidth = toWidthPrecision( 100 / newColumns ); + + // Redistribute in consideration of pending block insertion as + // constraining the available working width. + const widths = getRedistributedColumnWidths( + innerBlocks, + 100 - newColumnWidth + ); + + innerBlocks = [ + ...getMappedColumnWidths( innerBlocks, widths ), + ...times( newColumns - previousColumns, () => { + return createBlock( 'core/column', { + width: newColumnWidth, + } ); + } ), + ]; + } else if ( isAddingColumn ) { + innerBlocks = [ + ...innerBlocks, + ...times( newColumns - previousColumns, () => { + return createBlock( 'core/column' ); + } ), + ]; + } else { + // The removed column will be the last of the inner blocks. + innerBlocks = dropRight( + innerBlocks, + previousColumns - newColumns + ); + + if ( hasExplicitWidths ) { + // Redistribute as if block is already removed. + const widths = getRedistributedColumnWidths( + innerBlocks, + 100 + ); + + innerBlocks = getMappedColumnWidths( innerBlocks, widths ); + } } - } - replaceInnerBlocks( clientId, innerBlocks, false ); - }, -} ) )( ColumnsEditContainer ); + replaceInnerBlocks( clientId, innerBlocks, false ); + }, + } ) +)( ColumnsEditContainer ); const createBlocksFromInnerBlocksTemplate = ( innerBlocksTemplate ) => { return map( innerBlocksTemplate, ( [ name, attributes, innerBlocks = [] ] ) => - createBlock( name, attributes, createBlocksFromInnerBlocksTemplate( innerBlocks ) ) + createBlock( + name, + attributes, + createBlocksFromInnerBlocksTemplate( innerBlocks ) + ) ); }; const ColumnsEdit = ( props ) => { const { clientId, name } = props; - const { blockType, defaultPattern, hasInnerBlocks, patterns } = useSelect( ( select ) => { - const { - __experimentalGetBlockPatterns, - getBlockType, - __experimentalGetDefaultBlockPattern, - } = select( 'core/blocks' ); - - return { - blockType: getBlockType( name ), - defaultPattern: __experimentalGetDefaultBlockPattern( name, 'block' ), - hasInnerBlocks: select( 'core/block-editor' ).getBlocks( clientId ).length > 0, - patterns: __experimentalGetBlockPatterns( name, 'block' ), - }; - }, [ clientId, name ] ); + const { + blockType, + defaultVariation, + hasInnerBlocks, + variations, + } = useSelect( + ( select ) => { + const { + __experimentalGetBlockVariations, + getBlockType, + __experimentalGetDefaultBlockVariation, + } = select( 'core/blocks' ); + + return { + blockType: getBlockType( name ), + defaultVariation: __experimentalGetDefaultBlockVariation( + name, + 'block' + ), + hasInnerBlocks: + select( 'core/block-editor' ).getBlocks( clientId ).length > + 0, + variations: __experimentalGetBlockVariations( name, 'block' ), + }; + }, + [ clientId, name ] + ); const { replaceInnerBlocks } = useDispatch( 'core/block-editor' ); if ( hasInnerBlocks ) { - return ( - <ColumnsEditContainerWrapper { ...props } /> - ); + return <ColumnsEditContainerWrapper { ...props } />; } return ( - <__experimentalBlockPatternPicker + <__experimentalBlockVariationPicker icon={ get( blockType, [ 'icon', 'src' ] ) } label={ get( blockType, [ 'title' ] ) } - patterns={ patterns } - onSelect={ ( nextPattern = defaultPattern ) => { - if ( nextPattern.attributes ) { - props.setAttributes( nextPattern.attributes ); + variations={ variations } + onSelect={ ( nextVariation = defaultVariation ) => { + if ( nextVariation.attributes ) { + props.setAttributes( nextVariation.attributes ); } - if ( nextPattern.innerBlocks ) { + if ( nextVariation.innerBlocks ) { replaceInnerBlocks( props.clientId, - createBlocksFromInnerBlocksTemplate( nextPattern.innerBlocks ) + createBlocksFromInnerBlocksTemplate( + nextVariation.innerBlocks + ) ); } } } allowSkip - /> ); + /> + ); }; export default ColumnsEdit; diff --git a/packages/block-library/src/columns/index.js b/packages/block-library/src/columns/index.js index 3e7fd6a54f8818..ff932b86e9846f 100644 --- a/packages/block-library/src/columns/index.js +++ b/packages/block-library/src/columns/index.js @@ -10,8 +10,8 @@ import { columns as icon } from '@wordpress/icons'; import deprecated from './deprecated'; import edit from './edit'; import metadata from './block.json'; -import patterns from './patterns'; import save from './save'; +import variations from './variations'; const { name } = metadata; @@ -20,12 +20,14 @@ export { metadata, name }; export const settings = { title: __( 'Columns' ), icon, - description: __( 'Add a block that displays content in multiple columns, then add whatever content blocks you’d like.' ), + description: __( + 'Add a block that displays content in multiple columns, then add whatever content blocks you’d like.' + ), supports: { align: [ 'wide', 'full' ], html: false, }, - patterns, + variations, example: { innerBlocks: [ { @@ -35,20 +37,25 @@ export const settings = { name: 'core/paragraph', attributes: { /* translators: example text. */ - content: __( 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent et eros eu felis.' ), + content: __( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent et eros eu felis.' + ), }, }, { name: 'core/image', attributes: { - url: 'https://s.w.org/images/core/5.3/Windbuchencom.jpg', + url: + 'https://s.w.org/images/core/5.3/Windbuchencom.jpg', }, }, { name: 'core/paragraph', attributes: { /* translators: example text. */ - content: __( 'Suspendisse commodo neque lacus, a dictum orci interdum et.' ), + content: __( + 'Suspendisse commodo neque lacus, a dictum orci interdum et.' + ), }, }, ], @@ -60,14 +67,18 @@ export const settings = { name: 'core/paragraph', attributes: { /* translators: example text. */ - content: __( 'Etiam et egestas lorem. Vivamus sagittis sit amet dolor quis lobortis. Integer sed fermentum arcu, id vulputate lacus. Etiam fermentum sem eu quam hendrerit.' ), + content: __( + 'Etiam et egestas lorem. Vivamus sagittis sit amet dolor quis lobortis. Integer sed fermentum arcu, id vulputate lacus. Etiam fermentum sem eu quam hendrerit.' + ), }, }, { name: 'core/paragraph', attributes: { /* translators: example text. */ - content: __( 'Nam risus massa, ullamcorper consectetur eros fermentum, porta aliquet ligula. Sed vel mauris nec enim.' ), + content: __( + 'Nam risus massa, ullamcorper consectetur eros fermentum, porta aliquet ligula. Sed vel mauris nec enim.' + ), }, }, ], diff --git a/packages/block-library/src/columns/patterns.js b/packages/block-library/src/columns/patterns.js deleted file mode 100644 index 485af94980bb45..00000000000000 --- a/packages/block-library/src/columns/patterns.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * WordPress dependencies - */ -import { Path, SVG } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; - -/** - * Template option choices for predefined columns layouts. - * - * @type {WPBlockPattern[]} - */ -const patterns = [ - { - name: 'two-columns-equal', - title: __( 'Two columns; equal split' ), - icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M39 12C40.1046 12 41 12.8954 41 14V34C41 35.1046 40.1046 36 39 36H9C7.89543 36 7 35.1046 7 34V14C7 12.8954 7.89543 12 9 12H39ZM39 34V14H25V34H39ZM23 34H9V14H23V34Z" /></SVG>, - isDefault: true, - innerBlocks: [ - [ 'core/column' ], - [ 'core/column' ], - ], - scope: [ 'block' ], - }, - { - name: 'two-columns-one-third-two-thirds', - title: __( 'Two columns; one-third, two-thirds split' ), - icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M39 12C40.1046 12 41 12.8954 41 14V34C41 35.1046 40.1046 36 39 36H9C7.89543 36 7 35.1046 7 34V14C7 12.8954 7.89543 12 9 12H39ZM39 34V14H20V34H39ZM18 34H9V14H18V34Z" /></SVG>, - innerBlocks: [ - [ 'core/column', { width: 33.33 } ], - [ 'core/column', { width: 66.66 } ], - ], - scope: [ 'block' ], - }, - { - name: 'two-columns-two-thirds-one-third', - title: __( 'Two columns; two-thirds, one-third split' ), - icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M39 12C40.1046 12 41 12.8954 41 14V34C41 35.1046 40.1046 36 39 36H9C7.89543 36 7 35.1046 7 34V14C7 12.8954 7.89543 12 9 12H39ZM39 34V14H30V34H39ZM28 34H9V14H28V34Z" /></SVG>, - innerBlocks: [ - [ 'core/column', { width: 66.66 } ], - [ 'core/column', { width: 33.33 } ], - ], - scope: [ 'block' ], - }, - { - name: 'three-columns-equal', - title: __( 'Three columns; equal split' ), - icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" d="M41 14a2 2 0 0 0-2-2H9a2 2 0 0 0-2 2v20a2 2 0 0 0 2 2h30a2 2 0 0 0 2-2V14zM28.5 34h-9V14h9v20zm2 0V14H39v20h-8.5zm-13 0H9V14h8.5v20z" /></SVG>, - innerBlocks: [ - [ 'core/column' ], - [ 'core/column' ], - [ 'core/column' ], - ], - scope: [ 'block' ], - }, - { - name: 'three-columns-wider-center', - title: __( 'Three columns; wide center column' ), - icon: <SVG width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" d="M41 14a2 2 0 0 0-2-2H9a2 2 0 0 0-2 2v20a2 2 0 0 0 2 2h30a2 2 0 0 0 2-2V14zM31 34H17V14h14v20zm2 0V14h6v20h-6zm-18 0H9V14h6v20z" /></SVG>, - innerBlocks: [ - [ 'core/column', { width: 25 } ], - [ 'core/column', { width: 50 } ], - [ 'core/column', { width: 25 } ], - ], - scope: [ 'block' ], - }, -]; - -export default patterns; diff --git a/packages/block-library/src/columns/save.js b/packages/block-library/src/columns/save.js index 7dfa5ab214bd67..41d0f8d26e0e70 100644 --- a/packages/block-library/src/columns/save.js +++ b/packages/block-library/src/columns/save.js @@ -15,7 +15,10 @@ export default function save( { attributes } ) { customBackgroundColor, } = attributes; - const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + const backgroundClass = getColorClassName( + 'background-color', + backgroundColor + ); const className = classnames( { 'has-background': backgroundColor || customBackgroundColor, diff --git a/packages/block-library/src/columns/utils.js b/packages/block-library/src/columns/utils.js index 61b423f8ca761e..0631d59cf9dfb9 100644 --- a/packages/block-library/src/columns/utils.js +++ b/packages/block-library/src/columns/utils.js @@ -12,9 +12,7 @@ import { findIndex, sumBy, merge, mapValues } from 'lodash'; * @return {number} Value rounded to standard precision. */ export const toWidthPrecision = ( value ) => - Number.isFinite( value ) ? - parseFloat( value.toFixed( 2 ) ) : - undefined; + Number.isFinite( value ) ? parseFloat( value.toFixed( 2 ) ) : undefined; /** * Returns the considered adjacent to that of the specified `clientId` for @@ -57,8 +55,13 @@ export function getEffectiveColumnWidth( block, totalBlockCount ) { * * @return {number} Total width occupied by blocks. */ -export function getTotalColumnsWidth( blocks, totalBlockCount = blocks.length ) { - return sumBy( blocks, ( block ) => getEffectiveColumnWidth( block, totalBlockCount ) ); +export function getTotalColumnsWidth( + blocks, + totalBlockCount = blocks.length +) { + return sumBy( blocks, ( block ) => + getEffectiveColumnWidth( block, totalBlockCount ) + ); } /** @@ -89,14 +92,17 @@ export function getColumnWidths( blocks, totalBlockCount = blocks.length ) { * * @return {Object<string,number>} Redistributed column widths. */ -export function getRedistributedColumnWidths( blocks, availableWidth, totalBlockCount = blocks.length ) { +export function getRedistributedColumnWidths( + blocks, + availableWidth, + totalBlockCount = blocks.length +) { const totalWidth = getTotalColumnsWidth( blocks, totalBlockCount ); const difference = availableWidth - totalWidth; const adjustment = difference / blocks.length; - return mapValues( - getColumnWidths( blocks, totalBlockCount ), - ( width ) => toWidthPrecision( width + adjustment ), + return mapValues( getColumnWidths( blocks, totalBlockCount ), ( width ) => + toWidthPrecision( width + adjustment ) ); } @@ -109,7 +115,9 @@ export function getRedistributedColumnWidths( blocks, availableWidth, totalBlock * @return {boolean} Whether columns have explicit widths. */ export function hasExplicitColumnWidths( blocks ) { - return blocks.some( ( block ) => Number.isFinite( block.attributes.width ) ); + return blocks.some( ( block ) => + Number.isFinite( block.attributes.width ) + ); } /** @@ -122,9 +130,11 @@ export function hasExplicitColumnWidths( blocks ) { * @return {WPBlock[]} blocks Mapped block objects. */ export function getMappedColumnWidths( blocks, widths ) { - return blocks.map( ( block ) => merge( {}, block, { - attributes: { - width: widths[ block.clientId ], - }, - } ) ); + return blocks.map( ( block ) => + merge( {}, block, { + attributes: { + width: widths[ block.clientId ], + }, + } ) + ); } diff --git a/packages/block-library/src/columns/variations.js b/packages/block-library/src/columns/variations.js new file mode 100644 index 00000000000000..93e83796d137cc --- /dev/null +++ b/packages/block-library/src/columns/variations.js @@ -0,0 +1,130 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** @typedef {import('@wordpress/blocks').WPBlockVariation} WPBlockVariation */ + +/** + * Template option choices for predefined columns layouts. + * + * @type {WPBlockVariation[]} + */ +const variations = [ + { + name: 'two-columns-equal', + title: __( 'Two columns; equal split' ), + icon: ( + <SVG + width="48" + height="48" + viewBox="0 0 48 48" + xmlns="http://www.w3.org/2000/svg" + > + <Path + fillRule="evenodd" + clipRule="evenodd" + d="M39 12C40.1046 12 41 12.8954 41 14V34C41 35.1046 40.1046 36 39 36H9C7.89543 36 7 35.1046 7 34V14C7 12.8954 7.89543 12 9 12H39ZM39 34V14H25V34H39ZM23 34H9V14H23V34Z" + /> + </SVG> + ), + isDefault: true, + innerBlocks: [ [ 'core/column' ], [ 'core/column' ] ], + scope: [ 'block' ], + }, + { + name: 'two-columns-one-third-two-thirds', + title: __( 'Two columns; one-third, two-thirds split' ), + icon: ( + <SVG + width="48" + height="48" + viewBox="0 0 48 48" + xmlns="http://www.w3.org/2000/svg" + > + <Path + fillRule="evenodd" + clipRule="evenodd" + d="M39 12C40.1046 12 41 12.8954 41 14V34C41 35.1046 40.1046 36 39 36H9C7.89543 36 7 35.1046 7 34V14C7 12.8954 7.89543 12 9 12H39ZM39 34V14H20V34H39ZM18 34H9V14H18V34Z" + /> + </SVG> + ), + innerBlocks: [ + [ 'core/column', { width: 33.33 } ], + [ 'core/column', { width: 66.66 } ], + ], + scope: [ 'block' ], + }, + { + name: 'two-columns-two-thirds-one-third', + title: __( 'Two columns; two-thirds, one-third split' ), + icon: ( + <SVG + width="48" + height="48" + viewBox="0 0 48 48" + xmlns="http://www.w3.org/2000/svg" + > + <Path + fillRule="evenodd" + clipRule="evenodd" + d="M39 12C40.1046 12 41 12.8954 41 14V34C41 35.1046 40.1046 36 39 36H9C7.89543 36 7 35.1046 7 34V14C7 12.8954 7.89543 12 9 12H39ZM39 34V14H30V34H39ZM28 34H9V14H28V34Z" + /> + </SVG> + ), + innerBlocks: [ + [ 'core/column', { width: 66.66 } ], + [ 'core/column', { width: 33.33 } ], + ], + scope: [ 'block' ], + }, + { + name: 'three-columns-equal', + title: __( 'Three columns; equal split' ), + icon: ( + <SVG + width="48" + height="48" + viewBox="0 0 48 48" + xmlns="http://www.w3.org/2000/svg" + > + <Path + fillRule="evenodd" + d="M41 14a2 2 0 0 0-2-2H9a2 2 0 0 0-2 2v20a2 2 0 0 0 2 2h30a2 2 0 0 0 2-2V14zM28.5 34h-9V14h9v20zm2 0V14H39v20h-8.5zm-13 0H9V14h8.5v20z" + /> + </SVG> + ), + innerBlocks: [ + [ 'core/column' ], + [ 'core/column' ], + [ 'core/column' ], + ], + scope: [ 'block' ], + }, + { + name: 'three-columns-wider-center', + title: __( 'Three columns; wide center column' ), + icon: ( + <SVG + width="48" + height="48" + viewBox="0 0 48 48" + xmlns="http://www.w3.org/2000/svg" + > + <Path + fillRule="evenodd" + d="M41 14a2 2 0 0 0-2-2H9a2 2 0 0 0-2 2v20a2 2 0 0 0 2 2h30a2 2 0 0 0 2-2V14zM31 34H17V14h14v20zm2 0V14h6v20h-6zm-18 0H9V14h6v20z" + /> + </SVG> + ), + innerBlocks: [ + [ 'core/column', { width: 25 } ], + [ 'core/column', { width: 50 } ], + [ 'core/column', { width: 25 } ], + ], + scope: [ 'block' ], + }, +]; + +export default variations; diff --git a/packages/block-library/src/cover/deprecated.js b/packages/block-library/src/cover/deprecated.js index 2fe9c34a8f3af4..655dae9e71e455 100644 --- a/packages/block-library/src/cover/deprecated.js +++ b/packages/block-library/src/cover/deprecated.js @@ -92,17 +92,22 @@ const deprecated = [ url, minHeight, } = attributes; - const overlayColorClass = getColorClassName( 'background-color', overlayColor ); + const overlayColorClass = getColorClassName( + 'background-color', + overlayColor + ); const gradientClass = __experimentalGetGradientClass( gradient ); - const style = backgroundType === IMAGE_BACKGROUND_TYPE ? - backgroundImageStyles( url ) : - {}; + const style = + backgroundType === IMAGE_BACKGROUND_TYPE + ? backgroundImageStyles( url ) + : {}; if ( ! overlayColorClass ) { style.backgroundColor = customOverlayColor; } if ( focalPoint && ! hasParallax ) { - style.backgroundPosition = `${ focalPoint.x * 100 }% ${ focalPoint.y * 100 }%`; + style.backgroundPosition = `${ focalPoint.x * + 100 }% ${ focalPoint.y * 100 }%`; } if ( customGradient && ! url ) { style.background = customGradient; @@ -117,28 +122,36 @@ const deprecated = [ 'has-parallax': hasParallax, 'has-background-gradient': customGradient, [ gradientClass ]: ! url && gradientClass, - }, + } ); return ( <div className={ classes } style={ style }> - { url && ( gradient || customGradient ) && dimRatio !== 0 && ( - <span - aria-hidden="true" - className={ classnames( - 'wp-block-cover__gradient-background', - gradientClass - ) } - style={ customGradient ? { background: customGradient } : undefined } + { url && + ( gradient || customGradient ) && + dimRatio !== 0 && ( + <span + aria-hidden="true" + className={ classnames( + 'wp-block-cover__gradient-background', + gradientClass + ) } + style={ + customGradient + ? { background: customGradient } + : undefined + } + /> + ) } + { VIDEO_BACKGROUND_TYPE === backgroundType && url && ( + <video + className="wp-block-cover__video-background" + autoPlay + muted + loop + src={ url } /> ) } - { VIDEO_BACKGROUND_TYPE === backgroundType && url && ( <video - className="wp-block-cover__video-background" - autoPlay - muted - loop - src={ url } - /> ) } <div className="wp-block-cover__inner-container"> <InnerBlocks.Content /> </div> @@ -174,15 +187,20 @@ const deprecated = [ title, url, } = attributes; - const overlayColorClass = getColorClassName( 'background-color', overlayColor ); - const style = backgroundType === IMAGE_BACKGROUND_TYPE ? - backgroundImageStyles( url ) : - {}; + const overlayColorClass = getColorClassName( + 'background-color', + overlayColor + ); + const style = + backgroundType === IMAGE_BACKGROUND_TYPE + ? backgroundImageStyles( url ) + : {}; if ( ! overlayColorClass ) { style.backgroundColor = customOverlayColor; } if ( focalPoint && ! hasParallax ) { - style.backgroundPosition = `${ focalPoint.x * 100 }% ${ focalPoint.y * 100 }%`; + style.backgroundPosition = `${ focalPoint.x * + 100 }% ${ focalPoint.y * 100 }%`; } const classes = classnames( @@ -191,21 +209,28 @@ const deprecated = [ { 'has-background-dim': dimRatio !== 0, 'has-parallax': hasParallax, - [ `has-${ contentAlign }-content` ]: contentAlign !== 'center', - }, + [ `has-${ contentAlign }-content` ]: + contentAlign !== 'center', + } ); return ( <div className={ classes } style={ style }> - { VIDEO_BACKGROUND_TYPE === backgroundType && url && ( <video - className="wp-block-cover__video-background" - autoPlay - muted - loop - src={ url } - /> ) } + { VIDEO_BACKGROUND_TYPE === backgroundType && url && ( + <video + className="wp-block-cover__video-background" + autoPlay + muted + loop + src={ url } + /> + ) } { ! RichText.isEmpty( title ) && ( - <RichText.Content tagName="p" className="wp-block-cover-text" value={ title } /> + <RichText.Content + tagName="p" + className="wp-block-cover-text" + value={ title } + /> ) } </div> ); @@ -214,19 +239,17 @@ const deprecated = [ return [ omit( attributes, [ 'title', 'contentAlign' ] ), [ - createBlock( - 'core/paragraph', - { - content: attributes.title, - align: attributes.contentAlign, - fontSize: 'large', - placeholder: __( 'Write title…' ), - } - ), + createBlock( 'core/paragraph', { + content: attributes.title, + align: attributes.contentAlign, + fontSize: 'large', + placeholder: __( 'Write title…' ), + } ), ], ]; }, - }, { + }, + { attributes: { ...blockAttributes, title: { @@ -246,8 +269,20 @@ const deprecated = [ className: false, }, save( { attributes } ) { - const { url, title, hasParallax, dimRatio, align, contentAlign, overlayColor, customOverlayColor } = attributes; - const overlayColorClass = getColorClassName( 'background-color', overlayColor ); + const { + url, + title, + hasParallax, + dimRatio, + align, + contentAlign, + overlayColor, + customOverlayColor, + } = attributes; + const overlayColorClass = getColorClassName( + 'background-color', + overlayColor + ); const style = backgroundImageStyles( url ); if ( ! overlayColorClass ) { style.backgroundColor = customOverlayColor; @@ -260,15 +295,20 @@ const deprecated = [ { 'has-background-dim': dimRatio !== 0, 'has-parallax': hasParallax, - [ `has-${ contentAlign }-content` ]: contentAlign !== 'center', + [ `has-${ contentAlign }-content` ]: + contentAlign !== 'center', }, - align ? `align${ align }` : null, + align ? `align${ align }` : null ); return ( <div className={ classes } style={ style }> { ! RichText.isEmpty( title ) && ( - <RichText.Content tagName="p" className="wp-block-cover-image-text" value={ title } /> + <RichText.Content + tagName="p" + className="wp-block-cover-image-text" + value={ title } + /> ) } </div> ); @@ -277,19 +317,17 @@ const deprecated = [ return [ omit( attributes, [ 'title', 'contentAlign', 'align' ] ), [ - createBlock( - 'core/paragraph', - { - content: attributes.title, - align: attributes.contentAlign, - fontSize: 'large', - placeholder: __( 'Write title…' ), - } - ), + createBlock( 'core/paragraph', { + content: attributes.title, + align: attributes.contentAlign, + fontSize: 'large', + placeholder: __( 'Write title…' ), + } ), ], ]; }, - }, { + }, + { attributes: { ...blockAttributes, title: { @@ -318,7 +356,7 @@ const deprecated = [ 'has-background-dim': dimRatio !== 0, 'has-parallax': hasParallax, }, - align ? `align${ align }` : null, + align ? `align${ align }` : null ); return ( @@ -331,15 +369,12 @@ const deprecated = [ return [ omit( attributes, [ 'title', 'contentAlign', 'align' ] ), [ - createBlock( - 'core/paragraph', - { - content: attributes.title, - align: attributes.contentAlign, - fontSize: 'large', - placeholder: __( 'Write title…' ), - } - ), + createBlock( 'core/paragraph', { + content: attributes.title, + align: attributes.contentAlign, + fontSize: 'large', + placeholder: __( 'Write title…' ), + } ), ], ]; }, diff --git a/packages/block-library/src/cover/edit.js b/packages/block-library/src/cover/edit.js index cfed46da2799ff..959fa588f24b7f 100644 --- a/packages/block-library/src/cover/edit.js +++ b/packages/block-library/src/cover/edit.js @@ -8,11 +8,7 @@ import tinycolor from 'tinycolor2'; /** * WordPress dependencies */ -import { - useEffect, - useRef, - useState, -} from '@wordpress/element'; +import { useEffect, useRef, useState } from '@wordpress/element'; import { BaseControl, Button, @@ -57,11 +53,14 @@ import { */ const ALLOWED_MEDIA_TYPES = [ 'image', 'video' ]; const INNER_BLOCKS_TEMPLATE = [ - [ 'core/paragraph', { - align: 'center', - fontSize: 'large', - placeholder: __( 'Write title…' ), - } ], + [ + 'core/paragraph', + { + align: 'center', + fontSize: 'large', + placeholder: __( 'Write title…' ), + }, + ], ]; function retrieveFastAverageColor() { @@ -71,40 +70,47 @@ function retrieveFastAverageColor() { return retrieveFastAverageColor.fastAverageColor; } -const CoverHeightInput = withInstanceId( - function( { value = '', instanceId, onChange } ) { - const [ temporaryInput, setTemporaryInput ] = useState( null ); - const inputId = `block-cover-height-input-${ instanceId }`; - return ( - <BaseControl label={ __( 'Minimum height in pixels' ) } id={ inputId }> - <input - type="number" - id={ inputId } - onChange={ ( event ) => { - const unprocessedValue = event.target.value; - const inputValue = unprocessedValue !== '' ? - parseInt( event.target.value, 10 ) : - undefined; - if ( ( isNaN( inputValue ) || inputValue < COVER_MIN_HEIGHT ) && inputValue !== undefined ) { - setTemporaryInput( event.target.value ); - return; - } +const CoverHeightInput = withInstanceId( function( { + value = '', + instanceId, + onChange, +} ) { + const [ temporaryInput, setTemporaryInput ] = useState( null ); + const inputId = `block-cover-height-input-${ instanceId }`; + return ( + <BaseControl label={ __( 'Minimum height in pixels' ) } id={ inputId }> + <input + type="number" + id={ inputId } + onChange={ ( event ) => { + const unprocessedValue = event.target.value; + const inputValue = + unprocessedValue !== '' + ? parseInt( event.target.value, 10 ) + : undefined; + if ( + ( isNaN( inputValue ) || + inputValue < COVER_MIN_HEIGHT ) && + inputValue !== undefined + ) { + setTemporaryInput( event.target.value ); + return; + } + setTemporaryInput( null ); + onChange( inputValue ); + } } + onBlur={ () => { + if ( temporaryInput !== null ) { setTemporaryInput( null ); - onChange( inputValue ); - } } - onBlur={ () => { - if ( temporaryInput !== null ) { - setTemporaryInput( null ); - } - } } - value={ temporaryInput !== null ? temporaryInput : value } - min={ COVER_MIN_HEIGHT } - step="1" - /> - </BaseControl> - ); - } -); + } + } } + value={ temporaryInput !== null ? temporaryInput : value } + min={ COVER_MIN_HEIGHT } + step="1" + /> + </BaseControl> + ); +} ); const RESIZABLE_BOX_ENABLE_OPTION = { top: false, @@ -128,12 +134,9 @@ function ResizableCover( { return ( <ResizableBox - className={ classnames( - className, - { - 'is-resizing': isResizing, - } - ) } + className={ classnames( className, { + 'is-resizing': isResizing, + } ) } enable={ RESIZABLE_BOX_ENABLE_OPTION } onResizeStart={ ( event, direction, elt ) => { onResizeStart( elt.clientHeight ); @@ -172,7 +175,8 @@ function onCoverSelectMedia( setAttributes ) { // Videos contain the media type of 'file' in the object returned from the rest api. mediaType = VIDEO_BACKGROUND_TYPE; } - } else { // for media selections originated from existing files in the media library. + } else { + // for media selections originated from existing files in the media library. if ( media.type !== IMAGE_BACKGROUND_TYPE && media.type !== VIDEO_BACKGROUND_TYPE @@ -186,10 +190,9 @@ function onCoverSelectMedia( setAttributes ) { url: media.url, id: media.id, backgroundType: mediaType, - ...( mediaType === VIDEO_BACKGROUND_TYPE ? - { focalPoint: undefined, hasParallax: undefined } : - {} - ), + ...( mediaType === VIDEO_BACKGROUND_TYPE + ? { focalPoint: undefined, hasParallax: undefined } + : {} ), } ); }; } @@ -214,9 +217,12 @@ function useCoverIsDark( url, dimRatio = 50, overlayColor, elementRef ) { // If opacity is lower than 50 the dominant color is the image or video color, // so use that color for the dark mode computation. if ( url && dimRatio <= 50 && elementRef.current ) { - retrieveFastAverageColor().getColorAsync( elementRef.current, ( color ) => { - setIsDark( color.isDark ); - } ); + retrieveFastAverageColor().getColorAsync( + elementRef.current, + ( color ) => { + setIsDark( color.isDark ); + } + ); } }, [ url, url && dimRatio <= 50 && elementRef.current, setIsDark ] ); useEffect( () => { @@ -274,20 +280,23 @@ function CoverEdit( { }; const isDarkElement = useRef(); - const isDark = useCoverIsDark( url, dimRatio, overlayColor.color, isDarkElement ); + const isDark = useCoverIsDark( + url, + dimRatio, + overlayColor.color, + isDarkElement + ); const [ temporaryMinHeight, setTemporaryMinHeight ] = useState( null ); const { removeAllNotices, createErrorNotice } = noticeOperations; const style = { - ...( - backgroundType === IMAGE_BACKGROUND_TYPE ? - backgroundImageStyles( url ) : - {} - ), + ...( backgroundType === IMAGE_BACKGROUND_TYPE + ? backgroundImageStyles( url ) + : {} ), backgroundColor: overlayColor.color, - minHeight: ( temporaryMinHeight || minHeight ), + minHeight: temporaryMinHeight || minHeight, }; if ( gradientValue && ! url ) { @@ -295,7 +304,8 @@ function CoverEdit( { } if ( focalPoint ) { - style.backgroundPosition = `${ focalPoint.x * 100 }% ${ focalPoint.y * 100 }%`; + style.backgroundPosition = `${ focalPoint.x * 100 }% ${ focalPoint.y * + 100 }%`; } const hasBackground = !! ( url || overlayColor.color || gradientValue ); @@ -322,35 +332,37 @@ function CoverEdit( { onChange={ toggleParallax } /> ) } - { IMAGE_BACKGROUND_TYPE === backgroundType && ! hasParallax && ( - <FocalPointPicker - label={ __( 'Focal Point Picker' ) } - url={ url } - value={ focalPoint } - onChange={ ( newFocalPoint ) => setAttributes( { focalPoint: newFocalPoint } ) } - /> - ) } + { IMAGE_BACKGROUND_TYPE === backgroundType && + ! hasParallax && ( + <FocalPointPicker + label={ __( 'Focal Point Picker' ) } + url={ url } + value={ focalPoint } + onChange={ ( newFocalPoint ) => + setAttributes( { + focalPoint: newFocalPoint, + } ) + } + /> + ) } { VIDEO_BACKGROUND_TYPE === backgroundType && ( - <video - autoPlay - muted - loop - src={ url } - /> + <video autoPlay muted loop src={ url } /> ) } <PanelRow> <Button isSecondary isSmall className="block-library-cover__reset-button" - onClick={ () => setAttributes( { - url: undefined, - id: undefined, - backgroundType: undefined, - dimRatio: undefined, - focalPoint: undefined, - hasParallax: undefined, - } ) } + onClick={ () => + setAttributes( { + url: undefined, + id: undefined, + backgroundType: undefined, + dimRatio: undefined, + focalPoint: undefined, + hasParallax: undefined, + } ) + } > { __( 'Clear Media' ) } </Button> @@ -362,25 +374,33 @@ function CoverEdit( { <PanelBody title={ __( 'Dimensions' ) }> <CoverHeightInput value={ temporaryMinHeight || minHeight } - onChange={ ( newMinHeight ) => setAttributes( { minHeight: newMinHeight } ) } + onChange={ ( newMinHeight ) => + setAttributes( { minHeight: newMinHeight } ) + } /> </PanelBody> <PanelColorGradientSettings title={ __( 'Overlay' ) } initialOpen={ true } - settings={ [ { - colorValue: overlayColor.color, - gradientValue, - onColorChange: setOverlayColor, - onGradientChange: setGradient, - label: __( 'Overlay' ), - } ] } + settings={ [ + { + colorValue: overlayColor.color, + gradientValue, + onColorChange: setOverlayColor, + onGradientChange: setGradient, + label: __( 'Overlay' ), + }, + ] } > { !! url && ( <RangeControl label={ __( 'Background Opacity' ) } value={ dimRatio } - onChange={ ( newDimRation ) => setAttributes( { dimRatio: newDimRation } ) } + onChange={ ( newDimRation ) => + setAttributes( { + dimRatio: newDimRation, + } ) + } min={ 0 } max={ 100 } step={ 10 } @@ -406,7 +426,9 @@ function CoverEdit( { className={ className } labels={ { title: label, - instructions: __( 'Upload an image or video file, or pick one from your media library.' ), + instructions: __( + 'Upload an image or video file, or pick one from your media library.' + ), } } onSelect={ onSelectMedia } accept="image/*,video/*" @@ -417,9 +439,7 @@ function CoverEdit( { createErrorNotice( message ); } } > - <div - className="wp-block-cover__placeholder-background-options" - > + <div className="wp-block-cover__placeholder-background-options"> <ColorPalette disableCustomColors={ true } value={ overlayColor.color } @@ -432,18 +452,14 @@ function CoverEdit( { ); } - const classes = classnames( - className, - dimRatioToClass( dimRatio ), - { - 'is-dark-theme': isDark, - 'has-background-dim': dimRatio !== 0, - 'has-parallax': hasParallax, - [ overlayColor.class ]: overlayColor.class, - 'has-background-gradient': gradientValue, - [ gradientClass ]: ! url && gradientClass, - } - ); + const classes = classnames( className, dimRatioToClass( dimRatio ), { + 'is-dark-theme': isDark, + 'has-background-dim': dimRatio !== 0, + 'has-parallax': hasParallax, + [ overlayColor.class ]: overlayColor.class, + 'has-background-gradient': gradientValue, + [ gradientClass ]: ! url && gradientClass, + } ); return ( <> @@ -451,26 +467,21 @@ function CoverEdit( { <ResizableCover className={ classnames( 'block-library-cover__resize-container', - { 'is-selected': isSelected }, + { + 'is-selected': isSelected, + } ) } onResizeStart={ () => toggleSelection( false ) } onResize={ setTemporaryMinHeight } - onResizeStop={ - ( newMinHeight ) => { - toggleSelection( true ); - setAttributes( { minHeight: newMinHeight } ); - setTemporaryMinHeight( null ); - } - } + onResizeStop={ ( newMinHeight ) => { + toggleSelection( true ); + setAttributes( { minHeight: newMinHeight } ); + setTemporaryMinHeight( null ); + } } > - - <div - data-url={ url } - style={ style } - className={ classes } - > + <div data-url={ url } style={ style } className={ classes }> { IMAGE_BACKGROUND_TYPE === backgroundType && ( - // Used only to programmatically check if the image is dark or not + // Used only to programmatically check if the image is dark or not <img ref={ isDarkElement } aria-hidden @@ -486,7 +497,7 @@ function CoverEdit( { aria-hidden="true" className={ classnames( 'wp-block-cover__gradient-background', - gradientClass, + gradientClass ) } style={ { background: gradientValue } } /> @@ -502,9 +513,7 @@ function CoverEdit( { /> ) } <div className="wp-block-cover__inner-container"> - <InnerBlocks - template={ INNER_BLOCKS_TEMPLATE } - /> + <InnerBlocks template={ INNER_BLOCKS_TEMPLATE } /> </div> </div> </ResizableCover> diff --git a/packages/block-library/src/cover/index.js b/packages/block-library/src/cover/index.js index ad3ca2d0e9fa09..cba3d554e5f01a 100644 --- a/packages/block-library/src/cover/index.js +++ b/packages/block-library/src/cover/index.js @@ -19,7 +19,9 @@ export { metadata, name }; export const settings = { title: __( 'Cover' ), - description: __( 'Add an image or video with a text overlay — great for headers.' ), + description: __( + 'Add an image or video with a text overlay — great for headers.' + ), icon, supports: { align: true, diff --git a/packages/block-library/src/cover/save.js b/packages/block-library/src/cover/save.js index 9c2a1c0e51afd9..4f5d997176b7b3 100644 --- a/packages/block-library/src/cover/save.js +++ b/packages/block-library/src/cover/save.js @@ -35,17 +35,23 @@ export default function save( { attributes } ) { url, minHeight, } = attributes; - const overlayColorClass = getColorClassName( 'background-color', overlayColor ); + const overlayColorClass = getColorClassName( + 'background-color', + overlayColor + ); const gradientClass = __experimentalGetGradientClass( gradient ); - const style = backgroundType === IMAGE_BACKGROUND_TYPE ? - backgroundImageStyles( url ) : - {}; + const style = + backgroundType === IMAGE_BACKGROUND_TYPE + ? backgroundImageStyles( url ) + : {}; if ( ! overlayColorClass ) { style.backgroundColor = customOverlayColor; } if ( focalPoint && ! hasParallax ) { - style.backgroundPosition = `${ Math.round( focalPoint.x * 100 ) }% ${ Math.round( focalPoint.y * 100 ) }%`; + style.backgroundPosition = `${ Math.round( + focalPoint.x * 100 + ) }% ${ Math.round( focalPoint.y * 100 ) }%`; } if ( customGradient && ! url ) { style.background = customGradient; @@ -60,7 +66,7 @@ export default function save( { attributes } ) { 'has-parallax': hasParallax, 'has-background-gradient': customGradient, [ gradientClass ]: ! url && gradientClass, - }, + } ); return ( @@ -72,16 +78,22 @@ export default function save( { attributes } ) { 'wp-block-cover__gradient-background', gradientClass ) } - style={ customGradient ? { background: customGradient } : undefined } + style={ + customGradient + ? { background: customGradient } + : undefined + } + /> + ) } + { VIDEO_BACKGROUND_TYPE === backgroundType && url && ( + <video + className="wp-block-cover__video-background" + autoPlay + muted + loop + src={ url } /> ) } - { VIDEO_BACKGROUND_TYPE === backgroundType && url && ( <video - className="wp-block-cover__video-background" - autoPlay - muted - loop - src={ url } - /> ) } <div className="wp-block-cover__inner-container"> <InnerBlocks.Content /> </div> diff --git a/packages/block-library/src/cover/shared.js b/packages/block-library/src/cover/shared.js index 532504d0aa24b2..34f5bd8e257f60 100644 --- a/packages/block-library/src/cover/shared.js +++ b/packages/block-library/src/cover/shared.js @@ -2,13 +2,11 @@ export const IMAGE_BACKGROUND_TYPE = 'image'; export const VIDEO_BACKGROUND_TYPE = 'video'; export const COVER_MIN_HEIGHT = 50; export function backgroundImageStyles( url ) { - return url ? - { backgroundImage: `url(${ url })` } : - {}; + return url ? { backgroundImage: `url(${ url })` } : {}; } export function dimRatioToClass( ratio ) { - return ( ratio === 0 || ratio === 50 || ! ratio ) ? - null : - 'has-background-dim-' + ( 10 * Math.round( ratio / 10 ) ); + return ratio === 0 || ratio === 50 || ! ratio + ? null + : 'has-background-dim-' + 10 * Math.round( ratio / 10 ); } diff --git a/packages/block-library/src/cover/transforms.js b/packages/block-library/src/cover/transforms.js index 13d4082a4052d1..799cb7a730ae9b 100644 --- a/packages/block-library/src/cover/transforms.js +++ b/packages/block-library/src/cover/transforms.js @@ -13,69 +13,89 @@ const transforms = { { type: 'block', blocks: [ 'core/image' ], - transform: ( { caption, url, align, id } ) => ( + transform: ( { caption, url, align, id } ) => createBlock( 'core/cover', { title: caption, url, align, id, - } ) - ), + } ), }, { type: 'block', blocks: [ 'core/video' ], - transform: ( { caption, src, align, id } ) => ( + transform: ( { caption, src, align, id } ) => createBlock( 'core/cover', { title: caption, url: src, align, id, backgroundType: VIDEO_BACKGROUND_TYPE, - } ) - ), + } ), }, ], to: [ { type: 'block', blocks: [ 'core/image' ], - isMatch: ( { backgroundType, url, overlayColor, customOverlayColor, gradient, customGradient } ) => { + isMatch: ( { + backgroundType, + url, + overlayColor, + customOverlayColor, + gradient, + customGradient, + } ) => { if ( url ) { // If a url exists the transform could happen if that URL represents an image background. return backgroundType === IMAGE_BACKGROUND_TYPE; } // If a url is not set the transform could happen if the cover has no background color or gradient; - return ! overlayColor && ! customOverlayColor && ! gradient && ! customGradient; + return ( + ! overlayColor && + ! customOverlayColor && + ! gradient && + ! customGradient + ); }, - transform: ( { title, url, align, id } ) => ( + transform: ( { title, url, align, id } ) => createBlock( 'core/image', { caption: title, url, align, id, - } ) - ), + } ), }, { type: 'block', blocks: [ 'core/video' ], - isMatch: ( { backgroundType, url, overlayColor, customOverlayColor, gradient, customGradient } ) => { + isMatch: ( { + backgroundType, + url, + overlayColor, + customOverlayColor, + gradient, + customGradient, + } ) => { if ( url ) { // If a url exists the transform could happen if that URL represents a video background. return backgroundType === VIDEO_BACKGROUND_TYPE; } // If a url is not set the transform could happen if the cover has no background color or gradient; - return ! overlayColor && ! customOverlayColor && ! gradient && ! customGradient; + return ( + ! overlayColor && + ! customOverlayColor && + ! gradient && + ! customGradient + ); }, - transform: ( { title, url, align, id } ) => ( + transform: ( { title, url, align, id } ) => createBlock( 'core/video', { caption: title, src: url, id, align, - } ) - ), + } ), }, ], }; diff --git a/packages/block-library/src/embed/core-embeds.js b/packages/block-library/src/embed/core-embeds.js index 5fd67e73c648ab..92f3e8f683965a 100644 --- a/packages/block-library/src/embed/core-embeds.js +++ b/packages/block-library/src/embed/core-embeds.js @@ -44,7 +44,10 @@ export const common = [ keywords: [ __( 'music' ), __( 'video' ) ], description: __( 'Embed a YouTube video.' ), }, - patterns: [ /^https?:\/\/((m|www)\.)?youtube\.com\/.+/i, /^https?:\/\/youtu\.be\/.+/i ], + patterns: [ + /^https?:\/\/((m|www)\.)?youtube\.com\/.+/i, + /^https?:\/\/youtu\.be\/.+/i, + ], }, { name: 'core-embed/facebook', @@ -103,7 +106,10 @@ export const common = [ keywords: [ __( 'image' ) ], description: __( 'Embed Flickr content.' ), }, - patterns: [ /^https?:\/\/(www\.)?flickr\.com\/.+/i, /^https?:\/\/flic\.kr\/.+/i ], + patterns: [ + /^https?:\/\/(www\.)?flickr\.com\/.+/i, + /^https?:\/\/flic\.kr\/.+/i, + ], }, { name: 'core-embed/vimeo', @@ -155,18 +161,24 @@ export const others = [ title: 'Crowdsignal', icon: embedContentIcon, keywords: [ 'polldaddy' ], - transform: [ { - type: 'block', - blocks: [ 'core-embed/polldaddy' ], - transform: ( content ) => { - return createBlock( 'core-embed/crowdsignal', { - content, - } ); + transform: [ + { + type: 'block', + blocks: [ 'core-embed/polldaddy' ], + transform: ( content ) => { + return createBlock( 'core-embed/crowdsignal', { + content, + } ); + }, }, - } ], - description: __( 'Embed Crowdsignal (formerly Polldaddy) content.' ), + ], + description: __( + 'Embed Crowdsignal (formerly Polldaddy) content.' + ), }, - patterns: [ /^https?:\/\/((.+\.)?polldaddy\.com|poll\.fm|.+\.survey\.fm)\/.+/i ], + patterns: [ + /^https?:\/\/((.+\.)?polldaddy\.com|poll\.fm|.+\.survey\.fm)\/.+/i, + ], }, { name: 'core-embed/dailymotion', @@ -211,7 +223,10 @@ export const others = [ icon: embedContentIcon, description: __( 'Embed Kickstarter content.' ), }, - patterns: [ /^https?:\/\/(www\.)?kickstarter\.com\/.+/i, /^https?:\/\/kck\.st\/.+/i ], + patterns: [ + /^https?:\/\/(www\.)?kickstarter\.com\/.+/i, + /^https?:\/\/kck\.st\/.+/i, + ], }, { name: 'core-embed/meetup-com', @@ -316,15 +331,17 @@ export const others = [ settings: { title: 'Speaker Deck', icon: embedContentIcon, - transform: [ { - type: 'block', - blocks: [ 'core-embed/speaker' ], - transform: ( content ) => { - return createBlock( 'core-embed/speaker-deck', { - content, - } ); + transform: [ + { + type: 'block', + blocks: [ 'core-embed/speaker' ], + transform: ( content ) => { + return createBlock( 'core-embed/speaker-deck', { + content, + } ); + }, }, - } ], + ], description: __( 'Embed Speaker Deck content.' ), }, patterns: [ /^https?:\/\/(www\.)?speakerdeck\.com\/.+/i ], diff --git a/packages/block-library/src/embed/edit.js b/packages/block-library/src/embed/edit.js index b92d07d2927d30..875eb8f61da8aa 100644 --- a/packages/block-library/src/embed/edit.js +++ b/packages/block-library/src/embed/edit.js @@ -1,7 +1,12 @@ /** * Internal dependencies */ -import { createUpgradedEmbedBlock, getClassNames, fallback, getAttributesFromPreview } from './util'; +import { + createUpgradedEmbedBlock, + getClassNames, + fallback, + getAttributesFromPreview, +} from './util'; import EmbedControls from './embed-controls'; import EmbedLoading from './embed-loading'; import EmbedPlaceholder from './embed-placeholder'; @@ -28,7 +33,9 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { this.setMergedAttributes = this.setMergedAttributes.bind( this ); this.getResponsiveHelp = this.getResponsiveHelp.bind( this ); this.toggleResponsive = this.toggleResponsive.bind( this ); - this.handleIncomingPreview = this.handleIncomingPreview.bind( this ); + this.handleIncomingPreview = this.handleIncomingPreview.bind( + this + ); this.state = { editingURL: false, @@ -56,9 +63,14 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { componentDidUpdate( prevProps ) { const hasPreview = undefined !== this.props.preview; const hadPreview = undefined !== prevProps.preview; - const previewChanged = prevProps.preview && this.props.preview && this.props.preview.html !== prevProps.preview.html; - const switchedPreview = previewChanged || ( hasPreview && ! hadPreview ); - const switchedURL = this.props.attributes.url !== prevProps.attributes.url; + const previewChanged = + prevProps.preview && + this.props.preview && + this.props.preview.html !== prevProps.preview.html; + const switchedPreview = + previewChanged || ( hasPreview && ! hadPreview ); + const switchedURL = + this.props.attributes.url !== prevProps.attributes.url; if ( switchedPreview || switchedURL ) { if ( this.props.cannotEmbed ) { @@ -75,9 +87,12 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { } resubmitWithoutTrailingSlash() { - this.setState( ( prevState ) => ( { - url: prevState.url.replace( /\/$/, '' ), - } ), this.setUrl ); + this.setState( + ( prevState ) => ( { + url: prevState.url.replace( /\/$/, '' ), + } ), + this.setUrl + ); } setUrl( event ) { @@ -96,7 +111,16 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { getMergedAttributes() { const { preview } = this.props; const { className, allowResponsive } = this.props.attributes; - return { ...this.props.attributes, ...getAttributesFromPreview( preview, title, className, responsive, allowResponsive ) }; + return { + ...this.props.attributes, + ...getAttributesFromPreview( + preview, + title, + className, + responsive, + allowResponsive + ), + }; } /*** @@ -112,7 +136,13 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { } getResponsiveHelp( checked ) { - return checked ? __( 'This embed will preserve its aspect ratio when the browser is resized.' ) : __( 'This embed may not preserve its aspect ratio when the browser is resized.' ); + return checked + ? __( + 'This embed will preserve its aspect ratio when the browser is resized.' + ) + : __( + 'This embed may not preserve its aspect ratio when the browser is resized.' + ); } toggleResponsive() { @@ -120,22 +150,30 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { const { html } = this.props.preview; const newAllowResponsive = ! allowResponsive; - this.props.setAttributes( - { - allowResponsive: newAllowResponsive, - className: getClassNames( html, className, responsive && newAllowResponsive ), - } - ); + this.props.setAttributes( { + allowResponsive: newAllowResponsive, + className: getClassNames( + html, + className, + responsive && newAllowResponsive + ), + } ); } render() { const { url, editingURL } = this.state; - const { fetching, setAttributes, isSelected, preview, cannotEmbed, themeSupportsResponsive, tryAgain } = this.props; + const { + fetching, + setAttributes, + isSelected, + preview, + cannotEmbed, + themeSupportsResponsive, + tryAgain, + } = this.props; if ( fetching ) { - return ( - <EmbedLoading /> - ); + return <EmbedLoading />; } // translators: %s: type of embed e.g: "YouTube", "Twitter", etc. "Embed" is used when no specific type exists @@ -150,7 +188,9 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { onSubmit={ this.setUrl } value={ url } cannotEmbed={ cannotEmbed } - onChange={ ( event ) => this.setState( { url: event.target.value } ) } + onChange={ ( event ) => + this.setState( { url: event.target.value } ) + } fallback={ () => fallback( url, this.props.onReplace ) } tryAgain={ tryAgain } /> @@ -167,7 +207,10 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { // calculating them on every render. const previewAttributes = this.getMergedAttributes(); const { caption, type, allowResponsive } = previewAttributes; - const className = classnames( previewAttributes.className, this.props.className ); + const className = classnames( + previewAttributes.className, + this.props.className + ); return ( <> @@ -186,7 +229,9 @@ export function getEmbedEditComponent( title, icon, responsive = true ) { url={ url } type={ type } caption={ caption } - onCaptionChange={ ( value ) => setAttributes( { caption: value } ) } + onCaptionChange={ ( value ) => + setAttributes( { caption: value } ) + } isSelected={ isSelected } icon={ icon } label={ label } diff --git a/packages/block-library/src/embed/embed-controls.js b/packages/block-library/src/embed/embed-controls.js index 8b87a34e81fb35..8850337e529ebb 100644 --- a/packages/block-library/src/embed/embed-controls.js +++ b/packages/block-library/src/embed/embed-controls.js @@ -36,7 +36,10 @@ const EmbedControls = ( props ) => { </BlockControls> { themeSupportsResponsive && blockSupportsResponsive && ( <InspectorControls> - <PanelBody title={ __( 'Media settings' ) } className="blocks-responsive"> + <PanelBody + title={ __( 'Media settings' ) } + className="blocks-responsive" + > <ToggleControl label={ __( 'Resize for smaller devices' ) } checked={ allowResponsive } diff --git a/packages/block-library/src/embed/embed-placeholder.js b/packages/block-library/src/embed/embed-placeholder.js index 3d9053e3d156b6..83169d6758b296 100644 --- a/packages/block-library/src/embed/embed-placeholder.js +++ b/packages/block-library/src/embed/embed-placeholder.js @@ -6,13 +6,24 @@ import { Button, Placeholder, ExternalLink } from '@wordpress/components'; import { BlockIcon } from '@wordpress/block-editor'; const EmbedPlaceholder = ( props ) => { - const { icon, label, value, onSubmit, onChange, cannotEmbed, fallback, tryAgain } = props; + const { + icon, + label, + value, + onSubmit, + onChange, + cannotEmbed, + fallback, + tryAgain, + } = props; return ( <Placeholder icon={ <BlockIcon icon={ icon } showColors /> } label={ label } className="wp-block-embed" - instructions={ __( 'Paste a link to the content you want to display on your site.' ) } + instructions={ __( + 'Paste a link to the content you want to display on your site.' + ) } > <form onSubmit={ onSubmit }> <input @@ -21,27 +32,34 @@ const EmbedPlaceholder = ( props ) => { className="components-placeholder__input" aria-label={ label } placeholder={ __( 'Enter URL to embed here…' ) } - onChange={ onChange } /> - <Button - isSecondary - type="submit" - > + onChange={ onChange } + /> + <Button isSecondary type="submit"> { _x( 'Embed', 'button label' ) } </Button> </form> <div className="components-placeholder__learn-more"> - <ExternalLink href={ __( 'https://wordpress.org/support/article/embeds/' ) }> + <ExternalLink + href={ __( + 'https://wordpress.org/support/article/embeds/' + ) } + > { __( 'Learn more about embeds' ) } </ExternalLink> </div> - { cannotEmbed && + { cannotEmbed && ( <div className="components-placeholder__error"> <div className="components-placeholder__instructions"> { __( 'Sorry, this content could not be embedded.' ) } </div> - <Button isSecondary onClick={ tryAgain }>{ _x( 'Try again', 'button label' ) }</Button> <Button isSecondary onClick={ fallback }>{ _x( 'Convert to link', 'button label' ) }</Button> + <Button isSecondary onClick={ tryAgain }> + { _x( 'Try again', 'button label' ) } + </Button>{ ' ' } + <Button isSecondary onClick={ fallback }> + { _x( 'Convert to link', 'button label' ) } + </Button> </div> - } + ) } </Placeholder> ); }; diff --git a/packages/block-library/src/embed/embed-preview.js b/packages/block-library/src/embed/embed-preview.js index 878c3e7504ccae..db139e5292b7bd 100644 --- a/packages/block-library/src/embed/embed-preview.js +++ b/packages/block-library/src/embed/embed-preview.js @@ -54,55 +54,90 @@ class EmbedPreview extends Component { } render() { - const { preview, url, type, caption, onCaptionChange, isSelected, className, icon, label } = this.props; + const { + preview, + url, + type, + caption, + onCaptionChange, + isSelected, + className, + icon, + label, + } = this.props; const { scripts } = preview; const { interactive } = this.state; const html = 'photo' === type ? getPhotoHtml( preview ) : preview.html; const parsedHost = parse( url ).host.split( '.' ); - const parsedHostBaseUrl = parsedHost.splice( parsedHost.length - 2, parsedHost.length - 1 ).join( '.' ); + const parsedHostBaseUrl = parsedHost + .splice( parsedHost.length - 2, parsedHost.length - 1 ) + .join( '.' ); const cannotPreview = includes( HOSTS_NO_PREVIEWS, parsedHostBaseUrl ); // translators: %s: host providing embed content e.g: www.youtube.com - const iframeTitle = sprintf( __( 'Embedded content from %s' ), parsedHostBaseUrl ); - const sandboxClassnames = classnames( type, className, 'wp-block-embed__wrapper' ); + const iframeTitle = sprintf( + __( 'Embedded content from %s' ), + parsedHostBaseUrl + ); + const sandboxClassnames = classnames( + type, + className, + 'wp-block-embed__wrapper' + ); // Disabled because the overlay div doesn't actually have a role or functionality // as far as the user is concerned. We're just catching the first click so that // the block can be selected without interacting with the embed preview that the overlay covers. /* eslint-disable jsx-a11y/no-static-element-interactions */ - const embedWrapper = 'wp-embed' === type ? ( - <WpEmbedPreview - html={ html } - /> - ) : ( - <div className="wp-block-embed__wrapper"> - <SandBox - html={ html } - scripts={ scripts } - title={ iframeTitle } - type={ sandboxClassnames } - onFocus={ this.hideOverlay } - /> - { ! interactive && <div - className="block-library-embed__interactive-overlay" - onMouseUp={ this.hideOverlay } /> } - </div> - ); + const embedWrapper = + 'wp-embed' === type ? ( + <WpEmbedPreview html={ html } /> + ) : ( + <div className="wp-block-embed__wrapper"> + <SandBox + html={ html } + scripts={ scripts } + title={ iframeTitle } + type={ sandboxClassnames } + onFocus={ this.hideOverlay } + /> + { ! interactive && ( + <div + className="block-library-embed__interactive-overlay" + onMouseUp={ this.hideOverlay } + /> + ) } + </div> + ); /* eslint-enable jsx-a11y/no-static-element-interactions */ return ( - <figure className={ classnames( className, 'wp-block-embed', { 'is-type-video': 'video' === type } ) }> - { ( cannotPreview ) ? ( - <Placeholder icon={ <BlockIcon icon={ icon } showColors /> } label={ label }> - <p className="components-placeholder__error"><a href={ url }>{ url }</a></p> + <figure + className={ classnames( className, 'wp-block-embed', { + 'is-type-video': 'video' === type, + } ) } + > + { cannotPreview ? ( + <Placeholder + icon={ <BlockIcon icon={ icon } showColors /> } + label={ label } + > <p className="components-placeholder__error"> - { - /* translators: %s: host providing embed content e.g: www.youtube.com */ - sprintf( __( "Embedded content from %s can't be previewed in the editor." ), parsedHostBaseUrl ) - } + <a href={ url }>{ url }</a> + </p> + <p className="components-placeholder__error"> + { /* translators: %s: host providing embed content e.g: www.youtube.com */ + sprintf( + __( + "Embedded content from %s can't be previewed in the editor." + ), + parsedHostBaseUrl + ) } </p> </Placeholder> - ) : embedWrapper } + ) : ( + embedWrapper + ) } { ( ! RichText.isEmpty( caption ) || isSelected ) && ( <RichText tagName="figcaption" diff --git a/packages/block-library/src/embed/icons.js b/packages/block-library/src/embed/icons.js index 4c6aeb6a35666a..38b00c481a7db9 100644 --- a/packages/block-library/src/embed/icons.js +++ b/packages/block-library/src/embed/icons.js @@ -1,46 +1,116 @@ /** * WordPress dependencies */ -import { - G, - Path, - Polygon, - SVG, -} from '@wordpress/components'; +import { G, Path, Polygon, SVG } from '@wordpress/components'; -export const embedContentIcon = <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0,0h24v24H0V0z" fill="none" /><Path d="M19,4H5C3.89,4,3,4.9,3,6v12c0,1.1,0.89,2,2,2h14c1.1,0,2-0.9,2-2V6C21,4.9,20.11,4,19,4z M19,18H5V8h14V18z" /></SVG>; -export const embedAudioIcon = <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><Path d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14zM8 15c0-1.66 1.34-3 3-3 .35 0 .69.07 1 .18V6h5v2h-3v7.03c-.02 1.64-1.35 2.97-3 2.97-1.66 0-3-1.34-3-3z" /></SVG>; -export const embedPhotoIcon = <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0,0h24v24H0V0z" fill="none" /><Path d="M21,4H3C1.9,4,1,4.9,1,6v12c0,1.1,0.9,2,2,2h18c1.1,0,2-0.9,2-2V6C23,4.9,22.1,4,21,4z M21,18H3V6h18V18z" /><Polygon points="14.5 11 11 15.51 8.5 12.5 5 17 19 17" /></SVG>; -export const embedVideoIcon = <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0,0h24v24H0V0z" fill="none" /><Path d="m10 8v8l5-4-5-4zm9-5h-14c-1.1 0-2 0.9-2 2v14c0 1.1 0.9 2 2 2h14c1.1 0 2-0.9 2-2v-14c0-1.1-0.9-2-2-2zm0 16h-14v-14h14v14z" /></SVG>; +export const embedContentIcon = ( + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <Path d="M0,0h24v24H0V0z" fill="none" /> + <Path d="M19,4H5C3.89,4,3,4.9,3,6v12c0,1.1,0.89,2,2,2h14c1.1,0,2-0.9,2-2V6C21,4.9,20.11,4,19,4z M19,18H5V8h14V18z" /> + </SVG> +); +export const embedAudioIcon = ( + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <Path fill="none" d="M0 0h24v24H0V0z" /> + <Path d="M21 3H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14zM8 15c0-1.66 1.34-3 3-3 .35 0 .69.07 1 .18V6h5v2h-3v7.03c-.02 1.64-1.35 2.97-3 2.97-1.66 0-3-1.34-3-3z" /> + </SVG> +); +export const embedPhotoIcon = ( + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <Path d="M0,0h24v24H0V0z" fill="none" /> + <Path d="M21,4H3C1.9,4,1,4.9,1,6v12c0,1.1,0.9,2,2,2h18c1.1,0,2-0.9,2-2V6C23,4.9,22.1,4,21,4z M21,18H3V6h18V18z" /> + <Polygon points="14.5 11 11 15.51 8.5 12.5 5 17 19 17" /> + </SVG> +); +export const embedVideoIcon = ( + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <Path d="M0,0h24v24H0V0z" fill="none" /> + <Path d="m10 8v8l5-4-5-4zm9-5h-14c-1.1 0-2 0.9-2 2v14c0 1.1 0.9 2 2 2h14c1.1 0 2-0.9 2-2v-14c0-1.1-0.9-2-2-2zm0 16h-14v-14h14v14z" /> + </SVG> +); export const embedTwitterIcon = { foreground: '#1da1f2', - src: <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><G><Path d="M22.23 5.924c-.736.326-1.527.547-2.357.646.847-.508 1.498-1.312 1.804-2.27-.793.47-1.67.812-2.606.996C18.325 4.498 17.258 4 16.078 4c-2.266 0-4.103 1.837-4.103 4.103 0 .322.036.635.106.935-3.41-.17-6.433-1.804-8.457-4.287-.353.607-.556 1.312-.556 2.064 0 1.424.724 2.68 1.825 3.415-.673-.022-1.305-.207-1.86-.514v.052c0 1.988 1.415 3.647 3.293 4.023-.344.095-.707.145-1.08.145-.265 0-.522-.026-.773-.074.522 1.63 2.038 2.817 3.833 2.85-1.404 1.1-3.174 1.757-5.096 1.757-.332 0-.66-.02-.98-.057 1.816 1.164 3.973 1.843 6.29 1.843 7.547 0 11.675-6.252 11.675-11.675 0-.178-.004-.355-.012-.53.802-.578 1.497-1.3 2.047-2.124z"></Path></G></SVG>, + src: ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <G> + <Path d="M22.23 5.924c-.736.326-1.527.547-2.357.646.847-.508 1.498-1.312 1.804-2.27-.793.47-1.67.812-2.606.996C18.325 4.498 17.258 4 16.078 4c-2.266 0-4.103 1.837-4.103 4.103 0 .322.036.635.106.935-3.41-.17-6.433-1.804-8.457-4.287-.353.607-.556 1.312-.556 2.064 0 1.424.724 2.68 1.825 3.415-.673-.022-1.305-.207-1.86-.514v.052c0 1.988 1.415 3.647 3.293 4.023-.344.095-.707.145-1.08.145-.265 0-.522-.026-.773-.074.522 1.63 2.038 2.817 3.833 2.85-1.404 1.1-3.174 1.757-5.096 1.757-.332 0-.66-.02-.98-.057 1.816 1.164 3.973 1.843 6.29 1.843 7.547 0 11.675-6.252 11.675-11.675 0-.178-.004-.355-.012-.53.802-.578 1.497-1.3 2.047-2.124z"></Path> + </G> + </SVG> + ), }; export const embedYouTubeIcon = { foreground: '#ff0000', - src: <SVG viewBox="0 0 24 24"><Path d="M21.8 8s-.195-1.377-.795-1.984c-.76-.797-1.613-.8-2.004-.847-2.798-.203-6.996-.203-6.996-.203h-.01s-4.197 0-6.996.202c-.39.046-1.242.05-2.003.846C2.395 6.623 2.2 8 2.2 8S2 9.62 2 11.24v1.517c0 1.618.2 3.237.2 3.237s.195 1.378.795 1.985c.76.797 1.76.77 2.205.855 1.6.153 6.8.2 6.8.2s4.203-.005 7-.208c.392-.047 1.244-.05 2.005-.847.6-.607.795-1.985.795-1.985s.2-1.618.2-3.237v-1.517C22 9.62 21.8 8 21.8 8zM9.935 14.595v-5.62l5.403 2.82-5.403 2.8z" /></SVG>, + src: ( + <SVG viewBox="0 0 24 24"> + <Path d="M21.8 8s-.195-1.377-.795-1.984c-.76-.797-1.613-.8-2.004-.847-2.798-.203-6.996-.203-6.996-.203h-.01s-4.197 0-6.996.202c-.39.046-1.242.05-2.003.846C2.395 6.623 2.2 8 2.2 8S2 9.62 2 11.24v1.517c0 1.618.2 3.237.2 3.237s.195 1.378.795 1.985c.76.797 1.76.77 2.205.855 1.6.153 6.8.2 6.8.2s4.203-.005 7-.208c.392-.047 1.244-.05 2.005-.847.6-.607.795-1.985.795-1.985s.2-1.618.2-3.237v-1.517C22 9.62 21.8 8 21.8 8zM9.935 14.595v-5.62l5.403 2.82-5.403 2.8z" /> + </SVG> + ), }; export const embedFacebookIcon = { foreground: '#3b5998', - src: <SVG viewBox="0 0 24 24"><Path d="M20 3H4c-.6 0-1 .4-1 1v16c0 .5.4 1 1 1h8.6v-7h-2.3v-2.7h2.3v-2c0-2.3 1.4-3.6 3.5-3.6 1 0 1.8.1 2.1.1v2.4h-1.4c-1.1 0-1.3.5-1.3 1.3v1.7h2.7l-.4 2.8h-2.3v7H20c.5 0 1-.4 1-1V4c0-.6-.4-1-1-1z" /></SVG>, + src: ( + <SVG viewBox="0 0 24 24"> + <Path d="M20 3H4c-.6 0-1 .4-1 1v16c0 .5.4 1 1 1h8.6v-7h-2.3v-2.7h2.3v-2c0-2.3 1.4-3.6 3.5-3.6 1 0 1.8.1 2.1.1v2.4h-1.4c-1.1 0-1.3.5-1.3 1.3v1.7h2.7l-.4 2.8h-2.3v7H20c.5 0 1-.4 1-1V4c0-.6-.4-1-1-1z" /> + </SVG> + ), }; -export const embedInstagramIcon = <SVG viewBox="0 0 24 24"><G><Path d="M12 4.622c2.403 0 2.688.01 3.637.052.877.04 1.354.187 1.67.31.42.163.72.358 1.036.673.315.315.51.615.673 1.035.123.317.27.794.31 1.67.043.95.052 1.235.052 3.638s-.01 2.688-.052 3.637c-.04.877-.187 1.354-.31 1.67-.163.42-.358.72-.673 1.036-.315.315-.615.51-1.035.673-.317.123-.794.27-1.67.31-.95.043-1.234.052-3.638.052s-2.688-.01-3.637-.052c-.877-.04-1.354-.187-1.67-.31-.42-.163-.72-.358-1.036-.673-.315-.315-.51-.615-.673-1.035-.123-.317-.27-.794-.31-1.67-.043-.95-.052-1.235-.052-3.638s.01-2.688.052-3.637c.04-.877.187-1.354.31-1.67.163-.42.358-.72.673-1.036.315-.315.615-.51 1.035-.673.317-.123.794-.27 1.67-.31.95-.043 1.235-.052 3.638-.052M12 3c-2.444 0-2.75.01-3.71.054s-1.613.196-2.185.418c-.592.23-1.094.538-1.594 1.04-.5.5-.807 1-1.037 1.593-.223.572-.375 1.226-.42 2.184C3.01 9.25 3 9.555 3 12s.01 2.75.054 3.71.196 1.613.418 2.186c.23.592.538 1.094 1.038 1.594s1.002.808 1.594 1.038c.572.222 1.227.375 2.185.418.96.044 1.266.054 3.71.054s2.75-.01 3.71-.054 1.613-.196 2.186-.418c.592-.23 1.094-.538 1.594-1.038s.808-1.002 1.038-1.594c.222-.572.375-1.227.418-2.185.044-.96.054-1.266.054-3.71s-.01-2.75-.054-3.71-.196-1.613-.418-2.186c-.23-.592-.538-1.094-1.038-1.594s-1.002-.808-1.594-1.038c-.572-.222-1.227-.375-2.185-.418C14.75 3.01 14.445 3 12 3zm0 4.378c-2.552 0-4.622 2.07-4.622 4.622s2.07 4.622 4.622 4.622 4.622-2.07 4.622-4.622S14.552 7.378 12 7.378zM12 15c-1.657 0-3-1.343-3-3s1.343-3 3-3 3 1.343 3 3-1.343 3-3 3zm4.804-8.884c-.596 0-1.08.484-1.08 1.08s.484 1.08 1.08 1.08c.596 0 1.08-.484 1.08-1.08s-.483-1.08-1.08-1.08z"></Path></G></SVG>; +export const embedInstagramIcon = ( + <SVG viewBox="0 0 24 24"> + <G> + <Path d="M12 4.622c2.403 0 2.688.01 3.637.052.877.04 1.354.187 1.67.31.42.163.72.358 1.036.673.315.315.51.615.673 1.035.123.317.27.794.31 1.67.043.95.052 1.235.052 3.638s-.01 2.688-.052 3.637c-.04.877-.187 1.354-.31 1.67-.163.42-.358.72-.673 1.036-.315.315-.615.51-1.035.673-.317.123-.794.27-1.67.31-.95.043-1.234.052-3.638.052s-2.688-.01-3.637-.052c-.877-.04-1.354-.187-1.67-.31-.42-.163-.72-.358-1.036-.673-.315-.315-.51-.615-.673-1.035-.123-.317-.27-.794-.31-1.67-.043-.95-.052-1.235-.052-3.638s.01-2.688.052-3.637c.04-.877.187-1.354.31-1.67.163-.42.358-.72.673-1.036.315-.315.615-.51 1.035-.673.317-.123.794-.27 1.67-.31.95-.043 1.235-.052 3.638-.052M12 3c-2.444 0-2.75.01-3.71.054s-1.613.196-2.185.418c-.592.23-1.094.538-1.594 1.04-.5.5-.807 1-1.037 1.593-.223.572-.375 1.226-.42 2.184C3.01 9.25 3 9.555 3 12s.01 2.75.054 3.71.196 1.613.418 2.186c.23.592.538 1.094 1.038 1.594s1.002.808 1.594 1.038c.572.222 1.227.375 2.185.418.96.044 1.266.054 3.71.054s2.75-.01 3.71-.054 1.613-.196 2.186-.418c.592-.23 1.094-.538 1.594-1.038s.808-1.002 1.038-1.594c.222-.572.375-1.227.418-2.185.044-.96.054-1.266.054-3.71s-.01-2.75-.054-3.71-.196-1.613-.418-2.186c-.23-.592-.538-1.094-1.038-1.594s-1.002-.808-1.594-1.038c-.572-.222-1.227-.375-2.185-.418C14.75 3.01 14.445 3 12 3zm0 4.378c-2.552 0-4.622 2.07-4.622 4.622s2.07 4.622 4.622 4.622 4.622-2.07 4.622-4.622S14.552 7.378 12 7.378zM12 15c-1.657 0-3-1.343-3-3s1.343-3 3-3 3 1.343 3 3-1.343 3-3 3zm4.804-8.884c-.596 0-1.08.484-1.08 1.08s.484 1.08 1.08 1.08c.596 0 1.08-.484 1.08-1.08s-.483-1.08-1.08-1.08z"></Path> + </G> + </SVG> +); export const embedWordPressIcon = { foreground: '#0073AA', - src: <SVG viewBox="0 0 24 24"><G><Path d="M12.158 12.786l-2.698 7.84c.806.236 1.657.365 2.54.365 1.047 0 2.05-.18 2.986-.51-.024-.037-.046-.078-.065-.123l-2.762-7.57zM3.008 12c0 3.56 2.07 6.634 5.068 8.092L3.788 8.342c-.5 1.117-.78 2.354-.78 3.658zm15.06-.454c0-1.112-.398-1.88-.74-2.48-.456-.74-.883-1.368-.883-2.11 0-.825.627-1.595 1.51-1.595.04 0 .078.006.116.008-1.598-1.464-3.73-2.36-6.07-2.36-3.14 0-5.904 1.613-7.512 4.053.21.008.41.012.58.012.94 0 2.395-.114 2.395-.114.484-.028.54.684.057.74 0 0-.487.058-1.03.086l3.275 9.74 1.968-5.902-1.4-3.838c-.485-.028-.944-.085-.944-.085-.486-.03-.43-.77.056-.742 0 0 1.484.114 2.368.114.94 0 2.397-.114 2.397-.114.486-.028.543.684.058.74 0 0-.488.058-1.03.086l3.25 9.665.897-2.997c.456-1.17.684-2.137.684-2.907zm1.82-3.86c.04.286.06.593.06.924 0 .912-.17 1.938-.683 3.22l-2.746 7.94c2.672-1.558 4.47-4.454 4.47-7.77 0-1.564-.4-3.033-1.1-4.314zM12 22C6.486 22 2 17.514 2 12S6.486 2 12 2s10 4.486 10 10-4.486 10-10 10z"></Path></G></SVG>, + src: ( + <SVG viewBox="0 0 24 24"> + <G> + <Path d="M12.158 12.786l-2.698 7.84c.806.236 1.657.365 2.54.365 1.047 0 2.05-.18 2.986-.51-.024-.037-.046-.078-.065-.123l-2.762-7.57zM3.008 12c0 3.56 2.07 6.634 5.068 8.092L3.788 8.342c-.5 1.117-.78 2.354-.78 3.658zm15.06-.454c0-1.112-.398-1.88-.74-2.48-.456-.74-.883-1.368-.883-2.11 0-.825.627-1.595 1.51-1.595.04 0 .078.006.116.008-1.598-1.464-3.73-2.36-6.07-2.36-3.14 0-5.904 1.613-7.512 4.053.21.008.41.012.58.012.94 0 2.395-.114 2.395-.114.484-.028.54.684.057.74 0 0-.487.058-1.03.086l3.275 9.74 1.968-5.902-1.4-3.838c-.485-.028-.944-.085-.944-.085-.486-.03-.43-.77.056-.742 0 0 1.484.114 2.368.114.94 0 2.397-.114 2.397-.114.486-.028.543.684.058.74 0 0-.488.058-1.03.086l3.25 9.665.897-2.997c.456-1.17.684-2.137.684-2.907zm1.82-3.86c.04.286.06.593.06.924 0 .912-.17 1.938-.683 3.22l-2.746 7.94c2.672-1.558 4.47-4.454 4.47-7.77 0-1.564-.4-3.033-1.1-4.314zM12 22C6.486 22 2 17.514 2 12S6.486 2 12 2s10 4.486 10 10-4.486 10-10 10z"></Path> + </G> + </SVG> + ), }; export const embedSpotifyIcon = { foreground: '#1db954', - src: <SVG viewBox="0 0 24 24"><Path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2m4.586 14.424c-.18.295-.563.387-.857.207-2.35-1.434-5.305-1.76-8.786-.963-.335.077-.67-.133-.746-.47-.077-.334.132-.67.47-.745 3.808-.87 7.076-.496 9.712 1.115.293.18.386.563.206.857M17.81 13.7c-.226.367-.706.482-1.072.257-2.687-1.652-6.785-2.13-9.965-1.166-.413.127-.848-.106-.973-.517-.125-.413.108-.848.52-.973 3.632-1.102 8.147-.568 11.234 1.328.366.226.48.707.256 1.072m.105-2.835C14.692 8.95 9.375 8.775 6.297 9.71c-.493.15-1.016-.13-1.166-.624-.148-.495.13-1.017.625-1.167 3.532-1.073 9.404-.866 13.115 1.337.445.264.59.838.327 1.282-.264.443-.838.59-1.282.325" /></SVG>, + src: ( + <SVG viewBox="0 0 24 24"> + <Path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2m4.586 14.424c-.18.295-.563.387-.857.207-2.35-1.434-5.305-1.76-8.786-.963-.335.077-.67-.133-.746-.47-.077-.334.132-.67.47-.745 3.808-.87 7.076-.496 9.712 1.115.293.18.386.563.206.857M17.81 13.7c-.226.367-.706.482-1.072.257-2.687-1.652-6.785-2.13-9.965-1.166-.413.127-.848-.106-.973-.517-.125-.413.108-.848.52-.973 3.632-1.102 8.147-.568 11.234 1.328.366.226.48.707.256 1.072m.105-2.835C14.692 8.95 9.375 8.775 6.297 9.71c-.493.15-1.016-.13-1.166-.624-.148-.495.13-1.017.625-1.167 3.532-1.073 9.404-.866 13.115 1.337.445.264.59.838.327 1.282-.264.443-.838.59-1.282.325" /> + </SVG> + ), }; -export const embedFlickrIcon = <SVG viewBox="0 0 24 24"><Path d="m6.5 7c-2.75 0-5 2.25-5 5s2.25 5 5 5 5-2.25 5-5-2.25-5-5-5zm11 0c-2.75 0-5 2.25-5 5s2.25 5 5 5 5-2.25 5-5-2.25-5-5-5z" /></SVG>; +export const embedFlickrIcon = ( + <SVG viewBox="0 0 24 24"> + <Path d="m6.5 7c-2.75 0-5 2.25-5 5s2.25 5 5 5 5-2.25 5-5-2.25-5-5-5zm11 0c-2.75 0-5 2.25-5 5s2.25 5 5 5 5-2.25 5-5-2.25-5-5-5z" /> + </SVG> +); export const embedVimeoIcon = { foreground: '#1ab7ea', - src: <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><G><Path d="M22.396 7.164c-.093 2.026-1.507 4.8-4.245 8.32C15.323 19.16 12.93 21 10.97 21c-1.214 0-2.24-1.12-3.08-3.36-.56-2.052-1.118-4.105-1.68-6.158-.622-2.24-1.29-3.36-2.004-3.36-.156 0-.7.328-1.634.98l-.978-1.26c1.027-.903 2.04-1.806 3.037-2.71C6 3.95 7.03 3.328 7.716 3.265c1.62-.156 2.616.95 2.99 3.32.404 2.558.685 4.148.84 4.77.468 2.12.982 3.18 1.543 3.18.435 0 1.09-.687 1.963-2.064.872-1.376 1.34-2.422 1.402-3.142.125-1.187-.343-1.782-1.4-1.782-.5 0-1.013.115-1.542.34 1.023-3.35 2.977-4.976 5.862-4.883 2.14.063 3.148 1.45 3.024 4.16z"></Path></G></SVG>, + src: ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <G> + <Path d="M22.396 7.164c-.093 2.026-1.507 4.8-4.245 8.32C15.323 19.16 12.93 21 10.97 21c-1.214 0-2.24-1.12-3.08-3.36-.56-2.052-1.118-4.105-1.68-6.158-.622-2.24-1.29-3.36-2.004-3.36-.156 0-.7.328-1.634.98l-.978-1.26c1.027-.903 2.04-1.806 3.037-2.71C6 3.95 7.03 3.328 7.716 3.265c1.62-.156 2.616.95 2.99 3.32.404 2.558.685 4.148.84 4.77.468 2.12.982 3.18 1.543 3.18.435 0 1.09-.687 1.963-2.064.872-1.376 1.34-2.422 1.402-3.142.125-1.187-.343-1.782-1.4-1.782-.5 0-1.013.115-1.542.34 1.023-3.35 2.977-4.976 5.862-4.883 2.14.063 3.148 1.45 3.024 4.16z"></Path> + </G> + </SVG> + ), }; -export const embedRedditIcon = <SVG viewBox="0 0 24 24"><Path d="M22 11.816c0-1.256-1.02-2.277-2.277-2.277-.593 0-1.122.24-1.526.613-1.48-.965-3.455-1.594-5.647-1.69l1.17-3.702 3.18.75c.01 1.027.847 1.86 1.877 1.86 1.035 0 1.877-.84 1.877-1.877 0-1.035-.842-1.877-1.877-1.877-.77 0-1.43.466-1.72 1.13L13.55 3.92c-.204-.047-.4.067-.46.26l-1.35 4.27c-2.317.037-4.412.67-5.97 1.67-.402-.355-.917-.58-1.493-.58C3.02 9.54 2 10.56 2 11.815c0 .814.433 1.523 1.078 1.925-.037.222-.06.445-.06.673 0 3.292 4.01 5.97 8.94 5.97s8.94-2.678 8.94-5.97c0-.214-.02-.424-.052-.632.687-.39 1.154-1.12 1.154-1.964zm-3.224-7.422c.606 0 1.1.493 1.1 1.1s-.493 1.1-1.1 1.1-1.1-.494-1.1-1.1.493-1.1 1.1-1.1zm-16 7.422c0-.827.673-1.5 1.5-1.5.313 0 .598.103.838.27-.85.675-1.477 1.478-1.812 2.36-.32-.274-.525-.676-.525-1.13zm9.183 7.79c-4.502 0-8.165-2.33-8.165-5.193S7.457 9.22 11.96 9.22s8.163 2.33 8.163 5.193-3.663 5.193-8.164 5.193zM20.635 13c-.326-.89-.948-1.7-1.797-2.383.247-.186.55-.3.882-.3.827 0 1.5.672 1.5 1.5 0 .482-.23.91-.586 1.184zm-11.64 1.704c-.76 0-1.397-.616-1.397-1.376 0-.76.636-1.397 1.396-1.397.76 0 1.376.638 1.376 1.398 0 .76-.616 1.376-1.376 1.376zm7.405-1.376c0 .76-.615 1.376-1.375 1.376s-1.4-.616-1.4-1.376c0-.76.64-1.397 1.4-1.397.76 0 1.376.638 1.376 1.398zm-1.17 3.38c.15.152.15.398 0 .55-.675.674-1.728 1.002-3.22 1.002l-.01-.002-.012.002c-1.492 0-2.544-.328-3.218-1.002-.152-.152-.152-.398 0-.55.152-.152.4-.15.55 0 .52.52 1.394.775 2.67.775l.01.002.01-.002c1.276 0 2.15-.253 2.67-.775.15-.152.398-.152.55 0z" /></SVG>; +export const embedRedditIcon = ( + <SVG viewBox="0 0 24 24"> + <Path d="M22 11.816c0-1.256-1.02-2.277-2.277-2.277-.593 0-1.122.24-1.526.613-1.48-.965-3.455-1.594-5.647-1.69l1.17-3.702 3.18.75c.01 1.027.847 1.86 1.877 1.86 1.035 0 1.877-.84 1.877-1.877 0-1.035-.842-1.877-1.877-1.877-.77 0-1.43.466-1.72 1.13L13.55 3.92c-.204-.047-.4.067-.46.26l-1.35 4.27c-2.317.037-4.412.67-5.97 1.67-.402-.355-.917-.58-1.493-.58C3.02 9.54 2 10.56 2 11.815c0 .814.433 1.523 1.078 1.925-.037.222-.06.445-.06.673 0 3.292 4.01 5.97 8.94 5.97s8.94-2.678 8.94-5.97c0-.214-.02-.424-.052-.632.687-.39 1.154-1.12 1.154-1.964zm-3.224-7.422c.606 0 1.1.493 1.1 1.1s-.493 1.1-1.1 1.1-1.1-.494-1.1-1.1.493-1.1 1.1-1.1zm-16 7.422c0-.827.673-1.5 1.5-1.5.313 0 .598.103.838.27-.85.675-1.477 1.478-1.812 2.36-.32-.274-.525-.676-.525-1.13zm9.183 7.79c-4.502 0-8.165-2.33-8.165-5.193S7.457 9.22 11.96 9.22s8.163 2.33 8.163 5.193-3.663 5.193-8.164 5.193zM20.635 13c-.326-.89-.948-1.7-1.797-2.383.247-.186.55-.3.882-.3.827 0 1.5.672 1.5 1.5 0 .482-.23.91-.586 1.184zm-11.64 1.704c-.76 0-1.397-.616-1.397-1.376 0-.76.636-1.397 1.396-1.397.76 0 1.376.638 1.376 1.398 0 .76-.616 1.376-1.376 1.376zm7.405-1.376c0 .76-.615 1.376-1.375 1.376s-1.4-.616-1.4-1.376c0-.76.64-1.397 1.4-1.397.76 0 1.376.638 1.376 1.398zm-1.17 3.38c.15.152.15.398 0 .55-.675.674-1.728 1.002-3.22 1.002l-.01-.002-.012.002c-1.492 0-2.544-.328-3.218-1.002-.152-.152-.152-.398 0-.55.152-.152.4-.15.55 0 .52.52 1.394.775 2.67.775l.01.002.01-.002c1.276 0 2.15-.253 2.67-.775.15-.152.398-.152.55 0z" /> + </SVG> +); export const embedTumblrIcon = { foreground: '#35465c', - src: <SVG viewBox="0 0 24 24"><Path d="M19 3H5c-1.105 0-2 .895-2 2v14c0 1.105.895 2 2 2h14c1.105 0 2-.895 2-2V5c0-1.105-.895-2-2-2zm-5.57 14.265c-2.445.042-3.37-1.742-3.37-2.998V10.6H8.922V9.15c1.703-.615 2.113-2.15 2.21-3.026.006-.06.053-.084.08-.084h1.645V8.9h2.246v1.7H12.85v3.495c.008.476.182 1.13 1.08 1.107.3-.008.698-.094.907-.194l.54 1.6c-.205.297-1.12.642-1.946.657z" /></SVG>, + src: ( + <SVG viewBox="0 0 24 24"> + <Path d="M19 3H5c-1.105 0-2 .895-2 2v14c0 1.105.895 2 2 2h14c1.105 0 2-.895 2-2V5c0-1.105-.895-2-2-2zm-5.57 14.265c-2.445.042-3.37-1.742-3.37-2.998V10.6H8.922V9.15c1.703-.615 2.113-2.15 2.21-3.026.006-.06.053-.084.08-.084h1.645V8.9h2.246v1.7H12.85v3.495c.008.476.182 1.13 1.08 1.107.3-.008.698-.094.907-.194l.54 1.6c-.205.297-1.12.642-1.946.657z" /> + </SVG> + ), }; -export const embedAmazonIcon = <SVG viewBox="0 0 24 24"><Path d="M18.42 14.58c-.51-.66-1.05-1.23-1.05-2.5V7.87c0-1.8.15-3.45-1.2-4.68-1.05-1.02-2.79-1.35-4.14-1.35-2.6 0-5.52.96-6.12 4.14-.06.36.18.54.4.57l2.66.3c.24-.03.42-.27.48-.5.24-1.12 1.17-1.63 2.2-1.63.56 0 1.22.21 1.55.7.4.56.33 1.31.33 1.97v.36c-1.59.18-3.66.27-5.16.93a4.63 4.63 0 0 0-2.93 4.44c0 2.82 1.8 4.23 4.1 4.23 1.95 0 3.03-.45 4.53-1.98.51.72.66 1.08 1.59 1.83.18.09.45.09.63-.1v.04l2.1-1.8c.24-.21.2-.48.03-.75zm-5.4-1.2c-.45.75-1.14 1.23-1.92 1.23-1.05 0-1.65-.81-1.65-1.98 0-2.31 2.1-2.73 4.08-2.73v.6c0 1.05.03 1.92-.5 2.88z" /><Path d="M21.69 19.2a17.62 17.62 0 0 1-21.6-1.57c-.23-.2 0-.5.28-.33a23.88 23.88 0 0 0 20.93 1.3c.45-.19.84.3.39.6z" /><Path d="M22.8 17.96c-.36-.45-2.22-.2-3.1-.12-.23.03-.3-.18-.05-.36 1.5-1.05 3.96-.75 4.26-.39.3.36-.1 2.82-1.5 4.02-.21.18-.42.1-.3-.15.3-.8 1.02-2.58.69-3z" /></SVG>; +export const embedAmazonIcon = ( + <SVG viewBox="0 0 24 24"> + <Path d="M18.42 14.58c-.51-.66-1.05-1.23-1.05-2.5V7.87c0-1.8.15-3.45-1.2-4.68-1.05-1.02-2.79-1.35-4.14-1.35-2.6 0-5.52.96-6.12 4.14-.06.36.18.54.4.57l2.66.3c.24-.03.42-.27.48-.5.24-1.12 1.17-1.63 2.2-1.63.56 0 1.22.21 1.55.7.4.56.33 1.31.33 1.97v.36c-1.59.18-3.66.27-5.16.93a4.63 4.63 0 0 0-2.93 4.44c0 2.82 1.8 4.23 4.1 4.23 1.95 0 3.03-.45 4.53-1.98.51.72.66 1.08 1.59 1.83.18.09.45.09.63-.1v.04l2.1-1.8c.24-.21.2-.48.03-.75zm-5.4-1.2c-.45.75-1.14 1.23-1.92 1.23-1.05 0-1.65-.81-1.65-1.98 0-2.31 2.1-2.73 4.08-2.73v.6c0 1.05.03 1.92-.5 2.88z" /> + <Path d="M21.69 19.2a17.62 17.62 0 0 1-21.6-1.57c-.23-.2 0-.5.28-.33a23.88 23.88 0 0 0 20.93 1.3c.45-.19.84.3.39.6z" /> + <Path d="M22.8 17.96c-.36-.45-2.22-.2-3.1-.12-.23.03-.3-.18-.05-.36 1.5-1.05 3.96-.75 4.26-.39.3.36-.1 2.82-1.5 4.02-.21.18-.42.1-.3-.15.3-.8 1.02-2.58.69-3z" /> + </SVG> +); diff --git a/packages/block-library/src/embed/index.js b/packages/block-library/src/embed/index.js index 7bf859a002373d..05facf83fd1b9c 100644 --- a/packages/block-library/src/embed/index.js +++ b/packages/block-library/src/embed/index.js @@ -15,7 +15,9 @@ export const name = 'core/embed'; export const settings = getEmbedBlockSettings( { title: _x( 'Embed', 'block title' ), - description: __( 'Embed videos, images, tweets, audio, and other content from external sources.' ), + description: __( + 'Embed videos, images, tweets, audio, and other content from external sources.' + ), icon: embedContentIcon, // Unknown embeds should not be responsive by default. responsive: false, @@ -23,7 +25,9 @@ export const settings = getEmbedBlockSettings( { from: [ { type: 'raw', - isMatch: ( node ) => node.nodeName === 'P' && /^\s*(https?:\/\/\S+)\s*$/i.test( node.textContent ), + isMatch: ( node ) => + node.nodeName === 'P' && + /^\s*(https?:\/\/\S+)\s*$/i.test( node.textContent ), transform: ( node ) => { return createBlock( 'core/embed', { url: node.textContent.trim(), @@ -34,20 +38,16 @@ export const settings = getEmbedBlockSettings( { }, } ); -export const common = commonEmbeds.map( - ( embedDefinition ) => { - return { - ...embedDefinition, - settings: getEmbedBlockSettings( embedDefinition.settings ), - }; - } -); +export const common = commonEmbeds.map( ( embedDefinition ) => { + return { + ...embedDefinition, + settings: getEmbedBlockSettings( embedDefinition.settings ), + }; +} ); -export const others = otherEmbeds.map( - ( embedDefinition ) => { - return { - ...embedDefinition, - settings: getEmbedBlockSettings( embedDefinition.settings ), - }; - } -); +export const others = otherEmbeds.map( ( embedDefinition ) => { + return { + ...embedDefinition, + settings: getEmbedBlockSettings( embedDefinition.settings ), + }; +} ); diff --git a/packages/block-library/src/embed/settings.js b/packages/block-library/src/embed/settings.js index 17d97cee43da6d..20034c6d617a58 100644 --- a/packages/block-library/src/embed/settings.js +++ b/packages/block-library/src/embed/settings.js @@ -37,8 +37,21 @@ const embedAttributes = { }, }; -export function getEmbedBlockSettings( { title, description, icon, category = 'embed', transforms, keywords = [], supports = {}, responsive = true } ) { - const blockDescription = description || __( 'Add a block that displays content pulled from other sites, like Twitter, Instagram or YouTube.' ); +export function getEmbedBlockSettings( { + title, + description, + icon, + category = 'embed', + transforms, + keywords = [], + supports = {}, + responsive = true, +} ) { + const blockDescription = + description || + __( + 'Add a block that displays content pulled from other sites, like Twitter, Instagram or YouTube.' + ); const edit = getEmbedEditComponent( title, icon, responsive ); return { title, @@ -59,23 +72,38 @@ export function getEmbedBlockSettings( { title, description, icon, category = 'e withSelect( ( select, ownProps ) => { const { url } = ownProps.attributes; const core = select( 'core' ); - const { getEmbedPreview, isPreviewEmbedFallback, isRequestingEmbedPreview, getThemeSupports } = core; + const { + getEmbedPreview, + isPreviewEmbedFallback, + isRequestingEmbedPreview, + getThemeSupports, + } = core; const preview = undefined !== url && getEmbedPreview( url ); - const previewIsFallback = undefined !== url && isPreviewEmbedFallback( url ); - const fetching = undefined !== url && isRequestingEmbedPreview( url ); + const previewIsFallback = + undefined !== url && isPreviewEmbedFallback( url ); + const fetching = + undefined !== url && isRequestingEmbedPreview( url ); const themeSupports = getThemeSupports(); // The external oEmbed provider does not exist. We got no type info and no html. - const badEmbedProvider = !! preview && undefined === preview.type && false === preview.html; + const badEmbedProvider = + !! preview && + undefined === preview.type && + false === preview.html; // Some WordPress URLs that can't be embedded will cause the API to return // a valid JSON response with no HTML and `data.status` set to 404, rather // than generating a fallback response as other embeds do. - const wordpressCantEmbed = !! preview && preview.data && preview.data.status === 404; - const validPreview = !! preview && ! badEmbedProvider && ! wordpressCantEmbed; - const cannotEmbed = undefined !== url && ( ! validPreview || previewIsFallback ); + const wordpressCantEmbed = + !! preview && preview.data && preview.data.status === 404; + const validPreview = + !! preview && ! badEmbedProvider && ! wordpressCantEmbed; + const cannotEmbed = + undefined !== url && + ( ! validPreview || previewIsFallback ); return { preview: validPreview ? preview : undefined, fetching, - themeSupportsResponsive: themeSupports[ 'responsive-embeds' ], + themeSupportsResponsive: + themeSupports[ 'responsive-embeds' ], cannotEmbed, }; } ), @@ -83,7 +111,9 @@ export function getEmbedBlockSettings( { title, description, icon, category = 'e const { url } = ownProps.attributes; const coreData = dispatch( 'core/data' ); const tryAgain = () => { - coreData.invalidateResolution( 'core', 'getEmbedPreview', [ url ] ); + coreData.invalidateResolution( 'core', 'getEmbedPreview', [ + url, + ] ); }; return { tryAgain, @@ -108,7 +138,12 @@ export function getEmbedBlockSettings( { title, description, icon, category = 'e <div className="wp-block-embed__wrapper"> { `\n${ url }\n` /* URL needs to be on its own line. */ } </div> - { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> } + { ! RichText.isEmpty( caption ) && ( + <RichText.Content + tagName="figcaption" + value={ caption } + /> + ) } </figure> ); }, @@ -131,7 +166,12 @@ export function getEmbedBlockSettings( { title, description, icon, category = 'e return ( <figure className={ embedClassName }> { `\n${ url }\n` /* URL needs to be on its own line. */ } - { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> } + { ! RichText.isEmpty( caption ) && ( + <RichText.Content + tagName="figcaption" + value={ caption } + /> + ) } </figure> ); }, diff --git a/packages/block-library/src/embed/test/__snapshots__/index.js.snap b/packages/block-library/src/embed/test/__snapshots__/index.js.snap index eff8cabaaad89c..d5267ced08a436 100644 --- a/packages/block-library/src/embed/test/__snapshots__/index.js.snap +++ b/packages/block-library/src/embed/test/__snapshots__/index.js.snap @@ -2,7 +2,7 @@ exports[`core/embed block edit matches snapshot 1`] = ` <div - class="components-placeholder is-small wp-block-embed" + class="components-placeholder wp-block-embed" > <iframe aria-hidden="true" diff --git a/packages/block-library/src/embed/test/index.js b/packages/block-library/src/embed/test/index.js index 5f01d8ff77ee84..2f1e8546b770f7 100644 --- a/packages/block-library/src/embed/test/index.js +++ b/packages/block-library/src/embed/test/index.js @@ -47,27 +47,35 @@ describe( 'core/embed', () => { test( 'getClassNames preserves exsiting class names when removing responsive classes', () => { const html = '<iframe height="9" width="16"></iframe>'; const expected = 'lovely'; - expect( getClassNames( html, 'lovely wp-embed-aspect-16-9 wp-has-aspect-ratio', false ) ).toEqual( expected ); + expect( + getClassNames( + html, + 'lovely wp-embed-aspect-16-9 wp-has-aspect-ratio', + false + ) + ).toEqual( expected ); } ); test( 'createUpgradedEmbedBlock bails early when block type does not exist', () => { const youtubeURL = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'; - expect( createUpgradedEmbedBlock( { attributes: { url: youtubeURL } }, {} ) ).toBeUndefined(); + expect( + createUpgradedEmbedBlock( { attributes: { url: youtubeURL } }, {} ) + ).toBeUndefined(); } ); test( 'createUpgradedEmbedBlock returns a YouTube embed block when given a YouTube URL', () => { const youtubeURL = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'; - registerBlockType( - 'core-embed/youtube', - { - title: 'YouTube', - category: 'embed', - } - ); + registerBlockType( 'core-embed/youtube', { + title: 'YouTube', + category: 'embed', + } ); - const result = createUpgradedEmbedBlock( { attributes: { url: youtubeURL } }, {} ); + const result = createUpgradedEmbedBlock( + { attributes: { url: youtubeURL } }, + {} + ); unregisterBlockType( 'core-embed/youtube' ); diff --git a/packages/block-library/src/embed/util.js b/packages/block-library/src/embed/util.js index 0096f1bc3cd98a..071eee0980b6fe 100644 --- a/packages/block-library/src/embed/util.js +++ b/packages/block-library/src/embed/util.js @@ -2,7 +2,11 @@ * Internal dependencies */ import { common, others } from './core-embeds'; -import { DEFAULT_EMBED_BLOCK, WORDPRESS_EMBED_BLOCK, ASPECT_RATIOS } from './constants'; +import { + DEFAULT_EMBED_BLOCK, + WORDPRESS_EMBED_BLOCK, + ASPECT_RATIOS, +} from './constants'; /** * External dependencies @@ -53,8 +57,12 @@ export const isFromWordPress = ( html ) => { export const getPhotoHtml = ( photo ) => { // 100% width for the preview so it fits nicely into the document, some "thumbnails" are // actually the full size photo. If thumbnails not found, use full image. - const imageUrl = ( photo.thumbnail_url ) ? photo.thumbnail_url : photo.url; - const photoPreview = <p><img src={ imageUrl } alt={ photo.title } width="100%" /></p>; + const imageUrl = photo.thumbnail_url ? photo.thumbnail_url : photo.url; + const photoPreview = ( + <p> + <img src={ imageUrl } alt={ photo.title } width="100%" /> + </p> + ); return renderToString( photoPreview ); }; @@ -88,7 +96,10 @@ export const createUpgradedEmbedBlock = ( props, attributesFromPreview ) => { // WordPress blocks can work on multiple sites, and so don't have patterns, // so if we're in a WordPress block, assume the user has chosen it for a WordPress URL. - if ( WORDPRESS_EMBED_BLOCK !== name && DEFAULT_EMBED_BLOCK !== matchingBlock ) { + if ( + WORDPRESS_EMBED_BLOCK !== name && + DEFAULT_EMBED_BLOCK !== matchingBlock + ) { // At this point, we have discovered a more suitable block for this url, so transform it. if ( name !== matchingBlock ) { return createBlock( matchingBlock, { url } ); @@ -102,20 +113,17 @@ export const createUpgradedEmbedBlock = ( props, attributesFromPreview ) => { if ( isFromWordPress( html ) ) { // If this is not the WordPress embed block, transform it into one. if ( WORDPRESS_EMBED_BLOCK !== name ) { - return createBlock( - WORDPRESS_EMBED_BLOCK, - { - url, - // By now we have the preview, but when the new block first renders, it - // won't have had all the attributes set, and so won't get the correct - // type and it won't render correctly. So, we pass through the current attributes - // here so that the initial render works when we switch to the WordPress - // block. This only affects the WordPress block because it can't be - // rendered in the usual Sandbox (it has a sandbox of its own) and it - // relies on the preview to set the correct render type. - ...attributesFromPreview, - } - ); + return createBlock( WORDPRESS_EMBED_BLOCK, { + url, + // By now we have the preview, but when the new block first renders, it + // won't have had all the attributes set, and so won't get the correct + // type and it won't render correctly. So, we pass through the current attributes + // here so that the initial render works when we switch to the WordPress + // block. This only affects the WordPress block because it can't be + // rendered in the usual Sandbox (it has a sandbox of its own) and it + // relies on the preview to set the correct render type. + ...attributesFromPreview, + } ); } } } @@ -129,20 +137,25 @@ export const createUpgradedEmbedBlock = ( props, attributesFromPreview ) => { * @param {boolean} allowResponsive If the responsive class names should be added, or removed. * @return {string} Deduped class names. */ -export function getClassNames( html, existingClassNames = '', allowResponsive = true ) { +export function getClassNames( + html, + existingClassNames = '', + allowResponsive = true +) { if ( ! allowResponsive ) { // Remove all of the aspect ratio related class names. const aspectRatioClassNames = { 'wp-has-aspect-ratio': false, }; - for ( let ratioIndex = 0; ratioIndex < ASPECT_RATIOS.length; ratioIndex++ ) { + for ( + let ratioIndex = 0; + ratioIndex < ASPECT_RATIOS.length; + ratioIndex++ + ) { const aspectRatioToRemove = ASPECT_RATIOS[ ratioIndex ]; aspectRatioClassNames[ aspectRatioToRemove.className ] = false; } - return classnames( - existingClassNames, - aspectRatioClassNames - ); + return classnames( existingClassNames, aspectRatioClassNames ); } const previewDocument = document.implementation.createHTMLDocument( '' ); @@ -153,16 +166,17 @@ export function getClassNames( html, existingClassNames = '', allowResponsive = if ( iframe && iframe.height && iframe.width ) { const aspectRatio = ( iframe.width / iframe.height ).toFixed( 2 ); // Given the actual aspect ratio, find the widest ratio to support it. - for ( let ratioIndex = 0; ratioIndex < ASPECT_RATIOS.length; ratioIndex++ ) { + for ( + let ratioIndex = 0; + ratioIndex < ASPECT_RATIOS.length; + ratioIndex++ + ) { const potentialRatio = ASPECT_RATIOS[ ratioIndex ]; if ( aspectRatio >= potentialRatio.ratio ) { - return classnames( - existingClassNames, - { - [ potentialRatio.className ]: allowResponsive, - 'wp-has-aspect-ratio': allowResponsive, - } - ); + return classnames( existingClassNames, { + [ potentialRatio.className ]: allowResponsive, + 'wp-has-aspect-ratio': allowResponsive, + } ); } } } @@ -194,29 +208,43 @@ export function fallback( url, onReplace ) { * @param {boolean} allowResponsive Apply responsive classes to fixed size content. * @return {Object} Attributes and values. */ -export const getAttributesFromPreview = memoize( ( preview, title, currentClassNames, isResponsive, allowResponsive = true ) => { - if ( ! preview ) { - return {}; - } +export const getAttributesFromPreview = memoize( + ( + preview, + title, + currentClassNames, + isResponsive, + allowResponsive = true + ) => { + if ( ! preview ) { + return {}; + } - const attributes = {}; - // Some plugins only return HTML with no type info, so default this to 'rich'. - let { type = 'rich' } = preview; - // If we got a provider name from the API, use it for the slug, otherwise we use the title, - // because not all embed code gives us a provider name. - const { html, provider_name: providerName } = preview; - const providerNameSlug = kebabCase( toLower( '' !== providerName ? providerName : title ) ); + const attributes = {}; + // Some plugins only return HTML with no type info, so default this to 'rich'. + let { type = 'rich' } = preview; + // If we got a provider name from the API, use it for the slug, otherwise we use the title, + // because not all embed code gives us a provider name. + const { html, provider_name: providerName } = preview; + const providerNameSlug = kebabCase( + toLower( '' !== providerName ? providerName : title ) + ); - if ( isFromWordPress( html ) ) { - type = 'wp-embed'; - } + if ( isFromWordPress( html ) ) { + type = 'wp-embed'; + } - if ( html || 'photo' === type ) { - attributes.type = type; - attributes.providerNameSlug = providerNameSlug; - } + if ( html || 'photo' === type ) { + attributes.type = type; + attributes.providerNameSlug = providerNameSlug; + } - attributes.className = getClassNames( html, currentClassNames, isResponsive && allowResponsive ); + attributes.className = getClassNames( + html, + currentClassNames, + isResponsive && allowResponsive + ); - return attributes; -} ); + return attributes; + } +); diff --git a/packages/block-library/src/file/edit.js b/packages/block-library/src/file/edit.js index 94ad160e8f47fd..49b0b562c17a07 100644 --- a/packages/block-library/src/file/edit.js +++ b/packages/block-library/src/file/edit.js @@ -6,16 +6,8 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { - getBlobByURL, - isBlobURL, - revokeBlobURL, -} from '@wordpress/blob'; -import { - Animate, - ClipboardButton, - withNotices, -} from '@wordpress/components'; +import { getBlobByURL, isBlobURL, revokeBlobURL } from '@wordpress/blob'; +import { Animate, ClipboardButton, withNotices } from '@wordpress/components'; import { compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; import { @@ -41,9 +33,13 @@ class FileEdit extends Component { this.onSelectFile = this.onSelectFile.bind( this ); this.confirmCopyURL = this.confirmCopyURL.bind( this ); this.resetCopyConfirmation = this.resetCopyConfirmation.bind( this ); - this.changeLinkDestinationOption = this.changeLinkDestinationOption.bind( this ); + this.changeLinkDestinationOption = this.changeLinkDestinationOption.bind( + this + ); this.changeOpenInNewWindow = this.changeOpenInNewWindow.bind( this ); - this.changeShowDownloadButton = this.changeShowDownloadButton.bind( this ); + this.changeShowDownloadButton = this.changeShowDownloadButton.bind( + this + ); this.onUploadError = this.onUploadError.bind( this ); this.state = { @@ -158,7 +154,9 @@ class FileEdit extends Component { icon={ <BlockIcon icon={ icon } /> } labels={ { title: __( 'File' ), - instructions: __( 'Upload a file or pick one from your media library.' ), + instructions: __( + 'Upload a file or pick one from your media library.' + ), } } onSelect={ this.onSelectFile } notices={ noticeUI } @@ -179,7 +177,8 @@ class FileEdit extends Component { { ...{ openInNewWindow: !! textLinkTarget, showDownloadButton, - changeLinkDestinationOption: this.changeLinkDestinationOption, + changeLinkDestinationOption: this + .changeLinkDestinationOption, changeOpenInNewWindow: this.changeOpenInNewWindow, changeShowDownloadButton: this.changeShowDownloadButton, } } @@ -194,7 +193,12 @@ class FileEdit extends Component { </BlockControls> <Animate type={ isBlobURL( href ) ? 'loading' : null }> { ( { className: animateClassName } ) => ( - <div className={ classnames( classes, animateClassName ) }> + <div + className={ classnames( + classes, + animateClassName + ) } + > <div className={ 'wp-block-file__content-wrapper' }> <div className="wp-block-file__textlink"> <RichText @@ -202,35 +206,51 @@ class FileEdit extends Component { value={ fileName } placeholder={ __( 'Write file name…' ) } withoutInteractiveFormatting - onChange={ ( text ) => setAttributes( { fileName: text } ) } + onChange={ ( text ) => + setAttributes( { fileName: text } ) + } /> </div> - { showDownloadButton && - <div className={ 'wp-block-file__button-richtext-wrapper' }> + { showDownloadButton && ( + <div + className={ + 'wp-block-file__button-richtext-wrapper' + } + > { /* Using RichText here instead of PlainText so that it can be styled like a button */ } <RichText tagName="div" // must be block-level or else cursor disappears - className={ 'wp-block-file__button' } + className={ + 'wp-block-file__button' + } value={ downloadButtonText } withoutInteractiveFormatting placeholder={ __( 'Add text…' ) } - onChange={ ( text ) => setAttributes( { downloadButtonText: text } ) } + onChange={ ( text ) => + setAttributes( { + downloadButtonText: text, + } ) + } /> </div> - } + ) } </div> - { isSelected && + { isSelected && ( <ClipboardButton isSecondary text={ href } - className={ 'wp-block-file__copy-url-button' } + className={ + 'wp-block-file__copy-url-button' + } onCopy={ this.confirmCopyURL } onFinishCopy={ this.resetCopyConfirmation } disabled={ isBlobURL( href ) } > - { showCopyConfirmation ? __( 'Copied!' ) : __( 'Copy URL' ) } + { showCopyConfirmation + ? __( 'Copied!' ) + : __( 'Copy URL' ) } </ClipboardButton> - } + ) } </div> ) } </Animate> diff --git a/packages/block-library/src/file/inspector.js b/packages/block-library/src/file/inspector.js index f76ff53929d38f..60782743ac7de9 100644 --- a/packages/block-library/src/file/inspector.js +++ b/packages/block-library/src/file/inspector.js @@ -2,11 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { - PanelBody, - SelectControl, - ToggleControl, -} from '@wordpress/components'; +import { PanelBody, SelectControl, ToggleControl } from '@wordpress/components'; import { InspectorControls } from '@wordpress/block-editor'; export default function FileBlockInspector( { diff --git a/packages/block-library/src/file/save.js b/packages/block-library/src/file/save.js index 461018e4bb8a7c..25102dfd0f92d0 100644 --- a/packages/block-library/src/file/save.js +++ b/packages/block-library/src/file/save.js @@ -13,30 +13,28 @@ export default function save( { attributes } ) { downloadButtonText, } = attributes; - return ( href && - <div> - { ! RichText.isEmpty( fileName ) && - <a - href={ textLinkHref } - target={ textLinkTarget } - rel={ textLinkTarget ? 'noreferrer noopener' : false } - > - <RichText.Content - value={ fileName } - /> - </a> - } - { showDownloadButton && - <a - href={ href } - className="wp-block-file__button" - download={ true } - > - <RichText.Content - value={ downloadButtonText } - /> - </a> - } - </div> + return ( + href && ( + <div> + { ! RichText.isEmpty( fileName ) && ( + <a + href={ textLinkHref } + target={ textLinkTarget } + rel={ textLinkTarget ? 'noreferrer noopener' : false } + > + <RichText.Content value={ fileName } /> + </a> + ) } + { showDownloadButton && ( + <a + href={ href } + className="wp-block-file__button" + download={ true } + > + <RichText.Content value={ downloadButtonText } /> + </a> + ) } + </div> + ) ); } diff --git a/packages/block-library/src/file/transforms.js b/packages/block-library/src/file/transforms.js index a37aedfb45fcec..e48d5cffedbe0e 100644 --- a/packages/block-library/src/file/transforms.js +++ b/packages/block-library/src/file/transforms.js @@ -27,11 +27,13 @@ const transforms = { const blobURL = createBlobURL( file ); // File will be uploaded in componentDidMount() - blocks.push( createBlock( 'core/file', { - href: blobURL, - fileName: file.name, - textLinkHref: blobURL, - } ) ); + blocks.push( + createBlock( 'core/file', { + href: blobURL, + fileName: file.name, + textLinkHref: blobURL, + } ) + ); } ); return blocks; diff --git a/packages/block-library/src/gallery/deprecated.js b/packages/block-library/src/gallery/deprecated.js index 77c62488317caa..36196e6d6427dc 100644 --- a/packages/block-library/src/gallery/deprecated.js +++ b/packages/block-library/src/gallery/deprecated.js @@ -93,10 +93,20 @@ const deprecated = [ }; }, save( { attributes } ) { - const { images, columns = defaultColumnsNumber( attributes ), imageCrop, caption, linkTo } = attributes; + const { + images, + columns = defaultColumnsNumber( attributes ), + imageCrop, + caption, + linkTo, + } = attributes; return ( - <figure className={ `columns-${ columns } ${ imageCrop ? 'is-cropped' : '' }` }> + <figure + className={ `columns-${ columns } ${ + imageCrop ? 'is-cropped' : '' + }` } + > <ul className="blocks-gallery-grid"> { images.map( ( image ) => { let href; @@ -117,23 +127,46 @@ const deprecated = [ data-id={ image.id } data-full-url={ image.fullUrl } data-link={ image.link } - className={ image.id ? `wp-image-${ image.id }` : null } + className={ + image.id + ? `wp-image-${ image.id }` + : null + } /> ); return ( - <li key={ image.id || image.url } className="blocks-gallery-item"> + <li + key={ image.id || image.url } + className="blocks-gallery-item" + > <figure> - { href ? <a href={ href }>{ img }</a> : img } - { ! RichText.isEmpty( image.caption ) && ( - <RichText.Content tagName="figcaption" className="blocks-gallery-item__caption" value={ image.caption } /> + { href ? ( + <a href={ href }>{ img }</a> + ) : ( + img + ) } + { ! RichText.isEmpty( + image.caption + ) && ( + <RichText.Content + tagName="figcaption" + className="blocks-gallery-item__caption" + value={ image.caption } + /> ) } </figure> </li> ); } ) } </ul> - { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" className="blocks-gallery-caption" value={ caption } /> } + { ! RichText.isEmpty( caption ) && ( + <RichText.Content + tagName="figcaption" + className="blocks-gallery-caption" + value={ caption } + /> + ) } </figure> ); }, @@ -199,9 +232,18 @@ const deprecated = [ align: true, }, save( { attributes } ) { - const { images, columns = defaultColumnsNumber( attributes ), imageCrop, linkTo } = attributes; + const { + images, + columns = defaultColumnsNumber( attributes ), + imageCrop, + linkTo, + } = attributes; return ( - <ul className={ `columns-${ columns } ${ imageCrop ? 'is-cropped' : '' }` } > + <ul + className={ `columns-${ columns } ${ + imageCrop ? 'is-cropped' : '' + }` } + > { images.map( ( image ) => { let href; @@ -221,17 +263,30 @@ const deprecated = [ data-id={ image.id } data-full-url={ image.fullUrl } data-link={ image.link } - className={ image.id ? `wp-image-${ image.id }` : null } + className={ + image.id ? `wp-image-${ image.id }` : null + } /> ); return ( - <li key={ image.id || image.url } className="blocks-gallery-item"> + <li + key={ image.id || image.url } + className="blocks-gallery-item" + > <figure> - { href ? <a href={ href }>{ img }</a> : img } - { image.caption && image.caption.length > 0 && ( - <RichText.Content tagName="figcaption" value={ image.caption } /> + { href ? ( + <a href={ href }>{ img }</a> + ) : ( + img ) } + { image.caption && + image.caption.length > 0 && ( + <RichText.Content + tagName="figcaption" + value={ image.caption } + /> + ) } </figure> </li> ); @@ -289,18 +344,18 @@ const deprecated = [ }, }, isEligible( { images, ids } ) { - return images && + return ( + images && images.length > 0 && - ( - ( ! ids && images ) || + ( ( ! ids && images ) || ( ids && images && ids.length !== images.length ) || some( images, ( id, index ) => { if ( ! id && ids[ index ] !== null ) { return true; } return parseInt( id, 10 ) !== ids[ index ]; - } ) - ); + } ) ) + ); }, migrate( attributes ) { return { @@ -317,9 +372,18 @@ const deprecated = [ align: true, }, save( { attributes } ) { - const { images, columns = defaultColumnsNumber( attributes ), imageCrop, linkTo } = attributes; + const { + images, + columns = defaultColumnsNumber( attributes ), + imageCrop, + linkTo, + } = attributes; return ( - <ul className={ `columns-${ columns } ${ imageCrop ? 'is-cropped' : '' }` } > + <ul + className={ `columns-${ columns } ${ + imageCrop ? 'is-cropped' : '' + }` } + > { images.map( ( image ) => { let href; @@ -332,15 +396,36 @@ const deprecated = [ break; } - const img = <img src={ image.url } alt={ image.alt } data-id={ image.id } data-link={ image.link } className={ image.id ? `wp-image-${ image.id }` : null } />; + const img = ( + <img + src={ image.url } + alt={ image.alt } + data-id={ image.id } + data-link={ image.link } + className={ + image.id ? `wp-image-${ image.id }` : null + } + /> + ); return ( - <li key={ image.id || image.url } className="blocks-gallery-item"> + <li + key={ image.id || image.url } + className="blocks-gallery-item" + > <figure> - { href ? <a href={ href }>{ img }</a> : img } - { image.caption && image.caption.length > 0 && ( - <RichText.Content tagName="figcaption" value={ image.caption } /> + { href ? ( + <a href={ href }>{ img }</a> + ) : ( + img ) } + { image.caption && + image.caption.length > 0 && ( + <RichText.Content + tagName="figcaption" + value={ image.caption } + /> + ) } </figure> </li> ); @@ -355,7 +440,8 @@ const deprecated = [ type: 'array', default: [], source: 'query', - selector: 'div.wp-block-gallery figure.blocks-gallery-image img', + selector: + 'div.wp-block-gallery figure.blocks-gallery-image img', query: { url: { source: 'attribute', @@ -392,13 +478,19 @@ const deprecated = [ align: true, }, save( { attributes } ) { - const { images, columns = defaultColumnsNumber( attributes ), align, imageCrop, linkTo } = attributes; + const { + images, + columns = defaultColumnsNumber( attributes ), + align, + imageCrop, + linkTo, + } = attributes; const className = classnames( `columns-${ columns }`, { alignnone: align === 'none', 'is-cropped': imageCrop, } ); return ( - <div className={ className } > + <div className={ className }> { images.map( ( image ) => { let href; @@ -411,10 +503,19 @@ const deprecated = [ break; } - const img = <img src={ image.url } alt={ image.alt } data-id={ image.id } />; + const img = ( + <img + src={ image.url } + alt={ image.alt } + data-id={ image.id } + /> + ); return ( - <figure key={ image.id || image.url } className="blocks-gallery-image"> + <figure + key={ image.id || image.url } + className="blocks-gallery-image" + > { href ? <a href={ href }>{ img }</a> : img } </figure> ); diff --git a/packages/block-library/src/gallery/edit.js b/packages/block-library/src/gallery/edit.js index 9d7b57c77c8b66..0eda4370e76edd 100644 --- a/packages/block-library/src/gallery/edit.js +++ b/packages/block-library/src/gallery/edit.js @@ -24,10 +24,7 @@ import { ToggleControl, withNotices, } from '@wordpress/components'; -import { - MediaPlaceholder, - InspectorControls, -} from '@wordpress/block-editor'; +import { MediaPlaceholder, InspectorControls } from '@wordpress/block-editor'; import { Component, Platform } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { getBlobByURL, isBlobURL, revokeBlobURL } from '@wordpress/blob'; @@ -50,7 +47,9 @@ const linkOptions = [ const ALLOWED_MEDIA_TYPES = [ 'image' ]; const PLACEHOLDER_TEXT = Platform.select( { - web: __( 'Drag images, upload new ones or select files from your library.' ), + web: __( + 'Drag images, upload new ones or select files from your library.' + ), native: __( 'ADD MEDIA' ), } ); @@ -93,7 +92,9 @@ class GalleryEdit extends Component { setAttributes( attributes ) { if ( attributes.ids ) { - throw new Error( 'The "ids" attribute should not be changed directly. It is managed automatically when "images" attribute changes' ); + throw new Error( + 'The "ids" attribute should not be changed directly. It is managed automatically when "images" attribute changes' + ); } if ( attributes.images ) { @@ -144,7 +145,10 @@ class GalleryEdit extends Component { onRemoveImage( index ) { return () => { - const images = filter( this.props.attributes.images, ( img, i ) => index !== i ); + const images = filter( + this.props.attributes.images, + ( img, i ) => index !== i + ); const { columns } = this.props.attributes; this.setState( { selectedImage: null } ); this.setAttributes( { @@ -155,22 +159,20 @@ class GalleryEdit extends Component { } selectCaption( newImage, images, attachmentCaptions ) { - const currentImage = find( - images, { id: newImage.id } - ); + const currentImage = find( images, { id: newImage.id } ); - const currentImageCaption = currentImage ? currentImage.caption : newImage.caption; + const currentImageCaption = currentImage + ? currentImage.caption + : newImage.caption; if ( ! attachmentCaptions ) { return currentImageCaption; } - const attachment = find( - attachmentCaptions, { id: newImage.id } - ); + const attachment = find( attachmentCaptions, { id: newImage.id } ); // if the attachment caption is updated - if ( attachment && ( attachment.caption !== newImage.caption ) ) { + if ( attachment && attachment.caption !== newImage.caption ) { return newImage.caption; } @@ -180,18 +182,20 @@ class GalleryEdit extends Component { onSelectImages( newImages ) { const { columns, images, sizeSlug } = this.props.attributes; const { attachmentCaptions } = this.state; - this.setState( - { - attachmentCaptions: newImages.map( ( newImage ) => ( { - id: newImage.id, - caption: newImage.caption, - } ) ), - } - ); + this.setState( { + attachmentCaptions: newImages.map( ( newImage ) => ( { + id: newImage.id, + caption: newImage.caption, + } ) ), + } ); this.setAttributes( { images: newImages.map( ( newImage ) => ( { ...pickRelevantMediaFiles( newImage, sizeSlug ), - caption: this.selectCaption( newImage, images, attachmentCaptions ), + caption: this.selectCaption( + newImage, + images, + attachmentCaptions + ), } ) ), columns: columns ? Math.min( newImages.length, columns ) : columns, } ); @@ -216,7 +220,9 @@ class GalleryEdit extends Component { } getImageCropHelp( checked ) { - return checked ? __( 'Thumbnails are cropped to align.' ) : __( 'Thumbnails are not cropped.' ); + return checked + ? __( 'Thumbnails are cropped to align.' ) + : __( 'Thumbnails are not cropped.' ); } onFocusGalleryCaption() { @@ -226,7 +232,9 @@ class GalleryEdit extends Component { } setImageAttributes( index, attributes ) { - const { attributes: { images } } = this.props; + const { + attributes: { images }, + } = this.props; const { setAttributes } = this; if ( ! images[ index ] ) { return; @@ -246,22 +254,27 @@ class GalleryEdit extends Component { getImagesSizeOptions() { const { imageSizes, resizedImages } = this.props; return map( - filter( - imageSizes, - ( { slug } ) => some( resizedImages, ( sizes ) => ( sizes[ slug ] ) ) + filter( imageSizes, ( { slug } ) => + some( resizedImages, ( sizes ) => sizes[ slug ] ) ), ( { name, slug } ) => ( { value: slug, label: name } ) ); } updateImagesSize( sizeSlug ) { - const { attributes: { images }, resizedImages } = this.props; + const { + attributes: { images }, + resizedImages, + } = this.props; const updatedImages = map( images, ( image ) => { if ( ! image.id ) { return image; } - const url = get( resizedImages, [ parseInt( image.id, 10 ), sizeSlug ] ); + const url = get( resizedImages, [ + parseInt( image.id, 10 ), + sizeSlug, + ] ); return { ...image, ...( url && { url } ), @@ -276,7 +289,8 @@ class GalleryEdit extends Component { const { images } = attributes; if ( Platform.OS === 'web' && - images && images.length > 0 && + images && + images.length > 0 && every( images, ( { url } ) => isBlobURL( url ) ) ) { const filesList = map( images, ( { url } ) => getBlobByURL( url ) ); @@ -300,12 +314,7 @@ class GalleryEdit extends Component { } render() { - const { - attributes, - className, - isSelected, - noticeUI, - } = this.props; + const { attributes, className, isSelected, noticeUI } = this.props; const { columns = defaultColumnsNumber( attributes ), imageCrop, @@ -344,25 +353,28 @@ class GalleryEdit extends Component { } const imageSizeOptions = this.getImagesSizeOptions(); - const shouldShowSizeOptions = hasImages && ! isEmpty( imageSizeOptions ); + const shouldShowSizeOptions = + hasImages && ! isEmpty( imageSizeOptions ); // This is needed to fix a separator fence-post issue on mobile. - const mobileLinkToProps = shouldShowSizeOptions ? - MOBILE_CONTROL_PROPS : - MOBILE_CONTROL_PROPS_SEPARATOR_NONE; + const mobileLinkToProps = shouldShowSizeOptions + ? MOBILE_CONTROL_PROPS + : MOBILE_CONTROL_PROPS_SEPARATOR_NONE; return ( <> <InspectorControls> <PanelBody title={ __( 'Gallery settings' ) }> - { images.length > 1 && <RangeControl - label={ __( 'Columns' ) } - { ...MOBILE_CONTROL_PROPS } - value={ columns } - onChange={ this.setColumnsNumber } - min={ 1 } - max={ Math.min( MAX_COLUMNS, images.length ) } - required - /> } + { images.length > 1 && ( + <RangeControl + label={ __( 'Columns' ) } + { ...MOBILE_CONTROL_PROPS } + value={ columns } + onChange={ this.setColumnsNumber } + min={ 1 } + max={ Math.min( MAX_COLUMNS, images.length ) } + required + /> + ) } <ToggleControl label={ __( 'Crop Images' ) } { ...MOBILE_CONTROL_PROPS } @@ -408,32 +420,46 @@ export default compose( [ withSelect( ( select, { attributes: { ids }, isSelected } ) => { const { getMedia } = select( 'core' ); const { getSettings } = select( 'core/block-editor' ); - const { - imageSizes, - mediaUpload, - } = getSettings(); + const { imageSizes, mediaUpload } = getSettings(); let resizedImages = {}; if ( isSelected ) { - resizedImages = reduce( ids, ( currentResizedImages, id ) => { - if ( ! id ) { - return currentResizedImages; - } - const image = getMedia( id ); - const sizes = reduce( imageSizes, ( currentSizes, size ) => { - const defaultUrl = get( image, [ 'sizes', size.slug, 'url' ] ); - const mediaDetailsUrl = get( image, [ 'media_details', 'sizes', size.slug, 'source_url' ] ); + resizedImages = reduce( + ids, + ( currentResizedImages, id ) => { + if ( ! id ) { + return currentResizedImages; + } + const image = getMedia( id ); + const sizes = reduce( + imageSizes, + ( currentSizes, size ) => { + const defaultUrl = get( image, [ + 'sizes', + size.slug, + 'url', + ] ); + const mediaDetailsUrl = get( image, [ + 'media_details', + 'sizes', + size.slug, + 'source_url', + ] ); + return { + ...currentSizes, + [ size.slug ]: defaultUrl || mediaDetailsUrl, + }; + }, + {} + ); return { - ...currentSizes, - [ size.slug ]: defaultUrl || mediaDetailsUrl, + ...currentResizedImages, + [ parseInt( id, 10 ) ]: sizes, }; - }, {} ); - return { - ...currentResizedImages, - [ parseInt( id, 10 ) ]: sizes, - }; - }, {} ); + }, + {} + ); } return { diff --git a/packages/block-library/src/gallery/gallery-image.js b/packages/block-library/src/gallery/gallery-image.js index 99dce175a58a6c..089141fb5e2c66 100644 --- a/packages/block-library/src/gallery/gallery-image.js +++ b/packages/block-library/src/gallery/gallery-image.js @@ -64,7 +64,8 @@ class GalleryImage extends Component { onRemoveImage( event ) { if ( this.container === document.activeElement && - this.props.isSelected && [ BACKSPACE, DELETE ].indexOf( event.keyCode ) !== -1 + this.props.isSelected && + [ BACKSPACE, DELETE ].indexOf( event.keyCode ) !== -1 ) { event.stopPropagation(); event.preventDefault(); @@ -83,7 +84,11 @@ class GalleryImage extends Component { // unselect the caption so when the user selects other image and comeback // the caption is not immediately selected - if ( this.state.captionSelected && ! isSelected && prevProps.isSelected ) { + if ( + this.state.captionSelected && + ! isSelected && + prevProps.isSelected + ) { this.setState( { captionSelected: false, } ); @@ -91,7 +96,22 @@ class GalleryImage extends Component { } render() { - const { url, alt, id, linkTo, link, isFirstItem, isLastItem, isSelected, caption, onRemove, onMoveForward, onMoveBackward, setAttributes, 'aria-label': ariaLabel } = this.props; + const { + url, + alt, + id, + linkTo, + link, + isFirstItem, + isLastItem, + isSelected, + caption, + onRemove, + onMoveForward, + onMoveBackward, + setAttributes, + 'aria-label': ariaLabel, + } = this.props; let href; @@ -163,10 +183,14 @@ class GalleryImage extends Component { { ( isSelected || caption ) && ( <RichText tagName="figcaption" - placeholder={ isSelected ? __( 'Write caption…' ) : null } + placeholder={ + isSelected ? __( 'Write caption…' ) : null + } value={ caption } isSelected={ this.state.captionSelected } - onChange={ ( newCaption ) => setAttributes( { caption: newCaption } ) } + onChange={ ( newCaption ) => + setAttributes( { caption: newCaption } ) + } unstableOnFocus={ this.onSelectCaption } inlineToolbar /> diff --git a/packages/block-library/src/gallery/gallery-image.native.js b/packages/block-library/src/gallery/gallery-image.native.js index d1976bc42ea2d5..8cba7acd900dc2 100644 --- a/packages/block-library/src/gallery/gallery-image.native.js +++ b/packages/block-library/src/gallery/gallery-image.native.js @@ -1,7 +1,14 @@ /** * External dependencies */ -import { Image, StyleSheet, View, ScrollView, Text, TouchableWithoutFeedback } from 'react-native'; +import { + Image, + StyleSheet, + View, + ScrollView, + Text, + TouchableWithoutFeedback, +} from 'react-native'; import { requestImageFailedRetryDialog, requestImageUploadCancelDialog, @@ -26,7 +33,9 @@ import style from './gallery-image-style.scss'; const { compose } = StyleSheet; -const separatorStyle = compose( style.separator, { borderRightWidth: StyleSheet.hairlineWidth } ); +const separatorStyle = compose( style.separator, { + borderRightWidth: StyleSheet.hairlineWidth, +} ); const buttonStyle = compose( style.button, { aspectRatio: 1 } ); const removeButtonStyle = compose( style.removeButton, { aspectRatio: 1 } ); const ICON_SIZE_ARROW = 15; @@ -43,8 +52,12 @@ class GalleryImage extends Component { this.bindContainer = this.bindContainer.bind( this ); this.updateMediaProgress = this.updateMediaProgress.bind( this ); - this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this ); - this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( this ); + this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( + this + ); + this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( + this + ); this.renderContent = this.renderContent.bind( this ); this.state = { @@ -77,7 +90,7 @@ class GalleryImage extends Component { if ( this.state.isUploadInProgress ) { requestImageUploadCancelDialog( id ); - } else if ( ( this.state.didUploadFail ) || ( id && ! isURL( url ) ) ) { + } else if ( this.state.didUploadFail || ( id && ! isURL( url ) ) ) { requestImageFailedRetryDialog( id ); } } @@ -114,7 +127,11 @@ class GalleryImage extends Component { // unselect the caption so when the user selects other image and comeback // the caption is not immediately selected - if ( this.state.captionSelected && ! isSelected && prevProps.isSelected ) { + if ( + this.state.captionSelected && + ! isSelected && + prevProps.isSelected + ) { this.setState( { captionSelected: false, } ); @@ -128,15 +145,15 @@ class GalleryImage extends Component { } finishMediaUploadWithSuccess( payload ) { - this.props.setAttributes( { - id: payload.mediaServerId, - url: payload.mediaUrl, - } ); - this.setState( { isUploadInProgress: false, didUploadFail: false, } ); + + this.props.setAttributes( { + id: payload.mediaServerId, + url: payload.mediaUrl, + } ); } finishMediaUploadWithFailure() { @@ -148,34 +165,50 @@ class GalleryImage extends Component { renderContent( params ) { const { - url, isFirstItem, isLastItem, isSelected, caption, onRemove, - onMoveForward, onMoveBackward, 'aria-label': ariaLabel, - isCropped, getStylesFromColorScheme } = this.props; + url, + isFirstItem, + isLastItem, + isSelected, + caption, + onRemove, + onMoveForward, + onMoveBackward, + 'aria-label': ariaLabel, + isCropped, + getStylesFromColorScheme, + } = this.props; const { isUploadInProgress, captionSelected } = this.state; const { isUploadFailed, retryMessage } = params; const resizeMode = isCropped ? 'cover' : 'contain'; - const imageStyle = [ style.image, { resizeMode }, + const imageStyle = [ + style.image, + { resizeMode }, isUploadInProgress ? style.imageUploading : undefined, ]; - const overlayStyle = compose( style.overlay, - isSelected ? style.overlaySelected : undefined, + const overlayStyle = compose( + style.overlay, + isSelected ? style.overlaySelected : undefined ); - const captionPlaceholderStyle = getStylesFromColorScheme( style.captionPlaceholder, style.captionPlaceholderDark ); + const captionPlaceholderStyle = getStylesFromColorScheme( + style.captionPlaceholder, + style.captionPlaceholderDark + ); const shouldShowCaptionEditable = ! isUploadFailed && isSelected; - const shouldShowCaptionExpanded = ! isUploadFailed && ( ! isSelected && !! caption ); + const shouldShowCaptionExpanded = + ! isUploadFailed && ! isSelected && !! caption; - const captionContainerStyle = shouldShowCaptionExpanded ? - style.captionExpandedContainer : - style.captionContainer; + const captionContainerStyle = shouldShowCaptionExpanded + ? style.captionExpandedContainer + : style.captionContainer; - const captionStyle = shouldShowCaptionExpanded ? - style.captionExpanded : - getStylesFromColorScheme( style.caption, style.captionDark ); + const captionStyle = shouldShowCaptionExpanded + ? style.captionExpanded + : getStylesFromColorScheme( style.caption, style.captionDark ); return ( <> @@ -189,9 +222,14 @@ class GalleryImage extends Component { { isUploadFailed && ( <View style={ style.uploadFailedContainer }> <View style={ style.uploadFailed }> - <Icon icon={ 'warning' } { ...style.uploadFailedIcon } /> + <Icon + icon={ 'warning' } + { ...style.uploadFailedIcon } + /> </View> - <Text style={ style.uploadFailedText }>{ retryMessage }</Text> + <Text style={ style.uploadFailedText }> + { retryMessage } + </Text> </View> ) } <View style={ overlayStyle }> @@ -199,13 +237,19 @@ class GalleryImage extends Component { <> { isSelected && ( <View style={ style.toolbar }> - <View style={ style.moverButtonContainer } > + <View style={ style.moverButtonContainer }> <Button style={ buttonStyle } icon="arrow-left-alt" iconSize={ ICON_SIZE_ARROW } - onClick={ isFirstItem ? undefined : onMoveBackward } - accessibilityLabel={ __( 'Move Image Backward' ) } + onClick={ + isFirstItem + ? undefined + : onMoveBackward + } + accessibilityLabel={ __( + 'Move Image Backward' + ) } aria-disabled={ isFirstItem } disabled={ ! isSelected } /> @@ -214,8 +258,14 @@ class GalleryImage extends Component { style={ buttonStyle } icon="arrow-right-alt" iconSize={ ICON_SIZE_ARROW } - onClick={ isLastItem ? undefined : onMoveForward } - accessibilityLabel={ __( 'Move Image Forward' ) } + onClick={ + isLastItem + ? undefined + : onMoveForward + } + accessibilityLabel={ __( + 'Move Image Forward' + ) } aria-disabled={ isLastItem } disabled={ ! isSelected } /> @@ -225,21 +275,33 @@ class GalleryImage extends Component { icon="no-alt" iconSize={ ICON_SIZE_REMOVE } onClick={ onRemove } - accessibilityLabel={ __( 'Remove Image' ) } + accessibilityLabel={ __( + 'Remove Image' + ) } disabled={ ! isSelected } /> </View> ) } - { ( shouldShowCaptionEditable || shouldShowCaptionExpanded ) && ( + { ( shouldShowCaptionEditable || + shouldShowCaptionExpanded ) && ( <View style={ captionContainerStyle }> - <ScrollView nestedScrollEnabled keyboardShouldPersistTaps="handled"> + <ScrollView + nestedScrollEnabled + keyboardShouldPersistTaps="handled" + > <Caption inlineToolbar isSelected={ captionSelected } onChange={ this.onCaptionChange } onFocus={ this.onSelectCaption } - placeholder={ isSelected ? __( 'Write caption…' ) : null } - placeholderTextColor={ captionPlaceholderStyle.color } + placeholder={ + isSelected + ? __( 'Write caption…' ) + : null + } + placeholderTextColor={ + captionPlaceholderStyle.color + } style={ captionStyle } value={ caption } /> @@ -254,10 +316,17 @@ class GalleryImage extends Component { } render() { - const { id, onRemove, getStylesFromColorScheme, isSelected } = this.props; - - const containerStyle = getStylesFromColorScheme( style.galleryImageContainer, - style.galleryImageContainerDark ); + const { + id, + onRemove, + getStylesFromColorScheme, + isSelected, + } = this.props; + + const containerStyle = getStylesFromColorScheme( + style.galleryImageContainer, + style.galleryImageContainerDark + ); return ( <TouchableWithoutFeedback @@ -270,8 +339,12 @@ class GalleryImage extends Component { <MediaUploadProgress mediaId={ id } onUpdateMediaProgress={ this.updateMediaProgress } - onFinishMediaUploadWithSuccess={ this.finishMediaUploadWithSuccess } - onFinishMediaUploadWithFailure={ this.finishMediaUploadWithFailure } + onFinishMediaUploadWithSuccess={ + this.finishMediaUploadWithSuccess + } + onFinishMediaUploadWithFailure={ + this.finishMediaUploadWithFailure + } onMediaUploadStateReset={ onRemove } renderContent={ this.renderContent } /> @@ -283,10 +356,15 @@ class GalleryImage extends Component { accessibilityLabelImageContainer() { const { caption, 'aria-label': ariaLabel } = this.props; - return isEmpty( caption ) ? ariaLabel : ( ariaLabel + '. ' + sprintf( - /* translators: accessibility text. %s: image caption. */ - __( 'Image caption. %s' ), caption - ) ); + return isEmpty( caption ) + ? ariaLabel + : ariaLabel + + '. ' + + sprintf( + /* translators: accessibility text. %s: image caption. */ + __( 'Image caption. %s' ), + caption + ); } } diff --git a/packages/block-library/src/gallery/gallery.js b/packages/block-library/src/gallery/gallery.js index 2f1a53156c522f..5f910ca8f07d49 100644 --- a/packages/block-library/src/gallery/gallery.js +++ b/packages/block-library/src/gallery/gallery.js @@ -6,9 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { - RichText, -} from '@wordpress/block-editor'; +import { RichText } from '@wordpress/block-editor'; import { __, sprintf } from '@wordpress/i18n'; /** @@ -41,42 +39,48 @@ export const Gallery = ( props ) => { images, } = attributes; - const captionClassNames = classnames( - 'blocks-gallery-caption', - { - 'screen-reader-text': ! isSelected && RichText.isEmpty( caption ), - } - ); + const captionClassNames = classnames( 'blocks-gallery-caption', { + 'screen-reader-text': ! isSelected && RichText.isEmpty( caption ), + } ); return ( - <figure className={ classnames( - className, - { + <figure + className={ classnames( className, { [ `align${ align }` ]: align, [ `columns-${ columns }` ]: columns, 'is-cropped': imageCrop, - } - ) } + } ) } > <ul className="blocks-gallery-grid"> { images.map( ( img, index ) => { /* translators: %1$d is the order number of the image, %2$d is the total number of images. */ - const ariaLabel = sprintf( __( 'image %1$d of %2$d in gallery' ), ( index + 1 ), images.length ); + const ariaLabel = sprintf( + __( 'image %1$d of %2$d in gallery' ), + index + 1, + images.length + ); return ( - <li className="blocks-gallery-item" key={ img.id || img.url }> + <li + className="blocks-gallery-item" + key={ img.id || img.url } + > <GalleryImage url={ img.url } alt={ img.alt } id={ img.id } isFirstItem={ index === 0 } - isLastItem={ ( index + 1 ) === images.length } - isSelected={ isSelected && selectedImage === index } + isLastItem={ index + 1 === images.length } + isSelected={ + isSelected && selectedImage === index + } onMoveBackward={ onMoveBackward( index ) } onMoveForward={ onMoveForward( index ) } onRemove={ onRemoveImage( index ) } onSelect={ onSelectImage( index ) } - setAttributes={ ( attrs ) => onSetImageAttributes( index, attrs ) } + setAttributes={ ( attrs ) => + onSetImageAttributes( index, attrs ) + } caption={ img.caption } aria-label={ ariaLabel } /> diff --git a/packages/block-library/src/gallery/gallery.native.js b/packages/block-library/src/gallery/gallery.native.js index 4cc66c44e68887..27dc15df623e9f 100644 --- a/packages/block-library/src/gallery/gallery.native.js +++ b/packages/block-library/src/gallery/gallery.native.js @@ -7,6 +7,7 @@ import { isEmpty } from 'lodash'; /** * Internal dependencies */ +import { mediaUploadSync } from 'react-native-gutenberg-bridge'; import GalleryImage from './gallery-image'; import { defaultColumnsNumber } from './shared'; import styles from './gallery-styles.scss'; @@ -17,7 +18,7 @@ import Tiles from './tiles'; */ import { __, sprintf } from '@wordpress/i18n'; import { BlockCaption } from '@wordpress/block-editor'; -import { useState } from '@wordpress/element'; +import { useState, useEffect } from '@wordpress/element'; const TILE_SPACING = 15; @@ -27,6 +28,7 @@ const MAX_DISPLAYED_COLUMNS_NARROW = 2; export const Gallery = ( props ) => { const [ isCaptionSelected, setIsCaptionSelected ] = useState( false ); + useEffect( mediaUploadSync, [] ); const { clientId, @@ -53,9 +55,9 @@ export const Gallery = ( props ) => { // limit displayed columns when isNarrow is true (i.e. when viewport width is // less than "small", where small = 600) - const displayedColumns = isNarrow ? - Math.min( columns, MAX_DISPLAYED_COLUMNS_NARROW ) : - Math.min( columns, MAX_DISPLAYED_COLUMNS ); + const displayedColumns = isNarrow + ? Math.min( columns, MAX_DISPLAYED_COLUMNS_NARROW ) + : Math.min( columns, MAX_DISPLAYED_COLUMNS ); const selectImage = ( index ) => { return () => { @@ -79,21 +81,29 @@ export const Gallery = ( props ) => { <Tiles columns={ displayedColumns } spacing={ TILE_SPACING } - style={ isSelected ? styles.galleryTilesContainerSelected : undefined } + style={ + isSelected + ? styles.galleryTilesContainerSelected + : undefined + } > { images.map( ( img, index ) => { /* translators: %1$d is the order number of the image, %2$d is the total number of images. */ - const ariaLabel = sprintf( __( 'image %1$d of %2$d in gallery' ), ( index + 1 ), images.length ); + const ariaLabel = sprintf( + __( 'image %1$d of %2$d in gallery' ), + index + 1, + images.length + ); return ( <GalleryImage key={ img.id || img.url } url={ img.url } alt={ img.alt } - id={ img.id } + id={ parseInt( img.id, 10 ) } // make id an integer explicitly isCropped={ imageCrop } isFirstItem={ index === 0 } - isLastItem={ ( index + 1 ) === images.length } + isLastItem={ index + 1 === images.length } isSelected={ isSelected && selectedImage === index } isBlockSelected={ isSelected } onMoveBackward={ onMoveBackward( index ) } @@ -101,7 +111,9 @@ export const Gallery = ( props ) => { onRemove={ onRemoveImage( index ) } onSelect={ selectImage( index ) } onSelectBlock={ onFocus } - setAttributes={ ( attrs ) => onSetImageAttributes( index, attrs ) } + setAttributes={ ( attrs ) => + onSetImageAttributes( index, attrs ) + } caption={ img.caption } aria-label={ ariaLabel } /> @@ -114,13 +126,14 @@ export const Gallery = ( props ) => { isSelected={ isCaptionSelected } accessible={ true } accessibilityLabelCreator={ ( caption ) => - isEmpty( caption ) ? - /* translators: accessibility text. Empty gallery caption. */ - ( 'Gallery caption. Empty' ) : - sprintf( - /* translators: accessibility text. %s: gallery caption. */ - __( 'Gallery caption. %s' ), - caption ) + isEmpty( caption ) + ? /* translators: accessibility text. Empty gallery caption. */ + 'Gallery caption. Empty' + : sprintf( + /* translators: accessibility text. %s: gallery caption. */ + __( 'Gallery caption. %s' ), + caption + ) } onFocus={ focusGalleryCaption } onBlur={ onBlur } // always assign onBlur as props diff --git a/packages/block-library/src/gallery/icons.js b/packages/block-library/src/gallery/icons.js index 72efc9dbf14326..09d906f5939325 100644 --- a/packages/block-library/src/gallery/icons.js +++ b/packages/block-library/src/gallery/icons.js @@ -4,13 +4,23 @@ import { Path, SVG } from '@wordpress/components'; export const leftArrow = ( - <SVG width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"> + <SVG + width="18" + height="18" + viewBox="0 0 18 18" + xmlns="http://www.w3.org/2000/svg" + > <Path d="M5 8.70002L10.6 14.4L12 12.9L7.8 8.70002L12 4.50002L10.6 3.00002L5 8.70002Z" /> </SVG> ); export const rightArrow = ( - <SVG width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"> + <SVG + width="18" + height="18" + viewBox="0 0 18 18" + xmlns="http://www.w3.org/2000/svg" + > <Path d="M13 8.7L7.4 3L6 4.5L10.2 8.7L6 12.9L7.4 14.4L13 8.7Z" /> </SVG> ); diff --git a/packages/block-library/src/gallery/index.js b/packages/block-library/src/gallery/index.js index c40d96c628f6b9..66c2644a0e40b1 100644 --- a/packages/block-library/src/gallery/index.js +++ b/packages/block-library/src/gallery/index.js @@ -26,8 +26,14 @@ export const settings = { attributes: { columns: 2, images: [ - { url: 'https://s.w.org/images/core/5.3/Glacial_lakes%2C_Bhutan.jpg' }, - { url: 'https://s.w.org/images/core/5.3/Sediment_off_the_Yucatan_Peninsula.jpg' }, + { + url: + 'https://s.w.org/images/core/5.3/Glacial_lakes%2C_Bhutan.jpg', + }, + { + url: + 'https://s.w.org/images/core/5.3/Sediment_off_the_Yucatan_Peninsula.jpg', + }, ], }, }, diff --git a/packages/block-library/src/gallery/save.js b/packages/block-library/src/gallery/save.js index 9981c028598b7a..aef56dc6adb1d5 100644 --- a/packages/block-library/src/gallery/save.js +++ b/packages/block-library/src/gallery/save.js @@ -9,10 +9,20 @@ import { RichText } from '@wordpress/block-editor'; import { defaultColumnsNumber } from './shared'; export default function save( { attributes } ) { - const { images, columns = defaultColumnsNumber( attributes ), imageCrop, caption, linkTo } = attributes; + const { + images, + columns = defaultColumnsNumber( attributes ), + imageCrop, + caption, + linkTo, + } = attributes; return ( - <figure className={ `columns-${ columns } ${ imageCrop ? 'is-cropped' : '' }` }> + <figure + className={ `columns-${ columns } ${ + imageCrop ? 'is-cropped' : '' + }` } + > <ul className="blocks-gallery-grid"> { images.map( ( image ) => { let href; @@ -33,23 +43,38 @@ export default function save( { attributes } ) { data-id={ image.id } data-full-url={ image.fullUrl } data-link={ image.link } - className={ image.id ? `wp-image-${ image.id }` : null } + className={ + image.id ? `wp-image-${ image.id }` : null + } /> ); return ( - <li key={ image.id || image.url } className="blocks-gallery-item"> + <li + key={ image.id || image.url } + className="blocks-gallery-item" + > <figure> { href ? <a href={ href }>{ img }</a> : img } { ! RichText.isEmpty( image.caption ) && ( - <RichText.Content tagName="figcaption" className="blocks-gallery-item__caption" value={ image.caption } /> + <RichText.Content + tagName="figcaption" + className="blocks-gallery-item__caption" + value={ image.caption } + /> ) } </figure> </li> ); } ) } </ul> - { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" className="blocks-gallery-caption" value={ caption } /> } + { ! RichText.isEmpty( caption ) && ( + <RichText.Content + tagName="figcaption" + className="blocks-gallery-caption" + value={ caption } + /> + ) } </figure> ); } diff --git a/packages/block-library/src/gallery/shared-icon.native.js b/packages/block-library/src/gallery/shared-icon.native.js index c9faf935be6d49..16e89f5a95ac43 100644 --- a/packages/block-library/src/gallery/shared-icon.native.js +++ b/packages/block-library/src/gallery/shared-icon.native.js @@ -10,9 +10,14 @@ import { gallery as icon } from '@wordpress/icons'; */ import styles from './styles.scss'; -const IconWithColorScheme = withPreferredColorScheme( ( { getStylesFromColorScheme } ) => { - const colorSchemeStyles = getStylesFromColorScheme( styles.icon, styles.iconDark ); - return <Icon icon={ icon } { ...colorSchemeStyles } />; -} ); +const IconWithColorScheme = withPreferredColorScheme( + ( { getStylesFromColorScheme } ) => { + const colorSchemeStyles = getStylesFromColorScheme( + styles.icon, + styles.iconDark + ); + return <Icon icon={ icon } { ...colorSchemeStyles } />; + } +); export const sharedIcon = <IconWithColorScheme />; diff --git a/packages/block-library/src/gallery/shared.js b/packages/block-library/src/gallery/shared.js index 3a16b4744b4401..8c00115a8b6332 100644 --- a/packages/block-library/src/gallery/shared.js +++ b/packages/block-library/src/gallery/shared.js @@ -9,8 +9,13 @@ export function defaultColumnsNumber( attributes ) { export const pickRelevantMediaFiles = ( image, sizeSlug = 'large' ) => { const imageProps = pick( image, [ 'alt', 'id', 'link', 'caption' ] ); - imageProps.url = get( image, [ 'sizes', sizeSlug, 'url' ] ) || get( image, [ 'media_details', 'sizes', sizeSlug, 'source_url' ] ) || image.url; - const fullUrl = get( image, [ 'sizes', 'full', 'url' ] ) || get( image, [ 'media_details', 'sizes', 'full', 'source_url' ] ); + imageProps.url = + get( image, [ 'sizes', sizeSlug, 'url' ] ) || + get( image, [ 'media_details', 'sizes', sizeSlug, 'source_url' ] ) || + image.url; + const fullUrl = + get( image, [ 'sizes', 'full', 'url' ] ) || + get( image, [ 'media_details', 'sizes', 'full', 'source_url' ] ); if ( fullUrl ) { imageProps.fullUrl = fullUrl; } diff --git a/packages/block-library/src/gallery/tiles.native.js b/packages/block-library/src/gallery/tiles.native.js index 432f4bd69a308a..c5ce52b4ba24ba 100644 --- a/packages/block-library/src/gallery/tiles.native.js +++ b/packages/block-library/src/gallery/tiles.native.js @@ -14,12 +14,7 @@ import { Children } from '@wordpress/element'; import styles from './tiles-styles.scss'; function Tiles( props ) { - const { - columns, - children, - spacing = 10, - style, - } = props; + const { columns, children, spacing = 10, style } = props; const { compose } = StyleSheet; @@ -52,17 +47,24 @@ function Tiles( props ) { */ const row = Math.floor( index / columns ); - const rowLength = row === lastRow ? ( lastTile % columns ) + 1 : columns; + const rowLength = + row === lastRow ? ( lastTile % columns ) + 1 : columns; const indexInRow = index % columns; return ( - <View style={ [ styles.tileStyle, { - width: `${ 100 / rowLength }%`, - paddingLeft: spacing * ( indexInRow / rowLength ), - paddingRight: spacing * ( 1 - ( ( indexInRow + 1 ) / rowLength ) ), - paddingTop: row === 0 ? 0 : spacing / 2, - paddingBottom: row === lastRow ? 0 : spacing / 2, - } ] }> + <View + style={ [ + styles.tileStyle, + { + width: `${ 100 / rowLength }%`, + paddingLeft: spacing * ( indexInRow / rowLength ), + paddingRight: + spacing * ( 1 - ( indexInRow + 1 ) / rowLength ), + paddingTop: row === 0 ? 0 : spacing / 2, + paddingBottom: row === lastRow ? 0 : spacing / 2, + }, + ] } + > { child } </View> ); @@ -70,11 +72,7 @@ function Tiles( props ) { const containerStyle = compose( styles.containerStyle, style ); - return ( - <View style={ containerStyle }> - { wrappedChildren } - </View> - ); + return <View style={ containerStyle }>{ wrappedChildren }</View>; } export default Tiles; diff --git a/packages/block-library/src/gallery/transforms.js b/packages/block-library/src/gallery/transforms.js index d0c8c57240e2da..05220f5c0df887 100644 --- a/packages/block-library/src/gallery/transforms.js +++ b/packages/block-library/src/gallery/transforms.js @@ -19,9 +19,7 @@ const parseShortcodeIds = ( ids ) => { return []; } - return ids.split( ',' ).map( ( id ) => ( - parseInt( id, 10 ) - ) ); + return ids.split( ',' ).map( ( id ) => parseInt( id, 10 ) ); }; const transforms = { @@ -34,18 +32,24 @@ const transforms = { // Init the align and size from the first item which may be either the placeholder or an image. let { align, sizeSlug } = attributes[ 0 ]; // Loop through all the images and check if they have the same align and size. - align = every( attributes, [ 'align', align ] ) ? align : undefined; - sizeSlug = every( attributes, [ 'sizeSlug', sizeSlug ] ) ? sizeSlug : undefined; + align = every( attributes, [ 'align', align ] ) + ? align + : undefined; + sizeSlug = every( attributes, [ 'sizeSlug', sizeSlug ] ) + ? sizeSlug + : undefined; const validImages = filter( attributes, ( { url } ) => url ); return createBlock( 'core/gallery', { - images: validImages.map( ( { id, url, alt, caption } ) => ( { - id, - url, - alt, - caption, - } ) ), + images: validImages.map( + ( { id, url, alt, caption } ) => ( { + id, + url, + alt, + caption, + } ) + ), ids: validImages.map( ( { id } ) => id ), align, sizeSlug, @@ -88,13 +92,21 @@ const transforms = { // When created by drag and dropping multiple files on an insertion point type: 'files', isMatch( files ) { - return files.length !== 1 && every( files, ( file ) => file.type.indexOf( 'image/' ) === 0 ); + return ( + files.length !== 1 && + every( + files, + ( file ) => file.type.indexOf( 'image/' ) === 0 + ) + ); }, transform( files ) { const block = createBlock( 'core/gallery', { - images: files.map( ( file ) => pickRelevantMediaFiles( { - url: createBlobURL( file ), - } ) ), + images: files.map( ( file ) => + pickRelevantMediaFiles( { + url: createBlobURL( file ), + } ) + ), } ); return block; }, @@ -106,14 +118,16 @@ const transforms = { blocks: [ 'core/image' ], transform: ( { images, align, sizeSlug } ) => { if ( images.length > 0 ) { - return images.map( ( { id, url, alt, caption } ) => createBlock( 'core/image', { - id, - url, - alt, - caption, - align, - sizeSlug, - } ) ); + return images.map( ( { id, url, alt, caption } ) => + createBlock( 'core/image', { + id, + url, + alt, + caption, + align, + sizeSlug, + } ) + ); } return createBlock( 'core/image', { align } ); }, diff --git a/packages/block-library/src/group/deprecated.js b/packages/block-library/src/group/deprecated.js index ced2cfca58b993..39c69f21633652 100644 --- a/packages/block-library/src/group/deprecated.js +++ b/packages/block-library/src/group/deprecated.js @@ -27,13 +27,18 @@ const deprecated = [ save( { attributes } ) { const { backgroundColor, customBackgroundColor } = attributes; - const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + const backgroundClass = getColorClassName( + 'background-color', + backgroundColor + ); const className = classnames( backgroundClass, { 'has-background': backgroundColor || customBackgroundColor, } ); const styles = { - backgroundColor: backgroundClass ? undefined : customBackgroundColor, + backgroundColor: backgroundClass + ? undefined + : customBackgroundColor, }; return ( diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index 0a5ddae01ad050..6d3ec0111bb12b 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -3,15 +3,10 @@ */ import { withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; -import { - InnerBlocks, - __experimentalUseColors, -} from '@wordpress/block-editor'; +import { InnerBlocks, __experimentalUseColors } from '@wordpress/block-editor'; import { useRef } from '@wordpress/element'; -function GroupEdit( { - hasInnerBlocks, -} ) { +function GroupEdit( { hasInnerBlocks } ) { const ref = useRef(); const { TextColor, @@ -33,10 +28,13 @@ function GroupEdit( { { InspectorControlsColorPanel } <BackgroundColor> <TextColor> - <div className="wp-block-group" ref={ ref } > - <div className="wp-block-group__inner-container" > + <div className="wp-block-group" ref={ ref }> + <div className="wp-block-group__inner-container"> <InnerBlocks - renderAppender={ ! hasInnerBlocks && InnerBlocks.ButtonBlockAppender } + renderAppender={ + ! hasInnerBlocks && + InnerBlocks.ButtonBlockAppender + } /> </div> </div> @@ -48,9 +46,7 @@ function GroupEdit( { export default compose( [ withSelect( ( select, { clientId } ) => { - const { - getBlock, - } = select( 'core/block-editor' ); + const { getBlock } = select( 'core/block-editor' ); const block = getBlock( clientId ); diff --git a/packages/block-library/src/group/edit.native.js b/packages/block-library/src/group/edit.native.js index 3cfdd34700b81f..a1b261f3340c6c 100644 --- a/packages/block-library/src/group/edit.native.js +++ b/packages/block-library/src/group/edit.native.js @@ -1,4 +1,3 @@ - /** * External dependencies */ @@ -9,26 +8,27 @@ import { View } from 'react-native'; */ import { withSelect } from '@wordpress/data'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; -import { - InnerBlocks, - withColors, -} from '@wordpress/block-editor'; +import { InnerBlocks, withColors } from '@wordpress/block-editor'; /** * Internal dependencies */ import styles from './editor.scss'; -function GroupEdit( { - hasInnerBlocks, - isSelected, - getStylesFromColorScheme, -} ) { +function GroupEdit( { hasInnerBlocks, isSelected, getStylesFromColorScheme } ) { if ( ! isSelected && ! hasInnerBlocks ) { return ( - <View style={ [ - getStylesFromColorScheme( styles.groupPlaceholder, styles.groupPlaceholderDark ), - ! hasInnerBlocks && { ...styles.marginVerticalDense, ...styles.marginHorizontalNone }, - ] } /> + <View + style={ [ + getStylesFromColorScheme( + styles.groupPlaceholder, + styles.groupPlaceholderDark + ), + ! hasInnerBlocks && { + ...styles.marginVerticalDense, + ...styles.marginHorizontalNone, + }, + ] } + /> ); } @@ -42,9 +42,7 @@ function GroupEdit( { export default compose( [ withColors( 'backgroundColor' ), withSelect( ( select, { clientId } ) => { - const { - getBlock, - } = select( 'core/block-editor' ); + const { getBlock } = select( 'core/block-editor' ); const block = getBlock( clientId ); diff --git a/packages/block-library/src/group/icon.js b/packages/block-library/src/group/icon.js index f9d9dc7790ba95..8ec4a84f778a20 100644 --- a/packages/block-library/src/group/icon.js +++ b/packages/block-library/src/group/icon.js @@ -4,5 +4,21 @@ import { Path, SVG } from '@wordpress/components'; export default ( - <SVG width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M9 8a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-1v3a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h1V8zm2 3h4V9h-4v2zm2 2H9v2h4v-2z" /><Path fillRule="evenodd" clipRule="evenodd" d="M2 4.732A2 2 0 1 1 4.732 2h14.536A2 2 0 1 1 22 4.732v14.536A2 2 0 1 1 19.268 22H4.732A2 2 0 1 1 2 19.268V4.732zM4.732 4h14.536c.175.304.428.557.732.732v14.536a2.01 2.01 0 0 0-.732.732H4.732A2.01 2.01 0 0 0 4 19.268V4.732A2.01 2.01 0 0 0 4.732 4z" /></SVG> + <SVG + width="24" + height="24" + viewBox="0 0 24 24" + xmlns="http://www.w3.org/2000/svg" + > + <Path + fillRule="evenodd" + clipRule="evenodd" + d="M9 8a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-1v3a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h1V8zm2 3h4V9h-4v2zm2 2H9v2h4v-2z" + /> + <Path + fillRule="evenodd" + clipRule="evenodd" + d="M2 4.732A2 2 0 1 1 4.732 2h14.536A2 2 0 1 1 22 4.732v14.536A2 2 0 1 1 19.268 22H4.732A2 2 0 1 1 2 19.268V4.732zM4.732 4h14.536c.175.304.428.557.732.732v14.536a2.01 2.01 0 0 0-.732.732H4.732A2.01 2.01 0 0 0 4 19.268V4.732A2.01 2.01 0 0 0 4.732 4z" + /> + </SVG> ); diff --git a/packages/block-library/src/group/index.js b/packages/block-library/src/group/index.js index a307e35a07f372..225e6e2ed03496 100644 --- a/packages/block-library/src/group/index.js +++ b/packages/block-library/src/group/index.js @@ -21,7 +21,12 @@ export const settings = { title: __( 'Group' ), icon, description: __( 'A block that groups other blocks.' ), - keywords: [ __( 'container' ), __( 'wrapper' ), __( 'row' ), __( 'section' ) ], + keywords: [ + __( 'container' ), + __( 'wrapper' ), + __( 'row' ), + __( 'section' ), + ], example: { attributes: { customBackgroundColor: '#ffffff', @@ -91,17 +96,26 @@ export const settings = { blocks: [ '*' ], __experimentalConvert( blocks ) { // Avoid transforming a single `core/group` Block - if ( blocks.length === 1 && blocks[ 0 ].name === 'core/group' ) { + if ( + blocks.length === 1 && + blocks[ 0 ].name === 'core/group' + ) { return; } const alignments = [ 'wide', 'full' ]; // Determine the widest setting of all the blocks to be grouped - const widestAlignment = blocks.reduce( ( accumulator, block ) => { - const { align } = block.attributes; - return alignments.indexOf( align ) > alignments.indexOf( accumulator ) ? align : accumulator; - }, undefined ); + const widestAlignment = blocks.reduce( + ( accumulator, block ) => { + const { align } = block.attributes; + return alignments.indexOf( align ) > + alignments.indexOf( accumulator ) + ? align + : accumulator; + }, + undefined + ); // Clone the Blocks to be Grouped // Failing to create new block references causes the original blocks @@ -109,15 +123,22 @@ export const settings = { // are removed both from their original location and within the // new group block. const groupInnerBlocks = blocks.map( ( block ) => { - return createBlock( block.name, block.attributes, block.innerBlocks ); + return createBlock( + block.name, + block.attributes, + block.innerBlocks + ); } ); - return createBlock( 'core/group', { - align: widestAlignment, - }, groupInnerBlocks ); + return createBlock( + 'core/group', + { + align: widestAlignment, + }, + groupInnerBlocks + ); }, }, - ], }, diff --git a/packages/block-library/src/group/save.js b/packages/block-library/src/group/save.js index f37cb085659f31..9125c0563168f8 100644 --- a/packages/block-library/src/group/save.js +++ b/packages/block-library/src/group/save.js @@ -9,9 +9,17 @@ import classnames from 'classnames'; import { InnerBlocks, getColorClassName } from '@wordpress/block-editor'; export default function save( { attributes } ) { - const { backgroundColor, customBackgroundColor, textColor, customTextColor } = attributes; + const { + backgroundColor, + customBackgroundColor, + textColor, + customTextColor, + } = attributes; - const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + const backgroundClass = getColorClassName( + 'background-color', + backgroundColor + ); const textClass = getColorClassName( 'color', textColor ); const className = classnames( backgroundClass, { 'has-text-color': textColor || customTextColor, diff --git a/packages/block-library/src/heading/deprecated.js b/packages/block-library/src/heading/deprecated.js index e38ffb56171e88..fffa0f9b4e7976 100644 --- a/packages/block-library/src/heading/deprecated.js +++ b/packages/block-library/src/heading/deprecated.js @@ -6,10 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { - getColorClassName, - RichText, -} from '@wordpress/block-editor'; +import { getColorClassName, RichText } from '@wordpress/block-editor'; const blockSupports = { className: false, diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index eb50bdfeb99203..c1811738ea2e72 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -46,15 +46,33 @@ function HeadingEdit( { return ( <> <BlockControls> - <HeadingToolbar minLevel={ 2 } maxLevel={ 5 } selectedLevel={ level } onChange={ ( newLevel ) => setAttributes( { level: newLevel } ) } /> - <AlignmentToolbar value={ align } onChange={ ( nextAlign ) => { - setAttributes( { align: nextAlign } ); - } } /> + <HeadingToolbar + minLevel={ 2 } + maxLevel={ 5 } + selectedLevel={ level } + onChange={ ( newLevel ) => + setAttributes( { level: newLevel } ) + } + /> + <AlignmentToolbar + value={ align } + onChange={ ( nextAlign ) => { + setAttributes( { align: nextAlign } ); + } } + /> </BlockControls> <InspectorControls> <PanelBody title={ __( 'Heading settings' ) }> <p>{ __( 'Level' ) }</p> - <HeadingToolbar isCollapsed={ false } minLevel={ 1 } maxLevel={ 7 } selectedLevel={ level } onChange={ ( newLevel ) => setAttributes( { level: newLevel } ) } /> + <HeadingToolbar + isCollapsed={ false } + minLevel={ 1 } + maxLevel={ 7 } + selectedLevel={ level } + onChange={ ( newLevel ) => + setAttributes( { level: newLevel } ) + } + /> </PanelBody> </InspectorControls> { InspectorControlsColorPanel } @@ -64,7 +82,9 @@ function HeadingEdit( { identifier="content" tagName={ tagName } value={ content } - onChange={ ( value ) => setAttributes( { content: value } ) } + onChange={ ( value ) => + setAttributes( { content: value } ) + } onMerge={ mergeBlocks } onSplit={ ( value ) => { if ( ! value ) { diff --git a/packages/block-library/src/heading/edit.native.js b/packages/block-library/src/heading/edit.native.js index dcc0ca1e0291a9..fdf8480713bbf2 100644 --- a/packages/block-library/src/heading/edit.native.js +++ b/packages/block-library/src/heading/edit.native.js @@ -29,7 +29,9 @@ const HeadingEdit = ( { minLevel={ 2 } maxLevel={ 7 } selectedLevel={ attributes.level } - onChange={ ( newLevel ) => setAttributes( { level: newLevel } ) } + onChange={ ( newLevel ) => + setAttributes( { level: newLevel } ) + } isCollapsed={ false } /> </BlockControls> diff --git a/packages/block-library/src/heading/heading-level-icon.js b/packages/block-library/src/heading/heading-level-icon.js index 4780430c3ffed1..29a60cd93d7217 100644 --- a/packages/block-library/src/heading/heading-level-icon.js +++ b/packages/block-library/src/heading/heading-level-icon.js @@ -17,7 +17,13 @@ export default function HeadingLevelIcon( { level, isPressed = false } ) { } return ( - <SVG width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" isPressed={ isPressed } > + <SVG + width="20" + height="20" + viewBox="0 0 20 20" + xmlns="http://www.w3.org/2000/svg" + isPressed={ isPressed } + > <Path d={ levelToPath[ level ] } /> </SVG> ); diff --git a/packages/block-library/src/heading/heading-toolbar.js b/packages/block-library/src/heading/heading-toolbar.js index 0d836193a33b9f..49093ec6622dc0 100644 --- a/packages/block-library/src/heading/heading-toolbar.js +++ b/packages/block-library/src/heading/heading-toolbar.js @@ -19,7 +19,12 @@ class HeadingToolbar extends Component { createLevelControl( targetLevel, selectedLevel, onChange ) { const isActive = targetLevel === selectedLevel; return { - icon: <HeadingLevelIcon level={ targetLevel } isPressed={ isActive } />, + icon: ( + <HeadingLevelIcon + level={ targetLevel } + isPressed={ isActive } + /> + ), // translators: %s: heading level e.g: "1", "2", "3" title: sprintf( __( 'Heading %d' ), targetLevel ), isActive, @@ -28,15 +33,22 @@ class HeadingToolbar extends Component { } render() { - const { isCollapsed = true, minLevel, maxLevel, selectedLevel, onChange } = this.props; + const { + isCollapsed = true, + minLevel, + maxLevel, + selectedLevel, + onChange, + } = this.props; return ( <ToolbarGroup isCollapsed={ isCollapsed } icon={ <HeadingLevelIcon level={ selectedLevel } /> } - controls={ range( minLevel, maxLevel ).map( - ( index ) => this.createLevelControl( index, selectedLevel, onChange ) - ) } /> + controls={ range( minLevel, maxLevel ).map( ( index ) => + this.createLevelControl( index, selectedLevel, onChange ) + ) } + /> ); } } diff --git a/packages/block-library/src/heading/index.js b/packages/block-library/src/heading/index.js index 352aa701757472..c5b3cd4e8a9c1d 100644 --- a/packages/block-library/src/heading/index.js +++ b/packages/block-library/src/heading/index.js @@ -24,7 +24,9 @@ export { metadata, name }; export const settings = { title: __( 'Heading' ), - description: __( 'Introduce new sections and organize content to help visitors (and search engines) understand the structure of your content.' ), + description: __( + 'Introduce new sections and organize content to help visitors (and search engines) understand the structure of your content.' + ), icon, keywords: [ __( 'title' ), __( 'subtitle' ) ], supports: { @@ -42,25 +44,27 @@ export const settings = { if ( context === 'accessibility' ) { const { content, level } = attributes; - return isEmpty( content ) ? - sprintf( - /* translators: accessibility text. %s: heading level. */ - __( 'Level %s. Empty.' ), - level - ) : - sprintf( - /* translators: accessibility text. 1: heading level. 2: heading content. */ - __( 'Level %1$s. %2$s' ), - level, - content - ); + return isEmpty( content ) + ? sprintf( + /* translators: accessibility text. %s: heading level. */ + __( 'Level %s. Empty.' ), + level + ) + : sprintf( + /* translators: accessibility text. 1: heading level. 2: heading content. */ + __( 'Level %1$s. %2$s' ), + level, + content + ); } }, transforms, deprecated, merge( attributes, attributesToMerge ) { return { - content: ( attributes.content || '' ) + ( attributesToMerge.content || '' ), + content: + ( attributes.content || '' ) + + ( attributesToMerge.content || '' ), }; }, edit, diff --git a/packages/block-library/src/heading/save.js b/packages/block-library/src/heading/save.js index e8ace3129a6edc..5b2adacec2a633 100644 --- a/packages/block-library/src/heading/save.js +++ b/packages/block-library/src/heading/save.js @@ -6,19 +6,10 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { - getColorClassName, - RichText, -} from '@wordpress/block-editor'; +import { getColorClassName, RichText } from '@wordpress/block-editor'; export default function save( { attributes } ) { - const { - align, - content, - customTextColor, - level, - textColor, - } = attributes; + const { align, content, customTextColor, level, textColor } = attributes; const tagName = 'h' + level; const textClass = getColorClassName( 'color', textColor ); diff --git a/packages/block-library/src/heading/transforms.js b/packages/block-library/src/heading/transforms.js index 1d81708367af2c..6c08ca29d919ee 100644 --- a/packages/block-library/src/heading/transforms.js +++ b/packages/block-library/src/heading/transforms.js @@ -1,10 +1,7 @@ /** * WordPress dependencies */ -import { - createBlock, - getBlockAttributes, -} from '@wordpress/blocks'; +import { createBlock, getBlockAttributes } from '@wordpress/blocks'; /** * Internal dependencies diff --git a/packages/block-library/src/html/edit.js b/packages/block-library/src/html/edit.js index 5f9d42c61baefa..0004d72601b76b 100644 --- a/packages/block-library/src/html/edit.js +++ b/packages/block-library/src/html/edit.js @@ -8,12 +8,7 @@ import { PlainText, transformStyles, } from '@wordpress/block-editor'; -import { - Button, - Disabled, - SandBox, - ToolbarGroup, -} from '@wordpress/components'; +import { Button, Disabled, SandBox, ToolbarGroup } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; class HTMLEdit extends Component { @@ -41,10 +36,9 @@ class HTMLEdit extends Component { } `; - this.setState( { styles: [ - defaultStyles, - ...transformStyles( styles ), - ] } ); + this.setState( { + styles: [ defaultStyles, ...transformStyles( styles ) ], + } ); } switchToPreview() { @@ -80,18 +74,23 @@ class HTMLEdit extends Component { </ToolbarGroup> </BlockControls> <Disabled.Consumer> - { ( isDisabled ) => ( - ( isPreview || isDisabled ) ? ( - <SandBox html={ attributes.content } styles={ styles } /> + { ( isDisabled ) => + isPreview || isDisabled ? ( + <SandBox + html={ attributes.content } + styles={ styles } + /> ) : ( <PlainText value={ attributes.content } - onChange={ ( content ) => setAttributes( { content } ) } + onChange={ ( content ) => + setAttributes( { content } ) + } placeholder={ __( 'Write HTML…' ) } aria-label={ __( 'HTML' ) } /> ) - ) } + } </Disabled.Consumer> </div> ); diff --git a/packages/block-library/src/html/icon.js b/packages/block-library/src/html/icon.js index 789175b0cf0c46..f9e6d425dbcdbf 100644 --- a/packages/block-library/src/html/icon.js +++ b/packages/block-library/src/html/icon.js @@ -4,5 +4,7 @@ import { SVG, Path } from '@wordpress/components'; export default ( - <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M4.5,11h-2V9H1v6h1.5v-2.5h2V15H6V9H4.5V11z M7,10.5h1.5V15H10v-4.5h1.5V9H7V10.5z M14.5,10l-1-1H12v6h1.5v-3.9 l1,1l1-1V15H17V9h-1.5L14.5,10z M19.5,13.5V9H18v6h5v-1.5H19.5z" /></SVG> + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <Path d="M4.5,11h-2V9H1v6h1.5v-2.5h2V15H6V9H4.5V11z M7,10.5h1.5V15H10v-4.5h1.5V9H7V10.5z M14.5,10l-1-1H12v6h1.5v-3.9 l1,1l1-1V15H17V9h-1.5L14.5,10z M19.5,13.5V9H18v6h5v-1.5H19.5z" /> + </SVG> ); diff --git a/packages/block-library/src/html/index.js b/packages/block-library/src/html/index.js index 64675afd92b141..f3e592da15318e 100644 --- a/packages/block-library/src/html/index.js +++ b/packages/block-library/src/html/index.js @@ -23,7 +23,10 @@ export const settings = { keywords: [ __( 'embed' ) ], example: { attributes: { - content: '<marquee>' + __( 'Welcome to the wonderful world of blocks…' ) + '</marquee>', + content: + '<marquee>' + + __( 'Welcome to the wonderful world of blocks…' ) + + '</marquee>', }, }, supports: { diff --git a/packages/block-library/src/html/transforms.js b/packages/block-library/src/html/transforms.js index 9ab4169ad1dd1a..7b6c054f1c2080 100644 --- a/packages/block-library/src/html/transforms.js +++ b/packages/block-library/src/html/transforms.js @@ -2,13 +2,19 @@ const transforms = { from: [ { type: 'raw', - isMatch: ( node ) => node.nodeName === 'FIGURE' && !! node.querySelector( 'iframe' ), + isMatch: ( node ) => + node.nodeName === 'FIGURE' && !! node.querySelector( 'iframe' ), schema: ( { phrasingContentSchema } ) => ( { figure: { require: [ 'iframe' ], children: { iframe: { - attributes: [ 'src', 'allowfullscreen', 'height', 'width' ], + attributes: [ + 'src', + 'allowfullscreen', + 'height', + 'width', + ], }, figcaption: { children: phrasingContentSchema, diff --git a/packages/block-library/src/image/deprecated.js b/packages/block-library/src/image/deprecated.js index 952cb77b52cc16..08f43101618e2a 100644 --- a/packages/block-library/src/image/deprecated.js +++ b/packages/block-library/src/image/deprecated.js @@ -73,7 +73,16 @@ const deprecated = [ { attributes: blockAttributes, save( { attributes } ) { - const { url, alt, caption, align, href, width, height, id } = attributes; + const { + url, + alt, + caption, + align, + href, + width, + height, + id, + } = attributes; const classes = classnames( { [ `align${ align }` ]: align, @@ -93,7 +102,12 @@ const deprecated = [ return ( <figure className={ classes }> { href ? <a href={ href }>{ image }</a> : image } - { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> } + { ! RichText.isEmpty( caption ) && ( + <RichText.Content + tagName="figcaption" + value={ caption } + /> + ) } </figure> ); }, @@ -101,7 +115,16 @@ const deprecated = [ { attributes: blockAttributes, save( { attributes } ) { - const { url, alt, caption, align, href, width, height, id } = attributes; + const { + url, + alt, + caption, + align, + href, + width, + height, + id, + } = attributes; const image = ( <img @@ -114,9 +137,14 @@ const deprecated = [ ); return ( - <figure className={ align ? `align${ align }` : null } > + <figure className={ align ? `align${ align }` : null }> { href ? <a href={ href }>{ image }</a> : image } - { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> } + { ! RichText.isEmpty( caption ) && ( + <RichText.Content + tagName="figcaption" + value={ caption } + /> + ) } </figure> ); }, @@ -124,9 +152,19 @@ const deprecated = [ { attributes: blockAttributes, save( { attributes } ) { - const { url, alt, caption, align, href, width, height } = attributes; + const { + url, + alt, + caption, + align, + href, + width, + height, + } = attributes; const extraImageProps = width || height ? { width, height } : {}; - const image = <img src={ url } alt={ alt } { ...extraImageProps } />; + const image = ( + <img src={ url } alt={ alt } { ...extraImageProps } /> + ); let figureStyle = {}; @@ -137,9 +175,17 @@ const deprecated = [ } return ( - <figure className={ align ? `align${ align }` : null } style={ figureStyle }> + <figure + className={ align ? `align${ align }` : null } + style={ figureStyle } + > { href ? <a href={ href }>{ image }</a> : image } - { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> } + { ! RichText.isEmpty( caption ) && ( + <RichText.Content + tagName="figcaption" + value={ caption } + /> + ) } </figure> ); }, diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index d45885e5da51f7..56ab5c979e52f8 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -2,14 +2,7 @@ * External dependencies */ import classnames from 'classnames'; -import { - get, - filter, - map, - last, - omit, - pick, -} from 'lodash'; +import { get, filter, map, last, omit, pick } from 'lodash'; /** * WordPress dependencies @@ -39,9 +32,7 @@ import { __experimentalImageSizeControl as ImageSizeControl, __experimentalImageURLInputUI as ImageURLInputUI, } from '@wordpress/block-editor'; -import { - Component, -} from '@wordpress/element'; +import { Component } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { getPath } from '@wordpress/url'; import { withViewportMatch } from '@wordpress/viewport'; @@ -65,7 +56,10 @@ import { export const pickRelevantMediaFiles = ( image ) => { const imageProps = pick( image, [ 'alt', 'id', 'link', 'caption' ] ); - imageProps.url = get( image, [ 'sizes', 'large', 'url' ] ) || get( image, [ 'media_details', 'sizes', 'large', 'source_url' ] ) || image.url; + imageProps.url = + get( image, [ 'sizes', 'large', 'url' ] ) || + get( image, [ 'media_details', 'sizes', 'large', 'source_url' ] ) || + image.url; return imageProps; }; @@ -113,11 +107,7 @@ export class ImageEdit extends Component { } componentDidMount() { - const { - attributes, - mediaUpload, - noticeOperations, - } = this.props; + const { attributes, mediaUpload, noticeOperations } = this.props; const { id, url = '' } = attributes; if ( isTemporaryImage( id, url ) ) { @@ -142,11 +132,18 @@ export class ImageEdit extends Component { const { id: prevID, url: prevURL = '' } = prevProps.attributes; const { id, url = '' } = this.props.attributes; - if ( isTemporaryImage( prevID, prevURL ) && ! isTemporaryImage( id, url ) ) { + if ( + isTemporaryImage( prevID, prevURL ) && + ! isTemporaryImage( id, url ) + ) { revokeBlobURL( url ); } - if ( ! this.props.isSelected && prevProps.isSelected && this.state.captionFocused ) { + if ( + ! this.props.isSelected && + prevProps.isSelected && + this.state.captionFocused + ) { this.setState( { captionFocused: false, } ); @@ -171,7 +168,13 @@ export class ImageEdit extends Component { return; } - const { id, url, alt, caption, linkDestination } = this.props.attributes; + const { + id, + url, + alt, + caption, + linkDestination, + } = this.props.attributes; let mediaAttributes = pickRelevantMediaFiles( media ); @@ -231,9 +234,7 @@ export class ImageEdit extends Component { onImageError( url ) { // Check if there's an embed block that handles this URL. - const embedBlock = createUpgradedEmbedBlock( - { attributes: { url } } - ); + const embedBlock = createUpgradedEmbedBlock( { attributes: { url } } ); if ( undefined !== embedBlock ) { this.props.onReplace( embedBlock ); } @@ -269,16 +270,25 @@ export class ImageEdit extends Component { } updateAlignment( nextAlign ) { - const extraUpdatedAttributes = [ 'wide', 'full' ].indexOf( nextAlign ) !== -1 ? - { width: undefined, height: undefined } : - {}; - this.props.setAttributes( { ...extraUpdatedAttributes, align: nextAlign } ); + const extraUpdatedAttributes = + [ 'wide', 'full' ].indexOf( nextAlign ) !== -1 + ? { width: undefined, height: undefined } + : {}; + this.props.setAttributes( { + ...extraUpdatedAttributes, + align: nextAlign, + } ); } updateImage( sizeSlug ) { const { image } = this.props; - const url = get( image, [ 'media_details', 'sizes', sizeSlug, 'source_url' ] ); + const url = get( image, [ + 'media_details', + 'sizes', + sizeSlug, + 'source_url', + ] ); if ( ! url ) { return null; } @@ -301,7 +311,9 @@ export class ImageEdit extends Component { getImageSizeOptions() { const { imageSizes, image } = this.props; return map( - filter( imageSizes, ( { slug } ) => ( get( image, [ 'media_details', 'sizes', slug, 'source_url' ] ) ) ), + filter( imageSizes, ( { slug } ) => + get( image, [ 'media_details', 'sizes', slug, 'source_url' ] ) + ), ( { name, slug } ) => ( { value: slug, label: name } ) ); } @@ -343,22 +355,28 @@ export class ImageEdit extends Component { value={ align } onChange={ this.updateAlignment } /> - { url && <MediaReplaceFlow - mediaURL={ url } - allowedTypes={ ALLOWED_MEDIA_TYPES } - accept="image/*" - onSelect={ this.onSelectImage } - onSelectURL={ this.onSelectURL } - onError={ this.onUploadError } - /> } + { url && ( + <MediaReplaceFlow + mediaURL={ url } + allowedTypes={ ALLOWED_MEDIA_TYPES } + accept="image/*" + onSelect={ this.onSelectImage } + onSelectURL={ this.onSelectURL } + onError={ this.onUploadError } + /> + ) } { url && ( <ToolbarGroup> <ImageURLInputUI url={ href || '' } onChangeUrl={ this.onSetHref } linkDestination={ linkDestination } - mediaUrl={ this.props.image && this.props.image.source_url } - mediaLink={ this.props.image && this.props.image.link } + mediaUrl={ + this.props.image && this.props.image.source_url + } + mediaLink={ + this.props.image && this.props.image.link + } linkTarget={ linkTarget } linkClass={ linkClass } rel={ rel } @@ -370,14 +388,18 @@ export class ImageEdit extends Component { const src = isExternal ? url : undefined; const labels = { title: ! url ? __( 'Image' ) : __( 'Edit image' ), - instructions: __( 'Upload an image file, pick one from your media library, or add one with a URL.' ), + instructions: __( + 'Upload an image file, pick one from your media library, or add one with a URL.' + ), }; - const mediaPreview = ( !! url && <img - alt={ __( 'Edit image' ) } - title={ __( 'Edit image' ) } - className={ 'edit-image-preview' } - src={ url } - /> ); + const mediaPreview = !! url && ( + <img + alt={ __( 'Edit image' ) } + title={ __( 'Edit image' ) } + className={ 'edit-image-preview' } + src={ url } + /> + ); const mediaPlaceholder = ( <MediaPlaceholder icon={ <BlockIcon icon={ icon } /> } @@ -410,7 +432,8 @@ export class ImageEdit extends Component { [ `size-${ sizeSlug }` ]: sizeSlug, } ); - const isResizable = [ 'wide', 'full' ].indexOf( align ) === -1 && isLargeViewport; + const isResizable = + [ 'wide', 'full' ].indexOf( align ) === -1 && isLargeViewport; const imageSizeOptions = this.getImageSizeOptions(); @@ -425,9 +448,13 @@ export class ImageEdit extends Component { help={ <> <ExternalLink href="https://www.w3.org/WAI/tutorials/images/decision-tree"> - { __( 'Describe the purpose of the image' ) } + { __( + 'Describe the purpose of the image' + ) } </ExternalLink> - { __( 'Leave empty if the image is purely decorative.' ) } + { __( + 'Leave empty if the image is purely decorative.' + ) } </> } /> @@ -451,9 +478,13 @@ export class ImageEdit extends Component { onChange={ this.onSetTitle } help={ <> - { __( 'Describe the role of this image on the page.' ) } + { __( + 'Describe the role of this image on the page.' + ) } <ExternalLink href="https://www.w3.org/TR/html52/dom.html#the-title-attribute"> - { __( '(Note: many devices and browsers do not display this text.)' ) } + { __( + '(Note: many devices and browsers do not display this text.)' + ) } </ExternalLink> </> } @@ -482,9 +513,16 @@ export class ImageEdit extends Component { if ( alt ) { defaultedAlt = alt; } else if ( filename ) { - defaultedAlt = sprintf( __( 'This image has an empty alt attribute; its file name is %s' ), filename ); + defaultedAlt = sprintf( + __( + 'This image has an empty alt attribute; its file name is %s' + ), + filename + ); } else { - defaultedAlt = __( 'This image has an empty alt attribute' ); + defaultedAlt = __( + 'This image has an empty alt attribute' + ); } const img = ( @@ -496,17 +534,25 @@ export class ImageEdit extends Component { src={ url } alt={ defaultedAlt } onClick={ this.onImageClick } - onError={ () => this.onImageError( url ) } + onError={ () => + this.onImageError( url ) + } /> { isBlobURL( url ) && <Spinner /> } </> /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ ); - if ( ! isResizable || ! imageWidthWithinContainer ) { + if ( + ! isResizable || + ! imageWidthWithinContainer + ) { return ( <> - { getInspectorControls( imageWidth, imageHeight ) } + { getInspectorControls( + imageWidth, + imageHeight + ) } <div style={ { width, height } }> { img } </div> @@ -514,12 +560,20 @@ export class ImageEdit extends Component { ); } - const currentWidth = width || imageWidthWithinContainer; - const currentHeight = height || imageHeightWithinContainer; + const currentWidth = + width || imageWidthWithinContainer; + const currentHeight = + height || imageHeightWithinContainer; const ratio = imageWidth / imageHeight; - const minWidth = imageWidth < imageHeight ? MIN_SIZE : MIN_SIZE * ratio; - const minHeight = imageHeight < imageWidth ? MIN_SIZE : MIN_SIZE / ratio; + const minWidth = + imageWidth < imageHeight + ? MIN_SIZE + : MIN_SIZE * ratio; + const minHeight = + imageHeight < imageWidth + ? MIN_SIZE + : MIN_SIZE / ratio; // With the current implementation of ResizableBox, an image needs an explicit pixel value for the max-width. // In absence of being able to set the content-width, this max-width is currently dictated by the vanilla editor style. @@ -559,7 +613,10 @@ export class ImageEdit extends Component { return ( <> - { getInspectorControls( imageWidth, imageHeight ) } + { getInspectorControls( + imageWidth, + imageHeight + ) } <ResizableBox size={ { width, @@ -577,11 +634,23 @@ export class ImageEdit extends Component { left: showLeftHandle, } } onResizeStart={ onResizeStart } - onResizeStop={ ( event, direction, elt, delta ) => { + onResizeStop={ ( + event, + direction, + elt, + delta + ) => { onResizeStop(); setAttributes( { - width: parseInt( currentWidth + delta.width, 10 ), - height: parseInt( currentHeight + delta.height, 10 ), + width: parseInt( + currentWidth + delta.width, + 10 + ), + height: parseInt( + currentHeight + + delta.height, + 10 + ), } ); } } > @@ -597,7 +666,9 @@ export class ImageEdit extends Component { placeholder={ __( 'Write caption…' ) } value={ caption } unstableOnFocus={ this.onFocusCaption } - onChange={ ( value ) => setAttributes( { caption: value } ) } + onChange={ ( value ) => + setAttributes( { caption: value } ) + } isSelected={ this.state.captionFocused } inlineToolbar /> @@ -622,13 +693,11 @@ export default compose( [ withSelect( ( select, props ) => { const { getMedia } = select( 'core' ); const { getSettings } = select( 'core/block-editor' ); - const { attributes: { id }, isSelected } = props; const { - mediaUpload, - imageSizes, - isRTL, - maxWidth, - } = getSettings(); + attributes: { id }, + isSelected, + } = props; + const { mediaUpload, imageSizes, isRTL, maxWidth } = getSettings(); return { image: id && isSelected ? getMedia( id ) : null, diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 7f27f99dd1de39..d562742dfb6ae0 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -2,7 +2,13 @@ * External dependencies */ import React from 'react'; -import { View, ImageBackground, Text, TouchableWithoutFeedback, Dimensions } from 'react-native'; +import { + View, + ImageBackground, + Text, + TouchableWithoutFeedback, + Dimensions, +} from 'react-native'; import { requestMediaImport, mediaUploadSync, @@ -53,10 +59,7 @@ import SvgIconRetry from './icon-retry'; import SvgIconCustomize from './icon-customize'; import { getUpdatedLinkTargetSettings } from './utils'; -import { - LINK_DESTINATION_CUSTOM, - LINK_DESTINATION_NONE, -} from './constants'; +import { LINK_DESTINATION_CUSTOM, LINK_DESTINATION_NONE } from './constants'; const ICON_TYPE = { PLACEHOLDER: 'placeholder', @@ -74,7 +77,10 @@ const sizeOptionLabels = { [ IMAGE_SIZE_LARGE ]: __( 'Large' ), [ IMAGE_SIZE_FULL_SIZE ]: __( 'Full Size' ), }; -const sizeOptions = map( sizeOptionLabels, ( label, option ) => ( { value: option, label } ) ); +const sizeOptions = map( sizeOptionLabels, ( label, option ) => ( { + value: option, + label, +} ) ); // Default Image ratio 4:3 const IMAGE_ASPECT_RATIO = 4 / 3; @@ -91,10 +97,16 @@ export class ImageEdit extends React.Component { isCaptionSelected: false, }; - this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this ); - this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( this ); + this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( + this + ); + this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( + this + ); this.mediaUploadStateReset = this.mediaUploadStateReset.bind( this ); - this.onSelectMediaUploadOption = this.onSelectMediaUploadOption.bind( this ); + this.onSelectMediaUploadOption = this.onSelectMediaUploadOption.bind( + this + ); this.updateMediaProgress = this.updateMediaProgress.bind( this ); this.updateAlt = this.updateAlt.bind( this ); this.updateImageURL = this.updateImageURL.bind( this ); @@ -119,7 +131,11 @@ export class ImageEdit extends React.Component { } // Detect any pasted image and start an upload - if ( ! attributes.id && attributes.url && attributes.url.indexOf( 'file:' ) === 0 ) { + if ( + ! attributes.id && + attributes.url && + attributes.url.indexOf( 'file:' ) === 0 + ) { requestMediaImport( attributes.url, ( id, url ) => { if ( url ) { setAttributes( { id, url } ); @@ -136,8 +152,14 @@ export class ImageEdit extends React.Component { componentWillUnmount() { // this action will only exist if the user pressed the trash button on the block holder - if ( hasAction( 'blocks.onRemoveBlockCheckUpload' ) && this.state.isUploadInProgress ) { - doAction( 'blocks.onRemoveBlockCheckUpload', this.props.attributes.id ); + if ( + hasAction( 'blocks.onRemoveBlockCheckUpload' ) && + this.state.isUploadInProgress + ) { + doAction( + 'blocks.onRemoveBlockCheckUpload', + this.props.attributes.id + ); } } @@ -165,7 +187,10 @@ export class ImageEdit extends React.Component { } else if ( attributes.id && ! isURL( attributes.url ) ) { requestImageFailedRetryDialog( attributes.id ); } else if ( ! this.state.isCaptionSelected ) { - requestImageFullscreenPreview( attributes.url, image && image.source_url ); + requestImageFullscreenPreview( + attributes.url, + image && image.source_url + ); } this.setState( { @@ -210,7 +235,11 @@ export class ImageEdit extends React.Component { } updateImageURL( url ) { - this.props.setAttributes( { url, width: undefined, height: undefined } ); + this.props.setAttributes( { + url, + width: undefined, + height: undefined, + } ); } updateAlignment( nextAlign ) { @@ -225,7 +254,10 @@ export class ImageEdit extends React.Component { } onSetNewTab( value ) { - const updatedLinkTarget = getUpdatedLinkTargetSettings( value, this.props.attributes ); + const updatedLinkTarget = getUpdatedLinkTargetSettings( + value, + this.props.attributes + ); this.props.setAttributes( updatedLinkTarget ); } @@ -300,10 +332,16 @@ export class ImageEdit extends React.Component { case ICON_TYPE.RETRY: return <Icon icon={ SvgIconRetry } { ...styles.iconRetry } />; case ICON_TYPE.PLACEHOLDER: - iconStyle = this.props.getStylesFromColorScheme( styles.iconPlaceholder, styles.iconPlaceholderDark ); + iconStyle = this.props.getStylesFromColorScheme( + styles.iconPlaceholder, + styles.iconPlaceholderDark + ); break; case ICON_TYPE.UPLOAD: - iconStyle = this.props.getStylesFromColorScheme( styles.iconUpload, styles.iconUploadDark ); + iconStyle = this.props.getStylesFromColorScheme( + styles.iconUpload, + styles.iconUploadDark + ); break; } return <Icon icon={ icon } { ...iconStyle } />; @@ -311,9 +349,24 @@ export class ImageEdit extends React.Component { render() { const { attributes, isSelected, image } = this.props; - const { align, url, height, width, alt, href, id, linkTarget, sizeSlug } = attributes; + const { + align, + url, + height, + width, + alt, + href, + id, + linkTarget, + sizeSlug, + } = attributes; - const actions = [ { label: __( 'Clear All Settings' ), onPress: this.onClearSettings } ]; + const actions = [ + { + label: __( 'Clear All Settings' ), + onPress: this.onClearSettings, + }, + ]; const getToolbarEditButton = ( open ) => ( <BlockControls> @@ -334,7 +387,7 @@ export class ImageEdit extends React.Component { const getInspectorControls = () => ( <InspectorControls> - <PanelBody title={ __( 'Image settings' ) } > + <PanelBody title={ __( 'Image settings' ) }> <TextControl icon={ 'admin-links' } label={ __( 'Link To' ) } @@ -352,15 +405,22 @@ export class ImageEdit extends React.Component { onChange={ this.onSetNewTab } /> { // eslint-disable-next-line no-undef - image && __DEV__ && + image && __DEV__ && ( <SelectControl hideCancelButton icon={ 'editor-expand' } label={ __( 'Size' ) } - value={ sizeOptionLabels[ sizeSlug || DEFAULT_SIZE_SLUG ] } - onChangeValue={ ( newValue ) => this.onSetSizeSlug( newValue ) } + value={ + sizeOptionLabels[ + sizeSlug || DEFAULT_SIZE_SLUG + ] + } + onChangeValue={ ( newValue ) => + this.onSetSizeSlug( newValue ) + } options={ sizeOptions } - /> } + /> + ) } <TextControl icon={ 'editor-textcolor' } label={ __( 'Alt Text' ) } @@ -376,7 +436,7 @@ export class ImageEdit extends React.Component { if ( ! url ) { return ( - <View style={ { flex: 1 } } > + <View style={ { flex: 1 } }> <MediaPlaceholder allowedTypes={ [ MEDIA_TYPE_IMAGE ] } onSelect={ this.onSelectMediaUploadOption } @@ -395,14 +455,20 @@ export class ImageEdit extends React.Component { wide: 'center', }; - const imageContainerHeight = Dimensions.get( 'window' ).width / IMAGE_ASPECT_RATIO; + const imageContainerHeight = + Dimensions.get( 'window' ).width / IMAGE_ASPECT_RATIO; const editImageComponent = ( { open, mediaOptions } ) => ( - <TouchableWithoutFeedback - onPress={ open }> - <View style={ styles.edit }> - { mediaOptions() } - <Icon icon={ SvgIconCustomize } { ...styles.iconCustomise } /> + <TouchableWithoutFeedback onPress={ open }> + <View style={ styles.editContainer }> + <View style={ styles.edit }> + { mediaOptions() } + <Icon + size={ 16 } + icon={ SvgIconCustomize } + { ...styles.iconCustomise } + /> + </View> </View> </TouchableWithoutFeedback> ); @@ -417,21 +483,39 @@ export class ImageEdit extends React.Component { <View style={ { flex: 1 } }> { getInspectorControls() } { getMediaOptions() } - { ( ! this.state.isCaptionSelected ) && - getToolbarEditButton( openMediaOptions ) - } + { ! this.state.isCaptionSelected && + getToolbarEditButton( openMediaOptions ) } <MediaUploadProgress height={ height } width={ width } coverUrl={ url } mediaId={ id } onUpdateMediaProgress={ this.updateMediaProgress } - onFinishMediaUploadWithSuccess={ this.finishMediaUploadWithSuccess } - onFinishMediaUploadWithFailure={ this.finishMediaUploadWithFailure } + onFinishMediaUploadWithSuccess={ + this.finishMediaUploadWithSuccess + } + onFinishMediaUploadWithFailure={ + this.finishMediaUploadWithFailure + } onMediaUploadStateReset={ this.mediaUploadStateReset } - renderContent={ ( { isUploadInProgress, isUploadFailed, finalWidth, finalHeight, imageWidthWithinContainer, retryMessage } ) => { + renderContent={ ( { + isUploadInProgress, + isUploadFailed, + finalWidth, + finalHeight, + imageWidthWithinContainer, + retryMessage, + } ) => { const opacity = isUploadInProgress ? 0.3 : 1; - const imageBorderOnSelectedStyle = isSelected && ! ( isUploadInProgress || isUploadFailed || this.state.isCaptionSelected ) ? styles.imageBorder : ''; + const imageBorderOnSelectedStyle = + isSelected && + ! ( + isUploadInProgress || + isUploadFailed || + this.state.isCaptionSelected + ) + ? styles.imageBorder + : ''; const iconRetryContainer = ( <View style={ styles.modalIcon }> @@ -440,45 +524,104 @@ export class ImageEdit extends React.Component { ); return ( - <View style={ { - flex: 1, - // only set alignSelf if an image exists because alignSelf causes the placeholder - // to disappear when an aligned image can't be downloaded - // https://github.com/wordpress-mobile/gutenberg-mobile/issues/1592 - alignSelf: imageWidthWithinContainer && alignToFlex[ align ] } - } > - { ! imageWidthWithinContainer && - <View style={ [ this.props.getStylesFromColorScheme( styles.imageContainerUpload, styles.imageContainerUploadDark ), - { height: imageContainerHeight } ] } > - <View style={ styles.imageUploadingIconContainer }> - { this.getIcon( ICON_TYPE.UPLOAD ) } + <View + style={ { + flex: 1, + // only set alignSelf if an image exists because alignSelf causes the placeholder + // to disappear when an aligned image can't be downloaded + // https://github.com/wordpress-mobile/gutenberg-mobile/issues/1592 + alignSelf: + imageWidthWithinContainer && + alignToFlex[ align ], + } } + > + { ! imageWidthWithinContainer && ( + <View + style={ [ + this.props.getStylesFromColorScheme( + styles.imageContainerUpload, + styles.imageContainerUploadDark + ), + { + height: imageContainerHeight, + }, + ] } + > + <View + style={ + styles.imageUploadingIconContainer + } + > + { this.getIcon( + ICON_TYPE.UPLOAD + ) } </View> - </View> } + </View> + ) } <ImageBackground accessible={ true } disabled={ ! isSelected } accessibilityLabel={ alt } - accessibilityHint={ __( 'Double tap and hold to edit' ) } + accessibilityHint={ __( + 'Double tap and hold to edit' + ) } accessibilityRole={ 'imagebutton' } - style={ [ imageBorderOnSelectedStyle, { width: finalWidth, height: finalHeight, opacity } ] } + style={ [ + imageBorderOnSelectedStyle, + { + width: finalWidth, + height: finalHeight, + opacity, + }, + ] } resizeMethod="scale" source={ { uri: url } } key={ url } > - { isUploadFailed && - <View style={ [ styles.imageContainer, { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.5)' } ] } > + { isUploadFailed && ( + <View + style={ [ + styles.imageContainer, + { + flex: 1, + backgroundColor: + 'rgba(0, 0, 0, 0.5)', + }, + ] } + > { iconRetryContainer } - <Text style={ styles.uploadFailedText }>{ retryMessage }</Text> + <Text + style={ + styles.uploadFailedText + } + > + { retryMessage } + </Text> </View> - } - { ! isUploadInProgress && ! isUploadFailed && finalWidth && finalHeight && showMediaEditorButton && - <MediaEdit allowedTypes={ [ MEDIA_TYPE_IMAGE ] } - onSelect={ this.onSelectMediaUploadOption } - source={ { uri: url } } - openReplaceMediaOptions={ openMediaOptions } - render={ editImageComponent } - /> - } + ) } + { isSelected && + ! isUploadInProgress && + ! isUploadFailed && + finalWidth && + finalHeight && + showMediaEditorButton && ( + <MediaEdit + allowedTypes={ [ + MEDIA_TYPE_IMAGE, + ] } + onSelect={ + this + .onSelectMediaUploadOption + } + source={ { uri: url } } + openReplaceMediaOptions={ + openMediaOptions + } + render={ + editImageComponent + } + /> + ) } </ImageBackground> </View> ); @@ -489,13 +632,14 @@ export class ImageEdit extends React.Component { isSelected={ this.state.isCaptionSelected } accessible={ true } accessibilityLabelCreator={ ( caption ) => - isEmpty( caption ) ? - /* translators: accessibility text. Empty image caption. */ - ( 'Image caption. Empty' ) : - sprintf( - /* translators: accessibility text. %s: image caption. */ - __( 'Image caption. %s' ), - caption ) + isEmpty( caption ) + ? /* translators: accessibility text. Empty image caption. */ + 'Image caption. Empty' + : sprintf( + /* translators: accessibility text. %s: image caption. */ + __( 'Image caption. %s' ), + caption + ) } onFocus={ this.onFocusCaption } onBlur={ this.props.onBlur } // always assign onBlur as props @@ -505,7 +649,8 @@ export class ImageEdit extends React.Component { ); return ( - <MediaUpload allowedTypes={ [ MEDIA_TYPE_IMAGE ] } + <MediaUpload + allowedTypes={ [ MEDIA_TYPE_IMAGE ] } onSelect={ this.onSelectMediaUploadOption } render={ ( { open, getMediaOptions } ) => { return getImageComponent( open, getMediaOptions ); @@ -518,7 +663,10 @@ export class ImageEdit extends React.Component { export default compose( [ withSelect( ( select, props ) => { const { getMedia } = select( 'core' ); - const { attributes: { id }, isSelected } = props; + const { + attributes: { id }, + isSelected, + } = props; return { image: id && isSelected ? getMedia( id ) : null, diff --git a/packages/block-library/src/image/icon-customize.native.js b/packages/block-library/src/image/icon-customize.native.js index 89f3fb47b55177..57fc39c7c29f1b 100644 --- a/packages/block-library/src/image/icon-customize.native.js +++ b/packages/block-library/src/image/icon-customize.native.js @@ -3,4 +3,8 @@ */ import { Path, SVG } from '@wordpress/components'; -export default <SVG xmlns="http://www.w3.org/2000/svg"><Path d="M2 6c0-1.505.78-3.08 2-4 0 .845.69 2 2 2 1.657 0 3 1.343 3 3 0 .386-.08.752-.212 1.09.74.594 1.476 1.19 2.19 1.81L8.9 11.98c-.62-.716-1.214-1.454-1.807-2.192C6.753 9.92 6.387 10 6 10c-2.21 0-4-1.79-4-4zm12.152 6.848l1.34-1.34c.607.304 1.283.492 2.008.492 2.485 0 4.5-2.015 4.5-4.5 0-.725-.188-1.4-.493-2.007L18 9l-2-2 3.507-3.507C18.9 3.188 18.225 3 17.5 3 15.015 3 13 5.015 13 7.5c0 .725.188 1.4.493 2.007L3 20l2 2 6.848-6.848c1.885 1.928 3.874 3.753 5.977 5.45l1.425 1.148 1.5-1.5-1.15-1.425c-1.695-2.103-3.52-4.092-5.448-5.977z" /></SVG>; +export default ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M2 6c0-1.505.78-3.08 2-4 0 .845.69 2 2 2 1.657 0 3 1.343 3 3 0 .386-.08.752-.212 1.09.74.594 1.476 1.19 2.19 1.81L8.9 11.98c-.62-.716-1.214-1.454-1.807-2.192C6.753 9.92 6.387 10 6 10c-2.21 0-4-1.79-4-4zm12.152 6.848l1.34-1.34c.607.304 1.283.492 2.008.492 2.485 0 4.5-2.015 4.5-4.5 0-.725-.188-1.4-.493-2.007L18 9l-2-2 3.507-3.507C18.9 3.188 18.225 3 17.5 3 15.015 3 13 5.015 13 7.5c0 .725.188 1.4.493 2.007L3 20l2 2 6.848-6.848c1.885 1.928 3.874 3.753 5.977 5.45l1.425 1.148 1.5-1.5-1.15-1.425c-1.695-2.103-3.52-4.092-5.448-5.977z" /> + </SVG> +); diff --git a/packages/block-library/src/image/icon-retry.native.js b/packages/block-library/src/image/icon-retry.native.js index bdd40f69efd64a..580cd1fff188f5 100644 --- a/packages/block-library/src/image/icon-retry.native.js +++ b/packages/block-library/src/image/icon-retry.native.js @@ -3,4 +3,9 @@ */ import { Path, SVG } from '@wordpress/components'; -export default <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" /><Path d="M0 0h24v24H0z" fill={ 'none' } /></SVG>; +export default ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" /> + <Path d="M0 0h24v24H0z" fill={ 'none' } /> + </SVG> +); diff --git a/packages/block-library/src/image/icon.js b/packages/block-library/src/image/icon.js index 3c58853964ebc8..f3872e19ce444e 100644 --- a/packages/block-library/src/image/icon.js +++ b/packages/block-library/src/image/icon.js @@ -3,4 +3,11 @@ */ import { Path, Rect, SVG } from '@wordpress/components'; -export const editImageIcon = ( <SVG width={ 20 } height={ 20 } viewBox="0 0 20 20"><Rect x={ 11 } y={ 3 } width={ 7 } height={ 5 } rx={ 1 } /><Rect x={ 2 } y={ 12 } width={ 7 } height={ 5 } rx={ 1 } /><Path d="M13,12h1a3,3,0,0,1-3,3v2a5,5,0,0,0,5-5h1L15,9Z" /><Path d="M4,8H3l2,3L7,8H6A3,3,0,0,1,9,5V3A5,5,0,0,0,4,8Z" /></SVG> ); +export const editImageIcon = ( + <SVG width={ 20 } height={ 20 } viewBox="0 0 20 20"> + <Rect x={ 11 } y={ 3 } width={ 7 } height={ 5 } rx={ 1 } /> + <Rect x={ 2 } y={ 12 } width={ 7 } height={ 5 } rx={ 1 } /> + <Path d="M13,12h1a3,3,0,0,1-3,3v2a5,5,0,0,0,5-5h1L15,9Z" /> + <Path d="M4,8H3l2,3L7,8H6A3,3,0,0,1,9,5V3A5,5,0,0,0,4,8Z" /> + </SVG> +); diff --git a/packages/block-library/src/image/image-size.js b/packages/block-library/src/image/image-size.js index bc7ebaeaa59b46..3958aafcbb714e 100644 --- a/packages/block-library/src/image/image-size.js +++ b/packages/block-library/src/image/image-size.js @@ -60,7 +60,10 @@ class ImageSize extends Component { } calculateSize() { - const { width, height } = calculatePreferedImageSize( this.image, this.container ); + const { width, height } = calculatePreferedImageSize( + this.image, + this.container + ); this.setState( { width, height } ); } diff --git a/packages/block-library/src/image/index.js b/packages/block-library/src/image/index.js index e5361023ce5da2..332ab1afb062ca 100644 --- a/packages/block-library/src/image/index.js +++ b/packages/block-library/src/image/index.js @@ -34,7 +34,11 @@ export const settings = { }, }, styles: [ - { name: 'default', label: _x( 'Default', 'block style' ), isDefault: true }, + { + name: 'default', + label: _x( 'Default', 'block style' ), + isDefault: true, + }, { name: 'rounded', label: _x( 'Rounded', 'block style' ) }, ], __experimentalLabel( attributes, { context } ) { @@ -57,7 +61,13 @@ export const settings = { transforms, getEditWrapperProps( attributes ) { const { align, width } = attributes; - if ( 'left' === align || 'center' === align || 'right' === align || 'wide' === align || 'full' === align ) { + if ( + 'left' === align || + 'center' === align || + 'right' === align || + 'wide' === align || + 'full' === align + ) { return { 'data-align': align, 'data-resized': !! width }; } }, diff --git a/packages/block-library/src/image/save.js b/packages/block-library/src/image/save.js index 6f7ba6fa0f700d..4124e9c951e382 100644 --- a/packages/block-library/src/image/save.js +++ b/packages/block-library/src/image/save.js @@ -56,24 +56,22 @@ export default function save( { attributes } ) { > { image } </a> - ) : image } - { ! RichText.isEmpty( caption ) && <RichText.Content tagName="figcaption" value={ caption } /> } + ) : ( + image + ) } + { ! RichText.isEmpty( caption ) && ( + <RichText.Content tagName="figcaption" value={ caption } /> + ) } </> ); if ( 'left' === align || 'right' === align || 'center' === align ) { return ( <div> - <figure className={ classes }> - { figure } - </figure> + <figure className={ classes }>{ figure }</figure> </div> ); } - return ( - <figure className={ classes }> - { figure } - </figure> - ); + return <figure className={ classes }>{ figure }</figure>; } diff --git a/packages/block-library/src/image/styles.native.scss b/packages/block-library/src/image/styles.native.scss index a34c48fb7bbbc4..d4c512ea423cdd 100644 --- a/packages/block-library/src/image/styles.native.scss +++ b/packages/block-library/src/image/styles.native.scss @@ -84,10 +84,18 @@ align-items: center; } -.edit { +.editContainer { width: 44px; height: 44px; - background-color: $dark-opacity-500; + position: absolute; + top: 0; + right: 0; +} + +.edit { + width: 30px; + height: 30px; + background-color: $gray-dark; border-radius: 22px; position: absolute; top: 5px; @@ -97,6 +105,6 @@ .iconCustomise { fill: #fff; position: absolute; - top: 10px; - left: 10px; + top: 7px; + left: 7px; } diff --git a/packages/block-library/src/image/test/edit.js b/packages/block-library/src/image/test/edit.js index 506a3358a54e42..a7919f15730f35 100644 --- a/packages/block-library/src/image/test/edit.js +++ b/packages/block-library/src/image/test/edit.js @@ -18,7 +18,10 @@ describe( 'core/image/edit', () => { }; const setAttributes = jest.fn( () => {} ); const testRenderer = TestRenderer.create( - <ImageEdit attributes={ attributes } setAttributes={ setAttributes } /> + <ImageEdit + attributes={ attributes } + setAttributes={ setAttributes } + /> ); const instance = testRenderer.getInstance(); diff --git a/packages/block-library/src/image/test/edit.native.js b/packages/block-library/src/image/test/edit.native.js index 9c21c10d90dbaf..86b4751177da22 100644 --- a/packages/block-library/src/image/test/edit.native.js +++ b/packages/block-library/src/image/test/edit.native.js @@ -15,7 +15,7 @@ const getStylesFromColorScheme = () => { const setAttributes = jest.fn(); -const getImageComponent = ( attributes = { } ) => ( +const getImageComponent = ( attributes = {} ) => ( <ImageEdit setAttributes={ setAttributes } attributes={ attributes } @@ -40,16 +40,26 @@ describe( 'Image Block', () => { instance.onSetNewTab( true ); - expect( setAttributes ).toHaveBeenCalledWith( { linkTarget: '_blank', rel: undefined } ); + expect( setAttributes ).toHaveBeenCalledWith( { + linkTarget: '_blank', + rel: undefined, + } ); } ); it( 'unset link target', () => { - const component = renderer.create( getImageComponent( { linkTarget: '_blank', rel: NEW_TAB_REL.join( ' ' ) } ) ); + const component = renderer.create( + getImageComponent( { + linkTarget: '_blank', + rel: NEW_TAB_REL.join( ' ' ), + } ) + ); const instance = component.getInstance(); instance.onSetNewTab( false ); - expect( setAttributes ).toHaveBeenCalledWith( { linkTarget: undefined, rel: undefined } ); + expect( setAttributes ).toHaveBeenCalledWith( { + linkTarget: undefined, + rel: undefined, + } ); } ); } ); - diff --git a/packages/block-library/src/image/test/transforms.js b/packages/block-library/src/image/test/transforms.js index 14f2f8dc425807..fa9f99f0c94d1a 100644 --- a/packages/block-library/src/image/test/transforms.js +++ b/packages/block-library/src/image/test/transforms.js @@ -5,28 +5,62 @@ import { stripFirstImage } from '../transforms'; describe( 'stripFirstImage', () => { test( 'should do nothing if no image is present', () => { - expect( stripFirstImage( {}, { shortcode: { content: '' } } ) ).toEqual( '' ); - expect( stripFirstImage( {}, { shortcode: { content: 'Tucson' } } ) ).toEqual( 'Tucson' ); - expect( stripFirstImage( {}, { shortcode: { content: '<em>Tucson</em>' } } ) ).toEqual( '<em>Tucson</em>' ); + expect( stripFirstImage( {}, { shortcode: { content: '' } } ) ).toEqual( + '' + ); + expect( + stripFirstImage( {}, { shortcode: { content: 'Tucson' } } ) + ).toEqual( 'Tucson' ); + expect( + stripFirstImage( {}, { shortcode: { content: '<em>Tucson</em>' } } ) + ).toEqual( '<em>Tucson</em>' ); } ); test( 'should strip out image when leading as expected', () => { - expect( stripFirstImage( {}, { shortcode: { content: '<img>' } } ) ).toEqual( '' ); - expect( stripFirstImage( {}, { shortcode: { content: '<img>Image!' } } ) ).toEqual( 'Image!' ); - expect( stripFirstImage( {}, { shortcode: { content: '<img src="image.png">Image!' } } ) ).toEqual( 'Image!' ); + expect( + stripFirstImage( {}, { shortcode: { content: '<img>' } } ) + ).toEqual( '' ); + expect( + stripFirstImage( {}, { shortcode: { content: '<img>Image!' } } ) + ).toEqual( 'Image!' ); + expect( + stripFirstImage( + {}, + { shortcode: { content: '<img src="image.png">Image!' } } + ) + ).toEqual( 'Image!' ); } ); test( 'should strip out image when not in leading position as expected', () => { - expect( stripFirstImage( {}, { shortcode: { content: 'Before<img>' } } ) ).toEqual( 'Before' ); - expect( stripFirstImage( {}, { shortcode: { content: 'Before<img>Image!' } } ) ).toEqual( 'BeforeImage!' ); - expect( stripFirstImage( {}, { shortcode: { content: 'Before<img src="image.png">Image!' } } ) ).toEqual( 'BeforeImage!' ); + expect( + stripFirstImage( {}, { shortcode: { content: 'Before<img>' } } ) + ).toEqual( 'Before' ); + expect( + stripFirstImage( + {}, + { shortcode: { content: 'Before<img>Image!' } } + ) + ).toEqual( 'BeforeImage!' ); + expect( + stripFirstImage( + {}, + { shortcode: { content: 'Before<img src="image.png">Image!' } } + ) + ).toEqual( 'BeforeImage!' ); } ); test( 'should strip out only the first of many images', () => { - expect( stripFirstImage( {}, { shortcode: { content: '<img><img>' } } ) ).toEqual( '<img>' ); + expect( + stripFirstImage( {}, { shortcode: { content: '<img><img>' } } ) + ).toEqual( '<img>' ); } ); test( 'should strip out the first image and its wrapping parents', () => { - expect( stripFirstImage( {}, { shortcode: { content: '<p><a><img></a></p><p><img></p>' } } ) ).toEqual( '<p><img></p>' ); + expect( + stripFirstImage( + {}, + { shortcode: { content: '<p><a><img></a></p><p><img></p>' } } + ) + ).toEqual( '<p><img></p>' ); } ); } ); diff --git a/packages/block-library/src/image/transforms.js b/packages/block-library/src/image/transforms.js index 5d06d2e43ca3cc..f2cf8e61ba6634 100644 --- a/packages/block-library/src/image/transforms.js +++ b/packages/block-library/src/image/transforms.js @@ -2,10 +2,7 @@ * WordPress dependencies */ import { createBlobURL } from '@wordpress/blob'; -import { - createBlock, - getBlockAttributes, -} from '@wordpress/blocks'; +import { createBlock, getBlockAttributes } from '@wordpress/blocks'; export function stripFirstImage( attributes, { shortcode } ) { const { body } = document.implementation.createHTMLDocument( '' ); @@ -15,7 +12,11 @@ export function stripFirstImage( attributes, { shortcode } ) { let nodeToRemove = body.querySelector( 'img' ); // if an image has parents, find the topmost node to remove - while ( nodeToRemove && nodeToRemove.parentNode && nodeToRemove.parentNode !== body ) { + while ( + nodeToRemove && + nodeToRemove.parentNode && + nodeToRemove.parentNode !== body + ) { nodeToRemove = nodeToRemove.parentNode; } @@ -33,10 +34,7 @@ function getFirstAnchorAttributeFormHTML( html, attributeName ) { const { firstElementChild } = body; - if ( - firstElementChild && - firstElementChild.nodeName === 'A' - ) { + if ( firstElementChild && firstElementChild.nodeName === 'A' ) { return firstElementChild.getAttribute( attributeName ) || undefined; } } @@ -44,7 +42,13 @@ function getFirstAnchorAttributeFormHTML( html, attributeName ) { const imageSchema = { img: { attributes: [ 'src', 'alt', 'title' ], - classes: [ 'alignleft', 'aligncenter', 'alignright', 'alignnone', /^wp-image-\d+$/ ], + classes: [ + 'alignleft', + 'aligncenter', + 'alignright', + 'alignnone', + /^wp-image-\d+$/, + ], }, }; @@ -68,29 +72,61 @@ const transforms = { from: [ { type: 'raw', - isMatch: ( node ) => node.nodeName === 'FIGURE' && !! node.querySelector( 'img' ), + isMatch: ( node ) => + node.nodeName === 'FIGURE' && !! node.querySelector( 'img' ), schema, transform: ( node ) => { // Search both figure and image classes. Alignment could be // set on either. ID is set on the image. - const className = node.className + ' ' + node.querySelector( 'img' ).className; - const alignMatches = /(?:^|\s)align(left|center|right)(?:$|\s)/.exec( className ); + const className = + node.className + + ' ' + + node.querySelector( 'img' ).className; + const alignMatches = /(?:^|\s)align(left|center|right)(?:$|\s)/.exec( + className + ); const align = alignMatches ? alignMatches[ 1 ] : undefined; - const idMatches = /(?:^|\s)wp-image-(\d+)(?:$|\s)/.exec( className ); + const idMatches = /(?:^|\s)wp-image-(\d+)(?:$|\s)/.exec( + className + ); const id = idMatches ? Number( idMatches[ 1 ] ) : undefined; const anchorElement = node.querySelector( 'a' ); - const linkDestination = anchorElement && anchorElement.href ? 'custom' : undefined; - const href = anchorElement && anchorElement.href ? anchorElement.href : undefined; - const rel = anchorElement && anchorElement.rel ? anchorElement.rel : undefined; - const linkClass = anchorElement && anchorElement.className ? anchorElement.className : undefined; - const attributes = getBlockAttributes( 'core/image', node.outerHTML, { align, id, linkDestination, href, rel, linkClass } ); + const linkDestination = + anchorElement && anchorElement.href ? 'custom' : undefined; + const href = + anchorElement && anchorElement.href + ? anchorElement.href + : undefined; + const rel = + anchorElement && anchorElement.rel + ? anchorElement.rel + : undefined; + const linkClass = + anchorElement && anchorElement.className + ? anchorElement.className + : undefined; + const attributes = getBlockAttributes( + 'core/image', + node.outerHTML, + { + align, + id, + linkDestination, + href, + rel, + linkClass, + } + ); return createBlock( 'core/image', attributes ); }, }, { type: 'files', isMatch( files ) { - return files.length === 1 && files[ 0 ].type.indexOf( 'image/' ) === 0; + return ( + files.length === 1 && + files[ 0 ].type.indexOf( 'image/' ) === 0 + ); }, transform( files ) { const file = files[ 0 ]; @@ -123,17 +159,26 @@ const transforms = { }, href: { shortcode: ( attributes, { shortcode } ) => { - return getFirstAnchorAttributeFormHTML( shortcode.content, 'href' ); + return getFirstAnchorAttributeFormHTML( + shortcode.content, + 'href' + ); }, }, rel: { shortcode: ( attributes, { shortcode } ) => { - return getFirstAnchorAttributeFormHTML( shortcode.content, 'rel' ); + return getFirstAnchorAttributeFormHTML( + shortcode.content, + 'rel' + ); }, }, linkClass: { shortcode: ( attributes, { shortcode } ) => { - return getFirstAnchorAttributeFormHTML( shortcode.content, 'class' ); + return getFirstAnchorAttributeFormHTML( + shortcode.content, + 'class' + ); }, }, id: { diff --git a/packages/block-library/src/image/utils.js b/packages/block-library/src/image/utils.js index 1e3e8a07fc3748..3f156d659f89bc 100644 --- a/packages/block-library/src/image/utils.js +++ b/packages/block-library/src/image/utils.js @@ -1,10 +1,7 @@ /** * External dependencies */ -import { - isEmpty, - each, -} from 'lodash'; +import { isEmpty, each } from 'lodash'; /** * Internal dependencies diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 0c1d1c2f23391a..13ef75a96409d0 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -175,30 +175,30 @@ export const registerCoreBlocks = () => { * ``` */ export const __experimentalRegisterExperimentalCoreBlocks = - process.env.GUTENBERG_PHASE === 2 ? - ( settings ) => { - const { - __experimentalEnableLegacyWidgetBlock, - __experimentalEnableFullSiteEditing, - } = settings; + process.env.GUTENBERG_PHASE === 2 + ? ( settings ) => { + const { + __experimentalEnableLegacyWidgetBlock, + __experimentalEnableFullSiteEditing, + } = settings; - [ - __experimentalEnableLegacyWidgetBlock ? legacyWidget : null, - socialLinks, - ...socialLink.sites, + [ + __experimentalEnableLegacyWidgetBlock ? legacyWidget : null, + socialLinks, + ...socialLink.sites, - // Register Full Site Editing Blocks. - ...( __experimentalEnableFullSiteEditing ? - [ - siteTitle, - templatePart, - postTitle, - postContent, - postAuthor, - postDate, - postExcerpt, - ] : - [] ), - ].forEach( registerBlock ); - } : - undefined; + // Register Full Site Editing Blocks. + ...( __experimentalEnableFullSiteEditing + ? [ + siteTitle, + templatePart, + postTitle, + postContent, + postAuthor, + postDate, + postExcerpt, + ] + : [] ), + ].forEach( registerBlock ); + } + : undefined; diff --git a/packages/block-library/src/index.native.js b/packages/block-library/src/index.native.js index b61be5ada26ed7..b62c57cac2757c 100644 --- a/packages/block-library/src/index.native.js +++ b/packages/block-library/src/index.native.js @@ -120,7 +120,7 @@ const registerBlock = ( block ) => { // only enable code block for development // eslint-disable-next-line no-undef -const devOnly = ( block ) => !! __DEV__ ? block : null; +const devOnly = ( block ) => ( !! __DEV__ ? block : null ); /** * Function to register core blocks provided by the block editor. diff --git a/packages/block-library/src/latest-comments/edit.js b/packages/block-library/src/latest-comments/edit.js index b38eb0bb68458d..2d4ab81557e612 100644 --- a/packages/block-library/src/latest-comments/edit.js +++ b/packages/block-library/src/latest-comments/edit.js @@ -34,9 +34,13 @@ class LatestComments extends Component { // Create toggles for each attribute; we create them here rather than // passing `this.createToggleAttribute( 'displayAvatar' )` directly to // `onChange` to avoid re-renders. - this.toggleDisplayAvatar = this.createToggleAttribute( 'displayAvatar' ); + this.toggleDisplayAvatar = this.createToggleAttribute( + 'displayAvatar' + ); this.toggleDisplayDate = this.createToggleAttribute( 'displayDate' ); - this.toggleDisplayExcerpt = this.createToggleAttribute( 'displayExcerpt' ); + this.toggleDisplayExcerpt = this.createToggleAttribute( + 'displayExcerpt' + ); } createToggleAttribute( propName ) { diff --git a/packages/block-library/src/latest-posts/edit.js b/packages/block-library/src/latest-posts/edit.js index 79a8b4795befa7..1127716fd0833a 100644 --- a/packages/block-library/src/latest-posts/edit.js +++ b/packages/block-library/src/latest-posts/edit.js @@ -22,10 +22,7 @@ import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; import { __ } from '@wordpress/i18n'; import { dateI18n, format, __experimentalGetSettings } from '@wordpress/date'; -import { - InspectorControls, - BlockControls, -} from '@wordpress/block-editor'; +import { InspectorControls, BlockControls } from '@wordpress/block-editor'; import { withSelect } from '@wordpress/data'; /** @@ -48,19 +45,17 @@ class LatestPostsEdit extends Component { this.isStillMounted = true; this.fetchRequest = apiFetch( { path: addQueryArgs( `/wp/v2/categories`, CATEGORIES_LIST_QUERY ), - } ).then( - ( categoriesList ) => { + } ) + .then( ( categoriesList ) => { if ( this.isStillMounted ) { this.setState( { categoriesList } ); } - } - ).catch( - () => { + } ) + .catch( () => { if ( this.isStillMounted ) { this.setState( { categoriesList: [] } ); } - } - ); + } ); } componentWillUnmount() { @@ -70,7 +65,18 @@ class LatestPostsEdit extends Component { render() { const { attributes, setAttributes, latestPosts } = this.props; const { categoriesList } = this.state; - const { displayPostContentRadio, displayPostContent, displayPostDate, postLayout, columns, order, orderBy, categories, postsToShow, excerptLength } = attributes; + const { + displayPostContentRadio, + displayPostContent, + displayPostDate, + postLayout, + columns, + order, + orderBy, + categories, + postsToShow, + excerptLength, + } = attributes; const inspectorControls = ( <InspectorControls> @@ -78,35 +84,49 @@ class LatestPostsEdit extends Component { <ToggleControl label={ __( 'Post Content' ) } checked={ displayPostContent } - onChange={ ( value ) => setAttributes( { displayPostContent: value } ) } - /> - { displayPostContent && - <RadioControl - label={ __( 'Show:' ) } - selected={ displayPostContentRadio } - options={ [ - { label: __( 'Excerpt' ), value: 'excerpt' }, - { label: __( 'Full Post' ), value: 'full_post' }, - ] } - onChange={ ( value ) => setAttributes( { displayPostContentRadio: value } ) } + onChange={ ( value ) => + setAttributes( { displayPostContent: value } ) + } /> - } - { displayPostContent && displayPostContentRadio === 'excerpt' && - <RangeControl - label={ __( 'Max number of words in excerpt' ) } - value={ excerptLength } - onChange={ ( value ) => setAttributes( { excerptLength: value } ) } - min={ 10 } - max={ 100 } + { displayPostContent && ( + <RadioControl + label={ __( 'Show:' ) } + selected={ displayPostContentRadio } + options={ [ + { label: __( 'Excerpt' ), value: 'excerpt' }, + { + label: __( 'Full Post' ), + value: 'full_post', + }, + ] } + onChange={ ( value ) => + setAttributes( { + displayPostContentRadio: value, + } ) + } /> - } + ) } + { displayPostContent && + displayPostContentRadio === 'excerpt' && ( + <RangeControl + label={ __( 'Max number of words in excerpt' ) } + value={ excerptLength } + onChange={ ( value ) => + setAttributes( { excerptLength: value } ) + } + min={ 10 } + max={ 100 } + /> + ) } </PanelBody> <PanelBody title={ __( 'Post meta settings' ) }> <ToggleControl label={ __( 'Display post date' ) } checked={ displayPostDate } - onChange={ ( value ) => setAttributes( { displayPostDate: value } ) } + onChange={ ( value ) => + setAttributes( { displayPostDate: value } ) + } /> </PanelBody> @@ -116,21 +136,40 @@ class LatestPostsEdit extends Component { numberOfItems={ postsToShow } categoriesList={ categoriesList } selectedCategoryId={ categories } - onOrderChange={ ( value ) => setAttributes( { order: value } ) } - onOrderByChange={ ( value ) => setAttributes( { orderBy: value } ) } - onCategoryChange={ ( value ) => setAttributes( { categories: '' !== value ? value : undefined } ) } - onNumberOfItemsChange={ ( value ) => setAttributes( { postsToShow: value } ) } + onOrderChange={ ( value ) => + setAttributes( { order: value } ) + } + onOrderByChange={ ( value ) => + setAttributes( { orderBy: value } ) + } + onCategoryChange={ ( value ) => + setAttributes( { + categories: '' !== value ? value : undefined, + } ) + } + onNumberOfItemsChange={ ( value ) => + setAttributes( { postsToShow: value } ) + } /> - { postLayout === 'grid' && + { postLayout === 'grid' && ( <RangeControl label={ __( 'Columns' ) } value={ columns } - onChange={ ( value ) => setAttributes( { columns: value } ) } + onChange={ ( value ) => + setAttributes( { columns: value } ) + } min={ 2 } - max={ ! hasPosts ? MAX_POSTS_COLUMNS : Math.min( MAX_POSTS_COLUMNS, latestPosts.length ) } + max={ + ! hasPosts + ? MAX_POSTS_COLUMNS + : Math.min( + MAX_POSTS_COLUMNS, + latestPosts.length + ) + } required /> - } + ) } </PanelBody> </InspectorControls> ); @@ -144,19 +183,21 @@ class LatestPostsEdit extends Component { icon="admin-post" label={ __( 'Latest Posts' ) } > - { ! Array.isArray( latestPosts ) ? - <Spinner /> : + { ! Array.isArray( latestPosts ) ? ( + <Spinner /> + ) : ( __( 'No posts found.' ) - } + ) } </Placeholder> </> ); } // Removing posts from display should be instant. - const displayPosts = latestPosts.length > postsToShow ? - latestPosts.slice( 0, postsToShow ) : - latestPosts; + const displayPosts = + latestPosts.length > postsToShow + ? latestPosts.slice( 0, postsToShow ) + : latestPosts; const layoutControls = [ { @@ -197,43 +238,74 @@ class LatestPostsEdit extends Component { } const excerptElement = document.createElement( 'div' ); excerptElement.innerHTML = excerpt; - excerpt = excerptElement.textContent || excerptElement.innerText || ''; + excerpt = + excerptElement.textContent || + excerptElement.innerText || + ''; return ( <li key={ i }> - <a href={ post.link } target="_blank" rel="noreferrer noopener"> + <a + href={ post.link } + target="_blank" + rel="noreferrer noopener" + > { titleTrimmed ? ( - <RawHTML> - { titleTrimmed } - </RawHTML> - ) : + <RawHTML>{ titleTrimmed }</RawHTML> + ) : ( __( '(no title)' ) - } + ) } </a> - { displayPostDate && post.date_gmt && - <time dateTime={ format( 'c', post.date_gmt ) } className="wp-block-latest-posts__post-date"> - { dateI18n( dateFormat, post.date_gmt ) } - </time> - } - { displayPostContent && displayPostContentRadio === 'excerpt' && - <div className="wp-block-latest-posts__post-excerpt"> - <RawHTML - key="html" - > - { excerptLength < excerpt.trim().split( ' ' ).length ? - excerpt.trim().split( ' ', excerptLength ).join( ' ' ) + ' ... <a href=' + post.link + 'target="_blank" rel="noopener noreferrer">' + __( 'Read more' ) + '</a>' : - excerpt.trim().split( ' ', excerptLength ).join( ' ' ) } - </RawHTML> - </div> - } - { displayPostContent && displayPostContentRadio === 'full_post' && - <div className="wp-block-latest-posts__post-full-content"> - <RawHTML - key="html" + { displayPostDate && post.date_gmt && ( + <time + dateTime={ format( + 'c', + post.date_gmt + ) } + className="wp-block-latest-posts__post-date" > - { post.content.raw.trim() } - </RawHTML> - </div> - } + { dateI18n( + dateFormat, + post.date_gmt + ) } + </time> + ) } + { displayPostContent && + displayPostContentRadio === 'excerpt' && ( + <div className="wp-block-latest-posts__post-excerpt"> + <RawHTML key="html"> + { excerptLength < + excerpt.trim().split( ' ' ) + .length + ? excerpt + .trim() + .split( + ' ', + excerptLength + ) + .join( ' ' ) + + ' ... <a href=' + + post.link + + 'target="_blank" rel="noopener noreferrer">' + + __( 'Read more' ) + + '</a>' + : excerpt + .trim() + .split( + ' ', + excerptLength + ) + .join( ' ' ) } + </RawHTML> + </div> + ) } + { displayPostContent && + displayPostContentRadio === 'full_post' && ( + <div className="wp-block-latest-posts__post-full-content"> + <RawHTML key="html"> + { post.content.raw.trim() } + </RawHTML> + </div> + ) } </li> ); } ) } @@ -246,12 +318,15 @@ class LatestPostsEdit extends Component { export default withSelect( ( select, props ) => { const { postsToShow, order, orderBy, categories } = props.attributes; const { getEntityRecords } = select( 'core' ); - const latestPostsQuery = pickBy( { - categories, - order, - orderby: orderBy, - per_page: postsToShow, - }, ( value ) => ! isUndefined( value ) ); + const latestPostsQuery = pickBy( + { + categories, + order, + orderby: orderBy, + per_page: postsToShow, + }, + ( value ) => ! isUndefined( value ) + ); return { latestPosts: getEntityRecords( 'postType', 'post', latestPostsQuery ), }; diff --git a/packages/block-library/src/latest-posts/icon.js b/packages/block-library/src/latest-posts/icon.js index 7233084d330bcc..0a37c4a96c6262 100644 --- a/packages/block-library/src/latest-posts/icon.js +++ b/packages/block-library/src/latest-posts/icon.js @@ -4,5 +4,14 @@ import { Path, Rect, SVG } from '@wordpress/components'; export default ( - <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M0,0h24v24H0V0z" fill="none" /><Rect x="11" y="7" width="6" height="2" /><Rect x="11" y="11" width="6" height="2" /><Rect x="11" y="15" width="6" height="2" /><Rect x="7" y="7" width="2" height="2" /><Rect x="7" y="11" width="2" height="2" /><Rect x="7" y="15" width="2" height="2" /><Path d="M20.1,3H3.9C3.4,3,3,3.4,3,3.9v16.2C3,20.5,3.4,21,3.9,21h16.2c0.4,0,0.9-0.5,0.9-0.9V3.9C21,3.4,20.5,3,20.1,3z M19,19H5V5h14V19z" /></SVG> + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <Path d="M0,0h24v24H0V0z" fill="none" /> + <Rect x="11" y="7" width="6" height="2" /> + <Rect x="11" y="11" width="6" height="2" /> + <Rect x="11" y="15" width="6" height="2" /> + <Rect x="7" y="7" width="2" height="2" /> + <Rect x="7" y="11" width="2" height="2" /> + <Rect x="7" y="15" width="2" height="2" /> + <Path d="M20.1,3H3.9C3.4,3,3,3.4,3,3.9v16.2C3,20.5,3.4,21,3.9,21h16.2c0.4,0,0.9-0.5,0.9-0.9V3.9C21,3.4,20.5,3,20.1,3z M19,19H5V5h14V19z" /> + </SVG> ); diff --git a/packages/block-library/src/legacy-widget/edit/dom-manager.js b/packages/block-library/src/legacy-widget/edit/dom-manager.js index ccabc4408f0666..310ac16b871ea6 100644 --- a/packages/block-library/src/legacy-widget/edit/dom-manager.js +++ b/packages/block-library/src/legacy-widget/edit/dom-manager.js @@ -23,32 +23,37 @@ class LegacyWidgetEditDomManager extends Component { componentDidMount() { this.triggerWidgetEvent( 'widget-added' ); - this.previousFormData = new window.FormData( - this.formRef.current - ); + this.previousFormData = new window.FormData( this.formRef.current ); } shouldComponentUpdate( nextProps ) { let shouldTriggerWidgetUpdateEvent = false; // We can not leverage react render otherwise we would destroy dom changes applied by the plugins. // We manually update the required dom node replicating what the widget screen and the customizer do. - if ( nextProps.idBase !== this.props.idBase && this.idBaseInputRef.current ) { + if ( + nextProps.idBase !== this.props.idBase && + this.idBaseInputRef.current + ) { this.idBaseInputRef.current.value = nextProps.idBase; shouldTriggerWidgetUpdateEvent = true; } - if ( nextProps.number !== this.props.number && this.widgetNumberInputRef.current ) { + if ( + nextProps.number !== this.props.number && + this.widgetNumberInputRef.current + ) { this.widgetNumberInputRef.current.value = nextProps.number; } - if ( nextProps.form !== this.props.form && this.widgetContentRef.current ) { + if ( + nextProps.form !== this.props.form && + this.widgetContentRef.current + ) { const widgetContent = this.widgetContentRef.current; widgetContent.innerHTML = nextProps.form; shouldTriggerWidgetUpdateEvent = true; } if ( shouldTriggerWidgetUpdateEvent ) { this.triggerWidgetEvent( 'widget-updated' ); - this.previousFormData = new window.FormData( - this.formRef.current - ); + this.previousFormData = new window.FormData( this.formRef.current ); } return false; } @@ -65,7 +70,11 @@ class LegacyWidgetEditDomManager extends Component { if ( this.shouldTriggerInstanceUpdate() ) { if ( isReferenceWidget ) { if ( this.containerRef.current ) { - window.wpWidgets.save( window.jQuery( this.containerRef.current ) ); + window.wpWidgets.save( + window.jQuery( + this.containerRef.current + ) + ); } } this.props.onInstanceChange( @@ -81,11 +90,38 @@ class LegacyWidgetEditDomManager extends Component { /> { isReferenceWidget && ( <> - <input type="hidden" name="widget-id" className="widget-id" value={ id } /> - <input ref={ this.idBaseInputRef } type="hidden" name="id_base" className="id_base" value={ idBase } /> - <input ref={ this.widgetNumberInputRef } type="hidden" name="widget_number" className="widget_number" value={ number } /> - <input type="hidden" name="multi_number" className="multi_number" value="" /> - <input type="hidden" name="add_new" className="add_new" value="" /> + <input + type="hidden" + name="widget-id" + className="widget-id" + value={ id } + /> + <input + ref={ this.idBaseInputRef } + type="hidden" + name="id_base" + className="id_base" + value={ idBase } + /> + <input + ref={ this.widgetNumberInputRef } + type="hidden" + name="widget_number" + className="widget_number" + value={ number } + /> + <input + type="hidden" + name="multi_number" + className="multi_number" + value="" + /> + <input + type="hidden" + name="add_new" + className="add_new" + value="" + /> </> ) } </form> @@ -101,21 +137,19 @@ class LegacyWidgetEditDomManager extends Component { if ( ! this.previousFormData ) { return true; } - const currentFormData = new window.FormData( - this.formRef.current - ); + const currentFormData = new window.FormData( this.formRef.current ); const currentFormDataKeys = Array.from( currentFormData.keys() ); const previousFormDataKeys = Array.from( this.previousFormData.keys() ); - if ( - currentFormDataKeys.length !== previousFormDataKeys.length - ) { + if ( currentFormDataKeys.length !== previousFormDataKeys.length ) { return true; } for ( const rawKey of currentFormDataKeys ) { - if ( ! isShallowEqual( - currentFormData.getAll( rawKey ), - this.previousFormData.getAll( rawKey ) - ) ) { + if ( + ! isShallowEqual( + currentFormData.getAll( rawKey ), + this.previousFormData.getAll( rawKey ) + ) + ) { this.previousFormData = currentFormData; return true; } @@ -124,10 +158,9 @@ class LegacyWidgetEditDomManager extends Component { } triggerWidgetEvent( event ) { - window.jQuery( window.document ).trigger( - event, - [ window.jQuery( this.containerRef.current ) ] - ); + window + .jQuery( window.document ) + .trigger( event, [ window.jQuery( this.containerRef.current ) ] ); } retrieveUpdatedInstance() { @@ -138,14 +171,23 @@ class LegacyWidgetEditDomManager extends Component { for ( const rawKey of formData.keys() ) { // This fields are added to the form because the widget JavaScript code may use this values. // They are not relevant for the update mechanism. - if ( includes( - [ 'widget-id', 'id_base', 'widget_number', 'multi_number', 'add_new' ], - rawKey, - ) ) { + if ( + includes( + [ + 'widget-id', + 'id_base', + 'widget_number', + 'multi_number', + 'add_new', + ], + rawKey + ) + ) { continue; } const matches = rawKey.match( /[^\[]*\[[-\d]*\]\[([^\]]*)\]/ ); - const keyParsed = matches && matches[ 1 ] ? matches[ 1 ] : rawKey; + const keyParsed = + matches && matches[ 1 ] ? matches[ 1 ] : rawKey; const value = formData.getAll( rawKey ); if ( value.length > 1 ) { updatedInstance[ keyParsed ] = value; @@ -159,4 +201,3 @@ class LegacyWidgetEditDomManager extends Component { } export default LegacyWidgetEditDomManager; - diff --git a/packages/block-library/src/legacy-widget/edit/handler.js b/packages/block-library/src/legacy-widget/edit/handler.js index 96983ffac1bf29..a299d94024223a 100644 --- a/packages/block-library/src/legacy-widget/edit/handler.js +++ b/packages/block-library/src/legacy-widget/edit/handler.js @@ -59,7 +59,15 @@ class LegacyWidgetEditHandler extends Component { } render() { - const { instanceId, id, number, idBase, instance, isSelected, widgetName } = this.props; + const { + instanceId, + id, + number, + idBase, + instance, + isSelected, + widgetName, + } = this.props; const { form } = this.state; if ( ! form ) { @@ -94,7 +102,6 @@ class LegacyWidgetEditHandler extends Component { display: this.props.isVisible ? 'block' : 'none', } } > - <LegacyWidgetEditDomManager isReferenceWidget={ !! id } ref={ ( ref ) => { @@ -172,18 +179,16 @@ class LegacyWidgetEditHandler extends Component { instance_changes: instanceChanges, }, method: 'POST', - } ).then( - ( response ) => { - if ( isStillMounted ) { - this.setState( { - form: response.form, - } ); - if ( callback ) { - callback( response ); - } + } ).then( ( response ) => { + if ( isStillMounted ) { + this.setState( { + form: response.form, + } ); + if ( callback ) { + callback( response ); } } - ); + } ); } } } diff --git a/packages/block-library/src/legacy-widget/edit/index.js b/packages/block-library/src/legacy-widget/edit/index.js index 42023d2a881420..d12717c8e336c2 100644 --- a/packages/block-library/src/legacy-widget/edit/index.js +++ b/packages/block-library/src/legacy-widget/edit/index.js @@ -7,17 +7,10 @@ import { get } from 'lodash'; * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { - Button, - PanelBody, - ToolbarGroup, -} from '@wordpress/components'; +import { Button, PanelBody, ToolbarGroup } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { withSelect } from '@wordpress/data'; -import { - BlockControls, - InspectorControls, -} from '@wordpress/block-editor'; +import { BlockControls, InspectorControls } from '@wordpress/block-editor'; import ServerSideRender from '@wordpress/server-side-render'; /** @@ -55,13 +48,19 @@ class LegacyWidgetEdit extends Component { return ( <LegacyWidgetPlaceholder availableLegacyWidgets={ availableLegacyWidgets } - hasPermissionsToManageWidgets={ hasPermissionsToManageWidgets } + hasPermissionsToManageWidgets={ + hasPermissionsToManageWidgets + } onChangeWidget={ ( newWidget ) => { - const { isReferenceWidget } = availableLegacyWidgets[ newWidget ]; + const { isReferenceWidget } = availableLegacyWidgets[ + newWidget + ]; setAttributes( { instance: {}, id: isReferenceWidget ? newWidget : undefined, - widgetClass: isReferenceWidget ? undefined : newWidget, + widgetClass: isReferenceWidget + ? undefined + : newWidget, } ); } } /> @@ -88,7 +87,7 @@ class LegacyWidgetEdit extends Component { <> <BlockControls> <ToolbarGroup> - { ( widgetObject && ! widgetObject.isHidden ) && ( + { widgetObject && ! widgetObject.isHidden && ( <Button onClick={ this.changeWidget } label={ __( 'Change widget' ) } @@ -126,20 +125,18 @@ class LegacyWidgetEdit extends Component { widgetName={ get( widgetObject, [ 'name' ] ) } widgetClass={ attributes.widgetClass } instance={ attributes.instance } - onInstanceChange={ - ( newInstance, newHasEditForm ) => { - if ( newInstance ) { - this.props.setAttributes( { - instance: newInstance, - } ); - } - if ( newHasEditForm !== this.hasEditForm ) { - this.setState( { - hasEditForm: newHasEditForm, - } ); - } + onInstanceChange={ ( newInstance, newHasEditForm ) => { + if ( newInstance ) { + this.props.setAttributes( { + instance: newInstance, + } ); } - } + if ( newHasEditForm !== this.hasEditForm ) { + this.setState( { + hasEditForm: newHasEditForm, + } ); + } + } } /> ) } { ( isPreview || ! hasEditForm ) && this.renderWidgetPreview() } diff --git a/packages/block-library/src/legacy-widget/edit/placeholder.js b/packages/block-library/src/legacy-widget/edit/placeholder.js index 0a5e2ae0b4a39f..98fc64b6ad9b35 100644 --- a/packages/block-library/src/legacy-widget/edit/placeholder.js +++ b/packages/block-library/src/legacy-widget/edit/placeholder.js @@ -18,15 +18,14 @@ export default function LegacyWidgetPlaceholder( { onChangeWidget, } ) { const visibleLegacyWidgets = useMemo( - () => pickBy( - availableLegacyWidgets, - ( { isHidden } ) => ! isHidden - ), + () => pickBy( availableLegacyWidgets, ( { isHidden } ) => ! isHidden ), [ availableLegacyWidgets ] ); let placeholderContent; if ( ! hasPermissionsToManageWidgets ) { - placeholderContent = __( 'You don\'t have permissions to use widgets on this site.' ); + placeholderContent = __( + "You don't have permissions to use widgets on this site." + ); } else if ( isEmpty( visibleLegacyWidgets ) ) { placeholderContent = __( 'There are no widgets available.' ); } else { diff --git a/packages/block-library/src/list/edit.js b/packages/block-library/src/list/edit.js index 02a029c5a7892e..8f54fb79d5d9a0 100644 --- a/packages/block-library/src/list/edit.js +++ b/packages/block-library/src/list/edit.js @@ -8,9 +8,7 @@ import { BlockControls, RichTextShortcut, } from '@wordpress/block-editor'; -import { - ToolbarGroup, -} from '@wordpress/components'; +import { ToolbarGroup } from '@wordpress/components'; import { __unstableCanIndentListItems as canIndentListItems, __unstableCanOutdentListItems as canOutdentListItems, @@ -40,36 +38,42 @@ export default function ListEdit( { const controls = ( { value, onChange, onFocus } ) => ( <> - { ( isSelected && <> - <RichTextShortcut - type="primary" - character="[" - onUse={ () => { - onChange( outdentListItems( value ) ); - } } - /> - <RichTextShortcut - type="primary" - character="]" - onUse={ () => { - onChange( indentListItems( value, { type: tagName } ) ); - } } - /> - <RichTextShortcut - type="primary" - character="m" - onUse={ () => { - onChange( indentListItems( value, { type: tagName } ) ); - } } - /> - <RichTextShortcut - type="primaryShift" - character="m" - onUse={ () => { - onChange( outdentListItems( value ) ); - } } - /> - </> ) } + { isSelected && ( + <> + <RichTextShortcut + type="primary" + character="[" + onUse={ () => { + onChange( outdentListItems( value ) ); + } } + /> + <RichTextShortcut + type="primary" + character="]" + onUse={ () => { + onChange( + indentListItems( value, { type: tagName } ) + ); + } } + /> + <RichTextShortcut + type="primary" + character="m" + onUse={ () => { + onChange( + indentListItems( value, { type: tagName } ) + ); + } } + /> + <RichTextShortcut + type="primaryShift" + character="m" + onUse={ () => { + onChange( outdentListItems( value ) ); + } } + /> + </> + ) } <BlockControls> <ToolbarGroup controls={ [ @@ -78,7 +82,9 @@ export default function ListEdit( { title: __( 'Convert to unordered list' ), isActive: isActiveListType( value, 'ul', tagName ), onClick() { - onChange( changeListType( value, { type: 'ul' } ) ); + onChange( + changeListType( value, { type: 'ul' } ) + ); onFocus(); if ( isListRootSelected( value ) ) { @@ -91,7 +97,9 @@ export default function ListEdit( { title: __( 'Convert to ordered list' ), isActive: isActiveListType( value, 'ol', tagName ), onClick() { - onChange( changeListType( value, { type: 'ol' } ) ); + onChange( + changeListType( value, { type: 'ol' } ) + ); onFocus(); if ( isListRootSelected( value ) ) { @@ -115,7 +123,9 @@ export default function ListEdit( { shortcut: _x( 'Space', 'keyboard key' ), isDisabled: ! canIndentListItems( value ), onClick() { - onChange( indentListItems( value, { type: tagName } ) ); + onChange( + indentListItems( value, { type: tagName } ) + ); onFocus(); }, }, @@ -125,32 +135,41 @@ export default function ListEdit( { </> ); - return <> - <RichText - identifier="values" - multiline="li" - tagName={ tagName } - onChange={ ( nextValues ) => setAttributes( { values: nextValues } ) } - value={ values } - className={ className } - placeholder={ __( 'Write list…' ) } - onMerge={ mergeBlocks } - onSplit={ ( value ) => createBlock( name, { ...attributes, values: value } ) } - __unstableOnSplitMiddle={ () => createBlock( 'core/paragraph' ) } - onReplace={ onReplace } - onRemove={ () => onReplace( [] ) } - start={ start } - reversed={ reversed } - type={ type } - > - { controls } - </RichText> - { ordered && ( - <OrderedListSettings - setAttributes={ setAttributes } - ordered={ ordered } - reversed={ reversed } + return ( + <> + <RichText + identifier="values" + multiline="li" + tagName={ tagName } + onChange={ ( nextValues ) => + setAttributes( { values: nextValues } ) + } + value={ values } + className={ className } + placeholder={ __( 'Write list…' ) } + onMerge={ mergeBlocks } + onSplit={ ( value ) => + createBlock( name, { ...attributes, values: value } ) + } + __unstableOnSplitMiddle={ () => + createBlock( 'core/paragraph' ) + } + onReplace={ onReplace } + onRemove={ () => onReplace( [] ) } start={ start } - /> ) } - </>; + reversed={ reversed } + type={ type } + > + { controls } + </RichText> + { ordered && ( + <OrderedListSettings + setAttributes={ setAttributes } + ordered={ ordered } + reversed={ reversed } + start={ start } + /> + ) } + </> + ); } diff --git a/packages/block-library/src/list/index.js b/packages/block-library/src/list/index.js index 82eae9e42d57e8..dc1a05b080c24b 100644 --- a/packages/block-library/src/list/index.js +++ b/packages/block-library/src/list/index.js @@ -20,14 +20,19 @@ export const settings = { title: __( 'List' ), description: __( 'Create a bulleted or numbered list.' ), icon, - keywords: [ __( 'bullet list' ), __( 'ordered list' ), __( 'numbered list' ) ], + keywords: [ + __( 'bullet list' ), + __( 'ordered list' ), + __( 'numbered list' ), + ], supports: { className: false, __unstablePasteTextInline: true, }, example: { attributes: { - values: '<li>Alice.</li><li>The White Rabbit.</li><li>The Cheshire Cat.</li><li>The Mad Hatter.</li><li>The Queen of Hearts.</li>', + values: + '<li>Alice.</li><li>The White Rabbit.</li><li>The Cheshire Cat.</li><li>The Mad Hatter.</li><li>The Queen of Hearts.</li>', }, }, transforms, diff --git a/packages/block-library/src/list/ordered-list-settings.js b/packages/block-library/src/list/ordered-list-settings.js index a0a4d4afd2c69b..c4992a2cc667e7 100644 --- a/packages/block-library/src/list/ordered-list-settings.js +++ b/packages/block-library/src/list/ordered-list-settings.js @@ -3,11 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { InspectorControls } from '@wordpress/block-editor'; -import { - TextControl, - PanelBody, - ToggleControl, -} from '@wordpress/components'; +import { TextControl, PanelBody, ToggleControl } from '@wordpress/components'; const OrderedListSettings = ( { setAttributes, reversed, start } ) => ( <InspectorControls> @@ -38,6 +34,7 @@ const OrderedListSettings = ( { setAttributes, reversed, start } ) => ( } } /> </PanelBody> - </InspectorControls> ); + </InspectorControls> +); export default OrderedListSettings; diff --git a/packages/block-library/src/list/transforms.js b/packages/block-library/src/list/transforms.js index edfed2b3ed9c8a..13c2338aa54746 100644 --- a/packages/block-library/src/list/transforms.js +++ b/packages/block-library/src/list/transforms.js @@ -1,10 +1,7 @@ /** * WordPress dependencies */ -import { - createBlock, - getBlockAttributes, -} from '@wordpress/blocks'; +import { createBlock, getBlockAttributes } from '@wordpress/blocks'; import { __UNSTABLE_LINE_SEPARATOR, create, @@ -44,17 +41,24 @@ const transforms = { transform: ( blockAttributes ) => { return createBlock( 'core/list', { values: toHTMLString( { - value: join( blockAttributes.map( ( { content } ) => { - const value = create( { html: content } ); + value: join( + blockAttributes.map( ( { content } ) => { + const value = create( { html: content } ); - if ( blockAttributes.length > 1 ) { - return value; - } + if ( blockAttributes.length > 1 ) { + return value; + } - // When converting only one block, transform - // every line to a list item. - return replace( value, /\n/g, __UNSTABLE_LINE_SEPARATOR ); - } ), __UNSTABLE_LINE_SEPARATOR ), + // When converting only one block, transform + // every line to a list item. + return replace( + value, + /\n/g, + __UNSTABLE_LINE_SEPARATOR + ); + } ), + __UNSTABLE_LINE_SEPARATOR + ), multilineTag: 'li', } ), } ); @@ -137,16 +141,18 @@ const transforms = { type: 'block', blocks: [ 'core/paragraph' ], transform: ( { values } ) => - split( create( { - html: values, - multilineTag: 'li', - multilineWrapperTags: [ 'ul', 'ol' ], - } ), __UNSTABLE_LINE_SEPARATOR ) - .map( ( piece ) => - createBlock( 'core/paragraph', { - content: toHTMLString( { value: piece } ), - } ) - ), + split( + create( { + html: values, + multilineTag: 'li', + multilineWrapperTags: [ 'ul', 'ol' ], + } ), + __UNSTABLE_LINE_SEPARATOR + ).map( ( piece ) => + createBlock( 'core/paragraph', { + content: toHTMLString( { value: piece } ), + } ) + ), }, { type: 'block', diff --git a/packages/block-library/src/media-text/deprecated.js b/packages/block-library/src/media-text/deprecated.js index 721a6cf7f3a318..83512f4ee163fb 100644 --- a/packages/block-library/src/media-text/deprecated.js +++ b/packages/block-library/src/media-text/deprecated.js @@ -7,10 +7,7 @@ import { noop } from 'lodash'; /** * WordPress dependencies */ -import { - InnerBlocks, - getColorClassName, -} from '@wordpress/block-editor'; +import { InnerBlocks, getColorClassName } from '@wordpress/block-editor'; /** * Internal dependencies @@ -93,10 +90,23 @@ export default [ focalPoint, } = attributes; const mediaTypeRenders = { - image: () => <img src={ mediaUrl } alt={ mediaAlt } className={ ( mediaId && mediaType === 'image' ) ? `wp-image-${ mediaId }` : null } />, + image: () => ( + <img + src={ mediaUrl } + alt={ mediaAlt } + className={ + mediaId && mediaType === 'image' + ? `wp-image-${ mediaId }` + : null + } + /> + ), video: () => <video controls src={ mediaUrl } />, }; - const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + const backgroundClass = getColorClassName( + 'background-color', + backgroundColor + ); const className = classnames( { 'has-media-on-the-right': 'right' === mediaPosition, [ backgroundClass ]: backgroundClass, @@ -104,19 +114,29 @@ export default [ [ `is-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, 'is-image-fill': imageFill, } ); - const backgroundStyles = imageFill ? imageFillStyles( mediaUrl, focalPoint ) : {}; + const backgroundStyles = imageFill + ? imageFillStyles( mediaUrl, focalPoint ) + : {}; let gridTemplateColumns; if ( mediaWidth !== DEFAULT_MEDIA_WIDTH ) { - gridTemplateColumns = 'right' === mediaPosition ? `auto ${ mediaWidth }%` : `${ mediaWidth }% auto`; + gridTemplateColumns = + 'right' === mediaPosition + ? `auto ${ mediaWidth }%` + : `${ mediaWidth }% auto`; } const style = { - backgroundColor: backgroundClass ? undefined : customBackgroundColor, + backgroundColor: backgroundClass + ? undefined + : customBackgroundColor, gridTemplateColumns, }; return ( <div className={ className } style={ style }> - <figure className="wp-block-media-text__media" style={ backgroundStyles }> + <figure + className="wp-block-media-text__media" + style={ backgroundStyles } + > { ( mediaTypeRenders[ mediaType ] || noop )() } </figure> <div className="wp-block-media-text__content"> @@ -143,7 +163,10 @@ export default [ image: () => <img src={ mediaUrl } alt={ mediaAlt } />, video: () => <video controls src={ mediaUrl } />, }; - const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + const backgroundClass = getColorClassName( + 'background-color', + backgroundColor + ); const className = classnames( { 'has-media-on-the-right': 'right' === mediaPosition, [ backgroundClass ]: backgroundClass, @@ -152,15 +175,20 @@ export default [ let gridTemplateColumns; if ( mediaWidth !== DEFAULT_MEDIA_WIDTH ) { - gridTemplateColumns = 'right' === mediaPosition ? `auto ${ mediaWidth }%` : `${ mediaWidth }% auto`; + gridTemplateColumns = + 'right' === mediaPosition + ? `auto ${ mediaWidth }%` + : `${ mediaWidth }% auto`; } const style = { - backgroundColor: backgroundClass ? undefined : customBackgroundColor, + backgroundColor: backgroundClass + ? undefined + : customBackgroundColor, gridTemplateColumns, }; return ( <div className={ className } style={ style }> - <figure className="wp-block-media-text__media" > + <figure className="wp-block-media-text__media"> { ( mediaTypeRenders[ mediaType ] || noop )() } </figure> <div className="wp-block-media-text__content"> diff --git a/packages/block-library/src/media-text/edit.js b/packages/block-library/src/media-text/edit.js index 885c2b5abb8126..9aaeb3887135f6 100644 --- a/packages/block-library/src/media-text/edit.js +++ b/packages/block-library/src/media-text/edit.js @@ -37,11 +37,21 @@ import MediaContainer from './media-container'; * Constants */ const TEMPLATE = [ - [ 'core/paragraph', { fontSize: 'large', placeholder: _x( 'Content…', 'content placeholder' ) } ], + [ + 'core/paragraph', + { + fontSize: 'large', + placeholder: _x( 'Content…', 'content placeholder' ), + }, + ], ]; // this limits the resize to a safe zone to avoid making broken layouts const WIDTH_CONSTRAINT_PERCENTAGE = 15; -const applyWidthConstraints = ( width ) => Math.max( WIDTH_CONSTRAINT_PERCENTAGE, Math.min( width, 100 - WIDTH_CONSTRAINT_PERCENTAGE ) ); +const applyWidthConstraints = ( width ) => + Math.max( + WIDTH_CONSTRAINT_PERCENTAGE, + Math.min( width, 100 - WIDTH_CONSTRAINT_PERCENTAGE ) + ); const LINK_DESTINATION_MEDIA = 'media'; const LINK_DESTINATION_ATTACHMENT = 'attachment'; @@ -74,13 +84,21 @@ class MediaTextEdit extends Component { // video contain the media type of 'file' in the object returned from the rest api. mediaType = 'video'; } - } else { // for media selections originated from existing files in the media library. + } else { + // for media selections originated from existing files in the media library. mediaType = media.type; } if ( mediaType === 'image' ) { // Try the "large" size URL, falling back to the "full" size URL below. - src = get( media, [ 'sizes', 'large', 'url' ] ) || get( media, [ 'media_details', 'sizes', 'large', 'source_url' ] ); + src = + get( media, [ 'sizes', 'large', 'url' ] ) || + get( media, [ + 'media_details', + 'sizes', + 'large', + 'source_url', + ] ); } let newHref = href; @@ -129,14 +147,32 @@ class MediaTextEdit extends Component { renderMediaArea() { const { attributes } = this.props; - const { mediaAlt, mediaId, mediaPosition, mediaType, mediaUrl, mediaWidth, imageFill, focalPoint } = attributes; + const { + mediaAlt, + mediaId, + mediaPosition, + mediaType, + mediaUrl, + mediaWidth, + imageFill, + focalPoint, + } = attributes; return ( <MediaContainer className="block-library-media-text__media-container" onSelectMedia={ this.onSelectMedia } onWidthChange={ this.onWidthChange } commitWidthChange={ this.commitWidthChange } - { ...{ mediaAlt, mediaId, mediaType, mediaUrl, mediaPosition, mediaWidth, imageFill, focalPoint } } + { ...{ + mediaAlt, + mediaId, + mediaType, + mediaUrl, + mediaPosition, + mediaWidth, + imageFill, + focalPoint, + } } /> ); } @@ -172,35 +208,43 @@ class MediaTextEdit extends Component { const classNames = classnames( className, { 'has-media-on-the-right': 'right' === mediaPosition, 'is-selected': isSelected, - 'has-background': ( backgroundColor.class || backgroundColor.color ), + 'has-background': backgroundColor.class || backgroundColor.color, [ backgroundColor.class ]: backgroundColor.class, 'is-stacked-on-mobile': isStackedOnMobile, [ `is-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, 'is-image-fill': imageFill, } ); const widthString = `${ temporaryMediaWidth || mediaWidth }%`; - const gridTemplateColumns = 'right' === mediaPosition ? `1fr ${ widthString }` : `${ widthString } 1fr`; + const gridTemplateColumns = + 'right' === mediaPosition + ? `1fr ${ widthString }` + : `${ widthString } 1fr`; const style = { gridTemplateColumns, msGridColumns: gridTemplateColumns, backgroundColor: backgroundColor.color, }; - const colorSettings = [ { - value: backgroundColor.color, - onChange: setBackgroundColor, - label: __( 'Background Color' ), - } ]; - const toolbarControls = [ { - icon: 'align-pull-left', - title: __( 'Show media on left' ), - isActive: mediaPosition === 'left', - onClick: () => setAttributes( { mediaPosition: 'left' } ), - }, { - icon: 'align-pull-right', - title: __( 'Show media on right' ), - isActive: mediaPosition === 'right', - onClick: () => setAttributes( { mediaPosition: 'right' } ), - } ]; + const colorSettings = [ + { + value: backgroundColor.color, + onChange: setBackgroundColor, + label: __( 'Background Color' ), + }, + ]; + const toolbarControls = [ + { + icon: 'align-pull-left', + title: __( 'Show media on left' ), + isActive: mediaPosition === 'left', + onClick: () => setAttributes( { mediaPosition: 'left' } ), + }, + { + icon: 'align-pull-right', + title: __( 'Show media on right' ), + isActive: mediaPosition === 'right', + onClick: () => setAttributes( { mediaPosition: 'right' } ), + }, + ]; const onMediaAltChange = ( newMediaAlt ) => { setAttributes( { mediaAlt: newMediaAlt } ); }; @@ -212,36 +256,52 @@ class MediaTextEdit extends Component { <ToggleControl label={ __( 'Stack on mobile' ) } checked={ isStackedOnMobile } - onChange={ () => setAttributes( { - isStackedOnMobile: ! isStackedOnMobile, - } ) } - /> - { mediaType === 'image' && ( <ToggleControl - label={ __( 'Crop image to fill entire column' ) } - checked={ imageFill } - onChange={ () => setAttributes( { - imageFill: ! imageFill, - } ) } - /> ) } - { imageFill && ( <FocalPointPicker - label={ __( 'Focal Point Picker' ) } - url={ mediaUrl } - value={ focalPoint } - onChange={ ( value ) => setAttributes( { focalPoint: value } ) } - /> ) } - { mediaType === 'image' && ( <TextareaControl - label={ __( 'Alt Text (Alternative Text)' ) } - value={ mediaAlt } - onChange={ onMediaAltChange } - help={ - <> - <ExternalLink href="https://www.w3.org/WAI/tutorials/images/decision-tree"> - { __( 'Describe the purpose of the image' ) } - </ExternalLink> - { __( 'Leave empty if the image is purely decorative.' ) } - </> + onChange={ () => + setAttributes( { + isStackedOnMobile: ! isStackedOnMobile, + } ) } - /> ) } + /> + { mediaType === 'image' && ( + <ToggleControl + label={ __( 'Crop image to fill entire column' ) } + checked={ imageFill } + onChange={ () => + setAttributes( { + imageFill: ! imageFill, + } ) + } + /> + ) } + { imageFill && ( + <FocalPointPicker + label={ __( 'Focal Point Picker' ) } + url={ mediaUrl } + value={ focalPoint } + onChange={ ( value ) => + setAttributes( { focalPoint: value } ) + } + /> + ) } + { mediaType === 'image' && ( + <TextareaControl + label={ __( 'Alt Text (Alternative Text)' ) } + value={ mediaAlt } + onChange={ onMediaAltChange } + help={ + <> + <ExternalLink href="https://www.w3.org/WAI/tutorials/images/decision-tree"> + { __( + 'Describe the purpose of the image' + ) } + </ExternalLink> + { __( + 'Leave empty if the image is purely decorative.' + ) } + </> + } + /> + ) } </PanelBody> ); @@ -256,28 +316,28 @@ class MediaTextEdit extends Component { /> </InspectorControls> <BlockControls> - <ToolbarGroup - controls={ toolbarControls } - /> + <ToolbarGroup controls={ toolbarControls } /> <BlockVerticalAlignmentToolbar onChange={ onVerticalAlignmentChange } value={ verticalAlignment } /> - { mediaType === 'image' && ( <ToolbarGroup> - <ImageURLInputUI - url={ href || '' } - onChangeUrl={ this.onSetHref } - linkDestination={ linkDestination } - mediaType={ mediaType } - mediaUrl={ image && image.source_url } - mediaLink={ image && image.link } - linkTarget={ linkTarget } - linkClass={ linkClass } - rel={ rel } - /> - </ToolbarGroup> ) } + { mediaType === 'image' && ( + <ToolbarGroup> + <ImageURLInputUI + url={ href || '' } + onChangeUrl={ this.onSetHref } + linkDestination={ linkDestination } + mediaType={ mediaType } + mediaUrl={ image && image.source_url } + mediaLink={ image && image.link } + linkTarget={ linkTarget } + linkClass={ linkClass } + rel={ rel } + /> + </ToolbarGroup> + ) } </BlockControls> - <div className={ classNames } style={ style } > + <div className={ classNames } style={ style }> { this.renderMediaArea() } <InnerBlocks template={ TEMPLATE } @@ -293,7 +353,10 @@ export default compose( [ withColors( 'backgroundColor' ), withSelect( ( select, props ) => { const { getMedia } = select( 'core' ); - const { attributes: { mediaId }, isSelected } = props; + const { + attributes: { mediaId }, + isSelected, + } = props; return { image: mediaId && isSelected ? getMedia( mediaId ) : null, }; diff --git a/packages/block-library/src/media-text/edit.native.js b/packages/block-library/src/media-text/edit.native.js index 4dce710341d206..fbba05e96405cd 100644 --- a/packages/block-library/src/media-text/edit.native.js +++ b/packages/block-library/src/media-text/edit.native.js @@ -15,9 +15,7 @@ import { withColors, } from '@wordpress/block-editor'; import { Component } from '@wordpress/element'; -import { - ToolbarGroup, -} from '@wordpress/components'; +import { ToolbarGroup } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; @@ -30,16 +28,23 @@ import styles from './style.scss'; /** * Constants */ -const ALLOWED_BLOCKS = [ 'core/button', 'core/paragraph', 'core/heading', 'core/list' ]; -const TEMPLATE = [ - [ 'core/paragraph' ], +const ALLOWED_BLOCKS = [ + 'core/button', + 'core/paragraph', + 'core/heading', + 'core/list', ]; +const TEMPLATE = [ [ 'core/paragraph' ] ]; // this limits the resize to a safe zone to avoid making broken layouts const WIDTH_CONSTRAINT_PERCENTAGE = 15; const BREAKPOINTS = { mobile: 480, }; -const applyWidthConstraints = ( width ) => Math.max( WIDTH_CONSTRAINT_PERCENTAGE, Math.min( width, 100 - WIDTH_CONSTRAINT_PERCENTAGE ) ); +const applyWidthConstraints = ( width ) => + Math.max( + WIDTH_CONSTRAINT_PERCENTAGE, + Math.min( width, 100 - WIDTH_CONSTRAINT_PERCENTAGE ) + ); class MediaTextEdit extends Component { constructor() { @@ -71,13 +76,21 @@ class MediaTextEdit extends Component { // video contain the media type of 'file' in the object returned from the rest api. mediaType = 'video'; } - } else { // for media selections originated from existing files in the media library. + } else { + // for media selections originated from existing files in the media library. mediaType = media.type; } if ( mediaType === 'image' && media.sizes ) { // Try the "large" size URL, falling back to the "full" size URL below. - src = get( media, [ 'sizes', 'large', 'url' ] ) || get( media, [ 'media_details', 'sizes', 'large', 'source_url' ] ); + src = + get( media, [ 'sizes', 'large', 'url' ] ) || + get( media, [ + 'media_details', + 'sizes', + 'large', + 'source_url', + ] ); } setAttributes( { @@ -131,7 +144,16 @@ class MediaTextEdit extends Component { renderMediaArea() { const { attributes, isSelected } = this.props; - const { mediaAlt, mediaId, mediaPosition, mediaType, mediaUrl, mediaWidth, imageFill, focalPoint } = attributes; + const { + mediaAlt, + mediaId, + mediaPosition, + mediaType, + mediaUrl, + mediaWidth, + imageFill, + focalPoint, + } = attributes; return ( <MediaContainer @@ -140,7 +162,17 @@ class MediaTextEdit extends Component { onWidthChange={ this.onWidthChange } commitWidthChange={ this.commitWidthChange } onFocus={ this.props.onFocus } - { ...{ mediaAlt, mediaId, mediaType, mediaUrl, mediaPosition, mediaWidth, imageFill, focalPoint, isSelected } } + { ...{ + mediaAlt, + mediaId, + mediaType, + mediaUrl, + mediaPosition, + mediaWidth, + imageFill, + focalPoint, + isSelected, + } } /> ); } @@ -160,13 +192,13 @@ class MediaTextEdit extends Component { mediaWidth, verticalAlignment, } = attributes; - const { - containerWidth, - } = this.state; + const { containerWidth } = this.state; const isMobile = containerWidth < BREAKPOINTS.mobile; const shouldStack = isStackedOnMobile && isMobile; - const temporaryMediaWidth = shouldStack ? 100 : ( this.state.mediaWidth || mediaWidth ); + const temporaryMediaWidth = shouldStack + ? 100 + : this.state.mediaWidth || mediaWidth; const widthString = `${ temporaryMediaWidth }%`; const innerBlockContainerStyle = ! shouldStack && { @@ -176,30 +208,41 @@ class MediaTextEdit extends Component { }; const containerStyles = { ...styles[ 'wp-block-media-text' ], - ...styles[ `is-vertically-aligned-${ verticalAlignment || 'center' }` ], - ...( mediaPosition === 'right' ? styles[ 'has-media-on-the-right' ] : {} ), + ...styles[ + `is-vertically-aligned-${ verticalAlignment || 'center' }` + ], + ...( mediaPosition === 'right' + ? styles[ 'has-media-on-the-right' ] + : {} ), ...( shouldStack ? styles[ 'is-stacked-on-mobile' ] : {} ), - ...( shouldStack && mediaPosition === 'right' ? styles[ 'is-stacked-on-mobile.has-media-on-the-right' ] : {} ), + ...( shouldStack && mediaPosition === 'right' + ? styles[ 'is-stacked-on-mobile.has-media-on-the-right' ] + : {} ), backgroundColor: backgroundColor.color, }; - const innerBlockWidth = shouldStack ? 100 : ( 100 - temporaryMediaWidth ); + const innerBlockWidth = shouldStack ? 100 : 100 - temporaryMediaWidth; const innerBlockWidthString = `${ innerBlockWidth }%`; const mediaContainerStyle = { - ...( isParentSelected || isAncestorSelected ? styles.denseMediaPadding : styles.regularMediaPadding ), + ...( isParentSelected || isAncestorSelected + ? styles.denseMediaPadding + : styles.regularMediaPadding ), ...( isSelected && styles.innerPadding ), }; - const toolbarControls = [ { - icon: 'align-pull-left', - title: __( 'Show media on left' ), - isActive: mediaPosition === 'left', - onClick: () => setAttributes( { mediaPosition: 'left' } ), - }, { - icon: 'align-pull-right', - title: __( 'Show media on right' ), - isActive: mediaPosition === 'right', - onClick: () => setAttributes( { mediaPosition: 'right' } ), - } ]; + const toolbarControls = [ + { + icon: 'align-pull-left', + title: __( 'Show media on left' ), + isActive: mediaPosition === 'left', + onClick: () => setAttributes( { mediaPosition: 'left' } ), + }, + { + icon: 'align-pull-right', + title: __( 'Show media on right' ), + isActive: mediaPosition === 'right', + onClick: () => setAttributes( { mediaPosition: 'right' } ), + }, + ]; const onVerticalAlignmentChange = ( alignment ) => { setAttributes( { verticalAlignment: alignment } ); @@ -208,20 +251,28 @@ class MediaTextEdit extends Component { return ( <> <BlockControls> - <ToolbarGroup - controls={ toolbarControls } - /> + <ToolbarGroup controls={ toolbarControls } /> <BlockVerticalAlignmentToolbar onChange={ onVerticalAlignmentChange } value={ verticalAlignment } isCollapsed={ false } /> </BlockControls> - <View style={ containerStyles } onLayout={ this.onLayoutChange }> - <View style={ { width: widthString, ...mediaContainerStyle } } > + <View + style={ containerStyles } + onLayout={ this.onLayoutChange } + > + <View + style={ { width: widthString, ...mediaContainerStyle } } + > { this.renderMediaArea() } </View> - <View style={ { width: innerBlockWidthString, ...innerBlockContainerStyle } }> + <View + style={ { + width: innerBlockWidthString, + ...innerBlockContainerStyle, + } } + > <InnerBlocks allowedBlocks={ ALLOWED_BLOCKS } template={ TEMPLATE } @@ -246,13 +297,16 @@ export default compose( const parents = getBlockParents( clientId, true ); const selectedBlockClientId = getSelectedBlockClientId(); - const isParentSelected = selectedBlockClientId && selectedBlockClientId === getBlockRootClientId( clientId ); - const isAncestorSelected = selectedBlockClientId && parents.includes( selectedBlockClientId ); + const isParentSelected = + selectedBlockClientId && + selectedBlockClientId === getBlockRootClientId( clientId ); + const isAncestorSelected = + selectedBlockClientId && parents.includes( selectedBlockClientId ); return { isSelected: selectedBlockClientId === clientId, isParentSelected, isAncestorSelected, }; - } ), + } ) )( MediaTextEdit ); diff --git a/packages/block-library/src/media-text/icon-retry.native.js b/packages/block-library/src/media-text/icon-retry.native.js index bdd40f69efd64a..580cd1fff188f5 100644 --- a/packages/block-library/src/media-text/icon-retry.native.js +++ b/packages/block-library/src/media-text/icon-retry.native.js @@ -3,4 +3,9 @@ */ import { Path, SVG } from '@wordpress/components'; -export default <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" /><Path d="M0 0h24v24H0z" fill={ 'none' } /></SVG>; +export default ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" /> + <Path d="M0 0h24v24H0z" fill={ 'none' } /> + </SVG> +); diff --git a/packages/block-library/src/media-text/index.js b/packages/block-library/src/media-text/index.js index 7edb9e390d12e2..97996b6a8bb5e9 100644 --- a/packages/block-library/src/media-text/index.js +++ b/packages/block-library/src/media-text/index.js @@ -29,13 +29,16 @@ export const settings = { example: { attributes: { mediaType: 'image', - mediaUrl: 'https://s.w.org/images/core/5.3/Biologia_Centrali-Americana_-_Cantorchilus_semibadius_1902.jpg', + mediaUrl: + 'https://s.w.org/images/core/5.3/Biologia_Centrali-Americana_-_Cantorchilus_semibadius_1902.jpg', }, innerBlocks: [ { name: 'core/paragraph', attributes: { - content: __( 'The wren<br>Earns his living<br>Noiselessly.' ), + content: __( + 'The wren<br>Earns his living<br>Noiselessly.' + ), }, }, { diff --git a/packages/block-library/src/media-text/media-container-icon.js b/packages/block-library/src/media-text/media-container-icon.js index 6e6de30b7a61d5..e5eb65d261dc58 100644 --- a/packages/block-library/src/media-text/media-container-icon.js +++ b/packages/block-library/src/media-text/media-container-icon.js @@ -3,4 +3,10 @@ */ import { Path, SVG } from '@wordpress/components'; -export default <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M18 2l2 4h-2l-2-4h-3l2 4h-2l-2-4h-1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V2zm2 12H10V4.4L11.8 8H20z" /><Path d="M14 20H4V10h3V8H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-3h-2z" /><Path d="M5 19h8l-1.59-2H9.24l-.84 1.1L7 16.3 5 19z" /></SVG>; +export default ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M18 2l2 4h-2l-2-4h-3l2 4h-2l-2-4h-1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V2zm2 12H10V4.4L11.8 8H20z" /> + <Path d="M14 20H4V10h3V8H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-3h-2z" /> + <Path d="M5 19h8l-1.59-2H9.24l-.84 1.1L7 16.3 5 19z" /> + </SVG> +); diff --git a/packages/block-library/src/media-text/media-container.js b/packages/block-library/src/media-text/media-container.js index 86ceed8a09e265..2d0d2c13304ed9 100644 --- a/packages/block-library/src/media-text/media-container.js +++ b/packages/block-library/src/media-text/media-container.js @@ -24,12 +24,14 @@ import icon from './media-container-icon'; const ALLOWED_MEDIA_TYPES = [ 'image', 'video' ]; export function imageFillStyles( url, focalPoint ) { - return url ? - { - backgroundImage: `url(${ url })`, - backgroundPosition: focalPoint ? `${ focalPoint.x * 100 }% ${ focalPoint.y * 100 }%` : `50% 50%`, - } : - {}; + return url + ? { + backgroundImage: `url(${ url })`, + backgroundPosition: focalPoint + ? `${ focalPoint.x * 100 }% ${ focalPoint.y * 100 }%` + : `50% 50%`, + } + : {}; } class MediaContainer extends Component { @@ -59,8 +61,16 @@ class MediaContainer extends Component { } renderImage() { - const { mediaAlt, mediaUrl, className, imageFill, focalPoint } = this.props; - const backgroundStyles = imageFill ? imageFillStyles( mediaUrl, focalPoint ) : {}; + const { + mediaAlt, + mediaUrl, + className, + imageFill, + focalPoint, + } = this.props; + const backgroundStyles = imageFill + ? imageFillStyles( mediaUrl, focalPoint ) + : {}; return ( <> { this.renderToolbarEditButton() } @@ -102,7 +112,15 @@ class MediaContainer extends Component { } render() { - const { mediaPosition, mediaUrl, mediaType, mediaWidth, commitWidthChange, onWidthChange, toggleSelection } = this.props; + const { + mediaPosition, + mediaUrl, + mediaType, + mediaWidth, + commitWidthChange, + onWidthChange, + toggleSelection, + } = this.props; if ( mediaType && mediaUrl ) { const onResizeStart = () => { toggleSelection( false ); diff --git a/packages/block-library/src/media-text/media-container.native.js b/packages/block-library/src/media-text/media-container.native.js index 46cc2ad61e3728..515b66c4906810 100644 --- a/packages/block-library/src/media-text/media-container.native.js +++ b/packages/block-library/src/media-text/media-container.native.js @@ -1,7 +1,12 @@ /** * External dependencies */ -import { View, ImageBackground, Text, TouchableWithoutFeedback } from 'react-native'; +import { + View, + ImageBackground, + Text, + TouchableWithoutFeedback, +} from 'react-native'; import { mediaUploadSync, requestImageFailedRetryDialog, @@ -11,12 +16,7 @@ import { /** * WordPress dependencies */ -import { - Icon, - Button, - ToolbarGroup, - withNotices, -} from '@wordpress/components'; +import { Icon, Button, ToolbarGroup, withNotices } from '@wordpress/components'; import { BlockControls, MEDIA_TYPE_IMAGE, @@ -51,10 +51,16 @@ class MediaContainer extends Component { super( ...arguments ); this.onUploadError = this.onUploadError.bind( this ); this.updateMediaProgress = this.updateMediaProgress.bind( this ); - this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this ); - this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( this ); + this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( + this + ); + this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( + this + ); this.mediaUploadStateReset = this.mediaUploadStateReset.bind( this ); - this.onSelectMediaUploadOption = this.onSelectMediaUploadOption.bind( this ); + this.onSelectMediaUploadOption = this.onSelectMediaUploadOption.bind( + this + ); this.onMediaPressed = this.onMediaPressed.bind( this ); this.state = { @@ -101,10 +107,19 @@ class MediaContainer extends Component { getIcon( isRetryIcon, isVideo ) { if ( isRetryIcon ) { - return <Icon icon={ SvgIconRetry } { ...( styles.iconRetry, isVideo ? styles.iconRetryVideo : {} ) } />; + return ( + <Icon + icon={ SvgIconRetry } + { ...( styles.iconRetry, + isVideo ? styles.iconRetryVideo : {} ) } + /> + ); } - const iconStyle = this.props.getStylesFromColorScheme( styles.icon, styles.iconDark ); + const iconStyle = this.props.getStylesFromColorScheme( + styles.icon, + styles.iconDark + ); return <Icon icon={ icon } { ...iconStyle } />; } @@ -153,10 +168,18 @@ class MediaContainer extends Component { renderImage( params, openMediaOptions ) { const { isUploadInProgress } = this.state; const { mediaAlt, mediaUrl, isSelected } = this.props; - const { finalWidth, finalHeight, imageWidthWithinContainer, isUploadFailed, retryMessage } = params; + const { + finalWidth, + finalHeight, + imageWidthWithinContainer, + isUploadFailed, + retryMessage, + } = params; const opacity = isUploadInProgress ? 0.3 : 1; - const contentStyle = ! imageWidthWithinContainer ? styles.content : styles.contentCentered; + const contentStyle = ! imageWidthWithinContainer + ? styles.content + : styles.contentCentered; return ( <TouchableWithoutFeedback @@ -166,28 +189,42 @@ class MediaContainer extends Component { disabled={ ! isSelected } > <View style={ contentStyle }> - { ! imageWidthWithinContainer && + { ! imageWidthWithinContainer && ( <View style={ styles.imageContainer }> { this.getIcon( false ) } - </View> } + </View> + ) } <ImageBackground accessible={ true } accessibilityLabel={ mediaAlt } - accessibilityHint={ __( 'Double tap and hold to edit' ) } + accessibilityHint={ __( + 'Double tap and hold to edit' + ) } accessibilityRole={ 'imagebutton' } - style={ { width: finalWidth, height: finalHeight, opacity } } + style={ { + width: finalWidth, + height: finalHeight, + opacity, + } } resizeMethod="scale" source={ { uri: mediaUrl } } key={ mediaUrl } > - { isUploadFailed && - <View style={ [ styles.imageContainer, styles.uploadFailed ] }> + { isUploadFailed && ( + <View + style={ [ + styles.imageContainer, + styles.uploadFailed, + ] } + > <View style={ styles.modalIcon }> { this.getIcon( isUploadFailed ) } </View> - <Text style={ styles.uploadFailedText }>{ retryMessage }</Text> + <Text style={ styles.uploadFailedText }> + { retryMessage } + </Text> </View> - } + ) } </ImageBackground> </View> </TouchableWithoutFeedback> @@ -198,7 +235,8 @@ class MediaContainer extends Component { const { mediaUrl, isSelected } = this.props; const { isUploadInProgress } = this.state; const { isUploadFailed, retryMessage } = params; - const showVideo = isURL( mediaUrl ) && ! isUploadInProgress && ! isUploadFailed; + const showVideo = + isURL( mediaUrl ) && ! isUploadInProgress && ! isUploadFailed; return ( <TouchableWithoutFeedback @@ -208,7 +246,7 @@ class MediaContainer extends Component { disabled={ ! isSelected } > <View aspectRatio={ VIDEO_ASPECT_RATIO }> - { showVideo && + { showVideo && ( <View style={ styles.videoContainer }> <VideoPlayer isSelected={ isSelected } @@ -217,15 +255,26 @@ class MediaContainer extends Component { paused={ true } /> </View> - } - { ! showVideo && + ) } + { ! showVideo && ( <View style={ styles.videoPlaceholder }> - <View style={ styles.modalIcon } > - { isUploadFailed ? this.getIcon( isUploadFailed ) : this.getIcon( false ) } + <View style={ styles.modalIcon }> + { isUploadFailed + ? this.getIcon( isUploadFailed ) + : this.getIcon( false ) } </View> - { isUploadFailed && <Text style={ [ styles.uploadFailedText, styles.uploadFailedTextVideo ] }>{ retryMessage }</Text> } + { isUploadFailed && ( + <Text + style={ [ + styles.uploadFailedText, + styles.uploadFailedTextVideo, + ] } + > + { retryMessage } + </Text> + ) } </View> - } + ) } </View> </TouchableWithoutFeedback> ); @@ -281,14 +330,25 @@ class MediaContainer extends Component { <MediaUploadProgress coverUrl={ coverUrl } mediaId={ mediaId } - onUpdateMediaProgress={ this.updateMediaProgress } - onFinishMediaUploadWithSuccess={ this.finishMediaUploadWithSuccess } - onFinishMediaUploadWithFailure={ this.finishMediaUploadWithFailure } - onMediaUploadStateReset={ this.mediaUploadStateReset } + onUpdateMediaProgress={ + this.updateMediaProgress + } + onFinishMediaUploadWithSuccess={ + this.finishMediaUploadWithSuccess + } + onFinishMediaUploadWithFailure={ + this.finishMediaUploadWithFailure + } + onMediaUploadStateReset={ + this.mediaUploadStateReset + } renderContent={ ( params ) => { return ( <View style={ styles.content }> - { this.renderContent( params, open ) } + { this.renderContent( + params, + open + ) } </View> ); } } @@ -306,5 +366,5 @@ class MediaContainer extends Component { export default compose( withNotices, - withPreferredColorScheme, + withPreferredColorScheme )( MediaContainer ); diff --git a/packages/block-library/src/media-text/save.js b/packages/block-library/src/media-text/save.js index ac56a0f5babdcc..12a39e2d45f0a4 100644 --- a/packages/block-library/src/media-text/save.js +++ b/packages/block-library/src/media-text/save.js @@ -7,10 +7,7 @@ import { noop, isEmpty } from 'lodash'; /** * WordPress dependencies */ -import { - InnerBlocks, - getColorClassName, -} from '@wordpress/block-editor'; +import { InnerBlocks, getColorClassName } from '@wordpress/block-editor'; /** * Internal dependencies @@ -40,11 +37,17 @@ export default function save( { attributes } ) { } = attributes; const newRel = isEmpty( rel ) ? undefined : rel; - let image = <img - src={ mediaUrl } - alt={ mediaAlt } - className={ ( mediaId && mediaType === 'image' ) ? `wp-image-${ mediaId }` : null } - />; + let image = ( + <img + src={ mediaUrl } + alt={ mediaAlt } + className={ + mediaId && mediaType === 'image' + ? `wp-image-${ mediaId }` + : null + } + /> + ); if ( href ) { image = ( @@ -63,20 +66,28 @@ export default function save( { attributes } ) { image: () => image, video: () => <video controls src={ mediaUrl } />, }; - const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + const backgroundClass = getColorClassName( + 'background-color', + backgroundColor + ); const className = classnames( { 'has-media-on-the-right': 'right' === mediaPosition, - 'has-background': ( backgroundClass || customBackgroundColor ), + 'has-background': backgroundClass || customBackgroundColor, [ backgroundClass ]: backgroundClass, 'is-stacked-on-mobile': isStackedOnMobile, [ `is-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment, 'is-image-fill': imageFill, } ); - const backgroundStyles = imageFill ? imageFillStyles( mediaUrl, focalPoint ) : {}; + const backgroundStyles = imageFill + ? imageFillStyles( mediaUrl, focalPoint ) + : {}; let gridTemplateColumns; if ( mediaWidth !== DEFAULT_MEDIA_WIDTH ) { - gridTemplateColumns = 'right' === mediaPosition ? `auto ${ mediaWidth }%` : `${ mediaWidth }% auto`; + gridTemplateColumns = + 'right' === mediaPosition + ? `auto ${ mediaWidth }%` + : `${ mediaWidth }% auto`; } const style = { backgroundColor: backgroundClass ? undefined : customBackgroundColor, @@ -84,7 +95,10 @@ export default function save( { attributes } ) { }; return ( <div className={ className } style={ style }> - <figure className="wp-block-media-text__media" style={ backgroundStyles }> + <figure + className="wp-block-media-text__media" + style={ backgroundStyles } + > { ( mediaTypeRenders[ mediaType ] || noop )() } </figure> <div className="wp-block-media-text__content"> diff --git a/packages/block-library/src/media-text/transforms.js b/packages/block-library/src/media-text/transforms.js index f14246bed01b6e..9c884ab2181cb7 100644 --- a/packages/block-library/src/media-text/transforms.js +++ b/packages/block-library/src/media-text/transforms.js @@ -8,25 +8,23 @@ const transforms = { { type: 'block', blocks: [ 'core/image' ], - transform: ( { alt, url, id } ) => ( + transform: ( { alt, url, id } ) => createBlock( 'core/media-text', { mediaAlt: alt, mediaId: id, mediaUrl: url, mediaType: 'image', - } ) - ), + } ), }, { type: 'block', blocks: [ 'core/video' ], - transform: ( { src, id } ) => ( + transform: ( { src, id } ) => createBlock( 'core/media-text', { mediaId: id, mediaUrl: src, mediaType: 'video', - } ) - ), + } ), }, ], to: [ diff --git a/packages/block-library/src/missing/edit.js b/packages/block-library/src/missing/edit.js index 15d8a33946d422..b9caa890a5b950 100644 --- a/packages/block-library/src/missing/edit.js +++ b/packages/block-library/src/missing/edit.js @@ -17,7 +17,9 @@ function MissingBlockWarning( { attributes, convertToHTML } ) { let messageHTML; if ( hasContent && hasHTMLBlock ) { messageHTML = sprintf( - __( 'Your site doesn’t include support for the "%s" block. You can leave this block intact, convert its content to a Custom HTML block, or remove it entirely.' ), + __( + 'Your site doesn’t include support for the "%s" block. You can leave this block intact, convert its content to a Custom HTML block, or remove it entirely.' + ), originalName ); actions.push( @@ -27,16 +29,16 @@ function MissingBlockWarning( { attributes, convertToHTML } ) { ); } else { messageHTML = sprintf( - __( 'Your site doesn’t include support for the "%s" block. You can leave this block intact or remove it entirely.' ), + __( + 'Your site doesn’t include support for the "%s" block. You can leave this block intact or remove it entirely.' + ), originalName ); } return ( <> - <Warning actions={ actions }> - { messageHTML } - </Warning> + <Warning actions={ actions }>{ messageHTML }</Warning> <RawHTML>{ originalUndelimitedContent }</RawHTML> </> ); @@ -46,9 +48,12 @@ const MissingEdit = withDispatch( ( dispatch, { clientId, attributes } ) => { const { replaceBlock } = dispatch( 'core/block-editor' ); return { convertToHTML() { - replaceBlock( clientId, createBlock( 'core/html', { - content: attributes.originalUndelimitedContent, - } ) ); + replaceBlock( + clientId, + createBlock( 'core/html', { + content: attributes.originalUndelimitedContent, + } ) + ); }, }; } )( MissingBlockWarning ); diff --git a/packages/block-library/src/missing/edit.native.js b/packages/block-library/src/missing/edit.native.js index 92013b3032df0d..e06d7f5899a039 100644 --- a/packages/block-library/src/missing/edit.native.js +++ b/packages/block-library/src/missing/edit.native.js @@ -31,7 +31,10 @@ export class UnsupportedBlockEdit extends Component { } renderHelpIcon() { - const infoIconStyle = this.props.getStylesFromColorScheme( styles.infoIcon, styles.infoIconDark ); + const infoIconStyle = this.props.getStylesFromColorScheme( + styles.infoIcon, + styles.infoIconDark + ); return ( <TouchableWithoutFeedback @@ -40,7 +43,7 @@ export class UnsupportedBlockEdit extends Component { accessibilityHint={ __( 'Tap here to show help' ) } onPress={ this.toggleSheet.bind( this ) } > - <View style={ styles.helpIconContainer } > + <View style={ styles.helpIconContainer }> <Icon className="unsupported-icon-help" label={ __( 'Help icon' ) } @@ -54,18 +57,29 @@ export class UnsupportedBlockEdit extends Component { renderSheet( title ) { const { getStylesFromColorScheme } = this.props; - const infoTextStyle = getStylesFromColorScheme( styles.infoText, styles.infoTextDark ); - const infoTitleStyle = getStylesFromColorScheme( styles.infoTitle, styles.infoTitleDark ); - const infoDescriptionStyle = getStylesFromColorScheme( styles.infoDescription, styles.infoDescriptionDark ); - const infoSheetIconStyle = getStylesFromColorScheme( styles.infoSheetIcon, styles.infoSheetIconDark ); + const infoTextStyle = getStylesFromColorScheme( + styles.infoText, + styles.infoTextDark + ); + const infoTitleStyle = getStylesFromColorScheme( + styles.infoTitle, + styles.infoTitleDark + ); + const infoDescriptionStyle = getStylesFromColorScheme( + styles.infoDescription, + styles.infoDescriptionDark + ); + const infoSheetIconStyle = getStylesFromColorScheme( + styles.infoSheetIcon, + styles.infoSheetIconDark + ); // translators: %s: Name of the block - const titleFormat = Platform.OS === 'android' ? __( '\'%s\' isn\'t yet supported on WordPress for Android' ) : - __( '\'%s\' isn\'t yet supported on WordPress for iOS' ); - const infoTitle = sprintf( - titleFormat, - title, - ); + const titleFormat = + Platform.OS === 'android' + ? __( "'%s' isn't yet supported on WordPress for Android" ) + : __( "'%s' isn't yet supported on WordPress for iOS" ); + const infoTitle = sprintf( titleFormat, title ); return ( <BottomSheet @@ -73,13 +87,19 @@ export class UnsupportedBlockEdit extends Component { hideHeader onClose={ this.toggleSheet.bind( this ) } > - <View style={ styles.infoContainer } > - <Icon icon="editor-help" color={ infoSheetIconStyle.color } size={ styles.infoSheetIcon.size } /> + <View style={ styles.infoContainer }> + <Icon + icon="editor-help" + color={ infoSheetIconStyle.color } + size={ styles.infoSheetIcon.size } + /> <Text style={ [ infoTextStyle, infoTitleStyle ] }> { infoTitle } </Text> <Text style={ [ infoTextStyle, infoDescriptionStyle ] }> - { __( 'We are working hard to add more blocks with each release. In the meantime, you can also edit this post on the web.' ) } + { __( + 'We are working hard to add more blocks with each release. In the meantime, you can also edit this post on the web.' + ) } </Text> </View> </BottomSheet> @@ -92,18 +112,40 @@ export class UnsupportedBlockEdit extends Component { const blockType = coreBlocks[ originalName ]; const title = blockType ? blockType.settings.title : originalName; - const titleStyle = getStylesFromColorScheme( styles.unsupportedBlockMessage, styles.unsupportedBlockMessageDark ); + const titleStyle = getStylesFromColorScheme( + styles.unsupportedBlockMessage, + styles.unsupportedBlockMessageDark + ); - const subTitleStyle = getStylesFromColorScheme( styles.unsupportedBlockSubtitle, styles.unsupportedBlockSubtitleDark ); - const subtitle = <Text style={ subTitleStyle }>{ __( 'Unsupported' ) }</Text>; + const subTitleStyle = getStylesFromColorScheme( + styles.unsupportedBlockSubtitle, + styles.unsupportedBlockSubtitleDark + ); + const subtitle = ( + <Text style={ subTitleStyle }>{ __( 'Unsupported' ) }</Text> + ); - const icon = blockType ? normalizeIconObject( blockType.settings.icon ) : 'admin-plugins'; - const iconStyle = getStylesFromColorScheme( styles.unsupportedBlockIcon, styles.unsupportedBlockIconDark ); + const icon = blockType + ? normalizeIconObject( blockType.settings.icon ) + : 'admin-plugins'; + const iconStyle = getStylesFromColorScheme( + styles.unsupportedBlockIcon, + styles.unsupportedBlockIconDark + ); const iconClassName = 'unsupported-icon' + '-' + preferredColorScheme; return ( - <View style={ getStylesFromColorScheme( styles.unsupportedBlock, styles.unsupportedBlockDark ) }> + <View + style={ getStylesFromColorScheme( + styles.unsupportedBlock, + styles.unsupportedBlockDark + ) } + > { this.renderHelpIcon() } - <Icon className={ iconClassName } icon={ icon && icon.src ? icon.src : icon } color={ iconStyle.color } /> + <Icon + className={ iconClassName } + icon={ icon && icon.src ? icon.src : icon } + color={ iconStyle.color } + /> <Text style={ titleStyle }>{ title }</Text> { subtitle } { this.renderSheet( title ) } diff --git a/packages/block-library/src/missing/index.js b/packages/block-library/src/missing/index.js index eb0205046d5446..d399dc470946c2 100644 --- a/packages/block-library/src/missing/index.js +++ b/packages/block-library/src/missing/index.js @@ -30,7 +30,9 @@ export const settings = { if ( context === 'accessibility' ) { const { originalName } = attributes; - const originalBlockType = originalName ? getBlockType( originalName ) : undefined; + const originalBlockType = originalName + ? getBlockType( originalName ) + : undefined; if ( originalBlockType ) { return originalBlockType.settings.title || originalName; diff --git a/packages/block-library/src/missing/test/edit.native.js b/packages/block-library/src/missing/test/edit.native.js index 1bbfc0f342151d..932bbabb18274b 100644 --- a/packages/block-library/src/missing/test/edit.native.js +++ b/packages/block-library/src/missing/test/edit.native.js @@ -20,7 +20,9 @@ const defaultAttributes = { }; const getTestComponentWithContent = ( attributes = defaultAttributes ) => { - return renderer.create( <UnsupportedBlockEdit attributes={ attributes } /> ); + return renderer.create( + <UnsupportedBlockEdit attributes={ attributes } /> + ); }; describe( 'Missing block', () => { @@ -54,7 +56,12 @@ describe( 'Missing block', () => { const bottomSheet = testInstance.findByType( BottomSheet ); const children = bottomSheet.props.children.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 ); + expect( children[ 1 ].props.children ).toBe( + "'" + + defaultAttributes.originalName + + "' isn't yet supported on WordPress for " + + expectedOSString + ); } ); } ); diff --git a/packages/block-library/src/more/edit.js b/packages/block-library/src/more/edit.js index ce4c6233b63a52..a7d04b54cde75c 100644 --- a/packages/block-library/src/more/edit.js +++ b/packages/block-library/src/more/edit.js @@ -6,10 +6,7 @@ import { PanelBody, ToggleControl } from '@wordpress/components'; import { Component } from '@wordpress/element'; import { InspectorControls } from '@wordpress/block-editor'; import { ENTER } from '@wordpress/keycodes'; -import { - getDefaultBlockName, - createBlock, -} from '@wordpress/blocks'; +import { getDefaultBlockName, createBlock } from '@wordpress/blocks'; export default class MoreEdit extends Component { constructor() { @@ -28,7 +25,8 @@ export default class MoreEdit extends Component { defaultText: '', } ); - const value = event.target.value.length === 0 ? undefined : event.target.value; + const value = + event.target.value.length === 0 ? undefined : event.target.value; this.props.setAttributes( { customText: value } ); } @@ -41,16 +39,17 @@ export default class MoreEdit extends Component { } getHideExcerptHelp( checked ) { - return checked ? - __( 'The excerpt is hidden.' ) : - __( 'The excerpt is visible.' ); + return checked + ? __( 'The excerpt is hidden.' ) + : __( 'The excerpt is visible.' ); } render() { const { customText, noTeaser } = this.props.attributes; const { setAttributes } = this.props; - const toggleHideExcerpt = () => setAttributes( { noTeaser: ! noTeaser } ); + const toggleHideExcerpt = () => + setAttributes( { noTeaser: ! noTeaser } ); const { defaultText } = this.state; const value = customText !== undefined ? customText : defaultText; const inputLength = value.length + 1.2; @@ -61,7 +60,9 @@ export default class MoreEdit extends Component { <InspectorControls> <PanelBody> <ToggleControl - label={ __( 'Hide the excerpt on the full content page' ) } + label={ __( + 'Hide the excerpt on the full content page' + ) } checked={ !! noTeaser } onChange={ toggleHideExcerpt } help={ this.getHideExcerptHelp } diff --git a/packages/block-library/src/more/edit.native.js b/packages/block-library/src/more/edit.native.js index 88908d3f44837c..c140bcdc483674 100644 --- a/packages/block-library/src/more/edit.native.js +++ b/packages/block-library/src/more/edit.native.js @@ -31,8 +31,14 @@ export class MoreEdit extends Component { const { defaultText } = this.state; const content = customText || defaultText; - const textStyle = getStylesFromColorScheme( styles.moreText, styles.moreTextDark ); - const lineStyle = getStylesFromColorScheme( styles.moreLine, styles.moreLineDark ); + const textStyle = getStylesFromColorScheme( + styles.moreText, + styles.moreTextDark + ); + const lineStyle = getStylesFromColorScheme( + styles.moreLine, + styles.moreLineDark + ); return ( <View> diff --git a/packages/block-library/src/more/icon.js b/packages/block-library/src/more/icon.js index b73e5cdf9b3f63..3e24c0d3d471e0 100644 --- a/packages/block-library/src/more/icon.js +++ b/packages/block-library/src/more/icon.js @@ -4,5 +4,10 @@ import { G, Path, SVG } from '@wordpress/components'; export default ( - <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M2 9v2h19V9H2zm0 6h5v-2H2v2zm7 0h5v-2H9v2zm7 0h5v-2h-5v2z" /></G></SVG> + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <Path fill="none" d="M0 0h24v24H0V0z" /> + <G> + <Path d="M2 9v2h19V9H2zm0 6h5v-2H2v2zm7 0h5v-2H9v2zm7 0h5v-2h-5v2z" /> + </G> + </SVG> ); diff --git a/packages/block-library/src/more/index.js b/packages/block-library/src/more/index.js index 3ecb6568ea4a1a..c70c5b5412e556 100644 --- a/packages/block-library/src/more/index.js +++ b/packages/block-library/src/more/index.js @@ -18,7 +18,9 @@ export { metadata, name }; export const settings = { title: _x( 'More', 'block name' ), - description: __( 'Content before this block will be shown in the excerpt on your archives page.' ), + description: __( + 'Content before this block will be shown in the excerpt on your archives page.' + ), icon, supports: { customClassName: false, diff --git a/packages/block-library/src/more/save.js b/packages/block-library/src/more/save.js index f3483924d85ffb..49bef491d7f64a 100644 --- a/packages/block-library/src/more/save.js +++ b/packages/block-library/src/more/save.js @@ -11,17 +11,11 @@ import { RawHTML } from '@wordpress/element'; export default function save( { attributes } ) { const { customText, noTeaser } = attributes; - const moreTag = customText ? - `<!--more ${ customText }-->` : - '<!--more-->'; + const moreTag = customText ? `<!--more ${ customText }-->` : '<!--more-->'; - const noTeaserTag = noTeaser ? - '<!--noteaser-->' : - ''; + const noTeaserTag = noTeaser ? '<!--noteaser-->' : ''; return ( - <RawHTML> - { compact( [ moreTag, noTeaserTag ] ).join( '\n' ) } - </RawHTML> + <RawHTML>{ compact( [ moreTag, noTeaserTag ] ).join( '\n' ) }</RawHTML> ); } diff --git a/packages/block-library/src/more/transforms.js b/packages/block-library/src/more/transforms.js index 41cabf5183affa..3ba0f4055cc0bf 100644 --- a/packages/block-library/src/more/transforms.js +++ b/packages/block-library/src/more/transforms.js @@ -10,7 +10,8 @@ const transforms = { schema: { 'wp-block': { attributes: [ 'data-block' ] }, }, - isMatch: ( node ) => node.dataset && node.dataset.block === 'core/more', + isMatch: ( node ) => + node.dataset && node.dataset.block === 'core/more', transform( node ) { const { customText, noTeaser } = node.dataset; const attrs = {}; diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index 96d7853be64385..ff55c5dabfecd6 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -21,10 +21,7 @@ import { ToolbarButton, ToolbarGroup, } from '@wordpress/components'; -import { - rawShortcut, - displayShortcut, -} from '@wordpress/keycodes'; +import { rawShortcut, displayShortcut } from '@wordpress/keycodes'; import { __ } from '@wordpress/i18n'; import { BlockControls, @@ -33,8 +30,9 @@ import { RichText, __experimentalLinkControl as LinkControl, } from '@wordpress/block-editor'; -import { Fragment, useState, useEffect } from '@wordpress/element'; - +import { isURL, prependHTTP } from '@wordpress/url'; +import { Fragment, useState, useEffect, useRef } from '@wordpress/element'; +import { placeCaretAtHorizontalEdge } from '@wordpress/dom'; /** * Internal dependencies */ @@ -49,7 +47,14 @@ function NavigationLinkEdit( { showSubmenuIcon, insertLinkBlock, } ) { - const { label, opensInNewTab, title, url, nofollow, description } = attributes; + const { + label, + opensInNewTab, + title, + url, + nofollow, + description, + } = attributes; const link = { title: title ? unescape( title ) : '', url, @@ -57,6 +62,7 @@ function NavigationLinkEdit( { }; const [ isLinkOpen, setIsLinkOpen ] = useState( false ); const itemLabelPlaceholder = __( 'Add link…' ); + const ref = useRef(); // Show the LinkControl on mount if the URL is empty // ( When adding a new menu item) @@ -68,6 +74,49 @@ function NavigationLinkEdit( { } }, [] ); + /** + * The hook shouldn't be necessary but due to a focus loss happening + * when selecting a suggestion in the link popover, we force close on block unselection. + */ + useEffect( () => { + if ( ! isSelected ) { + setIsLinkOpen( false ); + } + }, [ isSelected ] ); + + // If the LinkControl popover is open and the URL has changed, close the LinkControl and focus the label text. + useEffect( () => { + if ( isLinkOpen && url ) { + // Close the link. + setIsLinkOpen( false ); + + // Does this look like a URL and have something TLD-ish? + if ( + isURL( prependHTTP( label ) ) && + /^.+\.[a-z]+/.test( label ) + ) { + // Focus and select the label text. + selectLabelText(); + } else { + // Focus it (but do not select). + placeCaretAtHorizontalEdge( ref.current, true ); + } + } + }, [ url ] ); + + /** + * Focus the navigation link label text and select it. + */ + function selectLabelText() { + ref.current.focus(); + const selection = window.getSelection(); + const range = document.createRange(); + // Get the range of the current ref contents so we can add this range to the selection. + range.selectNodeContents( ref.current ); + selection.removeAllRanges(); + selection.addRange( range ); + } + return ( <Fragment> <BlockControls> @@ -75,7 +124,8 @@ function NavigationLinkEdit( { <KeyboardShortcuts bindGlobal shortcuts={ { - [ rawShortcut.primary( 'k' ) ]: () => setIsLinkOpen( true ), + [ rawShortcut.primary( 'k' ) ]: () => + setIsLinkOpen( true ), } } /> <ToolbarButton @@ -94,16 +144,16 @@ function NavigationLinkEdit( { </ToolbarGroup> </BlockControls> <InspectorControls> - <PanelBody - title={ __( 'SEO settings' ) } - > + <PanelBody title={ __( 'SEO settings' ) }> <TextControl value={ title || '' } onChange={ ( titleValue ) => { setAttributes( { title: titleValue } ); } } label={ __( 'Title Attribute' ) } - help={ __( 'Provide more context about where the link goes.' ) } + help={ __( + 'Provide more context about where the link goes.' + ) } /> <ToggleControl checked={ nofollow } @@ -111,34 +161,38 @@ function NavigationLinkEdit( { setAttributes( { nofollow: nofollowValue } ); } } label={ __( 'Add nofollow to link' ) } - help={ ( + help={ <Fragment> - { __( 'Don\'t let search engines follow this link.' ) } + { __( + "Don't let search engines follow this link." + ) } <ExternalLink className="wp-block-navigation-link__nofollow-external-link" - href={ __( 'https://codex.wordpress.org/Nofollow' ) } + href={ __( + 'https://codex.wordpress.org/Nofollow' + ) } > - { __( 'What\'s this?' ) } + { __( "What's this?" ) } </ExternalLink> </Fragment> - ) } + } /> </PanelBody> - <PanelBody - title={ __( 'Link settings' ) } - > + <PanelBody title={ __( 'Link settings' ) }> <TextareaControl value={ description || '' } onChange={ ( descriptionValue ) => { setAttributes( { description: descriptionValue } ); } } label={ __( 'Description' ) } - help={ __( 'The description will be displayed in the menu if the current theme supports it.' ) } + help={ __( + 'The description will be displayed in the menu if the current theme supports it.' + ) } /> </PanelBody> </InspectorControls> - <div className={ classnames( - 'wp-block-navigation-link', { + <div + className={ classnames( 'wp-block-navigation-link', { 'is-editing': isSelected || isParentOfSelectedBlock, 'is-selected': isSelected, 'has-link': !! url, @@ -146,10 +200,13 @@ function NavigationLinkEdit( { > <div className="wp-block-navigation-link__content"> <RichText + ref={ ref } tagName="span" className="wp-block-navigation-link__label" value={ label } - onChange={ ( labelValue ) => setAttributes( { label: labelValue } ) } + onChange={ ( labelValue ) => + setAttributes( { label: labelValue } ) + } placeholder={ itemLabelPlaceholder } withoutInteractiveFormatting allowedFormats={ [ @@ -159,11 +216,11 @@ function NavigationLinkEdit( { 'core/strikethrough', ] } /> - { showSubmenuIcon && + { showSubmenuIcon && ( <span className="wp-block-navigation-link__submenu-icon"> { itemSubmenuIcon } </span> - } + ) } { isLinkOpen && ( <Popover position="bottom center" @@ -178,30 +235,48 @@ function NavigationLinkEdit( { url: newURL = '', opensInNewTab: newOpensInNewTab, id, - } = {} ) => setAttributes( { - title: escape( newTitle ), - url: encodeURI( newURL ), - label: ( () => { - const normalizedTitle = newTitle.replace( /http(s?):\/\//gi, '' ); - const normalizedURL = newURL.replace( /http(s?):\/\//gi, '' ); - if ( - newTitle !== '' && - normalizedTitle !== normalizedURL && - label !== newTitle ) { - return escape( newTitle ); - } - return label; - } )(), - opensInNewTab: newOpensInNewTab, - id, - } ) } + } = {} ) => + setAttributes( { + title: escape( newTitle ), + url: encodeURI( newURL ), + label: ( () => { + const normalizedTitle = newTitle.replace( + /http(s?):\/\//gi, + '' + ); + const normalizedURL = newURL.replace( + /http(s?):\/\//gi, + '' + ); + if ( + newTitle !== '' && + normalizedTitle !== + normalizedURL && + label !== newTitle + ) { + return escape( newTitle ); + } else if ( label ) { + return label; + } + // If there's no label, add the URL. + return escape( normalizedURL ); + } )(), + opensInNewTab: newOpensInNewTab, + id, + } ) + } /> </Popover> ) } </div> <InnerBlocks allowedBlocks={ [ 'core/navigation-link' ] } - renderAppender={ ( ( hasDescendants && isSelected ) || isParentOfSelectedBlock ) ? InnerBlocks.DefaultAppender : false } + renderAppender={ + ( hasDescendants && isSelected ) || + isParentOfSelectedBlock + ? InnerBlocks.DefaultAppender + : false + } /> </div> </Fragment> @@ -221,9 +296,13 @@ export default compose( [ const rootBlock = getBlockParents( clientId )[ 0 ]; const parentBlock = getBlockParents( clientId, true )[ 0 ]; const rootBlockAttributes = getBlockAttributes( rootBlock ); - const hasDescendants = !! getClientIdsOfDescendants( [ clientId ] ).length; + const hasDescendants = !! getClientIdsOfDescendants( [ clientId ] ) + .length; const isLevelZero = getBlockName( parentBlock ) === 'core/navigation'; - const showSubmenuIcon = rootBlockAttributes.showSubmenuIcon && isLevelZero && hasDescendants; + const showSubmenuIcon = + rootBlockAttributes.showSubmenuIcon && + isLevelZero && + hasDescendants; const isParentOfSelectedBlock = hasSelectedInnerBlock( clientId, true ); return { @@ -237,21 +316,17 @@ export default compose( [ insertLinkBlock() { const { clientId } = ownProps; - const { - insertBlock, - } = dispatch( 'core/block-editor' ); + const { insertBlock } = dispatch( 'core/block-editor' ); - const { getClientIdsOfDescendants } = registry.select( 'core/block-editor' ); + const { getClientIdsOfDescendants } = registry.select( + 'core/block-editor' + ); const navItems = getClientIdsOfDescendants( [ clientId ] ); const insertionPoint = navItems.length ? navItems.length : 0; const blockToInsert = createBlock( 'core/navigation-link' ); - insertBlock( - blockToInsert, - insertionPoint, - clientId, - ); + insertBlock( blockToInsert, insertionPoint, clientId ); }, }; } ), diff --git a/packages/block-library/src/navigation-link/icons.js b/packages/block-library/src/navigation-link/icons.js index ee169a6393cded..c01b5979dc36a5 100644 --- a/packages/block-library/src/navigation-link/icons.js +++ b/packages/block-library/src/navigation-link/icons.js @@ -11,7 +11,12 @@ export const toolbarSubmenuIcon = ( ); export const itemSubmenuIcon = ( - <SVG width="18" height="18" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"> + <SVG + width="18" + height="18" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 18 18" + > <Polygon points="9,13.5 14.7,7.9 13.2,6.5 9,10.7 4.8,6.5 3.3,7.9 " /> </SVG> ); diff --git a/packages/block-library/src/navigation-link/index.js b/packages/block-library/src/navigation-link/index.js index 2a418abf3f73b4..b53e4003c0ec01 100644 --- a/packages/block-library/src/navigation-link/index.js +++ b/packages/block-library/src/navigation-link/index.js @@ -33,4 +33,3 @@ export const settings = { edit, save, }; - diff --git a/packages/block-library/src/navigation-link/save.js b/packages/block-library/src/navigation-link/save.js index 1a0549243fae84..17571d8f30d2de 100644 --- a/packages/block-library/src/navigation-link/save.js +++ b/packages/block-library/src/navigation-link/save.js @@ -1,12 +1,8 @@ /** * WordPress dependencies */ -import { - InnerBlocks, -} from '@wordpress/block-editor'; +import { InnerBlocks } from '@wordpress/block-editor'; export default function save() { - return ( - <InnerBlocks.Content /> - ); + return <InnerBlocks.Content />; } diff --git a/packages/block-library/src/navigation/block-colors-selector.js b/packages/block-library/src/navigation/block-colors-selector.js index 51dc862aa39cad..688f614b03058f 100644 --- a/packages/block-library/src/navigation/block-colors-selector.js +++ b/packages/block-library/src/navigation/block-colors-selector.js @@ -1,8 +1,13 @@ - /** * WordPress dependencies */ -import { Button, Dropdown, ToolbarGroup, SVG, Path } from '@wordpress/components'; +import { + Button, + Dropdown, + ToolbarGroup, + SVG, + Path, +} from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { DOWN } from '@wordpress/keycodes'; @@ -37,7 +42,10 @@ const ColorSelectorIcon = ( { style, className } ) => { * @param {Object} colorControlProps colorControl properties. * @return {*} React toggle button component. */ -const renderToggleComponent = ( { TextColor, BackgroundColor } ) => ( { onToggle, isOpen } ) => { +const renderToggleComponent = ( { TextColor, BackgroundColor } ) => ( { + onToggle, + isOpen, +} ) => { const openOnArrowDown = ( event ) => { if ( ! isOpen && event.keyCode === DOWN ) { event.preventDefault(); diff --git a/packages/block-library/src/navigation/block-navigation-list.js b/packages/block-library/src/navigation/block-navigation-list.js index 77adcd01cae366..43bc0793e057e0 100644 --- a/packages/block-library/src/navigation/block-navigation-list.js +++ b/packages/block-library/src/navigation/block-navigation-list.js @@ -1,33 +1,25 @@ /** * WordPress dependencies */ -import { - __experimentalBlockNavigationList, -} from '@wordpress/block-editor'; -import { - useSelect, - useDispatch, -} from '@wordpress/data'; +import { __experimentalBlockNavigationList } from '@wordpress/block-editor'; +import { useSelect, useDispatch } from '@wordpress/data'; export default function BlockNavigationList( { clientId } ) { - const { - block, - selectedBlockClientId, - } = useSelect( ( select ) => { - const { - getSelectedBlockClientId, - getBlock, - } = select( 'core/block-editor' ); + const { block, selectedBlockClientId } = useSelect( + ( select ) => { + const { getSelectedBlockClientId, getBlock } = select( + 'core/block-editor' + ); - return { - block: getBlock( clientId ), - selectedBlockClientId: getSelectedBlockClientId(), - }; - }, [ clientId ] ); + return { + block: getBlock( clientId ), + selectedBlockClientId: getSelectedBlockClientId(), + }; + }, + [ clientId ] + ); - const { - selectBlock, - } = useDispatch( 'core/block-editor' ); + const { selectBlock } = useDispatch( 'core/block-editor' ); return ( <__experimentalBlockNavigationList diff --git a/packages/block-library/src/navigation/edit.js b/packages/block-library/src/navigation/edit.js index 23d9b2b53313a5..c50f07f8863af8 100644 --- a/packages/block-library/src/navigation/edit.js +++ b/packages/block-library/src/navigation/edit.js @@ -7,11 +7,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { - useMemo, - Fragment, - useRef, -} from '@wordpress/element'; +import { useMemo, Fragment, useRef } from '@wordpress/element'; import { InnerBlocks, InspectorControls, @@ -74,7 +70,13 @@ function Navigation( { { name: 'backgroundColor', className: 'background-color' }, ], { - contrastCheckers: [ { backgroundColor: true, textColor: true, fontSize: fontSize.size } ], + contrastCheckers: [ + { + backgroundColor: true, + textColor: true, + fontSize: fontSize.size, + }, + ], colorDetector: { targetRef: ref }, colorPanelProps: { initialOpen: true, @@ -84,37 +86,37 @@ function Navigation( { ); /* eslint-enable @wordpress/no-unused-vars-before-return */ - const { navigatorToolbarButton, navigatorModal } = useBlockNavigator( clientId ); + const { navigatorToolbarButton, navigatorModal } = useBlockNavigator( + clientId + ); // Builds navigation links from default Pages. - const defaultPagesNavigationItems = useMemo( - () => { - if ( ! pages ) { - return null; - } + const defaultPagesNavigationItems = useMemo( () => { + if ( ! pages ) { + return null; + } - return pages.map( ( { title, type, link: url, id } ) => ( - createBlock( 'core/navigation-link', - { - type, - id, - url, - label: ! title.rendered ? __( '(no title)' ) : escape( title.rendered ), - title: ! title.raw ? __( '(no title)' ) : escape( title.raw ), - opensInNewTab: false, - } - ) - ) ); - }, - [ pages ] - ); + return pages.map( ( { title, type, link: url, id } ) => + createBlock( 'core/navigation-link', { + type, + id, + url, + label: ! title.rendered + ? __( '(no title)' ) + : escape( title.rendered ), + title: ! title.raw ? __( '(no title)' ) : escape( title.raw ), + opensInNewTab: false, + } ) + ); + }, [ pages ] ); // // HANDLERS // function handleItemsAlignment( align ) { return () => { - const itemsJustification = attributes.itemsJustification === align ? undefined : align; + const itemsJustification = + attributes.itemsJustification === align ? undefined : align; setAttributes( { itemsJustification, } ); @@ -151,7 +153,9 @@ function Navigation( { className="wp-block-navigation-placeholder" icon="menu" label={ __( 'Navigation' ) } - instructions={ __( 'Create a Navigation from all existing pages, or create an empty one.' ) } + instructions={ __( + 'Create a Navigation from all existing pages, or create an empty one.' + ) } > <div ref={ ref } @@ -184,18 +188,40 @@ function Navigation( { <Fragment> <BlockControls> <Toolbar - icon={ attributes.itemsJustification ? navIcons[ `justify${ upperFirst( attributes.itemsJustification ) }Icon` ] : navIcons.justifyLeftIcon } + icon={ + attributes.itemsJustification + ? navIcons[ + `justify${ upperFirst( + attributes.itemsJustification + ) }Icon` + ] + : navIcons.justifyLeftIcon + } label={ __( 'Change items justification' ) } isCollapsed controls={ [ - { icon: navIcons.justifyLeftIcon, title: __( 'Justify items left' ), isActive: 'left' === attributes.itemsJustification, onClick: handleItemsAlignment( 'left' ) }, - { icon: navIcons.justifyCenterIcon, title: __( 'Justify items center' ), isActive: 'center' === attributes.itemsJustification, onClick: handleItemsAlignment( 'center' ) }, - { icon: navIcons.justifyRightIcon, title: __( 'Justify items right' ), isActive: 'right' === attributes.itemsJustification, onClick: handleItemsAlignment( 'right' ) }, + { + icon: navIcons.justifyLeftIcon, + title: __( 'Justify items left' ), + isActive: 'left' === attributes.itemsJustification, + onClick: handleItemsAlignment( 'left' ), + }, + { + icon: navIcons.justifyCenterIcon, + title: __( 'Justify items center' ), + isActive: + 'center' === attributes.itemsJustification, + onClick: handleItemsAlignment( 'center' ), + }, + { + icon: navIcons.justifyRightIcon, + title: __( 'Justify items right' ), + isActive: 'right' === attributes.itemsJustification, + onClick: handleItemsAlignment( 'right' ), + }, ] } /> - <ToolbarGroup> - { navigatorToolbarButton } - </ToolbarGroup> + <ToolbarGroup>{ navigatorToolbarButton }</ToolbarGroup> <BlockColorsStyleSelector TextColor={ TextColor } @@ -206,9 +232,7 @@ function Navigation( { </BlockControls> { navigatorModal } <InspectorControls> - <PanelBody - title={ __( 'Navigation Structure' ) } - > + <PanelBody title={ __( 'Navigation Structure' ) }> <BlockNavigationList clientId={ clientId } /> </PanelBody> <PanelBody title={ __( 'Text settings' ) }> @@ -220,9 +244,7 @@ function Navigation( { </InspectorControls> { InspectorControlsColorPanel } <InspectorControls> - <PanelBody - title={ __( 'Display settings' ) } - > + <PanelBody title={ __( 'Display settings' ) }> <ToggleControl checked={ attributes.showSubmenuIcon } onChange={ ( value ) => { @@ -239,14 +261,17 @@ function Navigation( { className={ blockClassNames } style={ blockInlineStyles } > - { ! hasExistingNavItems && isRequestingPages && <><Spinner /> { __( 'Loading Navigation…' ) } </> } + { ! hasExistingNavItems && isRequestingPages && ( + <> + <Spinner /> { __( 'Loading Navigation…' ) }{ ' ' } + </> + ) } <InnerBlocks allowedBlocks={ [ 'core/navigation-link' ] } templateInsertUpdatesSelection={ false } __experimentalMoverDirection={ 'horizontal' } /> - </div> </BackgroundColor> </TextColor> @@ -265,19 +290,34 @@ export default compose( [ orderby: 'id', }; - const pagesSelect = [ 'core', 'getEntityRecords', [ 'postType', 'page', filterDefaultPages ] ]; + const pagesSelect = [ + 'core', + 'getEntityRecords', + [ 'postType', 'page', filterDefaultPages ], + ]; return { hasExistingNavItems: !! innerBlocks.length, - pages: select( 'core' ).getEntityRecords( 'postType', 'page', filterDefaultPages ), - isRequestingPages: select( 'core/data' ).isResolving( ...pagesSelect ), - hasResolvedPages: select( 'core/data' ).hasFinishedResolution( ...pagesSelect ), + pages: select( 'core' ).getEntityRecords( + 'postType', + 'page', + filterDefaultPages + ), + isRequestingPages: select( 'core/data' ).isResolving( + ...pagesSelect + ), + hasResolvedPages: select( 'core/data' ).hasFinishedResolution( + ...pagesSelect + ), }; } ), withDispatch( ( dispatch, { clientId } ) => { return { updateNavItemBlocks( blocks ) { - dispatch( 'core/block-editor' ).replaceInnerBlocks( clientId, blocks ); + dispatch( 'core/block-editor' ).replaceInnerBlocks( + clientId, + blocks + ); }, }; } ), diff --git a/packages/block-library/src/navigation/icons.js b/packages/block-library/src/navigation/icons.js index fbfd742bd712f2..b18ef92f2a3065 100644 --- a/packages/block-library/src/navigation/icons.js +++ b/packages/block-library/src/navigation/icons.js @@ -4,17 +4,34 @@ import { Path, SVG } from '@wordpress/components'; export const justifyLeftIcon = ( - <SVG width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <SVG + width="20" + height="20" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + > <Path d="M11 16v-3h10v-2H11V8l-4 4 4 4zM5 4H3v16h2V4z" /> </SVG> ); export const justifyCenterIcon = ( - <SVG width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <SVG + width="20" + height="20" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + > <Path d="M5 8v3H1v2h4v3l4-4-4-4zm14 8v-3h4v-2h-4V8l-4 4 4 4zM13 4h-2v16h2V4z" /> </SVG> ); export const justifyRightIcon = ( - <SVG width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M13 8v3H3v2h10v3l4-4-4-4zm8-4h-2v16h2V4z" /></SVG> + <SVG + width="20" + height="20" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + > + <Path d="M13 8v3H3v2h10v3l4-4-4-4zm8-4h-2v16h2V4z" /> + </SVG> ); diff --git a/packages/block-library/src/navigation/index.js b/packages/block-library/src/navigation/index.js index 8c92b0f09a10ec..caf5d81bff121f 100644 --- a/packages/block-library/src/navigation/index.js +++ b/packages/block-library/src/navigation/index.js @@ -38,5 +38,4 @@ export const settings = { edit, save, - }; diff --git a/packages/block-library/src/navigation/save.js b/packages/block-library/src/navigation/save.js index 873432fe659547..17571d8f30d2de 100644 --- a/packages/block-library/src/navigation/save.js +++ b/packages/block-library/src/navigation/save.js @@ -4,7 +4,5 @@ import { InnerBlocks } from '@wordpress/block-editor'; export default function save() { - return ( - <InnerBlocks.Content /> - ); + return <InnerBlocks.Content />; } diff --git a/packages/block-library/src/navigation/use-block-navigator.js b/packages/block-library/src/navigation/use-block-navigator.js index 008f32e3074164..b259299ab7bd22 100644 --- a/packages/block-library/src/navigation/use-block-navigator.js +++ b/packages/block-library/src/navigation/use-block-navigator.js @@ -1,15 +1,8 @@ /** * WordPress dependencies */ -import { - useState, -} from '@wordpress/element'; -import { - Button, - SVG, - Path, - Modal, -} from '@wordpress/components'; +import { useState } from '@wordpress/element'; +import { Button, SVG, Path, Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; /** @@ -18,7 +11,12 @@ import { __ } from '@wordpress/i18n'; import BlockNavigationList from './block-navigation-list'; const NavigatorIcon = ( - <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20"> + <SVG + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + width="20" + height="20" + > <Path d="M5 5H3v2h2V5zm3 8h11v-2H8v2zm9-8H6v2h11V5zM7 11H5v2h2v-2zm0 8h2v-2H7v2zm3-2v2h11v-2H10z" /> </SVG> ); diff --git a/packages/block-library/src/nextpage/edit.native.js b/packages/block-library/src/nextpage/edit.native.js index 25591a00269cb7..8570b942fd60a6 100644 --- a/packages/block-library/src/nextpage/edit.native.js +++ b/packages/block-library/src/nextpage/edit.native.js @@ -15,31 +15,42 @@ import { withPreferredColorScheme } from '@wordpress/compose'; */ import styles from './editor.scss'; -export function NextPageEdit( { attributes, isSelected, onFocus, getStylesFromColorScheme } ) { +export function NextPageEdit( { + attributes, + isSelected, + onFocus, + getStylesFromColorScheme, +} ) { const { customText = __( 'Page break' ) } = attributes; const accessibilityTitle = attributes.customText || ''; const accessibilityState = isSelected ? [ 'selected' ] : []; - const textStyle = getStylesFromColorScheme( styles.nextpageText, styles.nextpageTextDark ); - const lineStyle = getStylesFromColorScheme( styles.nextpageLine, styles.nextpageLineDark ); + const textStyle = getStylesFromColorScheme( + styles.nextpageText, + styles.nextpageTextDark + ); + const lineStyle = getStylesFromColorScheme( + styles.nextpageLine, + styles.nextpageLineDark + ); return ( <View accessible - accessibilityLabel={ - sprintf( - /* translators: accessibility text. %s: Page break text. */ - __( 'Page break block. %s' ), - accessibilityTitle - ) - } + accessibilityLabel={ sprintf( + /* translators: accessibility text. %s: Page break text. */ + __( 'Page break block. %s' ), + accessibilityTitle + ) } accessibilityStates={ accessibilityState } onAccessibilityTap={ onFocus } > - <Hr text={ customText } + <Hr + text={ customText } marginLeft={ 0 } marginRight={ 0 } textStyle={ textStyle } - lineStyle={ lineStyle } /> + lineStyle={ lineStyle } + /> </View> ); } diff --git a/packages/block-library/src/nextpage/save.js b/packages/block-library/src/nextpage/save.js index f86dd0dc6b4be4..0692aecbf052f0 100644 --- a/packages/block-library/src/nextpage/save.js +++ b/packages/block-library/src/nextpage/save.js @@ -4,9 +4,5 @@ import { RawHTML } from '@wordpress/element'; export default function save() { - return ( - <RawHTML> - { '<!--nextpage-->' } - </RawHTML> - ); + return <RawHTML>{ '<!--nextpage-->' }</RawHTML>; } diff --git a/packages/block-library/src/nextpage/transforms.js b/packages/block-library/src/nextpage/transforms.js index e9aa6d2d9b1992..cc649946095752 100644 --- a/packages/block-library/src/nextpage/transforms.js +++ b/packages/block-library/src/nextpage/transforms.js @@ -10,7 +10,8 @@ const transforms = { schema: { 'wp-block': { attributes: [ 'data-block' ] }, }, - isMatch: ( node ) => node.dataset && node.dataset.block === 'core/nextpage', + isMatch: ( node ) => + node.dataset && node.dataset.block === 'core/nextpage', transform() { return createBlock( 'core/nextpage', {} ); }, diff --git a/packages/block-library/src/paragraph/deprecated.js b/packages/block-library/src/paragraph/deprecated.js index d7871b8854ca3a..8c0502d808079f 100644 --- a/packages/block-library/src/paragraph/deprecated.js +++ b/packages/block-library/src/paragraph/deprecated.js @@ -7,9 +7,7 @@ import { isFinite, omit } from 'lodash'; /** * WordPress dependencies */ -import { - RawHTML, -} from '@wordpress/element'; +import { RawHTML } from '@wordpress/element'; import { getColorClassName, getFontSizeClass, @@ -80,7 +78,10 @@ const deprecated = [ } = attributes; const textClass = getColorClassName( 'color', textColor ); - const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + const backgroundClass = getColorClassName( + 'background-color', + backgroundColor + ); const fontSizeClass = getFontSizeClass( fontSize ); const className = classnames( { @@ -93,7 +94,9 @@ const deprecated = [ } ); const styles = { - backgroundColor: backgroundClass ? undefined : customBackgroundColor, + backgroundColor: backgroundClass + ? undefined + : customBackgroundColor, color: textClass ? undefined : customTextColor, fontSize: fontSizeClass ? undefined : customFontSize, textAlign: align, @@ -133,7 +136,10 @@ const deprecated = [ } = attributes; const textClass = getColorClassName( 'color', textColor ); - const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + const backgroundClass = getColorClassName( + 'background-color', + backgroundColor + ); const fontSizeClass = fontSize && `is-${ fontSize }-text`; const className = classnames( { @@ -146,7 +152,9 @@ const deprecated = [ } ); const styles = { - backgroundColor: backgroundClass ? undefined : customBackgroundColor, + backgroundColor: backgroundClass + ? undefined + : customBackgroundColor, color: textClass ? undefined : customTextColor, fontSize: fontSizeClass ? undefined : customFontSize, textAlign: align, @@ -164,14 +172,27 @@ const deprecated = [ }, { supports, - attributes: omit( { - ...blockAttributes, - fontSize: { - type: 'number', + attributes: omit( + { + ...blockAttributes, + fontSize: { + type: 'number', + }, }, - }, 'customFontSize', 'customTextColor', 'customBackgroundColor' ), + 'customFontSize', + 'customTextColor', + 'customBackgroundColor' + ), save( { attributes } ) { - const { width, align, content, dropCap, backgroundColor, textColor, fontSize } = attributes; + const { + width, + align, + content, + dropCap, + backgroundColor, + textColor, + fontSize, + } = attributes; const className = classnames( { [ `align${ width }` ]: width, 'has-background': backgroundColor, @@ -184,15 +205,35 @@ const deprecated = [ textAlign: align, }; - return <p style={ styles } className={ className ? className : undefined }>{ content }</p>; + return ( + <p + style={ styles } + className={ className ? className : undefined } + > + { content } + </p> + ); }, migrate( attributes ) { - return omit( { - ...attributes, - customFontSize: isFinite( attributes.fontSize ) ? attributes.fontSize : undefined, - customTextColor: attributes.textColor && '#' === attributes.textColor[ 0 ] ? attributes.textColor : undefined, - customBackgroundColor: attributes.backgroundColor && '#' === attributes.backgroundColor[ 0 ] ? attributes.backgroundColor : undefined, - }, [ 'fontSize', 'textColor', 'backgroundColor' ] ); + return omit( + { + ...attributes, + customFontSize: isFinite( attributes.fontSize ) + ? attributes.fontSize + : undefined, + customTextColor: + attributes.textColor && + '#' === attributes.textColor[ 0 ] + ? attributes.textColor + : undefined, + customBackgroundColor: + attributes.backgroundColor && + '#' === attributes.backgroundColor[ 0 ] + ? attributes.backgroundColor + : undefined, + }, + [ 'fontSize', 'textColor', 'backgroundColor' ] + ); }, }, { diff --git a/packages/block-library/src/paragraph/edit.js b/packages/block-library/src/paragraph/edit.js index 983b92a1a15097..944a3952dd6162 100644 --- a/packages/block-library/src/paragraph/edit.js +++ b/packages/block-library/src/paragraph/edit.js @@ -7,11 +7,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __, _x } from '@wordpress/i18n'; -import { - PanelBody, - ToggleControl, - ToolbarGroup, -} from '@wordpress/components'; +import { PanelBody, ToggleControl, ToolbarGroup } from '@wordpress/components'; import { AlignmentToolbar, BlockControls, @@ -40,40 +36,38 @@ function ParagraphRTLToolbar( { direction, setDirection } ) { return !! select( 'core/block-editor' ).getSettings().isRTL; }, [] ); - return ( isRTL && ( - <ToolbarGroup - controls={ [ - { - icon: 'editor-ltr', - title: _x( 'Left to right', 'editor button' ), - isActive: direction === 'ltr', - onClick() { - setDirection( direction === 'ltr' ? undefined : 'ltr' ); + return ( + isRTL && ( + <ToolbarGroup + controls={ [ + { + icon: 'editor-ltr', + title: _x( 'Left to right', 'editor button' ), + isActive: direction === 'ltr', + onClick() { + setDirection( + direction === 'ltr' ? undefined : 'ltr' + ); + }, }, - }, - ] } - /> - ) ); + ] } + /> + ) + ); } function useDropCapMinimumHeight( isDropCap, deps ) { const [ minimumHeight, setMinimumHeight ] = useState(); - useEffect( - () => { - const element = querySelector( PARAGRAPH_DROP_CAP_SELECTOR ); - if ( isDropCap && element ) { - setMinimumHeight( - getComputedStyle( - element, - 'first-letter' - ).lineHeight - ); - } else if ( minimumHeight ) { - setMinimumHeight( undefined ); - } - }, - [ isDropCap, minimumHeight, setMinimumHeight, ...deps ] - ); + useEffect( () => { + const element = querySelector( PARAGRAPH_DROP_CAP_SELECTOR ); + if ( isDropCap && element ) { + setMinimumHeight( + getComputedStyle( element, 'first-letter' ).lineHeight + ); + } else if ( minimumHeight ) { + setMinimumHeight( undefined ); + } + }, [ isDropCap, minimumHeight, setMinimumHeight, ...deps ] ); return minimumHeight; } @@ -86,16 +80,12 @@ function ParagraphBlock( { setAttributes, setFontSize, } ) { - const { - align, - content, - dropCap, - placeholder, - direction, - } = attributes; + const { align, content, dropCap, placeholder, direction } = attributes; const ref = useRef(); - const dropCapMinimumHeight = useDropCapMinimumHeight( dropCap, [ fontSize.size ] ); + const dropCapMinimumHeight = useDropCapMinimumHeight( dropCap, [ + fontSize.size, + ] ); const { TextColor, BackgroundColor, @@ -106,7 +96,13 @@ function ParagraphBlock( { { name: 'backgroundColor', className: 'has-background' }, ], { - contrastCheckers: [ { backgroundColor: true, textColor: true, fontSize: fontSize.size } ], + contrastCheckers: [ + { + backgroundColor: true, + textColor: true, + fontSize: fontSize.size, + }, + ], colorDetector: { targetRef: ref }, }, [ fontSize.size ] @@ -117,11 +113,15 @@ function ParagraphBlock( { <BlockControls> <AlignmentToolbar value={ align } - onChange={ ( newAlign ) => setAttributes( { align: newAlign } ) } + onChange={ ( newAlign ) => + setAttributes( { align: newAlign } ) + } /> <ParagraphRTLToolbar direction={ direction } - setDirection={ ( newDirection ) => setAttributes( { direction: newDirection } ) } + setDirection={ ( newDirection ) => + setAttributes( { direction: newDirection } ) + } /> </BlockControls> <InspectorControls> @@ -133,10 +133,13 @@ function ParagraphBlock( { <ToggleControl label={ __( 'Drop Cap' ) } checked={ !! dropCap } - onChange={ () => setAttributes( { dropCap: ! dropCap } ) } - help={ dropCap ? - __( 'Showing large initial letter.' ) : - __( 'Toggle to show a large initial letter.' ) + onChange={ () => + setAttributes( { dropCap: ! dropCap } ) + } + help={ + dropCap + ? __( 'Showing large initial letter.' ) + : __( 'Toggle to show a large initial letter.' ) } /> </PanelBody> @@ -148,18 +151,26 @@ function ParagraphBlock( { ref={ ref } identifier="content" tagName="p" - className={ classnames( 'wp-block-paragraph', className, { - 'has-drop-cap': dropCap, - [ `has-text-align-${ align }` ]: align, - [ fontSize.class ]: fontSize.class, - } ) } + className={ classnames( + 'wp-block-paragraph', + className, + { + 'has-drop-cap': dropCap, + [ `has-text-align-${ align }` ]: align, + [ fontSize.class ]: fontSize.class, + } + ) } style={ { - fontSize: fontSize.size ? fontSize.size + 'px' : undefined, + fontSize: fontSize.size + ? fontSize.size + 'px' + : undefined, direction, minHeight: dropCapMinimumHeight, } } value={ content } - onChange={ ( newContent ) => setAttributes( { content: newContent } ) } + onChange={ ( newContent ) => + setAttributes( { content: newContent } ) + } onSplit={ ( value ) => { if ( ! value ) { return createBlock( name ); @@ -172,9 +183,20 @@ function ParagraphBlock( { } } onMerge={ mergeBlocks } onReplace={ onReplace } - onRemove={ onReplace ? () => onReplace( [] ) : undefined } - aria-label={ content ? __( 'Paragraph block' ) : __( 'Empty block; start writing or type forward slash to choose a block' ) } - placeholder={ placeholder || __( 'Start writing or type / to choose a block' ) } + onRemove={ + onReplace ? () => onReplace( [] ) : undefined + } + aria-label={ + content + ? __( 'Paragraph block' ) + : __( + 'Empty block; start writing or type forward slash to choose a block' + ) + } + placeholder={ + placeholder || + __( 'Start writing or type / to choose a block' ) + } __unstableEmbedURLOnPaste __unstableAllowPrefixTransformations /> @@ -184,8 +206,8 @@ function ParagraphBlock( { ); } -const ParagraphEdit = compose( [ - withFontSizes( 'fontSize' ), -] )( ParagraphBlock ); +const ParagraphEdit = compose( [ withFontSizes( 'fontSize' ) ] )( + ParagraphBlock +); export default ParagraphEdit; diff --git a/packages/block-library/src/paragraph/edit.native.js b/packages/block-library/src/paragraph/edit.native.js index 9d018a428040e8..ae9bfe819131ef 100644 --- a/packages/block-library/src/paragraph/edit.native.js +++ b/packages/block-library/src/paragraph/edit.native.js @@ -9,7 +9,11 @@ import { View } from 'react-native'; import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { createBlock } from '@wordpress/blocks'; -import { AlignmentToolbar, BlockControls, RichText } from '@wordpress/block-editor'; +import { + AlignmentToolbar, + BlockControls, + RichText, +} from '@wordpress/block-editor'; /** * Internal dependencies @@ -25,16 +29,19 @@ class ParagraphEdit extends Component { onReplace( blocks ) { const { attributes, onReplace } = this.props; - onReplace( blocks.map( ( block, index ) => ( - index === 0 && block.name === name ? - { ...block, - attributes: { - ...attributes, - ...block.attributes, - }, - } : - block - ) ) ); + onReplace( + blocks.map( ( block, index ) => + index === 0 && block.name === name + ? { + ...block, + attributes: { + ...attributes, + ...block.attributes, + }, + } + : block + ) + ); } render() { @@ -46,11 +53,7 @@ class ParagraphEdit extends Component { style, } = this.props; - const { - align, - content, - placeholder, - } = attributes; + const { align, content, placeholder } = attributes; return ( <View> diff --git a/packages/block-library/src/paragraph/index.js b/packages/block-library/src/paragraph/index.js index d95174b9acbea4..1e70972e29d112 100644 --- a/packages/block-library/src/paragraph/index.js +++ b/packages/block-library/src/paragraph/index.js @@ -29,7 +29,9 @@ export const settings = { keywords: [ __( 'text' ) ], example: { attributes: { - content: __( 'In a village of La Mancha, the name of which I have no desire to call to mind, there lived not long since one of those gentlemen that keep a lance in the lance-rack, an old buckler, a lean hack, and a greyhound for coursing.' ), + content: __( + 'In a village of La Mancha, the name of which I have no desire to call to mind, there lived not long since one of those gentlemen that keep a lance in the lance-rack, an old buckler, a lean hack, and a greyhound for coursing.' + ), customFontSize: 28, dropCap: true, }, @@ -48,7 +50,9 @@ export const settings = { deprecated, merge( attributes, attributesToMerge ) { return { - content: ( attributes.content || '' ) + ( attributesToMerge.content || '' ), + content: + ( attributes.content || '' ) + + ( attributesToMerge.content || '' ), }; }, getEditWrapperProps( attributes ) { diff --git a/packages/block-library/src/paragraph/save.js b/packages/block-library/src/paragraph/save.js index b2c1f47a4f32a2..2cdeeb46e60c9c 100644 --- a/packages/block-library/src/paragraph/save.js +++ b/packages/block-library/src/paragraph/save.js @@ -27,7 +27,10 @@ export default function save( { attributes } ) { } = attributes; const textClass = getColorClassName( 'color', textColor ); - const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + const backgroundClass = getColorClassName( + 'background-color', + backgroundColor + ); const fontSizeClass = getFontSizeClass( fontSize ); const className = classnames( { diff --git a/packages/block-library/src/post-author/edit.js b/packages/block-library/src/post-author/edit.js index f1f7044bfbf0f6..9ca968c5679f70 100644 --- a/packages/block-library/src/post-author/edit.js +++ b/packages/block-library/src/post-author/edit.js @@ -8,10 +8,13 @@ import { sprintf, __ } from '@wordpress/i18n'; function PostAuthorDisplay() { const [ authorId ] = useEntityProp( 'postType', 'post', 'author' ); const author = useSelect( - ( select ) => select( 'core' ).getEntityRecord( 'root', 'user', authorId ), + ( select ) => + select( 'core' ).getEntityRecord( 'root', 'user', authorId ), [ authorId ] ); - return author ? <address>{ sprintf( __( 'By %s' ), author.name ) }</address> : null; + return author ? ( + <address>{ sprintf( __( 'By %s' ), author.name ) }</address> + ) : null; } export default function PostAuthorEdit() { diff --git a/packages/block-library/src/post-excerpt/edit.js b/packages/block-library/src/post-excerpt/edit.js index 22bf69a583dc3a..7bbeff3a979003 100644 --- a/packages/block-library/src/post-excerpt/edit.js +++ b/packages/block-library/src/post-excerpt/edit.js @@ -5,7 +5,11 @@ import { useEntityProp, useEntityId } from '@wordpress/core-data'; import { PlainText } from '@wordpress/block-editor'; function PostExcerptDisplay() { - const [ excerpt, setExcerpt ] = useEntityProp( 'postType', 'post', 'excerpt' ); + const [ excerpt, setExcerpt ] = useEntityProp( + 'postType', + 'post', + 'excerpt' + ); return <PlainText value={ excerpt } onChange={ setExcerpt } />; } diff --git a/packages/block-library/src/preformatted/edit.js b/packages/block-library/src/preformatted/edit.js index d7ce79438080ea..6a9bd052f0b59c 100644 --- a/packages/block-library/src/preformatted/edit.js +++ b/packages/block-library/src/preformatted/edit.js @@ -4,7 +4,13 @@ import { __ } from '@wordpress/i18n'; import { RichText } from '@wordpress/block-editor'; -export default function PreformattedEdit( { attributes, mergeBlocks, setAttributes, className, style } ) { +export default function PreformattedEdit( { + attributes, + mergeBlocks, + setAttributes, + className, + style, +} ) { const { content } = attributes; return ( diff --git a/packages/block-library/src/preformatted/edit.native.js b/packages/block-library/src/preformatted/edit.native.js index 43d5200dba8754..88a7c56a79ff1c 100644 --- a/packages/block-library/src/preformatted/edit.native.js +++ b/packages/block-library/src/preformatted/edit.native.js @@ -14,17 +14,21 @@ import styles from './styles.scss'; function PreformattedEdit( props ) { const { getStylesFromColorScheme } = props; - const richTextStyle = getStylesFromColorScheme( styles.wpRichTextLight, styles.wpRichTextDark ); - const wpBlockPreformatted = getStylesFromColorScheme( styles.wpBlockPreformattedLight, styles.wpBlockPreformattedDark ); + const richTextStyle = getStylesFromColorScheme( + styles.wpRichTextLight, + styles.wpRichTextDark + ); + const wpBlockPreformatted = getStylesFromColorScheme( + styles.wpBlockPreformattedLight, + styles.wpBlockPreformattedDark + ); const propsWithStyle = { ...props, style: richTextStyle, }; return ( - <View style={ wpBlockPreformatted } > - <WebPreformattedEdit - { ...propsWithStyle } - /> + <View style={ wpBlockPreformatted }> + <WebPreformattedEdit { ...propsWithStyle } /> </View> ); } diff --git a/packages/block-library/src/preformatted/index.js b/packages/block-library/src/preformatted/index.js index 2acd8d99bd0062..cd18c639f7db1d 100644 --- a/packages/block-library/src/preformatted/index.js +++ b/packages/block-library/src/preformatted/index.js @@ -18,12 +18,16 @@ export { metadata, name }; export const settings = { title: __( 'Preformatted' ), - description: __( 'Add text that respects your spacing and tabs, and also allows styling.' ), + description: __( + 'Add text that respects your spacing and tabs, and also allows styling.' + ), icon, example: { attributes: { // translators: Sample content for the Preformatted block. Can be replaced with a more locale-adequate work. - content: __( 'EXT. XANADU - FAINT DAWN - 1940 (MINIATURE)\nWindow, very small in the distance, illuminated.\nAll around this is an almost totally black screen. Now, as the camera moves slowly towards the window which is almost a postage stamp in the frame, other forms appear;' ), + content: __( + 'EXT. XANADU - FAINT DAWN - 1940 (MINIATURE)\nWindow, very small in the distance, illuminated.\nAll around this is an almost totally black screen. Now, as the camera moves slowly towards the window which is almost a postage stamp in the frame, other forms appear;' + ), }, }, transforms, diff --git a/packages/block-library/src/preformatted/transforms.js b/packages/block-library/src/preformatted/transforms.js index b759773c0d3cbb..357ee93fe7ff00 100644 --- a/packages/block-library/src/preformatted/transforms.js +++ b/packages/block-library/src/preformatted/transforms.js @@ -15,13 +15,12 @@ const transforms = { }, { type: 'raw', - isMatch: ( node ) => ( + isMatch: ( node ) => node.nodeName === 'PRE' && ! ( node.children.length === 1 && node.firstChild.nodeName === 'CODE' - ) - ), + ), schema: ( { phrasingContentSchema } ) => ( { pre: { children: phrasingContentSchema, diff --git a/packages/block-library/src/pullquote/deprecated.js b/packages/block-library/src/pullquote/deprecated.js index 150bd1eb615c9c..8bb38be2c3b994 100644 --- a/packages/block-library/src/pullquote/deprecated.js +++ b/packages/block-library/src/pullquote/deprecated.js @@ -12,9 +12,7 @@ import { getColorObjectByAttributeValues, RichText, } from '@wordpress/block-editor'; -import { - select, -} from '@wordpress/data'; +import { select } from '@wordpress/data'; /** * Internal dependencies @@ -88,23 +86,28 @@ const deprecated = [ // Is solid color style if ( isSolidColorStyle ) { - const backgroundClass = getColorClassName( 'background-color', mainColor ); + const backgroundClass = getColorClassName( + 'background-color', + mainColor + ); figureClasses = classnames( { - 'has-background': ( backgroundClass || customMainColor ), + 'has-background': backgroundClass || customMainColor, [ backgroundClass ]: backgroundClass, } ); figureStyles = { - backgroundColor: backgroundClass ? undefined : customMainColor, + backgroundColor: backgroundClass + ? undefined + : customMainColor, }; - // Is normal style and a custom color is being used ( we can set a style directly with its value) + // Is normal style and a custom color is being used ( we can set a style directly with its value) } else if ( customMainColor ) { figureStyles = { borderColor: customMainColor, }; - // If normal style and a named color are being used, we need to retrieve the color value to set the style, - // as there is no expectation that themes create classes that set border colors. + // If normal style and a named color are being used, we need to retrieve the color value to set the style, + // as there is no expectation that themes create classes that set border colors. } else if ( mainColor ) { // Previously here we queried the color settings to know the color value // of a named color. This made the save function impure and the block was refactored, @@ -117,18 +120,33 @@ const deprecated = [ }; } - const blockquoteTextColorClass = getColorClassName( 'color', textColor ); - const blockquoteClasses = ( textColor || customTextColor ) && classnames( 'has-text-color', { - [ blockquoteTextColorClass ]: blockquoteTextColorClass, - } ); + const blockquoteTextColorClass = getColorClassName( + 'color', + textColor + ); + const blockquoteClasses = + ( textColor || customTextColor ) && + classnames( 'has-text-color', { + [ blockquoteTextColorClass ]: blockquoteTextColorClass, + } ); - const blockquoteStyles = blockquoteTextColorClass ? undefined : { color: customTextColor }; + const blockquoteStyles = blockquoteTextColorClass + ? undefined + : { color: customTextColor }; return ( <figure className={ figureClasses } style={ figureStyles }> - <blockquote className={ blockquoteClasses } style={ blockquoteStyles } > + <blockquote + className={ blockquoteClasses } + style={ blockquoteStyles } + > <RichText.Content value={ value } multiline /> - { ! RichText.isEmpty( citation ) && <RichText.Content tagName="cite" value={ citation } /> } + { ! RichText.isEmpty( citation ) && ( + <RichText.Content + tagName="cite" + value={ citation } + /> + ) } </blockquote> </figure> ); @@ -158,13 +176,24 @@ const deprecated = [ { attributes: blockAttributes, save( { attributes } ) { - const { mainColor, customMainColor, textColor, customTextColor, value, citation, className } = attributes; + const { + mainColor, + customMainColor, + textColor, + customTextColor, + value, + citation, + className, + } = attributes; const isSolidColorStyle = includes( className, SOLID_COLOR_CLASS ); let figureClass, figureStyles; // Is solid color style if ( isSolidColorStyle ) { - figureClass = getColorClassName( 'background-color', mainColor ); + figureClass = getColorClassName( + 'background-color', + mainColor + ); if ( ! figureClass ) { figureStyles = { backgroundColor: customMainColor, @@ -178,23 +207,46 @@ const deprecated = [ // Is normal style and a named color is being used, we need to retrieve the color value to set the style, // as there is no expectation that themes create classes that set border colors. } else if ( mainColor ) { - const colors = get( select( 'core/block-editor' ).getSettings(), [ 'colors' ], [] ); - const colorObject = getColorObjectByAttributeValues( colors, mainColor ); + const colors = get( + select( 'core/block-editor' ).getSettings(), + [ 'colors' ], + [] + ); + const colorObject = getColorObjectByAttributeValues( + colors, + mainColor + ); figureStyles = { borderColor: colorObject.color, }; } - const blockquoteTextColorClass = getColorClassName( 'color', textColor ); - const blockquoteClasses = textColor || customTextColor ? classnames( 'has-text-color', { - [ blockquoteTextColorClass ]: blockquoteTextColorClass, - } ) : undefined; - const blockquoteStyle = blockquoteTextColorClass ? undefined : { color: customTextColor }; + const blockquoteTextColorClass = getColorClassName( + 'color', + textColor + ); + const blockquoteClasses = + textColor || customTextColor + ? classnames( 'has-text-color', { + [ blockquoteTextColorClass ]: blockquoteTextColorClass, + } ) + : undefined; + const blockquoteStyle = blockquoteTextColorClass + ? undefined + : { color: customTextColor }; return ( <figure className={ figureClass } style={ figureStyles }> - <blockquote className={ blockquoteClasses } style={ blockquoteStyle } > + <blockquote + className={ blockquoteClasses } + style={ blockquoteStyle } + > <RichText.Content value={ value } multiline /> - { ! RichText.isEmpty( citation ) && <RichText.Content tagName="cite" value={ citation } /> } + { ! RichText.isEmpty( citation ) && ( + <RichText.Content + tagName="cite" + value={ citation } + /> + ) } </blockquote> </figure> ); @@ -209,11 +261,14 @@ const deprecated = [ return ( <blockquote> <RichText.Content value={ value } multiline /> - { ! RichText.isEmpty( citation ) && <RichText.Content tagName="cite" value={ citation } /> } + { ! RichText.isEmpty( citation ) && ( + <RichText.Content tagName="cite" value={ citation } /> + ) } </blockquote> ); }, - }, { + }, + { attributes: { ...blockAttributes, citation: { @@ -233,7 +288,9 @@ const deprecated = [ return ( <blockquote className={ `align${ align }` }> <RichText.Content value={ value } multiline /> - { ! RichText.isEmpty( citation ) && <RichText.Content tagName="footer" value={ citation } /> } + { ! RichText.isEmpty( citation ) && ( + <RichText.Content tagName="footer" value={ citation } /> + ) } </blockquote> ); }, diff --git a/packages/block-library/src/pullquote/edit.js b/packages/block-library/src/pullquote/edit.js index f9989bed415c8a..28103339b80e03 100644 --- a/packages/block-library/src/pullquote/edit.js +++ b/packages/block-library/src/pullquote/edit.js @@ -27,15 +27,28 @@ class PullQuoteEdit extends Component { super( props ); this.wasTextColorAutomaticallyComputed = false; - this.pullQuoteMainColorSetter = this.pullQuoteMainColorSetter.bind( this ); - this.pullQuoteTextColorSetter = this.pullQuoteTextColorSetter.bind( this ); + this.pullQuoteMainColorSetter = this.pullQuoteMainColorSetter.bind( + this + ); + this.pullQuoteTextColorSetter = this.pullQuoteTextColorSetter.bind( + this + ); } pullQuoteMainColorSetter( colorValue ) { - const { colorUtils, textColor, setAttributes, setTextColor, setMainColor, className } = this.props; + const { + colorUtils, + textColor, + setAttributes, + setTextColor, + setMainColor, + className, + } = this.props; const isSolidColorStyle = includes( className, SOLID_COLOR_CLASS ); - const needTextColor = ! textColor.color || this.wasTextColorAutomaticallyComputed; - const shouldSetTextColor = isSolidColorStyle && needTextColor && colorValue; + const needTextColor = + ! textColor.color || this.wasTextColorAutomaticallyComputed; + const shouldSetTextColor = + isSolidColorStyle && needTextColor && colorValue; if ( isSolidColorStyle ) { // If we use the solid color style, set the color using the normal mechanism. @@ -59,19 +72,21 @@ class PullQuoteEdit extends Component { } componentDidUpdate( prevProps ) { - const { - attributes, - className, - mainColor, - setAttributes, - } = this.props; + const { attributes, className, mainColor, setAttributes } = this.props; // If the block includes a named color and we switched from the // solid color style to the default style. - if ( attributes.mainColor && ! includes( className, SOLID_COLOR_CLASS ) && includes( prevProps.className, SOLID_COLOR_CLASS ) ) { + if ( + attributes.mainColor && + ! includes( className, SOLID_COLOR_CLASS ) && + includes( prevProps.className, SOLID_COLOR_CLASS ) + ) { // Remove the named color, and set the color as a custom color. // This is done because named colors use classes, in the default style we use a border color, // and themes don't set classes for border colors. - setAttributes( { mainColor: undefined, customMainColor: mainColor.color } ); + setAttributes( { + mainColor: undefined, + customMainColor: mainColor.color, + } ); } } @@ -88,9 +103,9 @@ class PullQuoteEdit extends Component { const { value, citation } = attributes; const isSolidColorStyle = includes( className, SOLID_COLOR_CLASS ); - const figureStyles = isSolidColorStyle ? - { backgroundColor: mainColor.color } : - { borderColor: mainColor.color }; + const figureStyles = isSolidColorStyle + ? { backgroundColor: mainColor.color } + : { borderColor: mainColor.color }; const figureClasses = classnames( className, { 'has-background': isSolidColorStyle && mainColor.color, @@ -101,20 +116,24 @@ class PullQuoteEdit extends Component { color: textColor.color, }; - const blockquoteClasses = textColor.color && classnames( - 'has-text-color', - { [ textColor.class ]: textColor.class } - ); + const blockquoteClasses = + textColor.color && + classnames( 'has-text-color', { + [ textColor.class ]: textColor.class, + } ); return ( <> <figure style={ figureStyles } className={ figureClasses }> - <blockquote style={ blockquoteStyles } className={ blockquoteClasses }> + <blockquote + style={ blockquoteStyles } + className={ blockquoteClasses } + > <RichText multiline value={ value } - onChange={ - ( nextValue ) => setAttributes( { + onChange={ ( nextValue ) => + setAttributes( { value: nextValue, } ) } @@ -130,8 +149,8 @@ class PullQuoteEdit extends Component { // translators: placeholder text used for the citation __( 'Write citation…' ) } - onChange={ - ( nextCitation ) => setAttributes( { + onChange={ ( nextCitation ) => + setAttributes( { citation: nextCitation, } ) } @@ -172,6 +191,7 @@ class PullQuoteEdit extends Component { } } -export default withColors( { mainColor: 'background-color', textColor: 'color' } )( - PullQuoteEdit -); +export default withColors( { + mainColor: 'background-color', + textColor: 'color', +} )( PullQuoteEdit ); diff --git a/packages/block-library/src/pullquote/index.js b/packages/block-library/src/pullquote/index.js index 98120582530911..0751f678b7c549 100644 --- a/packages/block-library/src/pullquote/index.js +++ b/packages/block-library/src/pullquote/index.js @@ -19,19 +19,28 @@ export { metadata, name }; export const settings = { title: __( 'Pullquote' ), - description: __( 'Give special visual emphasis to a quote from your text.' ), + description: __( + 'Give special visual emphasis to a quote from your text.' + ), icon, example: { attributes: { - value: '<p>' + - // translators: Quote serving as example for the Pullquote block. Attributed to Matt Mullenweg. - __( 'One of the hardest things to do in technology is disrupt yourself.' ) + - '</p>', + value: + '<p>' + + // translators: Quote serving as example for the Pullquote block. Attributed to Matt Mullenweg. + __( + 'One of the hardest things to do in technology is disrupt yourself.' + ) + + '</p>', citation: __( 'Matt Mullenweg' ), }, }, styles: [ - { name: 'default', label: _x( 'Default', 'block style' ), isDefault: true }, + { + name: 'default', + label: _x( 'Default', 'block style' ), + isDefault: true, + }, { name: SOLID_COLOR_STYLE_NAME, label: __( 'Solid Color' ) }, ], supports: { diff --git a/packages/block-library/src/pullquote/save.js b/packages/block-library/src/pullquote/save.js index c2f9833046fa00..85c6d3c062a51b 100644 --- a/packages/block-library/src/pullquote/save.js +++ b/packages/block-library/src/pullquote/save.js @@ -7,10 +7,7 @@ import { includes } from 'lodash'; /** * WordPress dependencies */ -import { - getColorClassName, - RichText, -} from '@wordpress/block-editor'; +import { getColorClassName, RichText } from '@wordpress/block-editor'; /** * Internal dependencies @@ -34,17 +31,20 @@ export default function save( { attributes } ) { // Is solid color style if ( isSolidColorStyle ) { - const backgroundClass = getColorClassName( 'background-color', mainColor ); + const backgroundClass = getColorClassName( + 'background-color', + mainColor + ); figureClasses = classnames( { - 'has-background': ( backgroundClass || customMainColor ), + 'has-background': backgroundClass || customMainColor, [ backgroundClass ]: backgroundClass, } ); figureStyles = { backgroundColor: backgroundClass ? undefined : customMainColor, }; - // Is normal style and a custom color is being used ( we can set a style directly with its value) + // Is normal style and a custom color is being used ( we can set a style directly with its value) } else if ( customMainColor ) { figureStyles = { borderColor: customMainColor, @@ -52,17 +52,26 @@ export default function save( { attributes } ) { } const blockquoteTextColorClass = getColorClassName( 'color', textColor ); - const blockquoteClasses = ( textColor || customTextColor ) && classnames( 'has-text-color', { - [ blockquoteTextColorClass ]: blockquoteTextColorClass, - } ); + const blockquoteClasses = + ( textColor || customTextColor ) && + classnames( 'has-text-color', { + [ blockquoteTextColorClass ]: blockquoteTextColorClass, + } ); - const blockquoteStyles = blockquoteTextColorClass ? undefined : { color: customTextColor }; + const blockquoteStyles = blockquoteTextColorClass + ? undefined + : { color: customTextColor }; return ( <figure className={ figureClasses } style={ figureStyles }> - <blockquote className={ blockquoteClasses } style={ blockquoteStyles } > + <blockquote + className={ blockquoteClasses } + style={ blockquoteStyles } + > <RichText.Content value={ value } multiline /> - { ! RichText.isEmpty( citation ) && <RichText.Content tagName="cite" value={ citation } /> } + { ! RichText.isEmpty( citation ) && ( + <RichText.Content tagName="cite" value={ citation } /> + ) } </blockquote> </figure> ); diff --git a/packages/block-library/src/quote/deprecated.js b/packages/block-library/src/quote/deprecated.js index 5b8637138a2ae8..f74236f7f5a263 100644 --- a/packages/block-library/src/quote/deprecated.js +++ b/packages/block-library/src/quote/deprecated.js @@ -36,7 +36,9 @@ const deprecated = [ return ( <blockquote style={ { textAlign: align ? align : null } }> <RichText.Content multiline value={ value } /> - { ! RichText.isEmpty( citation ) && <RichText.Content tagName="cite" value={ citation } /> } + { ! RichText.isEmpty( citation ) && ( + <RichText.Content tagName="cite" value={ citation } /> + ) } </blockquote> ); }, @@ -54,7 +56,9 @@ const deprecated = [ if ( attributes.style === 2 ) { return { ...omit( attributes, [ 'style' ] ), - className: attributes.className ? attributes.className + ' is-style-large' : 'is-style-large', + className: attributes.className + ? attributes.className + ' is-style-large' + : 'is-style-large', }; } @@ -70,7 +74,9 @@ const deprecated = [ style={ { textAlign: align ? align : null } } > <RichText.Content multiline value={ value } /> - { ! RichText.isEmpty( citation ) && <RichText.Content tagName="cite" value={ citation } /> } + { ! RichText.isEmpty( citation ) && ( + <RichText.Content tagName="cite" value={ citation } /> + ) } </blockquote> ); }, @@ -99,7 +105,9 @@ const deprecated = [ style={ { textAlign: align ? align : null } } > <RichText.Content multiline value={ value } /> - { ! RichText.isEmpty( citation ) && <RichText.Content tagName="footer" value={ citation } /> } + { ! RichText.isEmpty( citation ) && ( + <RichText.Content tagName="footer" value={ citation } /> + ) } </blockquote> ); }, diff --git a/packages/block-library/src/quote/edit.js b/packages/block-library/src/quote/edit.js index 32793f60692a60..e2bfc902cc0359 100644 --- a/packages/block-library/src/quote/edit.js +++ b/packages/block-library/src/quote/edit.js @@ -7,11 +7,22 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { AlignmentToolbar, BlockControls, RichText } from '@wordpress/block-editor'; +import { + AlignmentToolbar, + BlockControls, + RichText, +} from '@wordpress/block-editor'; import { BlockQuotation } from '@wordpress/components'; import { createBlock } from '@wordpress/blocks'; -export default function QuoteEdit( { attributes, setAttributes, isSelected, mergeBlocks, onReplace, className } ) { +export default function QuoteEdit( { + attributes, + setAttributes, + isSelected, + mergeBlocks, + onReplace, + className, +} ) { const { align, value, citation } = attributes; return ( @@ -33,14 +44,15 @@ export default function QuoteEdit( { attributes, setAttributes, isSelected, merg identifier="value" multiline value={ value } - onChange={ - ( nextValue ) => setAttributes( { + onChange={ ( nextValue ) => + setAttributes( { value: nextValue, } ) } onMerge={ mergeBlocks } onRemove={ ( forward ) => { - const hasEmptyCitation = ! citation || citation.length === 0; + const hasEmptyCitation = + ! citation || citation.length === 0; if ( ! forward && hasEmptyCitation ) { onReplace( [] ); } @@ -64,8 +76,8 @@ export default function QuoteEdit( { attributes, setAttributes, isSelected, merg <RichText identifier="citation" value={ citation } - onChange={ - ( nextCitation ) => setAttributes( { + onChange={ ( nextCitation ) => + setAttributes( { citation: nextCitation, } ) } diff --git a/packages/block-library/src/quote/index.js b/packages/block-library/src/quote/index.js index f38b1bbb48c33f..f96076b91e4761 100644 --- a/packages/block-library/src/quote/index.js +++ b/packages/block-library/src/quote/index.js @@ -19,18 +19,25 @@ export { metadata, name }; export const settings = { title: __( 'Quote' ), - description: __( 'Give quoted text visual emphasis. "In quoting others, we cite ourselves." — Julio Cortázar' ), + description: __( + 'Give quoted text visual emphasis. "In quoting others, we cite ourselves." — Julio Cortázar' + ), icon, keywords: [ __( 'blockquote' ), __( 'cite' ) ], example: { attributes: { - value: '<p>' + __( 'In quoting others, we cite ourselves.' ) + '</p>', + value: + '<p>' + __( 'In quoting others, we cite ourselves.' ) + '</p>', citation: 'Julio Cortázar', className: 'is-style-large', }, }, styles: [ - { name: 'default', label: _x( 'Default', 'block style' ), isDefault: true }, + { + name: 'default', + label: _x( 'Default', 'block style' ), + isDefault: true, + }, { name: 'large', label: _x( 'Large', 'block style' ) }, ], transforms, diff --git a/packages/block-library/src/quote/save.js b/packages/block-library/src/quote/save.js index 15d8445d7a740a..92374a593ba7fd 100644 --- a/packages/block-library/src/quote/save.js +++ b/packages/block-library/src/quote/save.js @@ -18,7 +18,9 @@ export default function save( { attributes } ) { return ( <blockquote className={ className }> <RichText.Content multiline value={ value } /> - { ! RichText.isEmpty( citation ) && <RichText.Content tagName="cite" value={ citation } /> } + { ! RichText.isEmpty( citation ) && ( + <RichText.Content tagName="cite" value={ citation } /> + ) } </blockquote> ); } diff --git a/packages/block-library/src/quote/transforms.js b/packages/block-library/src/quote/transforms.js index d7deb376ae2da9..574029cc07c1ba 100644 --- a/packages/block-library/src/quote/transforms.js +++ b/packages/block-library/src/quote/transforms.js @@ -13,9 +13,12 @@ const transforms = { transform: ( attributes ) => { return createBlock( 'core/quote', { value: toHTMLString( { - value: join( attributes.map( ( { content } ) => - create( { html: content } ) - ), '\u2028' ), + value: join( + attributes.map( ( { content } ) => + create( { html: content } ) + ), + '\u2028' + ), multilineTag: 'p', } ), } ); @@ -33,10 +36,11 @@ const transforms = { { type: 'block', blocks: [ 'core/pullquote' ], - transform: ( { value, citation } ) => createBlock( 'core/quote', { - value, - citation, - } ), + transform: ( { value, citation } ) => + createBlock( 'core/quote', { + value, + citation, + } ), }, { type: 'prefix', @@ -58,21 +62,20 @@ const transforms = { return true; } // Child is a cite and no other cite child exists before it. - if ( - ! hasCitation && - child.nodeName === 'CITE' - ) { + if ( ! hasCitation && child.nodeName === 'CITE' ) { hasCitation = true; return true; } }; } )(); - return node.nodeName === 'BLOCKQUOTE' && + return ( + node.nodeName === 'BLOCKQUOTE' && // The quote block can only handle multiline paragraph // content with an optional cite child. Array.from( node.childNodes ).every( isParagraphOrSingleCite - ); + ) + ); }, schema: ( { phrasingContentSchema } ) => ( { blockquote: { @@ -96,12 +99,14 @@ const transforms = { const paragraphs = []; if ( value && value !== '<p></p>' ) { paragraphs.push( - ...split( create( { html: value, multilineTag: 'p' } ), '\u2028' ) - .map( ( piece ) => - createBlock( 'core/paragraph', { - content: toHTMLString( { value: piece } ), - } ) - ) + ...split( + create( { html: value, multilineTag: 'p' } ), + '\u2028' + ).map( ( piece ) => + createBlock( 'core/paragraph', { + content: toHTMLString( { value: piece } ), + } ) + ) ); } if ( citation && citation !== '<p></p>' ) { @@ -134,7 +139,10 @@ const transforms = { } ); } - const pieces = split( create( { html: value, multilineTag: 'p' } ), '\u2028' ); + const pieces = split( + create( { html: value, multilineTag: 'p' } ), + '\u2028' + ); const headingBlock = createBlock( 'core/heading', { content: toHTMLString( { value: pieces[ 0 ] } ), @@ -150,7 +158,9 @@ const transforms = { ...attrs, citation, value: toHTMLString( { - value: quotePieces.length ? join( pieces.slice( 1 ), '\u2028' ) : create(), + value: quotePieces.length + ? join( pieces.slice( 1 ), '\u2028' ) + : create(), multilineTag: 'p', } ), } ); diff --git a/packages/block-library/src/rss/edit.js b/packages/block-library/src/rss/edit.js index 854ae8e53634c4..f38601fcb73915 100644 --- a/packages/block-library/src/rss/edit.js +++ b/packages/block-library/src/rss/edit.js @@ -13,10 +13,7 @@ import { ToolbarGroup, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { - BlockControls, - InspectorControls, -} from '@wordpress/block-editor'; +import { BlockControls, InspectorControls } from '@wordpress/block-editor'; import ServerSideRender from '@wordpress/server-side-render'; const DEFAULT_MIN_ITEMS = 1; @@ -67,15 +64,14 @@ class RSSEdit extends Component { if ( this.state.editing ) { return ( - <Placeholder - icon="rss" - label="RSS" - > + <Placeholder icon="rss" label="RSS"> <form onSubmit={ this.onSubmitURL }> <TextControl placeholder={ __( 'Enter URL here…' ) } value={ feedURL } - onChange={ ( value ) => setAttributes( { feedURL: value } ) } + onChange={ ( value ) => + setAttributes( { feedURL: value } ) + } className={ 'components-placeholder__input' } /> <Button isSecondary type="submit"> @@ -116,7 +112,9 @@ class RSSEdit extends Component { <RangeControl label={ __( 'Number of items' ) } value={ itemsToShow } - onChange={ ( value ) => setAttributes( { itemsToShow: value } ) } + onChange={ ( value ) => + setAttributes( { itemsToShow: value } ) + } min={ DEFAULT_MIN_ITEMS } max={ DEFAULT_MAX_ITEMS } required @@ -134,28 +132,34 @@ class RSSEdit extends Component { <ToggleControl label={ __( 'Display excerpt' ) } checked={ displayExcerpt } - onChange={ this.toggleAttribute( 'displayExcerpt' ) } + onChange={ this.toggleAttribute( + 'displayExcerpt' + ) } /> - { displayExcerpt && + { displayExcerpt && ( <RangeControl label={ __( 'Max number of words in excerpt' ) } value={ excerptLength } - onChange={ ( value ) => setAttributes( { excerptLength: value } ) } + onChange={ ( value ) => + setAttributes( { excerptLength: value } ) + } min={ 10 } max={ 100 } required /> - } - { blockLayout === 'grid' && + ) } + { blockLayout === 'grid' && ( <RangeControl label={ __( 'Columns' ) } value={ columns } - onChange={ ( value ) => setAttributes( { columns: value } ) } + onChange={ ( value ) => + setAttributes( { columns: value } ) + } min={ 2 } max={ 6 } required /> - } + ) } </PanelBody> </InspectorControls> <Disabled> diff --git a/packages/block-library/src/search/edit.js b/packages/block-library/src/search/edit.js index c58276670a44e0..5de591fb867aec 100644 --- a/packages/block-library/src/search/edit.js +++ b/packages/block-library/src/search/edit.js @@ -23,9 +23,13 @@ export default function SearchEdit( { className, attributes, setAttributes } ) { // We hide the placeholder field's placeholder when there is a value. This // stops screen readers from reading the placeholder field's placeholder // which is confusing. - placeholder={ placeholder ? undefined : __( 'Optional placeholder…' ) } + placeholder={ + placeholder ? undefined : __( 'Optional placeholder…' ) + } value={ placeholder } - onChange={ ( event ) => setAttributes( { placeholder: event.target.value } ) } + onChange={ ( event ) => + setAttributes( { placeholder: event.target.value } ) + } /> <RichText className="wp-block-search__button" diff --git a/packages/block-library/src/separator/edit.js b/packages/block-library/src/separator/edit.js index 30dd0e85d71549..854d0bb6adb3d2 100644 --- a/packages/block-library/src/separator/edit.js +++ b/packages/block-library/src/separator/edit.js @@ -17,21 +17,16 @@ function SeparatorEdit( { color, setColor, className } ) { return ( <> <HorizontalRule - className={ classnames( - className, { - 'has-background': color.color, - [ color.class ]: color.class, - } - ) } + className={ classnames( className, { + 'has-background': color.color, + [ color.class ]: color.class, + } ) } style={ { backgroundColor: color.color, color: color.color, } } /> - <SeparatorSettings - color={ color } - setColor={ setColor } - /> + <SeparatorSettings color={ color } setColor={ setColor } /> </> ); } diff --git a/packages/block-library/src/separator/index.js b/packages/block-library/src/separator/index.js index 23f1d47505e8fa..2ee3dd8f0aff61 100644 --- a/packages/block-library/src/separator/index.js +++ b/packages/block-library/src/separator/index.js @@ -18,14 +18,15 @@ export { metadata, name }; export const settings = { title: __( 'Separator' ), - description: __( 'Create a break between ideas or sections with a horizontal separator.' ), + description: __( + 'Create a break between ideas or sections with a horizontal separator.' + ), icon, keywords: [ __( 'horizontal-line' ), 'hr', __( 'divider' ) ], example: { attributes: { customColor: '#065174', className: 'is-style-wide', - }, }, styles: [ diff --git a/packages/block-library/src/separator/save.js b/packages/block-library/src/separator/save.js index d9b899486233c8..e986d1f7c37800 100644 --- a/packages/block-library/src/separator/save.js +++ b/packages/block-library/src/separator/save.js @@ -6,15 +6,10 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { - getColorClassName, -} from '@wordpress/block-editor'; +import { getColorClassName } from '@wordpress/block-editor'; export default function separatorSave( { attributes } ) { - const { - color, - customColor, - } = attributes; + const { color, customColor } = attributes; // the hr support changing color using border-color, since border-color // is not yet supported in the color palette, we use background-color @@ -34,8 +29,5 @@ export default function separatorSave( { attributes } ) { color: colorClass ? undefined : customColor, }; - return ( <hr - className={ separatorClasses } - style={ separatorStyle } - /> ); + return <hr className={ separatorClasses } style={ separatorStyle } />; } diff --git a/packages/block-library/src/separator/separator-settings.js b/packages/block-library/src/separator/separator-settings.js index 3a5e5d55b18b4a..320cfc862b93bd 100644 --- a/packages/block-library/src/separator/separator-settings.js +++ b/packages/block-library/src/separator/separator-settings.js @@ -2,10 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { - InspectorControls, - PanelColorSettings, -} from '@wordpress/block-editor'; +import { InspectorControls, PanelColorSettings } from '@wordpress/block-editor'; const SeparatorSettings = ( { color, setColor } ) => ( <InspectorControls> @@ -18,8 +15,7 @@ const SeparatorSettings = ( { color, setColor } ) => ( label: __( 'Color' ), }, ] } - > - </PanelColorSettings> + ></PanelColorSettings> </InspectorControls> ); diff --git a/packages/block-library/src/shortcode/edit.js b/packages/block-library/src/shortcode/edit.js index 6de2e296f614e3..c4705d9d7f3dec 100644 --- a/packages/block-library/src/shortcode/edit.js +++ b/packages/block-library/src/shortcode/edit.js @@ -12,7 +12,10 @@ export default function ShortcodeEdit( { attributes, setAttributes } ) { return ( <div className="wp-block-shortcode components-placeholder"> - <label htmlFor={ inputId } className="components-placeholder__label"> + <label + htmlFor={ inputId } + className="components-placeholder__label" + > <Dashicon icon="shortcode" /> { __( 'Shortcode' ) } </label> diff --git a/packages/block-library/src/shortcode/edit.native.js b/packages/block-library/src/shortcode/edit.native.js index 67aa699836dbe1..34a7627425566c 100644 --- a/packages/block-library/src/shortcode/edit.native.js +++ b/packages/block-library/src/shortcode/edit.native.js @@ -17,14 +17,29 @@ import { withPreferredColorScheme } from '@wordpress/compose'; import styles from './style.scss'; export function ShortcodeEdit( props ) { - const { attributes, setAttributes, onFocus, onBlur, getStylesFromColorScheme } = props; - const titleStyle = getStylesFromColorScheme( styles.blockTitle, styles.blockTitleDark ); - const shortcodeStyle = getStylesFromColorScheme( styles.blockShortcode, styles.blockShortcodeDark ); - const placeholderStyle = getStylesFromColorScheme( styles.placeholder, styles.placeholderDark ); + const { + attributes, + setAttributes, + onFocus, + onBlur, + getStylesFromColorScheme, + } = props; + const titleStyle = getStylesFromColorScheme( + styles.blockTitle, + styles.blockTitleDark + ); + const shortcodeStyle = getStylesFromColorScheme( + styles.blockShortcode, + styles.blockShortcodeDark + ); + const placeholderStyle = getStylesFromColorScheme( + styles.placeholder, + styles.placeholderDark + ); return ( <View> - <Text style={ titleStyle } >{ __( 'Shortcode' ) }</Text> + <Text style={ titleStyle }>{ __( 'Shortcode' ) }</Text> <PlainText value={ attributes.text } style={ shortcodeStyle } @@ -42,8 +57,10 @@ export function ShortcodeEdit( props ) { * For some devices autoCorrect and autoComplete are not enough to hide the suggestion toolbar. * Following the suggestion below we added the keyboard type as well. * https://stackoverflow.com/questions/37001070/how-to-avoid-the-suggestions-of-keyboard-for-android-in-react-native/51411575#51411575 - */ - keyboardType={ Platform.OS === 'ios' ? 'default' : 'visible-password' } + */ + keyboardType={ + Platform.OS === 'ios' ? 'default' : 'visible-password' + } placeholderTextColor={ placeholderStyle.color } /> </View> diff --git a/packages/block-library/src/shortcode/index.js b/packages/block-library/src/shortcode/index.js index efc7ca93e5a4e3..74ff32f216d733 100644 --- a/packages/block-library/src/shortcode/index.js +++ b/packages/block-library/src/shortcode/index.js @@ -18,7 +18,9 @@ export { metadata, name }; export const settings = { title: __( 'Shortcode' ), - description: __( 'Insert additional custom elements with a WordPress shortcode.' ), + description: __( + 'Insert additional custom elements with a WordPress shortcode.' + ), icon, transforms, supports: { diff --git a/packages/block-library/src/shortcode/test/edit.native.js b/packages/block-library/src/shortcode/test/edit.native.js index bf90133b05b72e..3fca97c6800cb5 100644 --- a/packages/block-library/src/shortcode/test/edit.native.js +++ b/packages/block-library/src/shortcode/test/edit.native.js @@ -11,16 +11,27 @@ import { TextInput } from 'react-native'; describe( 'Shortcode', () => { it( 'renders without crashing', () => { - const component = renderer.create( <Shortcode attributes={ { text: '' } } /> ); + const component = renderer.create( + <Shortcode attributes={ { text: '' } } /> + ); const rendered = component.toJSON(); expect( rendered ).toBeTruthy(); } ); it( 'renders given text without crashing', () => { - const component = renderer.create( <Shortcode attributes={ { text: '[youtube https://www.youtube.com/watch?v=ssfHW5lwFZg]' } } /> ); + const component = renderer.create( + <Shortcode + attributes={ { + text: + '[youtube https://www.youtube.com/watch?v=ssfHW5lwFZg]', + } } + /> + ); const testInstance = component.root; const textInput = testInstance.findByType( TextInput ); expect( textInput ).toBeTruthy(); - expect( textInput.props.value ).toBe( '[youtube https://www.youtube.com/watch?v=ssfHW5lwFZg]' ); + expect( textInput.props.value ).toBe( + '[youtube https://www.youtube.com/watch?v=ssfHW5lwFZg]' + ); } ); } ); diff --git a/packages/block-library/src/social-link/edit.js b/packages/block-library/src/social-link/edit.js index da5809de60411a..0652646c035560 100644 --- a/packages/block-library/src/social-link/edit.js +++ b/packages/block-library/src/social-link/edit.js @@ -6,7 +6,11 @@ import classNames from 'classnames'; /** * WordPress dependencies */ -import { InspectorControls, URLPopover, URLInput } from '@wordpress/block-editor'; +import { + InspectorControls, + URLPopover, + URLInput, +} from '@wordpress/block-editor'; import { Fragment, useState } from '@wordpress/element'; import { Button, @@ -24,11 +28,9 @@ import { getIconBySite, getNameBySite } from './social-list'; const SocialLinkEdit = ( { attributes, setAttributes, isSelected } ) => { const { url, site, label } = attributes; const [ showURLPopover, setPopover ] = useState( false ); - const classes = classNames( - 'wp-social-link', - 'wp-social-link-' + site, - { 'wp-social-link__is-incomplete': ! url }, - ); + const classes = classNames( 'wp-social-link', 'wp-social-link-' + site, { + 'wp-social-link__is-incomplete': ! url, + } ); // Import icon. const IconComponent = getIconBySite( site ); @@ -37,41 +39,50 @@ const SocialLinkEdit = ( { attributes, setAttributes, isSelected } ) => { return ( <Fragment> <InspectorControls> - <PanelBody title={ sprintf( __( '%s label' ), socialLinkName ) } initialOpen={ false }> + <PanelBody + title={ sprintf( __( '%s label' ), socialLinkName ) } + initialOpen={ false } + > <PanelRow> <TextControl label={ __( 'Link Label' ) } - help={ __( 'Briefly describe the link to help screen reader users.' ) } + help={ __( + 'Briefly describe the link to help screen reader users.' + ) } value={ label } - onChange={ ( value ) => setAttributes( { label: value } ) } + onChange={ ( value ) => + setAttributes( { label: value } ) + } /> </PanelRow> </PanelBody> </InspectorControls> - <Button - className={ classes } - onClick={ () => setPopover( true ) } - > + <Button className={ classes } onClick={ () => setPopover( true ) }> <IconComponent /> { isSelected && showURLPopover && ( - <URLPopover - onClose={ () => setPopover( false ) } - > + <URLPopover onClose={ () => setPopover( false ) }> <form className="block-editor-url-popover__link-editor" onSubmit={ ( event ) => { event.preventDefault(); setPopover( false ); - } } > + } } + > <div className="block-editor-url-input"> <URLInput value={ url } - onChange={ ( nextURL ) => setAttributes( { url: nextURL } ) } + onChange={ ( nextURL ) => + setAttributes( { url: nextURL } ) + } placeholder={ __( 'Enter Address' ) } disableSuggestions={ true } /> </div> - <Button icon="editor-break" label={ __( 'Apply' ) } type="submit" /> + <Button + icon="editor-break" + label={ __( 'Apply' ) } + type="submit" + /> </form> </URLPopover> ) } diff --git a/packages/block-library/src/social-link/icons/amazon.js b/packages/block-library/src/social-link/icons/amazon.js index cf906e4671e033..bac1ca6a7ddd6b 100644 --- a/packages/block-library/src/social-link/icons/amazon.js +++ b/packages/block-library/src/social-link/icons/amazon.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const AmazonIcon = ( ) => ( +export const AmazonIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M13.582,8.182C11.934,8.367,9.78,8.49,8.238,9.166c-1.781,0.769-3.03,2.337-3.03,4.644 c0,2.953,1.86,4.429,4.253,4.429c2.02,0,3.125-0.477,4.685-2.065c0.516,0.747,0.685,1.109,1.629,1.894 c0.212,0.114,0.483,0.103,0.672-0.066l0.006,0.006c0.567-0.505,1.599-1.401,2.18-1.888c0.231-0.188,0.19-0.496,0.009-0.754 c-0.52-0.718-1.072-1.303-1.072-2.634V8.305c0-1.876,0.133-3.599-1.249-4.891C15.23,2.369,13.422,2,12.04,2 C9.336,2,6.318,3.01,5.686,6.351C5.618,6.706,5.877,6.893,6.109,6.945l2.754,0.298C9.121,7.23,9.308,6.977,9.357,6.72 c0.236-1.151,1.2-1.706,2.284-1.706c0.584,0,1.249,0.215,1.595,0.738c0.398,0.584,0.346,1.384,0.346,2.061V8.182z M13.049,14.088 c-0.451,0.8-1.169,1.291-1.967,1.291c-1.09,0-1.728-0.83-1.728-2.061c0-2.42,2.171-2.86,4.227-2.86v0.615 C13.582,12.181,13.608,13.104,13.049,14.088z M20.683,19.339C18.329,21.076,14.917,22,11.979,22c-4.118,0-7.826-1.522-10.632-4.057 c-0.22-0.199-0.024-0.471,0.241-0.317c3.027,1.762,6.771,2.823,10.639,2.823c2.608,0,5.476-0.541,8.115-1.66 C20.739,18.62,21.072,19.051,20.683,19.339z M21.336,21.043c-0.194,0.163-0.379,0.076-0.293-0.139 c0.284-0.71,0.92-2.298,0.619-2.684c-0.301-0.386-1.99-0.183-2.749-0.092c-0.23,0.027-0.266-0.173-0.059-0.319 c1.348-0.946,3.555-0.673,3.811-0.356C22.925,17.773,22.599,19.986,21.336,21.043z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/bandcamp.js b/packages/block-library/src/social-link/icons/bandcamp.js index 4033d7406b7606..e9c80c2a13656e 100644 --- a/packages/block-library/src/social-link/icons/bandcamp.js +++ b/packages/block-library/src/social-link/icons/bandcamp.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const BandcampIcon = ( ) => ( +export const BandcampIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M15.27 17.289 3 17.289 8.73 6.711 21 6.711 15.27 17.289" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/behance.js b/packages/block-library/src/social-link/icons/behance.js index 2dc0cc5cd81e14..65097726167d66 100644 --- a/packages/block-library/src/social-link/icons/behance.js +++ b/packages/block-library/src/social-link/icons/behance.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const BehanceIcon = ( ) => ( +export const BehanceIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M7.799,5.698c0.589,0,1.12,0.051,1.606,0.156c0.482,0.102,0.894,0.273,1.241,0.507c0.344,0.235,0.612,0.546,0.804,0.938 c0.188,0.387,0.281,0.871,0.281,1.443c0,0.619-0.141,1.137-0.421,1.551c-0.284,0.413-0.7,0.751-1.255,1.014 c0.756,0.218,1.317,0.601,1.689,1.146c0.374,0.549,0.557,1.205,0.557,1.975c0,0.623-0.12,1.161-0.359,1.612 c-0.241,0.457-0.569,0.828-0.973,1.114c-0.408,0.288-0.876,0.5-1.399,0.637C9.052,17.931,8.514,18,7.963,18H2V5.698H7.799 M7.449,10.668c0.481,0,0.878-0.114,1.192-0.345c0.311-0.228,0.463-0.603,0.463-1.119c0-0.286-0.051-0.523-0.152-0.707 C8.848,8.315,8.711,8.171,8.536,8.07C8.362,7.966,8.166,7.894,7.94,7.854c-0.224-0.044-0.457-0.06-0.697-0.06H4.709v2.874H7.449z M7.6,15.905c0.267,0,0.521-0.024,0.759-0.077c0.243-0.053,0.457-0.137,0.637-0.261c0.182-0.12,0.332-0.283,0.441-0.491 C9.547,14.87,9.6,14.602,9.6,14.278c0-0.633-0.18-1.084-0.533-1.357c-0.356-0.27-0.83-0.404-1.413-0.404H4.709v3.388L7.6,15.905z M16.162,15.864c0.367,0.358,0.897,0.538,1.583,0.538c0.493,0,0.92-0.125,1.277-0.374c0.354-0.248,0.571-0.514,0.654-0.79h2.155 c-0.347,1.072-0.872,1.838-1.589,2.299C19.534,18,18.67,18.23,17.662,18.23c-0.701,0-1.332-0.113-1.899-0.337 c-0.567-0.227-1.041-0.544-1.439-0.958c-0.389-0.415-0.689-0.907-0.904-1.484c-0.213-0.574-0.32-1.21-0.32-1.899 c0-0.666,0.11-1.288,0.329-1.863c0.222-0.577,0.529-1.075,0.933-1.492c0.406-0.42,0.885-0.751,1.444-0.994 c0.558-0.241,1.175-0.363,1.857-0.363c0.754,0,1.414,0.145,1.98,0.44c0.563,0.291,1.026,0.686,1.389,1.181 c0.363,0.493,0.622,1.057,0.783,1.69c0.16,0.632,0.217,1.292,0.171,1.983h-6.428C15.557,14.84,15.795,15.506,16.162,15.864 M18.973,11.184c-0.291-0.321-0.783-0.496-1.384-0.496c-0.39,0-0.714,0.066-0.973,0.2c-0.254,0.132-0.461,0.297-0.621,0.491 c-0.157,0.197-0.265,0.405-0.328,0.628c-0.063,0.217-0.101,0.413-0.111,0.587h3.98C19.478,11.969,19.265,11.509,18.973,11.184z M15.057,7.738h4.985V6.524h-4.985L15.057,7.738z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/chain.js b/packages/block-library/src/social-link/icons/chain.js index 5347ff89f3e163..676269b0a9ff4b 100644 --- a/packages/block-library/src/social-link/icons/chain.js +++ b/packages/block-library/src/social-link/icons/chain.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const ChainIcon = ( ) => ( +export const ChainIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M19.647,16.706a1.134,1.134,0,0,0-.343-.833l-2.549-2.549a1.134,1.134,0,0,0-.833-.343,1.168,1.168,0,0,0-.883.392l.233.226q.2.189.264.264a2.922,2.922,0,0,1,.184.233.986.986,0,0,1,.159.312,1.242,1.242,0,0,1,.043.337,1.172,1.172,0,0,1-1.176,1.176,1.237,1.237,0,0,1-.337-.043,1,1,0,0,1-.312-.159,2.76,2.76,0,0,1-.233-.184q-.073-.068-.264-.264l-.226-.233a1.19,1.19,0,0,0-.4.895,1.134,1.134,0,0,0,.343.833L15.837,19.3a1.13,1.13,0,0,0,.833.331,1.18,1.18,0,0,0,.833-.318l1.8-1.789a1.12,1.12,0,0,0,.343-.821Zm-8.615-8.64a1.134,1.134,0,0,0-.343-.833L8.163,4.7a1.134,1.134,0,0,0-.833-.343,1.184,1.184,0,0,0-.833.331L4.7,6.473a1.12,1.12,0,0,0-.343.821,1.134,1.134,0,0,0,.343.833l2.549,2.549a1.13,1.13,0,0,0,.833.331,1.184,1.184,0,0,0,.883-.38L8.728,10.4q-.2-.189-.264-.264A2.922,2.922,0,0,1,8.28,9.9a.986.986,0,0,1-.159-.312,1.242,1.242,0,0,1-.043-.337A1.172,1.172,0,0,1,9.254,8.079a1.237,1.237,0,0,1,.337.043,1,1,0,0,1,.312.159,2.761,2.761,0,0,1,.233.184q.073.068.264.264l.226.233a1.19,1.19,0,0,0,.4-.895ZM22,16.706a3.343,3.343,0,0,1-1.042,2.488l-1.8,1.789a3.536,3.536,0,0,1-4.988-.025l-2.525-2.537a3.384,3.384,0,0,1-1.017-2.488,3.448,3.448,0,0,1,1.078-2.561l-1.078-1.078a3.434,3.434,0,0,1-2.549,1.078,3.4,3.4,0,0,1-2.5-1.029L3.029,9.794A3.4,3.4,0,0,1,2,7.294,3.343,3.343,0,0,1,3.042,4.806l1.8-1.789A3.384,3.384,0,0,1,7.331,2a3.357,3.357,0,0,1,2.5,1.042l2.525,2.537a3.384,3.384,0,0,1,1.017,2.488,3.448,3.448,0,0,1-1.078,2.561l1.078,1.078a3.551,3.551,0,0,1,5.049-.049l2.549,2.549A3.4,3.4,0,0,1,22,16.706Z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/codepen.js b/packages/block-library/src/social-link/icons/codepen.js index 0528466f4eb539..8ea31018ab2d76 100644 --- a/packages/block-library/src/social-link/icons/codepen.js +++ b/packages/block-library/src/social-link/icons/codepen.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const CodepenIcon = ( ) => ( +export const CodepenIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M22.016,8.84c-0.002-0.013-0.005-0.025-0.007-0.037c-0.005-0.025-0.008-0.048-0.015-0.072 c-0.003-0.015-0.01-0.028-0.013-0.042c-0.008-0.02-0.015-0.04-0.023-0.062c-0.007-0.015-0.013-0.028-0.02-0.042 c-0.008-0.02-0.018-0.037-0.03-0.057c-0.007-0.013-0.017-0.027-0.025-0.038c-0.012-0.018-0.023-0.035-0.035-0.052 c-0.01-0.013-0.02-0.025-0.03-0.037c-0.015-0.017-0.028-0.032-0.043-0.045c-0.01-0.012-0.022-0.023-0.035-0.035 c-0.015-0.015-0.032-0.028-0.048-0.04c-0.012-0.01-0.025-0.02-0.037-0.03c-0.005-0.003-0.01-0.008-0.015-0.012l-9.161-6.096 c-0.289-0.192-0.666-0.192-0.955,0L2.359,8.237C2.354,8.24,2.349,8.245,2.344,8.249L2.306,8.277 c-0.017,0.013-0.033,0.027-0.048,0.04C2.246,8.331,2.234,8.342,2.222,8.352c-0.015,0.015-0.028,0.03-0.042,0.047 c-0.012,0.013-0.022,0.023-0.03,0.037C2.139,8.453,2.125,8.471,2.115,8.488C2.107,8.501,2.099,8.514,2.09,8.526 C2.079,8.548,2.069,8.565,2.06,8.585C2.054,8.6,2.047,8.613,2.04,8.626C2.032,8.648,2.025,8.67,2.019,8.69 c-0.005,0.013-0.01,0.027-0.013,0.042C1.999,8.755,1.995,8.778,1.99,8.803C1.989,8.817,1.985,8.828,1.984,8.84 C1.978,8.879,1.975,8.915,1.975,8.954v6.093c0,0.037,0.003,0.075,0.008,0.112c0.002,0.012,0.005,0.025,0.007,0.038 c0.005,0.023,0.008,0.047,0.015,0.072c0.003,0.015,0.008,0.028,0.013,0.04c0.007,0.022,0.013,0.042,0.022,0.063 c0.007,0.015,0.013,0.028,0.02,0.04c0.008,0.02,0.018,0.038,0.03,0.058c0.007,0.013,0.015,0.027,0.025,0.038 c0.012,0.018,0.023,0.035,0.035,0.052c0.01,0.013,0.02,0.025,0.03,0.037c0.013,0.015,0.028,0.032,0.042,0.045 c0.012,0.012,0.023,0.023,0.035,0.035c0.015,0.013,0.032,0.028,0.048,0.04l0.038,0.03c0.005,0.003,0.01,0.007,0.013,0.01 l9.163,6.095C11.668,21.953,11.833,22,12,22c0.167,0,0.332-0.047,0.478-0.144l9.163-6.095l0.015-0.01 c0.013-0.01,0.027-0.02,0.037-0.03c0.018-0.013,0.035-0.028,0.048-0.04c0.013-0.012,0.025-0.023,0.035-0.035 c0.017-0.015,0.03-0.032,0.043-0.045c0.01-0.013,0.02-0.025,0.03-0.037c0.013-0.018,0.025-0.035,0.035-0.052 c0.008-0.013,0.018-0.027,0.025-0.038c0.012-0.02,0.022-0.038,0.03-0.058c0.007-0.013,0.013-0.027,0.02-0.04 c0.008-0.022,0.015-0.042,0.023-0.063c0.003-0.013,0.01-0.027,0.013-0.04c0.007-0.025,0.01-0.048,0.015-0.072 c0.002-0.013,0.005-0.027,0.007-0.037c0.003-0.042,0.007-0.079,0.007-0.117V8.954C22.025,8.915,22.022,8.879,22.016,8.84z M12.862,4.464l6.751,4.49l-3.016,2.013l-3.735-2.492V4.464z M11.138,4.464v4.009l-3.735,2.494L4.389,8.954L11.138,4.464z M3.699,10.562L5.853,12l-2.155,1.438V10.562z M11.138,19.536l-6.749-4.491l3.015-2.011l3.735,2.492V19.536z M12,14.035L8.953,12 L12,9.966L15.047,12L12,14.035z M12.862,19.536v-4.009l3.735-2.492l3.016,2.011L12.862,19.536z M20.303,13.438L18.147,12 l2.156-1.438L20.303,13.438z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/deviantart.js b/packages/block-library/src/social-link/icons/deviantart.js index f0724f09f6c45f..4cf01fdeed0281 100644 --- a/packages/block-library/src/social-link/icons/deviantart.js +++ b/packages/block-library/src/social-link/icons/deviantart.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const DeviantArtIcon = ( ) => ( +export const DeviantArtIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M 18.19 5.636 18.19 2 18.188 2 14.553 2 14.19 2.366 12.474 5.636 11.935 6 5.81 6 5.81 10.994 9.177 10.994 9.477 11.357 5.81 18.363 5.81 22 5.811 22 9.447 22 9.81 21.634 11.526 18.364 12.065 18 18.19 18 18.19 13.006 14.823 13.006 14.523 12.641 18.19 5.636z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/dribbble.js b/packages/block-library/src/social-link/icons/dribbble.js index e1d20d706a600f..ec250898473c6a 100644 --- a/packages/block-library/src/social-link/icons/dribbble.js +++ b/packages/block-library/src/social-link/icons/dribbble.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const DribbbleIcon = ( ) => ( +export const DribbbleIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M12,22C6.486,22,2,17.514,2,12S6.486,2,12,2c5.514,0,10,4.486,10,10S17.514,22,12,22z M20.434,13.369 c-0.292-0.092-2.644-0.794-5.32-0.365c1.117,3.07,1.572,5.57,1.659,6.09C18.689,17.798,20.053,15.745,20.434,13.369z M15.336,19.876c-0.127-0.749-0.623-3.361-1.822-6.477c-0.019,0.006-0.038,0.013-0.056,0.019c-4.818,1.679-6.547,5.02-6.701,5.334 c1.448,1.129,3.268,1.803,5.243,1.803C13.183,20.555,14.311,20.313,15.336,19.876z M5.654,17.724 c0.193-0.331,2.538-4.213,6.943-5.637c0.111-0.036,0.224-0.07,0.337-0.102c-0.214-0.485-0.448-0.971-0.692-1.45 c-4.266,1.277-8.405,1.223-8.778,1.216c-0.003,0.087-0.004,0.174-0.004,0.261C3.458,14.207,4.29,16.21,5.654,17.724z M3.639,10.264 c0.382,0.005,3.901,0.02,7.897-1.041c-1.415-2.516-2.942-4.631-3.167-4.94C5.979,5.41,4.193,7.613,3.639,10.264z M9.998,3.709 c0.236,0.316,1.787,2.429,3.187,5c3.037-1.138,4.323-2.867,4.477-3.085C16.154,4.286,14.17,3.471,12,3.471 C11.311,3.471,10.641,3.554,9.998,3.709z M18.612,6.612C18.432,6.855,17,8.69,13.842,9.979c0.199,0.407,0.389,0.821,0.567,1.237 c0.063,0.148,0.124,0.295,0.184,0.441c2.842-0.357,5.666,0.215,5.948,0.275C20.522,9.916,19.801,8.065,18.612,6.612z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/dropbox.js b/packages/block-library/src/social-link/icons/dropbox.js index 15eb1224623ed4..e0de4bda6d2318 100644 --- a/packages/block-library/src/social-link/icons/dropbox.js +++ b/packages/block-library/src/social-link/icons/dropbox.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const DropboxIcon = ( ) => ( +export const DropboxIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M12,6.134L6.069,9.797L2,6.54l5.883-3.843L12,6.134z M2,13.054l5.883,3.843L12,13.459L6.069,9.797L2,13.054z M12,13.459 l4.116,3.439L22,13.054l-4.069-3.257L12,13.459z M22,6.54l-5.884-3.843L12,6.134l5.931,3.663L22,6.54z M12.011,14.2l-4.129,3.426 l-1.767-1.153v1.291l5.896,3.539l5.897-3.539v-1.291l-1.769,1.153L12.011,14.2z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/etsy.js b/packages/block-library/src/social-link/icons/etsy.js index 73f3cd95ccc1cd..d691d9dca09eb1 100644 --- a/packages/block-library/src/social-link/icons/etsy.js +++ b/packages/block-library/src/social-link/icons/etsy.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const EtsyIcon = ( ) => ( +export const EtsyIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M9.16033,4.038c0-.27174.02717-.43478.48913-.43478h6.22283c1.087,0,1.68478.92391,2.11957,2.663l.35326,1.38587h1.05978C19.59511,3.712,19.75815,2,19.75815,2s-2.663.29891-4.23913.29891h-7.962L3.29076,2.163v1.1413L4.731,3.57609c1.00543.19022,1.25.40761,1.33152,1.33152,0,0,.08152,2.71739.08152,7.20109s-.08152,7.17391-.08152,7.17391c0,.81522-.32609,1.11413-1.33152,1.30435l-1.44022.27174V22l4.2663-.13587h7.11957c1.60326,0,5.32609.13587,5.32609.13587.08152-.97826.625-5.40761.70652-5.89674H19.7038L18.644,18.52174c-.84239,1.90217-2.06522,2.038-3.42391,2.038H11.1712c-1.3587,0-2.01087-.54348-2.01087-1.712V12.65217s3.0163,0,3.99457.08152c.76087.05435,1.22283.27174,1.46739,1.33152l.32609,1.413h1.16848l-.08152-3.55978.163-3.587H15.02989l-.38043,1.57609c-.24457,1.03261-.40761,1.22283-1.46739,1.33152-1.38587.13587-4.02174.1087-4.02174.1087Z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/facebook.js b/packages/block-library/src/social-link/icons/facebook.js index 13b9111625bbcb..77343d7e9d1ad3 100644 --- a/packages/block-library/src/social-link/icons/facebook.js +++ b/packages/block-library/src/social-link/icons/facebook.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const FacebookIcon = ( ) => ( +export const FacebookIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M12 2C6.5 2 2 6.5 2 12c0 5 3.7 9.1 8.4 9.9v-7H7.9V12h2.5V9.8c0-2.5 1.5-3.9 3.8-3.9 1.1 0 2.2.2 2.2.2v2.5h-1.3c-1.2 0-1.6.8-1.6 1.6V12h2.8l-.4 2.9h-2.3v7C18.3 21.1 22 17 22 12c0-5.5-4.5-10-10-10z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/feed.js b/packages/block-library/src/social-link/icons/feed.js index 6386cef02bb2f6..e3521f3d13b197 100644 --- a/packages/block-library/src/social-link/icons/feed.js +++ b/packages/block-library/src/social-link/icons/feed.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const FeedIcon = ( ) => ( +export const FeedIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M2,8.667V12c5.515,0,10,4.485,10,10h3.333C15.333,14.637,9.363,8.667,2,8.667z M2,2v3.333 c9.19,0,16.667,7.477,16.667,16.667H22C22,10.955,13.045,2,2,2z M4.5,17C3.118,17,2,18.12,2,19.5S3.118,22,4.5,22S7,20.88,7,19.5 S5.882,17,4.5,17z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/fivehundredpx.js b/packages/block-library/src/social-link/icons/fivehundredpx.js index 6961e2b7e5cc26..6b0111eaeb7d92 100644 --- a/packages/block-library/src/social-link/icons/fivehundredpx.js +++ b/packages/block-library/src/social-link/icons/fivehundredpx.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const FivehundredpxIcon = ( ) => ( +export const FivehundredpxIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M6.94026,15.1412c.00437.01213.108.29862.168.44064a6.55008,6.55008,0,1,0,6.03191-9.09557,6.68654,6.68654,0,0,0-2.58357.51467A8.53914,8.53914,0,0,0,8.21268,8.61344L8.209,8.61725V3.22948l9.0504-.00008c.32934-.0036.32934-.46353.32934-.61466s0-.61091-.33035-.61467L7.47248,2a.43.43,0,0,0-.43131.42692v7.58355c0,.24466.30476.42131.58793.4819.553.11812.68074-.05864.81617-.2457l.018-.02481A10.52673,10.52673,0,0,1,9.32258,9.258a5.35268,5.35268,0,1,1,7.58985,7.54976,5.417,5.417,0,0,1-3.80867,1.56365,5.17483,5.17483,0,0,1-2.69822-.74478l.00342-4.61111a2.79372,2.79372,0,0,1,.71372-1.78792,2.61611,2.61611,0,0,1,1.98282-.89477,2.75683,2.75683,0,0,1,1.95525.79477,2.66867,2.66867,0,0,1,.79656,1.909,2.724,2.724,0,0,1-2.75849,2.748,4.94651,4.94651,0,0,1-.86254-.13719c-.31234-.093-.44519.34058-.48892.48349-.16811.54966.08453.65862.13687.67489a3.75751,3.75751,0,0,0,1.25234.18375,3.94634,3.94634,0,1,0-2.82444-6.742,3.67478,3.67478,0,0,0-1.13028,2.584l-.00041.02323c-.0035.11667-.00579,2.881-.00644,3.78811l-.00407-.00451a6.18521,6.18521,0,0,1-1.0851-1.86092c-.10544-.27856-.34358-.22925-.66857-.12917-.14192.04372-.57386.17677-.47833.489Zm4.65165-1.08338a.51346.51346,0,0,0,.19513.31818l.02276.022a.52945.52945,0,0,0,.3517.18416.24242.24242,0,0,0,.16577-.0611c.05473-.05082.67382-.67812.73287-.738l.69041.68819a.28978.28978,0,0,0,.21437.11032.53239.53239,0,0,0,.35708-.19486c.29792-.30419.14885-.46821.07676-.54751l-.69954-.69975.72952-.73469c.16-.17311.01874-.35708-.12218-.498-.20461-.20461-.402-.25742-.52855-.14083l-.7254.72665-.73354-.73375a.20128.20128,0,0,0-.14179-.05695.54135.54135,0,0,0-.34379.19648c-.22561.22555-.274.38149-.15656.5059l.73374.7315-.72942.73072A.26589.26589,0,0,0,11.59191,14.05782Zm1.59866-9.915A8.86081,8.86081,0,0,0,9.854,4.776a.26169.26169,0,0,0-.16938.22759.92978.92978,0,0,0,.08619.42094c.05682.14524.20779.531.50006.41955a8.40969,8.40969,0,0,1,2.91968-.55484,7.87875,7.87875,0,0,1,3.086.62286,8.61817,8.61817,0,0,1,2.30562,1.49315.2781.2781,0,0,0,.18318.07586c.15529,0,.30425-.15253.43167-.29551.21268-.23861.35873-.4369.1492-.63538a8.50425,8.50425,0,0,0-2.62312-1.694A9.0177,9.0177,0,0,0,13.19058,4.14283ZM19.50945,18.6236h0a.93171.93171,0,0,0-.36642-.25406.26589.26589,0,0,0-.27613.06613l-.06943.06929A7.90606,7.90606,0,0,1,7.60639,18.505a7.57284,7.57284,0,0,1-1.696-2.51537,8.58715,8.58715,0,0,1-.5147-1.77754l-.00871-.04864c-.04939-.25873-.28755-.27684-.62981-.22448-.14234.02178-.5755.088-.53426.39969l.001.00712a9.08807,9.08807,0,0,0,15.406,4.99094c.00193-.00192.04753-.04718.0725-.07436C19.79425,19.16234,19.87422,18.98728,19.50945,18.6236Z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/flickr.js b/packages/block-library/src/social-link/icons/flickr.js index 7bafa14fabd288..a9d21c2c300f9e 100644 --- a/packages/block-library/src/social-link/icons/flickr.js +++ b/packages/block-library/src/social-link/icons/flickr.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const FlickrIcon = ( ) => ( +export const FlickrIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M6.5,7c-2.75,0-5,2.25-5,5s2.25,5,5,5s5-2.25,5-5S9.25,7,6.5,7z M17.5,7c-2.75,0-5,2.25-5,5s2.25,5,5,5s5-2.25,5-5 S20.25,7,17.5,7z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/foursquare.js b/packages/block-library/src/social-link/icons/foursquare.js index 1b3b49b8c3c78b..dbd91521a4e740 100644 --- a/packages/block-library/src/social-link/icons/foursquare.js +++ b/packages/block-library/src/social-link/icons/foursquare.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const FoursquareIcon = ( ) => ( +export const FoursquareIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M17.573,2c0,0-9.197,0-10.668,0S5,3.107,5,3.805s0,16.948,0,16.948c0,0.785,0.422,1.077,0.66,1.172 c0.238,0.097,0.892,0.177,1.285-0.275c0,0,5.035-5.843,5.122-5.93c0.132-0.132,0.132-0.132,0.262-0.132h3.26 c1.368,0,1.588-0.977,1.732-1.552c0.078-0.318,0.692-3.428,1.225-6.122l0.675-3.368C19.56,2.893,19.14,2,17.573,2z M16.495,7.22 c-0.053,0.252-0.372,0.518-0.665,0.518c-0.293,0-4.157,0-4.157,0c-0.467,0-0.802,0.318-0.802,0.787v0.508 c0,0.467,0.337,0.798,0.805,0.798c0,0,3.197,0,3.528,0s0.655,0.362,0.583,0.715c-0.072,0.353-0.407,2.102-0.448,2.295 c-0.04,0.193-0.262,0.523-0.655,0.523c-0.33,0-2.88,0-2.88,0c-0.523,0-0.683,0.068-1.033,0.503 c-0.35,0.437-3.505,4.223-3.505,4.223c-0.032,0.035-0.063,0.027-0.063-0.015V4.852c0-0.298,0.26-0.648,0.648-0.648 c0,0,8.228,0,8.562,0c0.315,0,0.61,0.297,0.528,0.683L16.495,7.22z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/github.js b/packages/block-library/src/social-link/icons/github.js index 37a65e13d0beab..9f9e1d1e98f25d 100644 --- a/packages/block-library/src/social-link/icons/github.js +++ b/packages/block-library/src/social-link/icons/github.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const GitHubIcon = ( ) => ( +export const GitHubIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M12,2C6.477,2,2,6.477,2,12c0,4.419,2.865,8.166,6.839,9.489c0.5,0.09,0.682-0.218,0.682-0.484 c0-0.236-0.009-0.866-0.014-1.699c-2.782,0.602-3.369-1.34-3.369-1.34c-0.455-1.157-1.11-1.465-1.11-1.465 c-0.909-0.62,0.069-0.608,0.069-0.608c1.004,0.071,1.532,1.03,1.532,1.03c0.891,1.529,2.341,1.089,2.91,0.833 c0.091-0.647,0.349-1.086,0.635-1.337c-2.22-0.251-4.555-1.111-4.555-4.943c0-1.091,0.39-1.984,1.03-2.682 C6.546,8.54,6.202,7.524,6.746,6.148c0,0,0.84-0.269,2.75,1.025C10.295,6.95,11.15,6.84,12,6.836 c0.85,0.004,1.705,0.114,2.504,0.336c1.909-1.294,2.748-1.025,2.748-1.025c0.546,1.376,0.202,2.394,0.1,2.646 c0.64,0.699,1.026,1.591,1.026,2.682c0,3.841-2.337,4.687-4.565,4.935c0.359,0.307,0.679,0.917,0.679,1.852 c0,1.335-0.012,2.415-0.012,2.741c0,0.269,0.18,0.579,0.688,0.481C19.138,20.161,22,16.416,22,12C22,6.477,17.523,2,12,2z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/goodreads.js b/packages/block-library/src/social-link/icons/goodreads.js index 7997519010ebcb..2a9374bc2f781b 100644 --- a/packages/block-library/src/social-link/icons/goodreads.js +++ b/packages/block-library/src/social-link/icons/goodreads.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const GoodreadsIcon = ( ) => ( +export const GoodreadsIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M17.3,17.5c-0.2,0.8-0.5,1.4-1,1.9c-0.4,0.5-1,0.9-1.7,1.2C13.9,20.9,13.1,21,12,21c-0.6,0-1.3-0.1-1.9-0.2 c-0.6-0.1-1.1-0.4-1.6-0.7c-0.5-0.3-0.9-0.7-1.2-1.2c-0.3-0.5-0.5-1.1-0.5-1.7h1.5c0.1,0.5,0.2,0.9,0.5,1.2 c0.2,0.3,0.5,0.6,0.9,0.8c0.3,0.2,0.7,0.3,1.1,0.4c0.4,0.1,0.8,0.1,1.2,0.1c1.4,0,2.5-0.4,3.1-1.2c0.6-0.8,1-2,1-3.5v-1.7h0 c-0.4,0.8-0.9,1.4-1.6,1.9c-0.7,0.5-1.5,0.7-2.4,0.7c-1,0-1.9-0.2-2.6-0.5C8.7,15,8.1,14.5,7.7,14c-0.5-0.6-0.8-1.3-1-2.1 c-0.2-0.8-0.3-1.6-0.3-2.5c0-0.9,0.1-1.7,0.4-2.5c0.3-0.8,0.6-1.5,1.1-2c0.5-0.6,1.1-1,1.8-1.4C10.3,3.2,11.1,3,12,3 c0.5,0,0.9,0.1,1.3,0.2c0.4,0.1,0.8,0.3,1.1,0.5c0.3,0.2,0.6,0.5,0.9,0.8c0.3,0.3,0.5,0.6,0.6,1h0V3.4h1.5V15 C17.6,15.9,17.5,16.7,17.3,17.5z M13.8,14.1c0.5-0.3,0.9-0.7,1.3-1.1c0.3-0.5,0.6-1,0.8-1.6c0.2-0.6,0.3-1.2,0.3-1.9 c0-0.6-0.1-1.2-0.2-1.9c-0.1-0.6-0.4-1.2-0.7-1.7c-0.3-0.5-0.7-0.9-1.3-1.2c-0.5-0.3-1.1-0.5-1.9-0.5s-1.4,0.2-1.9,0.5 c-0.5,0.3-1,0.7-1.3,1.2C8.5,6.4,8.3,7,8.1,7.6C8,8.2,7.9,8.9,7.9,9.5c0,0.6,0.1,1.3,0.2,1.9C8.3,12,8.6,12.5,8.9,13 c0.3,0.5,0.8,0.8,1.3,1.1c0.5,0.3,1.1,0.4,1.9,0.4C12.7,14.5,13.3,14.4,13.8,14.1z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/google.js b/packages/block-library/src/social-link/icons/google.js index fb7813791be9b1..336e8924ae7852 100644 --- a/packages/block-library/src/social-link/icons/google.js +++ b/packages/block-library/src/social-link/icons/google.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const GoogleIcon = ( ) => ( +export const GoogleIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M12.02,10.18v3.72v0.01h5.51c-0.26,1.57-1.67,4.22-5.5,4.22c-3.31,0-6.01-2.75-6.01-6.12s2.7-6.12,6.01-6.12 c1.87,0,3.13,0.8,3.85,1.48l2.84-2.76C16.99,2.99,14.73,2,12.03,2c-5.52,0-10,4.48-10,10s4.48,10,10,10c5.77,0,9.6-4.06,9.6-9.77 c0-0.83-0.11-1.42-0.25-2.05H12.02z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/index.js b/packages/block-library/src/social-link/icons/index.js index dddbfb2db12fcc..a57b6f3d048f75 100644 --- a/packages/block-library/src/social-link/icons/index.js +++ b/packages/block-library/src/social-link/icons/index.js @@ -1,4 +1,3 @@ - export * from './amazon'; export * from './bandcamp'; export * from './behance'; diff --git a/packages/block-library/src/social-link/icons/instagram.js b/packages/block-library/src/social-link/icons/instagram.js index 01d58e95bac49e..39c2fc7ba3b09b 100644 --- a/packages/block-library/src/social-link/icons/instagram.js +++ b/packages/block-library/src/social-link/icons/instagram.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const InstagramIcon = ( ) => ( +export const InstagramIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M12,4.622c2.403,0,2.688,0.009,3.637,0.052c0.877,0.04,1.354,0.187,1.671,0.31c0.42,0.163,0.72,0.358,1.035,0.673 c0.315,0.315,0.51,0.615,0.673,1.035c0.123,0.317,0.27,0.794,0.31,1.671c0.043,0.949,0.052,1.234,0.052,3.637 s-0.009,2.688-0.052,3.637c-0.04,0.877-0.187,1.354-0.31,1.671c-0.163,0.42-0.358,0.72-0.673,1.035 c-0.315,0.315-0.615,0.51-1.035,0.673c-0.317,0.123-0.794,0.27-1.671,0.31c-0.949,0.043-1.233,0.052-3.637,0.052 s-2.688-0.009-3.637-0.052c-0.877-0.04-1.354-0.187-1.671-0.31c-0.42-0.163-0.72-0.358-1.035-0.673 c-0.315-0.315-0.51-0.615-0.673-1.035c-0.123-0.317-0.27-0.794-0.31-1.671C4.631,14.688,4.622,14.403,4.622,12 s0.009-2.688,0.052-3.637c0.04-0.877,0.187-1.354,0.31-1.671c0.163-0.42,0.358-0.72,0.673-1.035 c0.315-0.315,0.615-0.51,1.035-0.673c0.317-0.123,0.794-0.27,1.671-0.31C9.312,4.631,9.597,4.622,12,4.622 M12,3 C9.556,3,9.249,3.01,8.289,3.054C7.331,3.098,6.677,3.25,6.105,3.472C5.513,3.702,5.011,4.01,4.511,4.511 c-0.5,0.5-0.808,1.002-1.038,1.594C3.25,6.677,3.098,7.331,3.054,8.289C3.01,9.249,3,9.556,3,12c0,2.444,0.01,2.751,0.054,3.711 c0.044,0.958,0.196,1.612,0.418,2.185c0.23,0.592,0.538,1.094,1.038,1.594c0.5,0.5,1.002,0.808,1.594,1.038 c0.572,0.222,1.227,0.375,2.185,0.418C9.249,20.99,9.556,21,12,21s2.751-0.01,3.711-0.054c0.958-0.044,1.612-0.196,2.185-0.418 c0.592-0.23,1.094-0.538,1.594-1.038c0.5-0.5,0.808-1.002,1.038-1.594c0.222-0.572,0.375-1.227,0.418-2.185 C20.99,14.751,21,14.444,21,12s-0.01-2.751-0.054-3.711c-0.044-0.958-0.196-1.612-0.418-2.185c-0.23-0.592-0.538-1.094-1.038-1.594 c-0.5-0.5-1.002-0.808-1.594-1.038c-0.572-0.222-1.227-0.375-2.185-0.418C14.751,3.01,14.444,3,12,3L12,3z M12,7.378 c-2.552,0-4.622,2.069-4.622,4.622S9.448,16.622,12,16.622s4.622-2.069,4.622-4.622S14.552,7.378,12,7.378z M12,15 c-1.657,0-3-1.343-3-3s1.343-3,3-3s3,1.343,3,3S13.657,15,12,15z M16.804,6.116c-0.596,0-1.08,0.484-1.08,1.08 s0.484,1.08,1.08,1.08c0.596,0,1.08-0.484,1.08-1.08S17.401,6.116,16.804,6.116z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/lastfm.js b/packages/block-library/src/social-link/icons/lastfm.js index b615e1bf0fddbc..aa3e5d9f279ee7 100644 --- a/packages/block-library/src/social-link/icons/lastfm.js +++ b/packages/block-library/src/social-link/icons/lastfm.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const LastfmIcon = ( ) => ( +export const LastfmIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M10.5002,0 C4.7006,0 0,4.70109753 0,10.4998496 C0,16.2989526 4.7006,21 10.5002,21 C16.299,21 21,16.2989526 21,10.4998496 C21,4.70109753 16.299,0 10.5002,0 Z M14.69735,14.7204413 C13.3164,14.7151781 12.4346,14.0870017 11.83445,12.6859357 L11.6816001,12.3451305 L10.35405,9.31011397 C9.92709997,8.26875064 8.85260001,7.57120012 7.68010001,7.57120012 C6.06945001,7.57120012 4.75925001,8.88509738 4.75925001,10.5009524 C4.75925001,12.1164565 6.06945001,13.4303036 7.68010001,13.4303036 C8.77200001,13.4303036 9.76514999,12.827541 10.2719501,11.8567047 C10.2893,11.8235214 10.3239,11.8019673 10.36305,11.8038219 C10.4007,11.8053759 10.43535,11.8287847 10.4504,11.8631709 L10.98655,13.1045863 C11.0016,13.1389726 10.9956,13.17782 10.97225,13.2068931 C10.1605001,14.1995341 8.96020001,14.7683115 7.68010001,14.7683115 C5.33305,14.7683115 3.42340001,12.8535563 3.42340001,10.5009524 C3.42340001,8.14679459 5.33300001,6.23203946 7.68010001,6.23203946 C9.45720002,6.23203946 10.8909,7.19074535 11.6138,8.86359341 C11.6205501,8.88018505 12.3412,10.5707777 12.97445,12.0190621 C13.34865,12.8739575 13.64615,13.3959676 14.6288,13.4291508 C15.5663001,13.4612814 16.25375,12.9121534 16.25375,12.1484869 C16.25375,11.4691321 15.8320501,11.3003585 14.8803,10.98216 C13.2365,10.4397989 12.34495,9.88605929 12.34495,8.51817658 C12.34495,7.1809207 13.26665,6.31615054 14.692,6.31615054 C15.62875,6.31615054 16.3155,6.7286858 16.79215,7.5768142 C16.80495,7.60062396 16.8079001,7.62814302 16.8004001,7.65420843 C16.7929,7.68027384 16.7748,7.70212868 16.7507001,7.713808 L15.86145,8.16900031 C15.8178001,8.19200805 15.7643,8.17807308 15.73565,8.13847371 C15.43295,7.71345711 15.0956,7.52513451 14.6423,7.52513451 C14.05125,7.52513451 13.6220001,7.92899802 13.6220001,8.48649708 C13.6220001,9.17382194 14.1529001,9.34144259 15.0339,9.61923972 C15.14915,9.65578139 15.26955,9.69397731 15.39385,9.73432853 C16.7763,10.1865133 17.57675,10.7311301 17.57675,12.1836251 C17.57685,13.629654 16.3389,14.7204413 14.69735,14.7204413 Z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/linkedin.js b/packages/block-library/src/social-link/icons/linkedin.js index 41b287c19ab03b..b4afc099625375 100644 --- a/packages/block-library/src/social-link/icons/linkedin.js +++ b/packages/block-library/src/social-link/icons/linkedin.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const LinkedInIcon = ( ) => ( +export const LinkedInIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M19.7,3H4.3C3.582,3,3,3.582,3,4.3v15.4C3,20.418,3.582,21,4.3,21h15.4c0.718,0,1.3-0.582,1.3-1.3V4.3 C21,3.582,20.418,3,19.7,3z M8.339,18.338H5.667v-8.59h2.672V18.338z M7.004,8.574c-0.857,0-1.549-0.694-1.549-1.548 c0-0.855,0.691-1.548,1.549-1.548c0.854,0,1.547,0.694,1.547,1.548C8.551,7.881,7.858,8.574,7.004,8.574z M18.339,18.338h-2.669 v-4.177c0-0.996-0.017-2.278-1.387-2.278c-1.389,0-1.601,1.086-1.601,2.206v4.249h-2.667v-8.59h2.559v1.174h0.037 c0.356-0.675,1.227-1.387,2.526-1.387c2.703,0,3.203,1.779,3.203,4.092V18.338z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/mail.js b/packages/block-library/src/social-link/icons/mail.js index 8e9847cfa370c8..ab43f106adca94 100644 --- a/packages/block-library/src/social-link/icons/mail.js +++ b/packages/block-library/src/social-link/icons/mail.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const MailIcon = ( ) => ( +export const MailIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M20,4H4C2.895,4,2,4.895,2,6v12c0,1.105,0.895,2,2,2h16c1.105,0,2-0.895,2-2V6C22,4.895,21.105,4,20,4z M20,8.236l-8,4.882 L4,8.236V6h16V8.236z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/mastodon.js b/packages/block-library/src/social-link/icons/mastodon.js index 1c1a196d28e6d0..5ee0ed256a2641 100644 --- a/packages/block-library/src/social-link/icons/mastodon.js +++ b/packages/block-library/src/social-link/icons/mastodon.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const MastodonIcon = ( ) => ( +export const MastodonIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M23.193 7.879c0-5.206-3.411-6.732-3.411-6.732C18.062.357 15.108.025 12.041 0h-.076c-3.068.025-6.02.357-7.74 1.147 0 0-3.411 1.526-3.411 6.732 0 1.192-.023 2.618.015 4.129.124 5.092.934 10.109 5.641 11.355 2.17.574 4.034.695 5.535.612 2.722-.15 4.25-.972 4.25-.972l-.09-1.975s-1.945.613-4.129.539c-2.165-.074-4.449-.233-4.799-2.891a5.499 5.499 0 0 1-.048-.745s2.125.52 4.817.643c1.646.075 3.19-.097 4.758-.283 3.007-.359 5.625-2.212 5.954-3.905.517-2.665.475-6.507.475-6.507zm-4.024 6.709h-2.497V8.469c0-1.29-.543-1.944-1.628-1.944-1.2 0-1.802.776-1.802 2.312v3.349h-2.483v-3.35c0-1.536-.602-2.312-1.802-2.312-1.085 0-1.628.655-1.628 1.944v6.119H4.832V8.284c0-1.289.328-2.313.987-3.07.68-.758 1.569-1.146 2.674-1.146 1.278 0 2.246.491 2.886 1.474L12 6.585l.622-1.043c.64-.983 1.608-1.474 2.886-1.474 1.104 0 1.994.388 2.674 1.146.658.757.986 1.781.986 3.07v6.304z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/medium.js b/packages/block-library/src/social-link/icons/medium.js index c15c3ca62728fe..4cf5b265398d89 100644 --- a/packages/block-library/src/social-link/icons/medium.js +++ b/packages/block-library/src/social-link/icons/medium.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const MediumIcon = ( ) => ( +export const MediumIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M20.962,7.257l-5.457,8.867l-3.923-6.375l3.126-5.08c0.112-0.182,0.319-0.286,0.527-0.286c0.05,0,0.1,0.008,0.149,0.02 c0.039,0.01,0.078,0.023,0.114,0.041l5.43,2.715l0.006,0.003c0.004,0.002,0.007,0.006,0.011,0.008 C20.971,7.191,20.98,7.227,20.962,7.257z M9.86,8.592v5.783l5.14,2.57L9.86,8.592z M15.772,17.331l4.231,2.115 C20.554,19.721,21,19.529,21,19.016V8.835L15.772,17.331z M8.968,7.178L3.665,4.527C3.569,4.479,3.478,4.456,3.395,4.456 C3.163,4.456,3,4.636,3,4.938v11.45c0,0.306,0.224,0.669,0.498,0.806l4.671,2.335c0.12,0.06,0.234,0.088,0.337,0.088 c0.29,0,0.494-0.225,0.494-0.602V7.231C9,7.208,8.988,7.188,8.968,7.178z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/meetup.js b/packages/block-library/src/social-link/icons/meetup.js index a1f6d86e009a95..8f12e1566ce970 100644 --- a/packages/block-library/src/social-link/icons/meetup.js +++ b/packages/block-library/src/social-link/icons/meetup.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const MeetupIcon = ( ) => ( +export const MeetupIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M19.24775,14.722a3.57032,3.57032,0,0,1-2.94457,3.52073,3.61886,3.61886,0,0,1-.64652.05634c-.07314-.0008-.10187.02846-.12507.09547A2.38881,2.38881,0,0,1,13.49453,20.094a2.33092,2.33092,0,0,1-1.827-.50716.13635.13635,0,0,0-.19878-.00408,3.191,3.191,0,0,1-2.104.60248,3.26309,3.26309,0,0,1-3.00324-2.71993,2.19076,2.19076,0,0,1-.03512-.30865c-.00156-.08579-.03413-.1189-.11608-.13493a2.86421,2.86421,0,0,1-1.23189-.56111,2.945,2.945,0,0,1-1.166-2.05749,2.97484,2.97484,0,0,1,.87524-2.50774.112.112,0,0,0,.02091-.16107,2.7213,2.7213,0,0,1-.36648-1.48A2.81256,2.81256,0,0,1,6.57673,7.58838a.35764.35764,0,0,0,.28869-.22819,4.2208,4.2208,0,0,1,6.02892-1.90111.25161.25161,0,0,0,.22023.0243,3.65608,3.65608,0,0,1,3.76031.90678A3.57244,3.57244,0,0,1,17.95918,8.626a2.97339,2.97339,0,0,1,.01829.57356.10637.10637,0,0,0,.0853.12792,1.97669,1.97669,0,0,1,1.27939,1.33733,2.00266,2.00266,0,0,1-.57112,2.12652c-.05284.05166-.04168.08328-.01173.13489A3.51189,3.51189,0,0,1,19.24775,14.722Zm-6.35959-.27836a1.6984,1.6984,0,0,0,1.14556,1.61113,3.82039,3.82039,0,0,0,1.036.17935,1.46888,1.46888,0,0,0,.73509-.12255.44082.44082,0,0,0,.26057-.44274.45312.45312,0,0,0-.29211-.43375.97191.97191,0,0,0-.20678-.063c-.21326-.03806-.42754-.0701-.63973-.11215a.54787.54787,0,0,1-.50172-.60926,2.75864,2.75864,0,0,1,.1773-.901c.1763-.535.414-1.045.64183-1.55913A12.686,12.686,0,0,0,15.85,10.47863a1.58461,1.58461,0,0,0,.04861-.87208,1.04531,1.04531,0,0,0-.85432-.83981,1.60658,1.60658,0,0,0-1.23654.16594.27593.27593,0,0,1-.36286-.03413c-.085-.0747-.16594-.15379-.24918-.23055a.98682.98682,0,0,0-1.33577-.04933,6.1468,6.1468,0,0,1-.4989.41615.47762.47762,0,0,1-.51535.03566c-.17448-.09307-.35512-.175-.53531-.25665a1.74949,1.74949,0,0,0-.56476-.2016,1.69943,1.69943,0,0,0-1.61654.91787,8.05815,8.05815,0,0,0-.32952.80126c-.45471,1.2557-.82507,2.53825-1.20838,3.81639a1.24151,1.24151,0,0,0,.51532,1.44389,1.42659,1.42659,0,0,0,1.22008.17166,1.09728,1.09728,0,0,0,.66994-.69764c.44145-1.04111.839-2.09989,1.25981-3.14926.11581-.28876.22792-.57874.35078-.86438a.44548.44548,0,0,1,.69189-.19539.50521.50521,0,0,1,.15044.43836,1.75625,1.75625,0,0,1-.14731.50453c-.27379.69219-.55265,1.38236-.82766,2.074a2.0836,2.0836,0,0,0-.14038.42876.50719.50719,0,0,0,.27082.57722.87236.87236,0,0,0,.66145.02739.99137.99137,0,0,0,.53406-.532q.61571-1.20914,1.228-2.42031.28423-.55863.57585-1.1133a.87189.87189,0,0,1,.29055-.35253.34987.34987,0,0,1,.37634-.01265.30291.30291,0,0,1,.12434.31459.56716.56716,0,0,1-.04655.1915c-.05318.12739-.10286.25669-.16183.38156-.34118.71775-.68754,1.43273-1.02568,2.152A2.00213,2.00213,0,0,0,12.88816,14.44366Zm4.78568,5.28972a.88573.88573,0,0,0-1.77139.00465.8857.8857,0,0,0,1.77139-.00465Zm-14.83838-7.296a.84329.84329,0,1,0,.00827-1.68655.8433.8433,0,0,0-.00827,1.68655Zm10.366-9.43673a.83506.83506,0,1,0-.0091,1.67.83505.83505,0,0,0,.0091-1.67Zm6.85014,5.22a.71651.71651,0,0,0-1.433.0093.71656.71656,0,0,0,1.433-.0093ZM5.37528,6.17908A.63823.63823,0,1,0,6.015,5.54483.62292.62292,0,0,0,5.37528,6.17908Zm6.68214,14.80843a.54949.54949,0,1,0-.55052.541A.54556.54556,0,0,0,12.05742,20.98752Zm8.53235-8.49689a.54777.54777,0,0,0-.54027.54023.53327.53327,0,0,0,.532.52293.51548.51548,0,0,0,.53272-.5237A.53187.53187,0,0,0,20.58977,12.49063ZM7.82846,2.4715a.44927.44927,0,1,0,.44484.44766A.43821.43821,0,0,0,7.82846,2.4715Zm13.775,7.60492a.41186.41186,0,0,0-.40065.39623.40178.40178,0,0,0,.40168.40168A.38994.38994,0,0,0,22,10.48172.39946.39946,0,0,0,21.60349,10.07642ZM5.79193,17.96207a.40469.40469,0,0,0-.397-.39646.399.399,0,0,0-.396.405.39234.39234,0,0,0,.39939.389A.39857.39857,0,0,0,5.79193,17.96207Z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/pinterest.js b/packages/block-library/src/social-link/icons/pinterest.js index d84704a9ba884b..70782c6189f81c 100644 --- a/packages/block-library/src/social-link/icons/pinterest.js +++ b/packages/block-library/src/social-link/icons/pinterest.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const PinterestIcon = ( ) => ( +export const PinterestIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M12.289,2C6.617,2,3.606,5.648,3.606,9.622c0,1.846,1.025,4.146,2.666,4.878c0.25,0.111,0.381,0.063,0.439-0.169 c0.044-0.175,0.267-1.029,0.365-1.428c0.032-0.128,0.017-0.237-0.091-0.362C6.445,11.911,6.01,10.75,6.01,9.668 c0-2.777,2.194-5.464,5.933-5.464c3.23,0,5.49,2.108,5.49,5.122c0,3.407-1.794,5.768-4.13,5.768c-1.291,0-2.257-1.021-1.948-2.277 c0.372-1.495,1.089-3.112,1.089-4.191c0-0.967-0.542-1.775-1.663-1.775c-1.319,0-2.379,1.309-2.379,3.059 c0,1.115,0.394,1.869,0.394,1.869s-1.302,5.279-1.54,6.261c-0.405,1.666,0.053,4.368,0.094,4.604 c0.021,0.126,0.167,0.169,0.25,0.063c0.129-0.165,1.699-2.419,2.142-4.051c0.158-0.59,0.817-2.995,0.817-2.995 c0.43,0.784,1.681,1.446,3.013,1.446c3.963,0,6.822-3.494,6.822-7.833C20.394,5.112,16.849,2,12.289,2" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/pocket.js b/packages/block-library/src/social-link/icons/pocket.js index 532138ff04f991..43f3661cd201d1 100644 --- a/packages/block-library/src/social-link/icons/pocket.js +++ b/packages/block-library/src/social-link/icons/pocket.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const PocketIcon = ( ) => ( +export const PocketIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M21.927,4.194C21.667,3.48,20.982,3,20.222,3h-0.01h-1.721H3.839C3.092,3,2.411,3.47,2.145,4.17 C2.066,4.378,2.026,4.594,2.026,4.814v6.035l0.069,1.2c0.29,2.73,1.707,5.115,3.899,6.778c0.039,0.03,0.079,0.059,0.119,0.089 l0.025,0.018c1.175,0.859,2.491,1.441,3.91,1.727c0.655,0.132,1.325,0.2,1.991,0.2c0.615,0,1.232-0.057,1.839-0.17 c0.073-0.014,0.145-0.028,0.219-0.044c0.02-0.004,0.042-0.012,0.064-0.023c1.359-0.297,2.621-0.864,3.753-1.691l0.025-0.018 c0.04-0.029,0.08-0.058,0.119-0.089c2.192-1.664,3.609-4.049,3.898-6.778l0.069-1.2V4.814C22.026,4.605,22,4.398,21.927,4.194z M17.692,10.481l-4.704,4.512c-0.266,0.254-0.608,0.382-0.949,0.382c-0.342,0-0.684-0.128-0.949-0.382l-4.705-4.512 C5.838,9.957,5.82,9.089,6.344,8.542c0.524-0.547,1.392-0.565,1.939-0.04l3.756,3.601l3.755-3.601 c0.547-0.524,1.415-0.506,1.939,0.04C18.256,9.089,18.238,9.956,17.692,10.481z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/reddit.js b/packages/block-library/src/social-link/icons/reddit.js index 4a8c7bd14abf71..9a17f0a19a1bfa 100644 --- a/packages/block-library/src/social-link/icons/reddit.js +++ b/packages/block-library/src/social-link/icons/reddit.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const RedditIcon = ( ) => ( +export const RedditIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M22,11.816c0-1.256-1.021-2.277-2.277-2.277c-0.593,0-1.122,0.24-1.526,0.614c-1.481-0.965-3.455-1.594-5.647-1.69 l1.171-3.702l3.18,0.748c0.008,1.028,0.846,1.862,1.876,1.862c1.035,0,1.877-0.842,1.877-1.878c0-1.035-0.842-1.877-1.877-1.877 c-0.769,0-1.431,0.466-1.72,1.13l-3.508-0.826c-0.203-0.047-0.399,0.067-0.46,0.261l-1.35,4.268 c-2.316,0.038-4.411,0.67-5.97,1.671C5.368,9.765,4.853,9.539,4.277,9.539C3.021,9.539,2,10.56,2,11.816 c0,0.814,0.433,1.523,1.078,1.925c-0.037,0.221-0.061,0.444-0.061,0.672c0,3.292,4.011,5.97,8.941,5.97s8.941-2.678,8.941-5.97 c0-0.214-0.02-0.424-0.053-0.632C21.533,13.39,22,12.661,22,11.816z M18.776,4.394c0.606,0,1.1,0.493,1.1,1.1s-0.493,1.1-1.1,1.1 s-1.1-0.494-1.1-1.1S18.169,4.394,18.776,4.394z M2.777,11.816c0-0.827,0.672-1.5,1.499-1.5c0.313,0,0.598,0.103,0.838,0.269 c-0.851,0.676-1.477,1.479-1.812,2.36C2.983,12.672,2.777,12.27,2.777,11.816z M11.959,19.606c-4.501,0-8.164-2.329-8.164-5.193 S7.457,9.22,11.959,9.22s8.164,2.329,8.164,5.193S16.46,19.606,11.959,19.606z M20.636,13.001c-0.326-0.89-0.948-1.701-1.797-2.384 c0.248-0.186,0.55-0.301,0.883-0.301c0.827,0,1.5,0.673,1.5,1.5C21.223,12.299,20.992,12.727,20.636,13.001z M8.996,14.704 c-0.76,0-1.397-0.616-1.397-1.376c0-0.76,0.637-1.397,1.397-1.397c0.76,0,1.376,0.637,1.376,1.397 C10.372,14.088,9.756,14.704,8.996,14.704z M16.401,13.328c0,0.76-0.616,1.376-1.376,1.376c-0.76,0-1.399-0.616-1.399-1.376 c0-0.76,0.639-1.397,1.399-1.397C15.785,11.931,16.401,12.568,16.401,13.328z M15.229,16.708c0.152,0.152,0.152,0.398,0,0.55 c-0.674,0.674-1.727,1.002-3.219,1.002c-0.004,0-0.007-0.002-0.011-0.002c-0.004,0-0.007,0.002-0.011,0.002 c-1.492,0-2.544-0.328-3.218-1.002c-0.152-0.152-0.152-0.398,0-0.55c0.152-0.152,0.399-0.151,0.55,0 c0.521,0.521,1.394,0.775,2.669,0.775c0.004,0,0.007,0.002,0.011,0.002c0.004,0,0.007-0.002,0.011-0.002 c1.275,0,2.148-0.253,2.669-0.775C14.831,16.556,15.078,16.556,15.229,16.708z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/skype.js b/packages/block-library/src/social-link/icons/skype.js index 3c34e3f1c5d73d..4dc946541a83f6 100644 --- a/packages/block-library/src/social-link/icons/skype.js +++ b/packages/block-library/src/social-link/icons/skype.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const SkypeIcon = ( ) => ( +export const SkypeIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M10.113,2.699c0.033-0.006,0.067-0.013,0.1-0.02c0.033,0.017,0.066,0.033,0.098,0.051L10.113,2.699z M2.72,10.223 c-0.006,0.034-0.011,0.069-0.017,0.103c0.018,0.032,0.033,0.064,0.051,0.095L2.72,10.223z M21.275,13.771 c0.007-0.035,0.011-0.071,0.018-0.106c-0.018-0.031-0.033-0.064-0.052-0.095L21.275,13.771z M13.563,21.199 c0.032,0.019,0.065,0.035,0.096,0.053c0.036-0.006,0.071-0.011,0.105-0.017L13.563,21.199z M22,16.386 c0,1.494-0.581,2.898-1.637,3.953c-1.056,1.057-2.459,1.637-3.953,1.637c-0.967,0-1.914-0.251-2.75-0.725 c0.036-0.006,0.071-0.011,0.105-0.017l-0.202-0.035c0.032,0.019,0.065,0.035,0.096,0.053c-0.543,0.096-1.099,0.147-1.654,0.147 c-1.275,0-2.512-0.25-3.676-0.743c-1.125-0.474-2.135-1.156-3.002-2.023c-0.867-0.867-1.548-1.877-2.023-3.002 c-0.493-1.164-0.743-2.401-0.743-3.676c0-0.546,0.049-1.093,0.142-1.628c0.018,0.032,0.033,0.064,0.051,0.095L2.72,10.223 c-0.006,0.034-0.011,0.069-0.017,0.103C2.244,9.5,2,8.566,2,7.615c0-1.493,0.582-2.898,1.637-3.953 c1.056-1.056,2.46-1.638,3.953-1.638c0.915,0,1.818,0.228,2.622,0.655c-0.033,0.007-0.067,0.013-0.1,0.02l0.199,0.031 c-0.032-0.018-0.066-0.034-0.098-0.051c0.002,0,0.003-0.001,0.004-0.001c0.586-0.112,1.187-0.169,1.788-0.169 c1.275,0,2.512,0.249,3.676,0.742c1.124,0.476,2.135,1.156,3.002,2.024c0.868,0.867,1.548,1.877,2.024,3.002 c0.493,1.164,0.743,2.401,0.743,3.676c0,0.575-0.054,1.15-0.157,1.712c-0.018-0.031-0.033-0.064-0.052-0.095l0.034,0.201 c0.007-0.035,0.011-0.071,0.018-0.106C21.754,14.494,22,15.432,22,16.386z M16.817,14.138c0-1.331-0.613-2.743-3.033-3.282 l-2.209-0.49c-0.84-0.192-1.807-0.444-1.807-1.237c0-0.794,0.679-1.348,1.903-1.348c2.468,0,2.243,1.696,3.468,1.696 c0.645,0,1.209-0.379,1.209-1.031c0-1.521-2.435-2.663-4.5-2.663c-2.242,0-4.63,0.952-4.63,3.488c0,1.221,0.436,2.521,2.839,3.123 l2.984,0.745c0.903,0.223,1.129,0.731,1.129,1.189c0,0.762-0.758,1.507-2.129,1.507c-2.679,0-2.307-2.062-3.743-2.062 c-0.645,0-1.113,0.444-1.113,1.078c0,1.236,1.501,2.886,4.856,2.886C15.236,17.737,16.817,16.199,16.817,14.138z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/snapchat.js b/packages/block-library/src/social-link/icons/snapchat.js index eaddf4d7080e11..73f93ee6279289 100644 --- a/packages/block-library/src/social-link/icons/snapchat.js +++ b/packages/block-library/src/social-link/icons/snapchat.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const SnapchatIcon = ( ) => ( +export const SnapchatIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M12.065,2a5.526,5.526,0,0,1,3.132.892A5.854,5.854,0,0,1,17.326,5.4a5.821,5.821,0,0,1,.351,2.33q0,.612-.117,2.487a.809.809,0,0,0,.365.091,1.93,1.93,0,0,0,.664-.176,1.93,1.93,0,0,1,.664-.176,1.3,1.3,0,0,1,.729.234.7.7,0,0,1,.351.6.839.839,0,0,1-.41.7,2.732,2.732,0,0,1-.9.41,3.192,3.192,0,0,0-.9.378.728.728,0,0,0-.41.618,1.575,1.575,0,0,0,.156.56,6.9,6.9,0,0,0,1.334,1.953,5.6,5.6,0,0,0,1.881,1.315,5.875,5.875,0,0,0,1.042.3.42.42,0,0,1,.365.456q0,.911-2.852,1.341a1.379,1.379,0,0,0-.143.507,1.8,1.8,0,0,1-.182.605.451.451,0,0,1-.429.241,5.878,5.878,0,0,1-.807-.085,5.917,5.917,0,0,0-.833-.085,4.217,4.217,0,0,0-.807.065,2.42,2.42,0,0,0-.82.293,6.682,6.682,0,0,0-.755.5q-.351.267-.755.527a3.886,3.886,0,0,1-.989.436A4.471,4.471,0,0,1,11.831,22a4.307,4.307,0,0,1-1.256-.176,3.784,3.784,0,0,1-.976-.436q-.4-.26-.749-.527a6.682,6.682,0,0,0-.755-.5,2.422,2.422,0,0,0-.807-.293,4.432,4.432,0,0,0-.82-.065,5.089,5.089,0,0,0-.853.1,5,5,0,0,1-.762.1.474.474,0,0,1-.456-.241,1.819,1.819,0,0,1-.182-.618,1.411,1.411,0,0,0-.143-.521q-2.852-.429-2.852-1.341a.42.42,0,0,1,.365-.456,5.793,5.793,0,0,0,1.042-.3,5.524,5.524,0,0,0,1.881-1.315,6.789,6.789,0,0,0,1.334-1.953A1.575,1.575,0,0,0,6,12.9a.728.728,0,0,0-.41-.618,3.323,3.323,0,0,0-.9-.384,2.912,2.912,0,0,1-.9-.41.814.814,0,0,1-.41-.684.71.71,0,0,1,.338-.593,1.208,1.208,0,0,1,.716-.241,1.976,1.976,0,0,1,.625.169,2.008,2.008,0,0,0,.69.169.919.919,0,0,0,.416-.091q-.117-1.849-.117-2.474A5.861,5.861,0,0,1,6.385,5.4,5.516,5.516,0,0,1,8.625,2.819,7.075,7.075,0,0,1,12.062,2Z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/soundcloud.js b/packages/block-library/src/social-link/icons/soundcloud.js index 558f52f547c2b9..538d1f867d4191 100644 --- a/packages/block-library/src/social-link/icons/soundcloud.js +++ b/packages/block-library/src/social-link/icons/soundcloud.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const SoundCloudIcon = ( ) => ( +export const SoundCloudIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M8.9,16.1L9,14L8.9,9.5c0-0.1,0-0.1-0.1-0.1c0,0-0.1-0.1-0.1-0.1c-0.1,0-0.1,0-0.1,0.1c0,0-0.1,0.1-0.1,0.1L8.3,14l0.1,2.1 c0,0.1,0,0.1,0.1,0.1c0,0,0.1,0.1,0.1,0.1C8.8,16.3,8.9,16.3,8.9,16.1z M11.4,15.9l0.1-1.8L11.4,9c0-0.1,0-0.2-0.1-0.2 c0,0-0.1,0-0.1,0s-0.1,0-0.1,0c-0.1,0-0.1,0.1-0.1,0.2l0,0.1l-0.1,5c0,0,0,0.7,0.1,2v0c0,0.1,0,0.1,0.1,0.1c0.1,0.1,0.1,0.1,0.2,0.1 c0.1,0,0.1,0,0.2-0.1c0.1,0,0.1-0.1,0.1-0.2L11.4,15.9z M2.4,12.9L2.5,14l-0.2,1.1c0,0.1,0,0.1-0.1,0.1c0,0-0.1,0-0.1-0.1L2.1,14 l0.1-1.1C2.2,12.9,2.3,12.9,2.4,12.9C2.3,12.9,2.4,12.9,2.4,12.9z M3.1,12.2L3.3,14l-0.2,1.8c0,0.1,0,0.1-0.1,0.1 c-0.1,0-0.1,0-0.1-0.1L2.8,14L3,12.2C3,12.2,3,12.2,3.1,12.2C3.1,12.2,3.1,12.2,3.1,12.2z M3.9,11.9L4.1,14l-0.2,2.1 c0,0.1,0,0.1-0.1,0.1c-0.1,0-0.1,0-0.1-0.1L3.5,14l0.2-2.1c0-0.1,0-0.1,0.1-0.1C3.9,11.8,3.9,11.8,3.9,11.9z M4.7,11.9L4.9,14 l-0.2,2.1c0,0.1-0.1,0.1-0.1,0.1c-0.1,0-0.1,0-0.1-0.1L4.3,14l0.2-2.2c0-0.1,0-0.1,0.1-0.1C4.7,11.7,4.7,11.8,4.7,11.9z M5.6,12 l0.2,2l-0.2,2.1c0,0.1-0.1,0.1-0.1,0.1c0,0-0.1,0-0.1,0c0,0,0-0.1,0-0.1L5.1,14l0.2-2c0,0,0-0.1,0-0.1s0.1,0,0.1,0 C5.5,11.9,5.5,11.9,5.6,12L5.6,12z M6.4,10.7L6.6,14l-0.2,2.1c0,0,0,0.1,0,0.1c0,0-0.1,0-0.1,0c-0.1,0-0.1-0.1-0.2-0.2L5.9,14 l0.2-3.3c0-0.1,0.1-0.2,0.2-0.2c0,0,0.1,0,0.1,0C6.4,10.7,6.4,10.7,6.4,10.7z M7.2,10l0.2,4.1l-0.2,2.1c0,0,0,0.1,0,0.1 c0,0-0.1,0-0.1,0c-0.1,0-0.2-0.1-0.2-0.2l-0.1-2.1L6.8,10c0-0.1,0.1-0.2,0.2-0.2c0,0,0.1,0,0.1,0S7.2,9.9,7.2,10z M8,9.6L8.2,14 L8,16.1c0,0.1-0.1,0.2-0.2,0.2c-0.1,0-0.2-0.1-0.2-0.2L7.5,14l0.1-4.4c0-0.1,0-0.1,0.1-0.1c0,0,0.1-0.1,0.1-0.1c0.1,0,0.1,0,0.1,0.1 C8,9.6,8,9.6,8,9.6z M11.4,16.1L11.4,16.1L11.4,16.1z M9.7,9.6L9.8,14l-0.1,2.1c0,0.1,0,0.1-0.1,0.2s-0.1,0.1-0.2,0.1 c-0.1,0-0.1,0-0.1-0.1s-0.1-0.1-0.1-0.2L9.2,14l0.1-4.4c0-0.1,0-0.1,0.1-0.2s0.1-0.1,0.2-0.1c0.1,0,0.1,0,0.2,0.1S9.7,9.5,9.7,9.6 L9.7,9.6z M10.6,9.8l0.1,4.3l-0.1,2c0,0.1,0,0.1-0.1,0.2c0,0-0.1,0.1-0.2,0.1c-0.1,0-0.1,0-0.2-0.1c0,0-0.1-0.1-0.1-0.2L10,14 l0.1-4.3c0-0.1,0-0.1,0.1-0.2c0,0,0.1-0.1,0.2-0.1c0.1,0,0.1,0,0.2,0.1S10.6,9.7,10.6,9.8z M12.4,14l-0.1,2c0,0.1,0,0.1-0.1,0.2 c-0.1,0.1-0.1,0.1-0.2,0.1c-0.1,0-0.1,0-0.2-0.1c-0.1-0.1-0.1-0.1-0.1-0.2l-0.1-1l-0.1-1l0.1-5.5v0c0-0.1,0-0.2,0.1-0.2 c0.1,0,0.1-0.1,0.2-0.1c0,0,0.1,0,0.1,0c0.1,0,0.1,0.1,0.1,0.2L12.4,14z M22.1,13.9c0,0.7-0.2,1.3-0.7,1.7c-0.5,0.5-1.1,0.7-1.7,0.7 h-6.8c-0.1,0-0.1,0-0.2-0.1c-0.1-0.1-0.1-0.1-0.1-0.2V8.2c0-0.1,0.1-0.2,0.2-0.3c0.5-0.2,1-0.3,1.6-0.3c1.1,0,2.1,0.4,2.9,1.1 c0.8,0.8,1.3,1.7,1.4,2.8c0.3-0.1,0.6-0.2,1-0.2c0.7,0,1.3,0.2,1.7,0.7C21.8,12.6,22.1,13.2,22.1,13.9L22.1,13.9z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/spotify.js b/packages/block-library/src/social-link/icons/spotify.js index fe702c4d697d4a..03256e1b31e048 100644 --- a/packages/block-library/src/social-link/icons/spotify.js +++ b/packages/block-library/src/social-link/icons/spotify.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const SpotifyIcon = ( ) => ( +export const SpotifyIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M12,2C6.477,2,2,6.477,2,12c0,5.523,4.477,10,10,10c5.523,0,10-4.477,10-10C22,6.477,17.523,2,12,2 M16.586,16.424 c-0.18,0.295-0.563,0.387-0.857,0.207c-2.348-1.435-5.304-1.76-8.785-0.964c-0.335,0.077-0.67-0.133-0.746-0.469 c-0.077-0.335,0.132-0.67,0.469-0.746c3.809-0.871,7.077-0.496,9.713,1.115C16.673,15.746,16.766,16.13,16.586,16.424 M17.81,13.7 c-0.226,0.367-0.706,0.482-1.072,0.257c-2.687-1.652-6.785-2.131-9.965-1.166C6.36,12.917,5.925,12.684,5.8,12.273 C5.675,11.86,5.908,11.425,6.32,11.3c3.632-1.102,8.147-0.568,11.234,1.328C17.92,12.854,18.035,13.335,17.81,13.7 M17.915,10.865 c-3.223-1.914-8.54-2.09-11.618-1.156C5.804,9.859,5.281,9.58,5.131,9.086C4.982,8.591,5.26,8.069,5.755,7.919 c3.532-1.072,9.404-0.865,13.115,1.338c0.445,0.264,0.59,0.838,0.327,1.282C18.933,10.983,18.359,11.129,17.915,10.865" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/tumblr.js b/packages/block-library/src/social-link/icons/tumblr.js index 060a2cf75434ee..946f7e1b040de0 100644 --- a/packages/block-library/src/social-link/icons/tumblr.js +++ b/packages/block-library/src/social-link/icons/tumblr.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const TumblrIcon = ( ) => ( +export const TumblrIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M16.749,17.396c-0.357,0.17-1.041,0.319-1.551,0.332c-1.539,0.041-1.837-1.081-1.85-1.896V9.847h3.861V6.937h-3.847V2.039 c0,0-2.77,0-2.817,0c-0.046,0-0.127,0.041-0.138,0.144c-0.165,1.499-0.867,4.13-3.783,5.181v2.484h1.945v6.282 c0,2.151,1.587,5.206,5.775,5.135c1.413-0.024,2.982-0.616,3.329-1.126L16.749,17.396z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/twitch.js b/packages/block-library/src/social-link/icons/twitch.js index ddce3166b74d2c..6bbbc13b2a4358 100644 --- a/packages/block-library/src/social-link/icons/twitch.js +++ b/packages/block-library/src/social-link/icons/twitch.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const TwitchIcon = ( ) => ( +export const TwitchIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M16.499,8.089h-1.636v4.91h1.636V8.089z M12,8.089h-1.637v4.91H12V8.089z M4.228,3.178L3,6.451v13.092h4.499V22h2.456 l2.454-2.456h3.681L21,14.636V3.178H4.228z M19.364,13.816l-2.864,2.865H12l-2.453,2.453V16.68H5.863V4.814h13.501V13.816z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/twitter.js b/packages/block-library/src/social-link/icons/twitter.js index 1b6641cadb441c..95e7435c6176e8 100644 --- a/packages/block-library/src/social-link/icons/twitter.js +++ b/packages/block-library/src/social-link/icons/twitter.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const TwitterIcon = ( ) => ( +export const TwitterIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M22.23,5.924c-0.736,0.326-1.527,0.547-2.357,0.646c0.847-0.508,1.498-1.312,1.804-2.27 c-0.793,0.47-1.671,0.812-2.606,0.996C18.324,4.498,17.257,4,16.077,4c-2.266,0-4.103,1.837-4.103,4.103 c0,0.322,0.036,0.635,0.106,0.935C8.67,8.867,5.647,7.234,3.623,4.751C3.27,5.357,3.067,6.062,3.067,6.814 c0,1.424,0.724,2.679,1.825,3.415c-0.673-0.021-1.305-0.206-1.859-0.513c0,0.017,0,0.034,0,0.052c0,1.988,1.414,3.647,3.292,4.023 c-0.344,0.094-0.707,0.144-1.081,0.144c-0.264,0-0.521-0.026-0.772-0.074c0.522,1.63,2.038,2.816,3.833,2.85 c-1.404,1.1-3.174,1.756-5.096,1.756c-0.331,0-0.658-0.019-0.979-0.057c1.816,1.164,3.973,1.843,6.29,1.843 c7.547,0,11.675-6.252,11.675-11.675c0-0.178-0.004-0.355-0.012-0.531C20.985,7.47,21.68,6.747,22.23,5.924z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/vimeo.js b/packages/block-library/src/social-link/icons/vimeo.js index 02940e55afc42f..1e11bda150ed44 100644 --- a/packages/block-library/src/social-link/icons/vimeo.js +++ b/packages/block-library/src/social-link/icons/vimeo.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const VimeoIcon = ( ) => ( +export const VimeoIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M22.396,7.164c-0.093,2.026-1.507,4.799-4.245,8.32C15.322,19.161,12.928,21,10.97,21c-1.214,0-2.24-1.119-3.079-3.359 c-0.56-2.053-1.119-4.106-1.68-6.159C5.588,9.243,4.921,8.122,4.206,8.122c-0.156,0-0.701,0.328-1.634,0.98L1.594,7.841 c1.027-0.902,2.04-1.805,3.037-2.708C6.001,3.95,7.03,3.327,7.715,3.264c1.619-0.156,2.616,0.951,2.99,3.321 c0.404,2.557,0.685,4.147,0.841,4.769c0.467,2.121,0.981,3.181,1.542,3.181c0.435,0,1.09-0.688,1.963-2.065 c0.871-1.376,1.338-2.422,1.401-3.142c0.125-1.187-0.343-1.782-1.401-1.782c-0.498,0-1.012,0.115-1.541,0.341 c1.023-3.35,2.977-4.977,5.862-4.884C21.511,3.066,22.52,4.453,22.396,7.164z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/vk.js b/packages/block-library/src/social-link/icons/vk.js index 14e186d920f136..97ac09e842fe1b 100644 --- a/packages/block-library/src/social-link/icons/vk.js +++ b/packages/block-library/src/social-link/icons/vk.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const VkIcon = ( ) => ( +export const VkIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M22,7.1c0.2,0.4-0.4,1.5-1.6,3.1c-0.2,0.2-0.4,0.5-0.7,0.9c-0.5,0.7-0.9,1.1-0.9,1.4c-0.1,0.3-0.1,0.6,0.1,0.8 c0.1,0.1,0.4,0.4,0.8,0.9h0l0,0c1,0.9,1.6,1.7,2,2.3c0,0,0,0.1,0.1,0.1c0,0.1,0,0.1,0.1,0.3c0,0.1,0,0.2,0,0.4 c0,0.1-0.1,0.2-0.3,0.3c-0.1,0.1-0.4,0.1-0.6,0.1l-2.7,0c-0.2,0-0.4,0-0.6-0.1c-0.2-0.1-0.4-0.1-0.5-0.2l-0.2-0.1 c-0.2-0.1-0.5-0.4-0.7-0.7s-0.5-0.6-0.7-0.8c-0.2-0.2-0.4-0.4-0.6-0.6C14.8,15,14.6,15,14.4,15c0,0,0,0-0.1,0c0,0-0.1,0.1-0.2,0.2 c-0.1,0.1-0.2,0.2-0.2,0.3c-0.1,0.1-0.1,0.3-0.2,0.5c-0.1,0.2-0.1,0.5-0.1,0.8c0,0.1,0,0.2,0,0.3c0,0.1-0.1,0.2-0.1,0.2l0,0.1 c-0.1,0.1-0.3,0.2-0.6,0.2h-1.2c-0.5,0-1,0-1.5-0.2c-0.5-0.1-1-0.3-1.4-0.6s-0.7-0.5-1.1-0.7s-0.6-0.4-0.7-0.6l-0.3-0.3 c-0.1-0.1-0.2-0.2-0.3-0.3s-0.4-0.5-0.7-0.9s-0.7-1-1.1-1.6c-0.4-0.6-0.8-1.3-1.3-2.2C2.9,9.4,2.5,8.5,2.1,7.5C2,7.4,2,7.3,2,7.2 c0-0.1,0-0.1,0-0.2l0-0.1c0.1-0.1,0.3-0.2,0.6-0.2l2.9,0c0.1,0,0.2,0,0.2,0.1S5.9,6.9,5.9,7L6,7c0.1,0.1,0.2,0.2,0.3,0.3 C6.4,7.7,6.5,8,6.7,8.4C6.9,8.8,7,9,7.1,9.2l0.2,0.3c0.2,0.4,0.4,0.8,0.6,1.1c0.2,0.3,0.4,0.5,0.5,0.7s0.3,0.3,0.4,0.4 c0.1,0.1,0.3,0.1,0.4,0.1c0.1,0,0.2,0,0.3-0.1c0,0,0,0,0.1-0.1c0,0,0.1-0.1,0.1-0.2c0.1-0.1,0.1-0.3,0.1-0.5c0-0.2,0.1-0.5,0.1-0.8 c0-0.4,0-0.8,0-1.3c0-0.3,0-0.5-0.1-0.8c0-0.2-0.1-0.4-0.1-0.5L9.6,7.6C9.4,7.3,9.1,7.2,8.7,7.1C8.6,7.1,8.6,7,8.7,6.9 C8.9,6.7,9,6.6,9.1,6.5c0.4-0.2,1.2-0.3,2.5-0.3c0.6,0,1,0.1,1.4,0.1c0.1,0,0.3,0.1,0.3,0.1c0.1,0.1,0.2,0.1,0.2,0.3 c0,0.1,0.1,0.2,0.1,0.3s0,0.3,0,0.5c0,0.2,0,0.4,0,0.6c0,0.2,0,0.4,0,0.7c0,0.3,0,0.6,0,0.9c0,0.1,0,0.2,0,0.4c0,0.2,0,0.4,0,0.5 c0,0.1,0,0.3,0,0.4s0.1,0.3,0.1,0.4c0.1,0.1,0.1,0.2,0.2,0.3c0.1,0,0.1,0,0.2,0c0.1,0,0.2,0,0.3-0.1c0.1-0.1,0.2-0.2,0.4-0.4 s0.3-0.4,0.5-0.7c0.2-0.3,0.5-0.7,0.7-1.1c0.4-0.7,0.8-1.5,1.1-2.3c0-0.1,0.1-0.1,0.1-0.2c0-0.1,0.1-0.1,0.1-0.1l0,0l0.1,0 c0,0,0,0,0.1,0s0.2,0,0.2,0l3,0c0.3,0,0.5,0,0.7,0S21.9,7,21.9,7L22,7.1z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/wordpress.js b/packages/block-library/src/social-link/icons/wordpress.js index a6523d67385052..0fc780d08954ef 100644 --- a/packages/block-library/src/social-link/icons/wordpress.js +++ b/packages/block-library/src/social-link/icons/wordpress.js @@ -2,13 +2,16 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const WordPressIcon = ( ) => ( - <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg"> +export const WordPressIcon = () => ( + <SVG + width="24" + height="24" + viewBox="0 0 24 24" + version="1.1" + xmlns="http://www.w3.org/2000/svg" + > <Path d="M12.158,12.786L9.46,20.625c0.806,0.237,1.657,0.366,2.54,0.366c1.047,0,2.051-0.181,2.986-0.51 c-0.024-0.038-0.046-0.079-0.065-0.124L12.158,12.786z M3.009,12c0,3.559,2.068,6.634,5.067,8.092L3.788,8.341 C3.289,9.459,3.009,10.696,3.009,12z M18.069,11.546c0-1.112-0.399-1.881-0.741-2.48c-0.456-0.741-0.883-1.368-0.883-2.109 c0-0.826,0.627-1.596,1.51-1.596c0.04,0,0.078,0.005,0.116,0.007C16.472,3.904,14.34,3.009,12,3.009 c-3.141,0-5.904,1.612-7.512,4.052c0.211,0.007,0.41,0.011,0.579,0.011c0.94,0,2.396-0.114,2.396-0.114 C7.947,6.93,8.004,7.642,7.52,7.699c0,0-0.487,0.057-1.029,0.085l3.274,9.739l1.968-5.901l-1.401-3.838 C9.848,7.756,9.389,7.699,9.389,7.699C8.904,7.67,8.961,6.93,9.446,6.958c0,0,1.484,0.114,2.368,0.114 c0.94,0,2.397-0.114,2.397-0.114c0.485-0.028,0.542,0.684,0.057,0.741c0,0-0.488,0.057-1.029,0.085l3.249,9.665l0.897-2.996 C17.841,13.284,18.069,12.316,18.069,11.546z M19.889,7.686c0.039,0.286,0.06,0.593,0.06,0.924c0,0.912-0.171,1.938-0.684,3.22 l-2.746,7.94c2.673-1.558,4.47-4.454,4.47-7.771C20.991,10.436,20.591,8.967,19.889,7.686z M12,22C6.486,22,2,17.514,2,12 C2,6.486,6.486,2,12,2c5.514,0,10,4.486,10,10C22,17.514,17.514,22,12,22z" /> </SVG> ); diff --git a/packages/block-library/src/social-link/icons/yelp.js b/packages/block-library/src/social-link/icons/yelp.js index 953db89b6bbd72..83b1d76200258c 100644 --- a/packages/block-library/src/social-link/icons/yelp.js +++ b/packages/block-library/src/social-link/icons/yelp.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const YelpIcon = ( ) => ( +export const YelpIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M12.271,16.718v1.417q-.011,3.257-.067,3.4a.707.707,0,0,1-.569.446,4.637,4.637,0,0,1-2.024-.424A4.609,4.609,0,0,1,7.8,20.565a.844.844,0,0,1-.19-.4.692.692,0,0,1,.044-.29,3.181,3.181,0,0,1,.379-.524q.335-.412,2.019-2.409.011,0,.669-.781a.757.757,0,0,1,.44-.274.965.965,0,0,1,.552.039.945.945,0,0,1,.418.324.732.732,0,0,1,.139.468Zm-1.662-2.8a.783.783,0,0,1-.58.781l-1.339.435q-3.067.981-3.257.981a.711.711,0,0,1-.6-.4,2.636,2.636,0,0,1-.19-.836,9.134,9.134,0,0,1,.011-1.857,3.559,3.559,0,0,1,.335-1.389.659.659,0,0,1,.625-.357,22.629,22.629,0,0,1,2.253.859q.781.324,1.283.524l.937.379a.771.771,0,0,1,.4.34A.982.982,0,0,1,10.609,13.917Zm9.213,3.313a4.467,4.467,0,0,1-1.021,1.8,4.559,4.559,0,0,1-1.512,1.417.671.671,0,0,1-.7-.078q-.156-.112-2.052-3.2l-.524-.859a.761.761,0,0,1-.128-.513.957.957,0,0,1,.217-.513.774.774,0,0,1,.926-.29q.011.011,1.327.446,2.264.736,2.7.887a2.082,2.082,0,0,1,.524.229.673.673,0,0,1,.245.68Zm-7.5-7.049q.056,1.137-.6,1.361-.647.19-1.272-.792L6.237,4.08a.7.7,0,0,1,.212-.691,5.788,5.788,0,0,1,2.314-1,5.928,5.928,0,0,1,2.5-.352.681.681,0,0,1,.547.5q.034.2.245,3.407T12.327,10.181Zm7.384,1.2a.679.679,0,0,1-.29.658q-.167.112-3.67.959-.747.167-1.015.257l.011-.022a.769.769,0,0,1-.513-.044.914.914,0,0,1-.413-.357.786.786,0,0,1,0-.971q.011-.011.836-1.137,1.394-1.908,1.673-2.275a2.423,2.423,0,0,1,.379-.435A.7.7,0,0,1,17.435,8a4.482,4.482,0,0,1,1.372,1.489,4.81,4.81,0,0,1,.9,1.868v.034Z" /> </SVG> diff --git a/packages/block-library/src/social-link/icons/youtube.js b/packages/block-library/src/social-link/icons/youtube.js index 35eca5e5d8af37..7256a177262f7e 100644 --- a/packages/block-library/src/social-link/icons/youtube.js +++ b/packages/block-library/src/social-link/icons/youtube.js @@ -2,12 +2,9 @@ * WordPress dependencies */ -import { - Path, - SVG, -} from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const YouTubeIcon = ( ) => ( +export const YouTubeIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> <Path d="M21.8,8.001c0,0-0.195-1.378-0.795-1.985c-0.76-0.797-1.613-0.801-2.004-0.847c-2.799-0.202-6.997-0.202-6.997-0.202 h-0.009c0,0-4.198,0-6.997,0.202C4.608,5.216,3.756,5.22,2.995,6.016C2.395,6.623,2.2,8.001,2.2,8.001S2,9.62,2,11.238v1.517 c0,1.618,0.2,3.237,0.2,3.237s0.195,1.378,0.795,1.985c0.761,0.797,1.76,0.771,2.205,0.855c1.6,0.153,6.8,0.201,6.8,0.201 s4.203-0.006,7.001-0.209c0.391-0.047,1.243-0.051,2.004-0.847c0.6-0.607,0.795-1.985,0.795-1.985s0.2-1.618,0.2-3.237v-1.517 C22,9.62,21.8,8.001,21.8,8.001z M9.935,14.594l-0.001-5.62l5.404,2.82L9.935,14.594z" /> </SVG> diff --git a/packages/block-library/src/social-link/index.js b/packages/block-library/src/social-link/index.js index a6013644310250..9df66a4a50635a 100644 --- a/packages/block-library/src/social-link/index.js +++ b/packages/block-library/src/social-link/index.js @@ -20,29 +20,27 @@ const commonAttributes = { }; // Create individual blocks out of each site in social-list.js -export const sites = Object.keys( socialList ).map( - ( site ) => { - const siteParams = socialList[ site ]; - return { - name: 'core/social-link-' + site, - settings: { - title: siteParams.name, - icon: siteParams.icon, - description: __( 'Link to ' + siteParams.name ), - ...commonAttributes, - attributes: { - url: { - type: 'string', - }, - site: { - type: 'string', - default: site, - }, - label: { - type: 'string', - }, +export const sites = Object.keys( socialList ).map( ( site ) => { + const siteParams = socialList[ site ]; + return { + name: 'core/social-link-' + site, + settings: { + title: siteParams.name, + icon: siteParams.icon, + description: __( 'Link to ' + siteParams.name ), + ...commonAttributes, + attributes: { + url: { + type: 'string', + }, + site: { + type: 'string', + default: site, + }, + label: { + type: 'string', }, }, - }; - } -); + }, + }; +} ); diff --git a/packages/block-library/src/social-links/edit.js b/packages/block-library/src/social-links/edit.js index 56c25ca57727dd..a164d5f6611ce7 100644 --- a/packages/block-library/src/social-links/edit.js +++ b/packages/block-library/src/social-links/edit.js @@ -2,9 +2,7 @@ * WordPress dependencies */ -import { - InnerBlocks, -} from '@wordpress/block-editor'; +import { InnerBlocks } from '@wordpress/block-editor'; /** * Internal dependencies diff --git a/packages/block-library/src/social-links/index.js b/packages/block-library/src/social-links/index.js index 651ef3ca9f1e01..13549051c9030c 100644 --- a/packages/block-library/src/social-links/index.js +++ b/packages/block-library/src/social-links/index.js @@ -16,15 +16,26 @@ export { metadata, name }; export const settings = { title: __( 'Social links' ), - description: __( 'Create a block of links to your social media or external sites' ), + description: __( + 'Create a block of links to your social media or external sites' + ), supports: { align: [ 'left', 'center', 'right' ], }, example: { innerBlocks: [ - { name: 'core/social-link-wordpress', attributes: { url: 'https://wordpress.org' } }, - { name: 'core/social-link-facebook', attributes: { url: 'https://www.facebook.com/WordPress/' } }, - { name: 'core/social-link-twitter', attributes: { url: 'https://twitter.com/WordPress' } }, + { + name: 'core/social-link-wordpress', + attributes: { url: 'https://wordpress.org' }, + }, + { + name: 'core/social-link-facebook', + attributes: { url: 'https://www.facebook.com/WordPress/' }, + }, + { + name: 'core/social-link-twitter', + attributes: { url: 'https://twitter.com/WordPress' }, + }, ], }, styles: [ diff --git a/packages/block-library/src/spacer/edit.js b/packages/block-library/src/spacer/edit.js index 8bb08385fa9b1e..181290fab71f35 100644 --- a/packages/block-library/src/spacer/edit.js +++ b/packages/block-library/src/spacer/edit.js @@ -13,7 +13,14 @@ import { BaseControl, PanelBody, ResizableBox } from '@wordpress/components'; import { compose, withInstanceId } from '@wordpress/compose'; import { withDispatch } from '@wordpress/data'; -const SpacerEdit = ( { attributes, isSelected, setAttributes, instanceId, onResizeStart, onResizeStop } ) => { +const SpacerEdit = ( { + attributes, + isSelected, + setAttributes, + instanceId, + onResizeStart, + onResizeStop, +} ) => { const { height } = attributes; const id = `block-spacer-height-input-${ instanceId }`; const [ inputHeightValue, setInputHeightValue ] = useState( height ); @@ -23,7 +30,9 @@ const SpacerEdit = ( { attributes, isSelected, setAttributes, instanceId, onResi <ResizableBox className={ classnames( 'block-library-spacer__resize-container', - { 'is-selected': isSelected }, + { + 'is-selected': isSelected, + } ) } size={ { height, @@ -56,7 +65,10 @@ const SpacerEdit = ( { attributes, isSelected, setAttributes, instanceId, onResi type="number" id={ id } onChange={ ( event ) => { - let spacerHeight = parseInt( event.target.value, 10 ); + let spacerHeight = parseInt( + event.target.value, + 10 + ); setInputHeightValue( spacerHeight ); if ( isNaN( spacerHeight ) ) { // Set spacer height to default size and input box to empty string diff --git a/packages/block-library/src/spacer/edit.native.js b/packages/block-library/src/spacer/edit.native.js index a9861f39790d6b..8c5b34ba66fc93 100644 --- a/packages/block-library/src/spacer/edit.native.js +++ b/packages/block-library/src/spacer/edit.native.js @@ -1,4 +1,3 @@ - /** * External dependencies */ @@ -6,15 +5,10 @@ import { View } from 'react-native'; /** * WordPress dependencies */ -import { - RangeControl, - PanelBody, -} from '@wordpress/components'; +import { RangeControl, PanelBody } from '@wordpress/components'; import { withPreferredColorScheme } from '@wordpress/compose'; import { useState, useEffect } from '@wordpress/element'; -import { - InspectorControls, -} from '@wordpress/block-editor'; +import { InspectorControls } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; /** @@ -25,7 +19,12 @@ import styles from './editor.scss'; const minSpacerHeight = 20; const maxSpacerHeight = 500; -const SpacerEdit = ( { isSelected, attributes, setAttributes, getStylesFromColorScheme } ) => { +const SpacerEdit = ( { + isSelected, + attributes, + setAttributes, + getStylesFromColorScheme, +} ) => { const { height } = attributes; const [ sliderSpacerMaxHeight, setSpacerMaxHeight ] = useState( height ); @@ -33,7 +32,9 @@ const SpacerEdit = ( { isSelected, attributes, setAttributes, getStylesFromColor // `maxSpacerHeight`, so there is a need to `setSpacerMaxHeight` // after the initial render. useEffect( () => { - setSpacerMaxHeight( height > maxSpacerHeight ? height * 2 : maxSpacerHeight ); + setSpacerMaxHeight( + height > maxSpacerHeight ? height * 2 : maxSpacerHeight + ); }, [] ); const changeAttribute = ( value ) => { @@ -42,12 +43,21 @@ const SpacerEdit = ( { isSelected, attributes, setAttributes, getStylesFromColor } ); }; - const defaultStyle = getStylesFromColorScheme( styles.staticSpacer, styles.staticDarkSpacer ); + const defaultStyle = getStylesFromColorScheme( + styles.staticSpacer, + styles.staticDarkSpacer + ); return ( - <View style={ [ defaultStyle, isSelected && styles.selectedSpacer, { height } ] }> + <View + style={ [ + defaultStyle, + isSelected && styles.selectedSpacer, + { height }, + ] } + > <InspectorControls> - <PanelBody title={ __( 'Spacer settings' ) } > + <PanelBody title={ __( 'Spacer settings' ) }> <RangeControl label={ __( 'Height in pixels' ) } minimumValue={ minSpacerHeight } diff --git a/packages/block-library/src/spacer/index.js b/packages/block-library/src/spacer/index.js index 374e096c104e3d..5d2082aa8782ad 100644 --- a/packages/block-library/src/spacer/index.js +++ b/packages/block-library/src/spacer/index.js @@ -17,7 +17,9 @@ export { metadata, name }; export const settings = { title: __( 'Spacer' ), - description: __( 'Add white space between blocks and customize its height.' ), + description: __( + 'Add white space between blocks and customize its height.' + ), icon, edit, save, diff --git a/packages/block-library/src/subhead/edit.js b/packages/block-library/src/subhead/edit.js index 8ceda8116ad7cc..0e613c3fe0d36f 100644 --- a/packages/block-library/src/subhead/edit.js +++ b/packages/block-library/src/subhead/edit.js @@ -9,7 +9,11 @@ import { AlignmentToolbar, } from '@wordpress/block-editor'; -export default function SubheadEdit( { attributes, setAttributes, className } ) { +export default function SubheadEdit( { + attributes, + setAttributes, + className, +} ) { const { align, content, placeholder } = attributes; deprecated( 'The Subheading block', { diff --git a/packages/block-library/src/subhead/index.js b/packages/block-library/src/subhead/index.js index 936bc5b6117e77..491bf098e39a44 100644 --- a/packages/block-library/src/subhead/index.js +++ b/packages/block-library/src/subhead/index.js @@ -18,7 +18,9 @@ export { metadata, name }; export const settings = { title: __( 'Subheading (deprecated)' ), - description: __( 'This block is deprecated. Please use the Paragraph block instead.' ), + description: __( + 'This block is deprecated. Please use the Paragraph block instead.' + ), icon: ( <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <Path d="M7.1 6l-.5 3h4.5L9.4 19h3l1.8-10h4.5l.5-3H7.1z" /> diff --git a/packages/block-library/src/table/deprecated.js b/packages/block-library/src/table/deprecated.js index 1dd641db9bc07e..deb9dadc30b966 100644 --- a/packages/block-library/src/table/deprecated.js +++ b/packages/block-library/src/table/deprecated.js @@ -128,7 +128,10 @@ const deprecated = [ return null; } - const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + const backgroundClass = getColorClassName( + 'background-color', + backgroundColor + ); const classes = classnames( backgroundClass, { 'has-fixed-layout': hasFixedLayout, @@ -146,13 +149,17 @@ const deprecated = [ <Tag> { rows.map( ( { cells }, rowIndex ) => ( <tr key={ rowIndex }> - { cells.map( ( { content, tag, scope }, cellIndex ) => - <RichText.Content - tagName={ tag } - value={ content } - key={ cellIndex } - scope={ tag === 'th' ? scope : undefined } - /> + { cells.map( + ( { content, tag, scope }, cellIndex ) => ( + <RichText.Content + tagName={ tag } + value={ content } + key={ cellIndex } + scope={ + tag === 'th' ? scope : undefined + } + /> + ) ) } </tr> ) ) } diff --git a/packages/block-library/src/table/edit.js b/packages/block-library/src/table/edit.js index 33c0966e8376a6..d26e21c0a93083 100644 --- a/packages/block-library/src/table/edit.js +++ b/packages/block-library/src/table/edit.js @@ -26,7 +26,12 @@ import { ToggleControl, ToolbarGroup, } from '@wordpress/components'; -import { table as icon, alignLeft, alignRight, alignCenter } from '@wordpress/icons'; +import { + table as icon, + alignLeft, + alignRight, + alignCenter, +} from '@wordpress/icons'; /** * Internal dependencies @@ -93,8 +98,12 @@ export class TableEdit extends Component { this.onCreateTable = this.onCreateTable.bind( this ); this.onChangeFixedLayout = this.onChangeFixedLayout.bind( this ); this.onChange = this.onChange.bind( this ); - this.onChangeInitialColumnCount = this.onChangeInitialColumnCount.bind( this ); - this.onChangeInitialRowCount = this.onChangeInitialRowCount.bind( this ); + this.onChangeInitialColumnCount = this.onChangeInitialColumnCount.bind( + this + ); + this.onChangeInitialRowCount = this.onChangeInitialRowCount.bind( + this + ); this.renderSection = this.renderSection.bind( this ); this.getTableControls = this.getTableControls.bind( this ); this.onInsertRow = this.onInsertRow.bind( this ); @@ -107,7 +116,9 @@ export class TableEdit extends Component { this.onDeleteColumn = this.onDeleteColumn.bind( this ); this.onToggleHeaderSection = this.onToggleHeaderSection.bind( this ); this.onToggleFooterSection = this.onToggleFooterSection.bind( this ); - this.onChangeColumnAlignment = this.onChangeColumnAlignment.bind( this ); + this.onChangeColumnAlignment = this.onChangeColumnAlignment.bind( + this + ); this.getCellAlignment = this.getCellAlignment.bind( this ); this.state = { @@ -149,10 +160,12 @@ export class TableEdit extends Component { initialRowCount = parseInt( initialRowCount, 10 ) || 2; initialColumnCount = parseInt( initialColumnCount, 10 ) || 2; - setAttributes( createTable( { - rowCount: initialRowCount, - columnCount: initialColumnCount, - } ) ); + setAttributes( + createTable( { + rowCount: initialRowCount, + columnCount: initialColumnCount, + } ) + ); } /** @@ -179,11 +192,16 @@ export class TableEdit extends Component { const { attributes, setAttributes } = this.props; - setAttributes( updateSelectedCell( - attributes, - selectedCell, - ( cellAttributes ) => ( { ...cellAttributes, content } ), - ) ); + setAttributes( + updateSelectedCell( + attributes, + selectedCell, + ( cellAttributes ) => ( { + ...cellAttributes, + content, + } ) + ) + ); } /** @@ -209,7 +227,10 @@ export class TableEdit extends Component { const newAttributes = updateSelectedCell( attributes, columnSelection, - ( cellAttributes ) => ( { ...cellAttributes, align } ), + ( cellAttributes ) => ( { + ...cellAttributes, + align, + } ) ); setAttributes( newAttributes ); } @@ -263,10 +284,12 @@ export class TableEdit extends Component { const { sectionName, rowIndex } = selectedCell; this.setState( { selectedCell: null } ); - setAttributes( insertRow( attributes, { - sectionName, - rowIndex: rowIndex + delta, - } ) ); + setAttributes( + insertRow( attributes, { + sectionName, + rowIndex: rowIndex + delta, + } ) + ); } /** @@ -316,9 +339,11 @@ export class TableEdit extends Component { const { columnIndex } = selectedCell; this.setState( { selectedCell: null } ); - setAttributes( insertColumn( attributes, { - columnIndex: columnIndex + delta, - } ) ); + setAttributes( + insertColumn( attributes, { + columnIndex: columnIndex + delta, + } ) + ); } /** @@ -349,7 +374,9 @@ export class TableEdit extends Component { const { sectionName, columnIndex } = selectedCell; this.setState( { selectedCell: null } ); - setAttributes( deleteColumn( attributes, { sectionName, columnIndex } ) ); + setAttributes( + deleteColumn( attributes, { sectionName, columnIndex } ) + ); } /** @@ -439,37 +466,49 @@ export class TableEdit extends Component { <Tag> { rows.map( ( { cells }, rowIndex ) => ( <tr key={ rowIndex }> - { cells.map( ( { content, tag: CellTag, scope, align }, columnIndex ) => { - const cellLocation = { - sectionName: name, - rowIndex, - columnIndex, - }; - - const cellClasses = classnames( { - [ `has-text-align-${ align }` ]: align, - }, 'wp-block-table__cell-content' ); - - let placeholder = ''; - if ( name === 'head' ) { - placeholder = __( 'Header label' ); - } else if ( name === 'foot' ) { - placeholder = __( 'Footer label' ); + { cells.map( + ( + { content, tag: CellTag, scope, align }, + columnIndex + ) => { + const cellLocation = { + sectionName: name, + rowIndex, + columnIndex, + }; + + const cellClasses = classnames( + { + [ `has-text-align-${ align }` ]: align, + }, + 'wp-block-table__cell-content' + ); + + let placeholder = ''; + if ( name === 'head' ) { + placeholder = __( 'Header label' ); + } else if ( name === 'foot' ) { + placeholder = __( 'Footer label' ); + } + + return ( + <RichText + tagName={ CellTag } + key={ columnIndex } + className={ cellClasses } + scope={ + CellTag === 'th' ? scope : undefined + } + value={ content } + onChange={ this.onChange } + unstableOnFocus={ this.createOnFocus( + cellLocation + ) } + placeholder={ placeholder } + /> + ); } - - return ( - <RichText - tagName={ CellTag } - key={ columnIndex } - className={ cellClasses } - scope={ CellTag === 'th' ? scope : undefined } - value={ content } - onChange={ this.onChange } - unstableOnFocus={ this.createOnFocus( cellLocation ) } - placeholder={ placeholder } - /> - ); - } ) } + ) } </tr> ) ) } </Tag> @@ -494,14 +533,11 @@ export class TableEdit extends Component { setAttributes, } = this.props; const { initialRowCount, initialColumnCount } = this.state; - const { - hasFixedLayout, - caption, - head, - body, - foot, - } = attributes; - const isEmpty = isEmptyTableSection( head ) && isEmptyTableSection( body ) && isEmptyTableSection( foot ); + const { hasFixedLayout, caption, head, body, foot } = attributes; + const isEmpty = + isEmptyTableSection( head ) && + isEmptyTableSection( body ) && + isEmptyTableSection( foot ); const Section = this.renderSection; if ( isEmpty ) { @@ -511,7 +547,10 @@ export class TableEdit extends Component { icon={ <BlockIcon icon={ icon } showColors /> } instructions={ __( 'Insert a table for sharing data.' ) } > - <form className="wp-block-table__placeholder-form" onSubmit={ this.onCreateTable }> + <form + className="wp-block-table__placeholder-form" + onSubmit={ this.onCreateTable } + > <TextControl type="number" label={ __( 'Column Count' ) } @@ -528,7 +567,13 @@ export class TableEdit extends Component { min="1" className="wp-block-table__placeholder-input" /> - <Button className="wp-block-table__placeholder-button" isSecondary type="submit">{ __( 'Create Table' ) }</Button> + <Button + className="wp-block-table__placeholder-button" + isSecondary + type="submit" + > + { __( 'Create Table' ) } + </Button> </form> </Placeholder> ); @@ -554,12 +599,17 @@ export class TableEdit extends Component { label={ __( 'Change column alignment' ) } alignmentControls={ ALIGNMENT_CONTROLS } value={ this.getCellAlignment() } - onChange={ ( nextAlign ) => this.onChangeColumnAlignment( nextAlign ) } + onChange={ ( nextAlign ) => + this.onChangeColumnAlignment( nextAlign ) + } onHover={ this.onHoverAlignment } /> </BlockControls> <InspectorControls> - <PanelBody title={ __( 'Table settings' ) } className="blocks-table-settings"> + <PanelBody + title={ __( 'Table settings' ) } + className="blocks-table-settings" + > <ToggleControl label={ __( 'Fixed width table cells' ) } checked={ !! hasFixedLayout } @@ -600,9 +650,13 @@ export class TableEdit extends Component { tagName="figcaption" placeholder={ __( 'Write caption…' ) } value={ caption } - onChange={ ( value ) => setAttributes( { caption: value } ) } + onChange={ ( value ) => + setAttributes( { caption: value } ) + } // Deselect the selected table cell when the caption is focused. - unstableOnFocus={ () => this.setState( { selectedCell: null } ) } + unstableOnFocus={ () => + this.setState( { selectedCell: null } ) + } /> </figure> </> diff --git a/packages/block-library/src/table/index.js b/packages/block-library/src/table/index.js index f755a7f05de6fb..599c564665556f 100644 --- a/packages/block-library/src/table/index.js +++ b/packages/block-library/src/table/index.js @@ -23,22 +23,24 @@ export const settings = { icon, example: { attributes: { - head: [ { - cells: [ - { - content: __( 'Version' ), - tag: 'th', - }, - { - content: __( 'Jazz Musician' ), - tag: 'th', - }, - { - content: __( 'Release Date' ), - tag: 'th', - }, - ], - } ], + head: [ + { + cells: [ + { + content: __( 'Version' ), + tag: 'th', + }, + { + content: __( 'Jazz Musician' ), + tag: 'th', + }, + { + content: __( 'Release Date' ), + tag: 'th', + }, + ], + }, + ], body: [ { cells: [ @@ -92,7 +94,11 @@ export const settings = { }, }, styles: [ - { name: 'regular', label: _x( 'Default', 'block style' ), isDefault: true }, + { + name: 'regular', + label: _x( 'Default', 'block style' ), + isDefault: true, + }, { name: 'stripes', label: __( 'Stripes' ) }, ], supports: { diff --git a/packages/block-library/src/table/save.js b/packages/block-library/src/table/save.js index dbceac8aaab15e..a4e78fbd895a4f 100644 --- a/packages/block-library/src/table/save.js +++ b/packages/block-library/src/table/save.js @@ -23,7 +23,10 @@ export default function save( { attributes } ) { return null; } - const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + const backgroundClass = getColorClassName( + 'background-color', + backgroundColor + ); const classes = classnames( backgroundClass, { 'has-fixed-layout': hasFixedLayout, @@ -43,22 +46,30 @@ export default function save( { attributes } ) { <Tag> { rows.map( ( { cells }, rowIndex ) => ( <tr key={ rowIndex }> - { cells.map( ( { content, tag, scope, align }, cellIndex ) => { - const cellClasses = classnames( { - [ `has-text-align-${ align }` ]: align, - } ); + { cells.map( + ( { content, tag, scope, align }, cellIndex ) => { + const cellClasses = classnames( { + [ `has-text-align-${ align }` ]: align, + } ); - return ( - <RichText.Content - className={ cellClasses ? cellClasses : undefined } - data-align={ align } - tagName={ tag } - value={ content } - key={ cellIndex } - scope={ tag === 'th' ? scope : undefined } - /> - ); - } ) } + return ( + <RichText.Content + className={ + cellClasses + ? cellClasses + : undefined + } + data-align={ align } + tagName={ tag } + value={ content } + key={ cellIndex } + scope={ + tag === 'th' ? scope : undefined + } + /> + ); + } + ) } </tr> ) ) } </Tag> @@ -73,10 +84,7 @@ export default function save( { attributes } ) { <Section type="foot" rows={ foot } /> </table> { hasCaption && ( - <RichText.Content - tagName="figcaption" - value={ caption } - /> + <RichText.Content tagName="figcaption" value={ caption } /> ) } </figure> ); diff --git a/packages/block-library/src/table/state.js b/packages/block-library/src/table/state.js index f8ee18d7f2390b..5a018472aee952 100644 --- a/packages/block-library/src/table/state.js +++ b/packages/block-library/src/table/state.js @@ -14,10 +14,7 @@ const INHERITED_COLUMN_ATTRIBUTES = [ 'align' ]; * * @return {Object} New table state. */ -export function createTable( { - rowCount, - columnCount, -} ) { +export function createTable( { rowCount, columnCount } ) { return { body: times( rowCount, () => ( { cells: times( columnCount, () => ( { @@ -57,12 +54,14 @@ export function getFirstRow( state ) { * @return {*} The attribute value. */ export function getCellAttribute( state, cellLocation, attributeName ) { - const { + const { sectionName, rowIndex, columnIndex } = cellLocation; + return get( state, [ sectionName, rowIndex, + 'cells', columnIndex, - } = cellLocation; - return get( state, [ sectionName, rowIndex, 'cells', columnIndex, attributeName ] ); + attributeName, + ] ); } /** @@ -129,12 +128,17 @@ export function isCellSelected( cellLocation, selection ) { switch ( selection.type ) { case 'column': - return selection.type === 'column' && cellLocation.columnIndex === selection.columnIndex; + return ( + selection.type === 'column' && + cellLocation.columnIndex === selection.columnIndex + ); case 'cell': - return selection.type === 'cell' && + return ( + selection.type === 'cell' && cellLocation.sectionName === selection.sectionName && cellLocation.columnIndex === selection.columnIndex && - cellLocation.rowIndex === selection.rowIndex; + cellLocation.rowIndex === selection.rowIndex + ); } } @@ -148,13 +152,12 @@ export function isCellSelected( cellLocation, selection ) { * * @return {Object} New table state. */ -export function insertRow( state, { - sectionName, - rowIndex, - columnCount, -} ) { +export function insertRow( state, { sectionName, rowIndex, columnCount } ) { const firstRow = getFirstRow( state ); - const cellCount = columnCount === undefined ? get( firstRow, [ 'cells', 'length' ] ) : columnCount; + const cellCount = + columnCount === undefined + ? get( firstRow, [ 'cells', 'length' ] ) + : columnCount; // Bail early if the function cannot determine how many cells to add. if ( ! cellCount ) { @@ -166,8 +169,15 @@ export function insertRow( state, { ...state[ sectionName ].slice( 0, rowIndex ), { cells: times( cellCount, ( index ) => { - const firstCellInColumn = get( firstRow, [ 'cells', index ], {} ); - const inheritedAttributes = pick( firstCellInColumn, INHERITED_COLUMN_ATTRIBUTES ); + const firstCellInColumn = get( + firstRow, + [ 'cells', index ], + {} + ); + const inheritedAttributes = pick( + firstCellInColumn, + INHERITED_COLUMN_ATTRIBUTES + ); return { ...inheritedAttributes, @@ -191,12 +201,11 @@ export function insertRow( state, { * * @return {Object} New table state. */ -export function deleteRow( state, { - sectionName, - rowIndex, -} ) { +export function deleteRow( state, { sectionName, rowIndex } ) { return { - [ sectionName ]: state[ sectionName ].filter( ( row, index ) => index !== rowIndex ), + [ sectionName ]: state[ sectionName ].filter( + ( row, index ) => index !== rowIndex + ), }; } @@ -209,9 +218,7 @@ export function deleteRow( state, { * * @return {Object} New table state. */ -export function insertColumn( state, { - columnIndex, -} ) { +export function insertColumn( state, { columnIndex } ) { const tableSections = pick( state, [ 'head', 'body', 'foot' ] ); return mapValues( tableSections, ( section, sectionName ) => { @@ -250,9 +257,7 @@ export function insertColumn( state, { * * @return {Object} New table state. */ -export function deleteColumn( state, { - columnIndex, -} ) { +export function deleteColumn( state, { columnIndex } ) { const tableSections = pick( state, [ 'head', 'body', 'foot' ] ); return mapValues( tableSections, ( section ) => { @@ -261,9 +266,16 @@ export function deleteColumn( state, { return section; } - return section.map( ( row ) => ( { - cells: row.cells.length >= columnIndex ? row.cells.filter( ( cell, index ) => index !== columnIndex ) : row.cells, - } ) ).filter( ( row ) => row.cells.length ); + return section + .map( ( row ) => ( { + cells: + row.cells.length >= columnIndex + ? row.cells.filter( + ( cell, index ) => index !== columnIndex + ) + : row.cells, + } ) ) + .filter( ( row ) => row.cells.length ); } ); } diff --git a/packages/block-library/src/table/test/state.js b/packages/block-library/src/table/test/state.js index b946d45fcb50b7..1daa8e06919606 100644 --- a/packages/block-library/src/table/test/state.js +++ b/packages/block-library/src/table/test/state.js @@ -168,7 +168,11 @@ describe( 'getCellAttribute', () => { rowIndex: 1, columnIndex: 1, }; - const state = getCellAttribute( tableWithAttribute, cellLocation, 'testAttr' ); + const state = getCellAttribute( + tableWithAttribute, + cellLocation, + 'testAttr' + ); expect( state ).toBe( 'testVal' ); } ); @@ -1102,71 +1106,189 @@ describe( 'isCellSelected', () => { } ); it( 'returns false when no selection is provided', () => { - const cellLocation = { sectionName: 'head', columnIndex: 0, rowIndex: 0 }; + const cellLocation = { + sectionName: 'head', + columnIndex: 0, + rowIndex: 0, + }; expect( isCellSelected( cellLocation ) ).toBe( false ); } ); it( `considers only cells with the same columnIndex to be selected when the selection.type is 'column'`, () => { // Valid locations and selections. - const headCellLocationA = { sectionName: 'head', columnIndex: 0, rowIndex: 0 }; - const headCellLocationB = { sectionName: 'head', columnIndex: 0, rowIndex: 1 }; - const bodyCellLocationA = { sectionName: 'body', columnIndex: 0, rowIndex: 0 }; - const bodyCellLocationB = { sectionName: 'body', columnIndex: 0, rowIndex: 1 }; - const footCellLocationA = { sectionName: 'foot', columnIndex: 0, rowIndex: 0 }; - const footCellLocationB = { sectionName: 'foot', columnIndex: 0, rowIndex: 1 }; + const headCellLocationA = { + sectionName: 'head', + columnIndex: 0, + rowIndex: 0, + }; + const headCellLocationB = { + sectionName: 'head', + columnIndex: 0, + rowIndex: 1, + }; + const bodyCellLocationA = { + sectionName: 'body', + columnIndex: 0, + rowIndex: 0, + }; + const bodyCellLocationB = { + sectionName: 'body', + columnIndex: 0, + rowIndex: 1, + }; + const footCellLocationA = { + sectionName: 'foot', + columnIndex: 0, + rowIndex: 0, + }; + const footCellLocationB = { + sectionName: 'foot', + columnIndex: 0, + rowIndex: 1, + }; const columnSelection = { type: 'column', columnIndex: 0 }; // Invalid locations and selections. - const otherColumnCellLocationA = { sectionName: 'head', columnIndex: 1, rowIndex: 0 }; - const otherColumnCellLocationB = { sectionName: 'body', columnIndex: 2, rowIndex: 0 }; - const otherColumnCellLocationC = { sectionName: 'foot', columnIndex: 3, rowIndex: 0 }; - - expect( isCellSelected( headCellLocationA, columnSelection ) ).toBe( true ); - expect( isCellSelected( headCellLocationB, columnSelection ) ).toBe( true ); - expect( isCellSelected( bodyCellLocationA, columnSelection ) ).toBe( true ); - expect( isCellSelected( bodyCellLocationB, columnSelection ) ).toBe( true ); - expect( isCellSelected( footCellLocationA, columnSelection ) ).toBe( true ); - expect( isCellSelected( footCellLocationB, columnSelection ) ).toBe( true ); - expect( isCellSelected( otherColumnCellLocationA, columnSelection ) ).toBe( false ); - expect( isCellSelected( otherColumnCellLocationB, columnSelection ) ).toBe( false ); - expect( isCellSelected( otherColumnCellLocationC, columnSelection ) ).toBe( false ); + const otherColumnCellLocationA = { + sectionName: 'head', + columnIndex: 1, + rowIndex: 0, + }; + const otherColumnCellLocationB = { + sectionName: 'body', + columnIndex: 2, + rowIndex: 0, + }; + const otherColumnCellLocationC = { + sectionName: 'foot', + columnIndex: 3, + rowIndex: 0, + }; + + expect( isCellSelected( headCellLocationA, columnSelection ) ).toBe( + true + ); + expect( isCellSelected( headCellLocationB, columnSelection ) ).toBe( + true + ); + expect( isCellSelected( bodyCellLocationA, columnSelection ) ).toBe( + true + ); + expect( isCellSelected( bodyCellLocationB, columnSelection ) ).toBe( + true + ); + expect( isCellSelected( footCellLocationA, columnSelection ) ).toBe( + true + ); + expect( isCellSelected( footCellLocationB, columnSelection ) ).toBe( + true + ); + expect( + isCellSelected( otherColumnCellLocationA, columnSelection ) + ).toBe( false ); + expect( + isCellSelected( otherColumnCellLocationB, columnSelection ) + ).toBe( false ); + expect( + isCellSelected( otherColumnCellLocationC, columnSelection ) + ).toBe( false ); } ); it( `considers only cells with the same section, columnIndex and rowIndex to be selected when the selection.type is 'cell'`, () => { // Valid locations and selections. - const cellLocation = { sectionName: 'head', columnIndex: 0, rowIndex: 0 }; - const cellSelection = { type: 'cell', sectionName: 'head', rowIndex: 0, columnIndex: 0 }; + const cellLocation = { + sectionName: 'head', + columnIndex: 0, + rowIndex: 0, + }; + const cellSelection = { + type: 'cell', + sectionName: 'head', + rowIndex: 0, + columnIndex: 0, + }; // Invalid locations and selections. - const otherColumnCellLocation = { sectionName: 'head', columnIndex: 1, rowIndex: 0 }; - const otherRowCellLocation = { sectionName: 'head', columnIndex: 0, rowIndex: 1 }; - const bodyCellLocation = { sectionName: 'body', columnIndex: 0, rowIndex: 0 }; - const footCellLocation = { sectionName: 'foot', columnIndex: 0, rowIndex: 0 }; + const otherColumnCellLocation = { + sectionName: 'head', + columnIndex: 1, + rowIndex: 0, + }; + const otherRowCellLocation = { + sectionName: 'head', + columnIndex: 0, + rowIndex: 1, + }; + const bodyCellLocation = { + sectionName: 'body', + columnIndex: 0, + rowIndex: 0, + }; + const footCellLocation = { + sectionName: 'foot', + columnIndex: 0, + rowIndex: 0, + }; expect( isCellSelected( cellLocation, cellSelection ) ).toBe( true ); - expect( isCellSelected( otherColumnCellLocation, cellSelection ) ).toBe( false ); - expect( isCellSelected( otherRowCellLocation, cellSelection ) ).toBe( false ); - expect( isCellSelected( bodyCellLocation, cellSelection ) ).toBe( false ); - expect( isCellSelected( footCellLocation, cellSelection ) ).toBe( false ); + expect( isCellSelected( otherColumnCellLocation, cellSelection ) ).toBe( + false + ); + expect( isCellSelected( otherRowCellLocation, cellSelection ) ).toBe( + false + ); + expect( isCellSelected( bodyCellLocation, cellSelection ) ).toBe( + false + ); + expect( isCellSelected( footCellLocation, cellSelection ) ).toBe( + false + ); } ); } ); describe( 'updateSelectedCell', () => { it( 'returns an unchanged table state if there is no selection', () => { - const updated = updateSelectedCell( table, undefined, ( cell ) => ( { ...cell, content: 'test' } ) ); + const updated = updateSelectedCell( table, undefined, ( cell ) => ( { + ...cell, + content: 'test', + } ) ); expect( table ).toEqual( updated ); } ); it( 'returns an unchanged table state if the selection is outside the bounds of the table', () => { - const cellSelection = { type: 'cell', sectionName: 'body', rowIndex: 100, columnIndex: 100 }; - const updated = updateSelectedCell( table, cellSelection, ( cell ) => ( { ...cell, content: 'test' } ) ); + const cellSelection = { + type: 'cell', + sectionName: 'body', + rowIndex: 100, + columnIndex: 100, + }; + const updated = updateSelectedCell( + table, + cellSelection, + ( cell ) => ( { + ...cell, + content: 'test', + } ) + ); expect( table ).toEqual( updated ); } ); it( 'updates only the individual cell when the selection type is `cell`', () => { - const cellSelection = { type: 'cell', sectionName: 'body', rowIndex: 0, columnIndex: 0 }; - const updated = updateSelectedCell( table, cellSelection, ( cell ) => ( { ...cell, content: 'test' } ) ); + const cellSelection = { + type: 'cell', + sectionName: 'body', + rowIndex: 0, + columnIndex: 0, + }; + const updated = updateSelectedCell( + table, + cellSelection, + ( cell ) => ( { + ...cell, + content: 'test', + } ) + ); expect( updated ).toEqual( { body: [ @@ -1186,7 +1308,14 @@ describe( 'updateSelectedCell', () => { it( 'updates every cell in the column when the selection type is `column`', () => { const cellSelection = { type: 'column', columnIndex: 1 }; - const updated = updateSelectedCell( table, cellSelection, ( cell ) => ( { ...cell, content: 'test' } ) ); + const updated = updateSelectedCell( + table, + cellSelection, + ( cell ) => ( { + ...cell, + content: 'test', + } ) + ); expect( updated ).toEqual( { body: [ diff --git a/packages/block-library/src/tag-cloud/edit.js b/packages/block-library/src/tag-cloud/edit.js index e3d04470a239c0..3ba4e027565c68 100644 --- a/packages/block-library/src/tag-cloud/edit.js +++ b/packages/block-library/src/tag-cloud/edit.js @@ -7,11 +7,7 @@ import { map, filter } from 'lodash'; * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { - PanelBody, - ToggleControl, - SelectControl, -} from '@wordpress/components'; +import { PanelBody, ToggleControl, SelectControl } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { InspectorControls } from '@wordpress/block-editor'; diff --git a/packages/block-library/src/template-part/edit/index.js b/packages/block-library/src/template-part/edit/index.js index d40180e17c783f..2ec12a9e3d4e01 100644 --- a/packages/block-library/src/template-part/edit/index.js +++ b/packages/block-library/src/template-part/edit/index.js @@ -25,7 +25,8 @@ export default function TemplatePartEdit( { // Set the post ID, once found, so that edits persist. useEffect( () => { if ( - ( initialPostId.current === undefined || initialPostId.current === null ) && + ( initialPostId.current === undefined || + initialPostId.current === null ) && postId !== undefined && postId !== null ) { @@ -36,7 +37,11 @@ export default function TemplatePartEdit( { if ( postId ) { // Part of a template file, post ID already resolved. return ( - <EntityProvider kind="postType" type="wp_template_part" id={ postId }> + <EntityProvider + kind="postType" + type="wp_template_part" + id={ postId } + > <TemplatePartInnerBlocks /> </EntityProvider> ); diff --git a/packages/block-library/src/template-part/edit/placeholder.js b/packages/block-library/src/template-part/edit/placeholder.js index 2258b1a90e6912..1c682162f83c57 100644 --- a/packages/block-library/src/template-part/edit/placeholder.js +++ b/packages/block-library/src/template-part/edit/placeholder.js @@ -47,7 +47,11 @@ export default function TemplatePartPlaceholder( { setAttributes } ) { ); if ( templatePart ) { return ( - <EntityProvider kind="postType" type="wp_template_part" id={ postId }> + <EntityProvider + kind="postType" + type="wp_template_part" + id={ postId } + > <TemplatePartPreview /> </EntityProvider> ); @@ -114,7 +118,11 @@ export default function TemplatePartPlaceholder( { setAttributes } ) { /> </div> { preview } - <Button isPrimary disabled={ ! slug || ! theme } onClick={ onChooseOrCreate }> + <Button + isPrimary + disabled={ ! slug || ! theme } + onClick={ onChooseOrCreate } + > { postId ? __( 'Choose' ) : __( 'Create' ) } </Button> </Placeholder> diff --git a/packages/block-library/src/template-part/edit/use-template-part-post.js b/packages/block-library/src/template-part/edit/use-template-part-post.js index 54391be3e0ce97..bef260ffd3a7b1 100644 --- a/packages/block-library/src/template-part/edit/use-template-part-post.js +++ b/packages/block-library/src/template-part/edit/use-template-part-post.js @@ -34,7 +34,10 @@ export default function useTemplatePartPost( postId, slug, theme ) { const foundPost = posts && posts.find( - ( post ) => post.slug === slug && post.meta && post.meta.theme === theme + ( post ) => + post.slug === slug && + post.meta && + post.meta.theme === theme ); return foundPost && foundPost.id; } diff --git a/packages/block-library/src/text-columns/edit.js b/packages/block-library/src/text-columns/edit.js index ec3368acdeccca..24f9dbd16d1eeb 100644 --- a/packages/block-library/src/text-columns/edit.js +++ b/packages/block-library/src/text-columns/edit.js @@ -16,7 +16,11 @@ import { } from '@wordpress/block-editor'; import deprecated from '@wordpress/deprecated'; -export default function TextColumnsEdit( { attributes, setAttributes, className } ) { +export default function TextColumnsEdit( { + attributes, + setAttributes, + className, +} ) { const { width, content, columns } = attributes; deprecated( 'The Text Columns block', { @@ -29,7 +33,9 @@ export default function TextColumnsEdit( { attributes, setAttributes, className <BlockControls> <BlockAlignmentToolbar value={ width } - onChange={ ( nextWidth ) => setAttributes( { width: nextWidth } ) } + onChange={ ( nextWidth ) => + setAttributes( { width: nextWidth } ) + } controls={ [ 'center', 'wide', 'full' ] } /> </BlockControls> @@ -38,17 +44,24 @@ export default function TextColumnsEdit( { attributes, setAttributes, className <RangeControl label={ __( 'Columns' ) } value={ columns } - onChange={ ( value ) => setAttributes( { columns: value } ) } + onChange={ ( value ) => + setAttributes( { columns: value } ) + } min={ 2 } max={ 4 } required /> </PanelBody> </InspectorControls> - <div className={ `${ className } align${ width } columns-${ columns }` }> + <div + className={ `${ className } align${ width } columns-${ columns }` } + > { times( columns, ( index ) => { return ( - <div className="wp-block-column" key={ `column-${ index }` }> + <div + className="wp-block-column" + key={ `column-${ index }` } + > <RichText tagName="p" value={ get( content, [ index, 'children' ] ) } diff --git a/packages/block-library/src/text-columns/index.js b/packages/block-library/src/text-columns/index.js index 50c48dedd984a0..159e4939687a3e 100644 --- a/packages/block-library/src/text-columns/index.js +++ b/packages/block-library/src/text-columns/index.js @@ -21,7 +21,9 @@ export const settings = { inserter: false, }, title: __( 'Text Columns (deprecated)' ), - description: __( 'This block is deprecated. Please use the Columns block instead.' ), + description: __( + 'This block is deprecated. Please use the Columns block instead.' + ), transforms, getEditWrapperProps( attributes ) { const { width } = attributes; diff --git a/packages/block-library/src/text-columns/save.js b/packages/block-library/src/text-columns/save.js index 4952c163fc2086..6333cb83fc310b 100644 --- a/packages/block-library/src/text-columns/save.js +++ b/packages/block-library/src/text-columns/save.js @@ -12,11 +12,14 @@ export default function save( { attributes } ) { const { width, content, columns } = attributes; return ( <div className={ `align${ width } columns-${ columns }` }> - { times( columns, ( index ) => + { times( columns, ( index ) => ( <div className="wp-block-column" key={ `column-${ index }` }> - <RichText.Content tagName="p" value={ get( content, [ index, 'children' ] ) } /> + <RichText.Content + tagName="p" + value={ get( content, [ index, 'children' ] ) } + /> </div> - ) } + ) ) } </div> ); } diff --git a/packages/block-library/src/text-columns/transforms.js b/packages/block-library/src/text-columns/transforms.js index 050e49c65bfa04..d6271cdc50457d 100644 --- a/packages/block-library/src/text-columns/transforms.js +++ b/packages/block-library/src/text-columns/transforms.js @@ -8,23 +8,25 @@ const transforms = { { type: 'block', blocks: [ 'core/columns' ], - transform: ( { className, columns, content, width } ) => ( + transform: ( { className, columns, content, width } ) => createBlock( 'core/columns', { - align: ( 'wide' === width || 'full' === width ) ? width : undefined, + align: + 'wide' === width || 'full' === width + ? width + : undefined, className, columns, }, content.map( ( { children } ) => - createBlock( - 'core/column', - {}, - [ createBlock( 'core/paragraph', { content: children } ) ] - ) + createBlock( 'core/column', {}, [ + createBlock( 'core/paragraph', { + content: children, + } ), + ] ) ) - ) - ), + ), }, ], }; diff --git a/packages/block-library/src/verse/edit.js b/packages/block-library/src/verse/edit.js index 5d971cd834fbf2..82a184ce167904 100644 --- a/packages/block-library/src/verse/edit.js +++ b/packages/block-library/src/verse/edit.js @@ -13,7 +13,12 @@ import { AlignmentToolbar, } from '@wordpress/block-editor'; -export default function VerseEdit( { attributes, setAttributes, className, mergeBlocks } ) { +export default function VerseEdit( { + attributes, + setAttributes, + className, + mergeBlocks, +} ) { const { textAlign, content } = attributes; return ( diff --git a/packages/block-library/src/verse/index.js b/packages/block-library/src/verse/index.js index ca11d71bc89ee8..e05ab62bf2f27f 100644 --- a/packages/block-library/src/verse/index.js +++ b/packages/block-library/src/verse/index.js @@ -19,12 +19,16 @@ export { metadata, name }; export const settings = { title: __( 'Verse' ), - description: __( 'Insert poetry. Use special spacing formats. Or quote song lyrics.' ), + description: __( + 'Insert poetry. Use special spacing formats. Or quote song lyrics.' + ), icon, example: { attributes: { // translators: Sample content for the Verse block. Can be replaced with a more locale-adequate work. - content: __( 'WHAT was he doing, the great god Pan,\n Down in the reeds by the river?\nSpreading ruin and scattering ban,\nSplashing and paddling with hoofs of a goat,\nAnd breaking the golden lilies afloat\n With the dragon-fly on the river.' ), + content: __( + 'WHAT was he doing, the great god Pan,\n Down in the reeds by the river?\nSpreading ruin and scattering ban,\nSplashing and paddling with hoofs of a goat,\nAnd breaking the golden lilies afloat\n With the dragon-fly on the river.' + ), }, }, keywords: [ __( 'poetry' ), __( 'poem' ) ], diff --git a/packages/block-library/src/video/edit-common-settings.js b/packages/block-library/src/video/edit-common-settings.js index 0d46bce952feb0..e6bec731d759fd 100644 --- a/packages/block-library/src/video/edit-common-settings.js +++ b/packages/block-library/src/video/edit-common-settings.js @@ -2,10 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { - ToggleControl, - SelectControl, -} from '@wordpress/components'; +import { ToggleControl, SelectControl } from '@wordpress/components'; const VideoSettings = ( { setAttributes, attributes } ) => { const { @@ -18,7 +15,11 @@ const VideoSettings = ( { setAttributes, attributes } ) => { } = attributes; const getAutoplayHelp = ( checked ) => { - return checked ? __( 'Note: Autoplaying videos may cause usability issues for some visitors.' ) : null; + return checked + ? __( + 'Note: Autoplaying videos may cause usability issues for some visitors.' + ) + : null; }; const toggleAttribute = ( attribute ) => { diff --git a/packages/block-library/src/video/edit.js b/packages/block-library/src/video/edit.js index 17aa67a032f8fe..a167df7736e1f6 100644 --- a/packages/block-library/src/video/edit.js +++ b/packages/block-library/src/video/edit.js @@ -20,17 +20,9 @@ import { RichText, } from '@wordpress/block-editor'; import { Component, createRef } from '@wordpress/element'; -import { - __, - sprintf, -} from '@wordpress/i18n'; -import { - compose, - withInstanceId, -} from '@wordpress/compose'; -import { - withSelect, -} from '@wordpress/data'; +import { __, sprintf } from '@wordpress/i18n'; +import { compose, withInstanceId } from '@wordpress/compose'; +import { withSelect } from '@wordpress/data'; import { video as icon } from '@wordpress/icons'; /** @@ -90,9 +82,9 @@ class VideoEdit extends Component { if ( newSrc !== src ) { // Check if there's an embed block that handles this URL. - const embedBlock = createUpgradedEmbedBlock( - { attributes: { url: newSrc } } - ); + const embedBlock = createUpgradedEmbedBlock( { + attributes: { url: newSrc }, + } ); if ( undefined !== embedBlock ) { this.props.onReplace( embedBlock ); return; @@ -121,12 +113,7 @@ class VideoEdit extends Component { } render() { - const { - caption, - controls, - poster, - src, - } = this.props.attributes; + const { caption, controls, poster, src } = this.props.attributes; const { className, instanceId, @@ -184,41 +171,52 @@ class VideoEdit extends Component { attributes={ attributes } /> <MediaUploadCheck> - <BaseControl - className="editor-video-poster-control" - > + <BaseControl className="editor-video-poster-control"> <BaseControl.VisualLabel> { __( 'Poster Image' ) } </BaseControl.VisualLabel> <MediaUpload title={ __( 'Select Poster Image' ) } onSelect={ this.onSelectPoster } - allowedTypes={ VIDEO_POSTER_ALLOWED_MEDIA_TYPES } + allowedTypes={ + VIDEO_POSTER_ALLOWED_MEDIA_TYPES + } render={ ( { open } ) => ( <Button isSecondary onClick={ open } ref={ this.posterImageButton } - aria-describedby={ videoPosterDescription } + aria-describedby={ + videoPosterDescription + } > - { ! this.props.attributes.poster ? __( 'Select Poster Image' ) : __( 'Replace image' ) } + { ! this.props.attributes.poster + ? __( 'Select Poster Image' ) + : __( 'Replace image' ) } </Button> ) } /> - <p - id={ videoPosterDescription } - hidden - > - { this.props.attributes.poster ? - sprintf( __( 'The current poster image url is %s' ), this.props.attributes.poster ) : - __( 'There is no poster image currently selected' ) - } + <p id={ videoPosterDescription } hidden> + { this.props.attributes.poster + ? sprintf( + __( + 'The current poster image url is %s' + ), + this.props.attributes.poster + ) + : __( + 'There is no poster image currently selected' + ) } </p> - { !! this.props.attributes.poster && - <Button onClick={ this.onRemovePoster } isLink isDestructive> + { !! this.props.attributes.poster && ( + <Button + onClick={ this.onRemovePoster } + isLink + isDestructive + > { __( 'Remove Poster Image' ) } </Button> - } + ) } </BaseControl> </MediaUploadCheck> </PanelBody> @@ -241,7 +239,9 @@ class VideoEdit extends Component { tagName="figcaption" placeholder={ __( 'Write caption…' ) } value={ caption } - onChange={ ( value ) => setAttributes( { caption: value } ) } + onChange={ ( value ) => + setAttributes( { caption: value } ) + } inlineToolbar /> ) } diff --git a/packages/block-library/src/video/edit.native.js b/packages/block-library/src/video/edit.native.js index de1c27fb6e8ac3..ed114902e33bb4 100644 --- a/packages/block-library/src/video/edit.native.js +++ b/packages/block-library/src/video/edit.native.js @@ -63,9 +63,15 @@ class VideoEdit extends React.Component { }; this.mediaUploadStateReset = this.mediaUploadStateReset.bind( this ); - this.onSelectMediaUploadOption = this.onSelectMediaUploadOption.bind( this ); - this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( this ); - this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( this ); + this.onSelectMediaUploadOption = this.onSelectMediaUploadOption.bind( + this + ); + this.finishMediaUploadWithSuccess = this.finishMediaUploadWithSuccess.bind( + this + ); + this.finishMediaUploadWithFailure = this.finishMediaUploadWithFailure.bind( + this + ); this.updateMediaProgress = this.updateMediaProgress.bind( this ); this.onVideoPressed = this.onVideoPressed.bind( this ); this.onVideoContanerLayout = this.onVideoContanerLayout.bind( this ); @@ -81,8 +87,14 @@ class VideoEdit extends React.Component { componentWillUnmount() { // this action will only exist if the user pressed the trash button on the block holder - if ( hasAction( 'blocks.onRemoveBlockCheckUpload' ) && this.state.isUploadInProgress ) { - doAction( 'blocks.onRemoveBlockCheckUpload', this.props.attributes.id ); + if ( + hasAction( 'blocks.onRemoveBlockCheckUpload' ) && + this.state.isUploadInProgress + ) { + doAction( + 'blocks.onRemoveBlockCheckUpload', + this.props.attributes.id + ); } } @@ -161,10 +173,16 @@ class VideoEdit extends React.Component { case ICON_TYPE.RETRY: return <Icon icon={ SvgIconRetry } { ...style.icon } />; case ICON_TYPE.PLACEHOLDER: - iconStyle = this.props.getStylesFromColorScheme( style.icon, style.iconDark ); + iconStyle = this.props.getStylesFromColorScheme( + style.icon, + style.iconDark + ); break; case ICON_TYPE.UPLOAD: - iconStyle = this.props.getStylesFromColorScheme( style.iconUploading, style.iconUploadingDark ); + iconStyle = this.props.getStylesFromColorScheme( + style.iconUploading, + style.iconUploadingDark + ); break; } @@ -173,14 +191,12 @@ class VideoEdit extends React.Component { render() { const { setAttributes, attributes, isSelected } = this.props; - const { - id, - src, - } = attributes; + const { id, src } = attributes; const { videoContainerHeight } = this.state; const toolbarEditButton = ( - <MediaUpload allowedTypes={ [ MEDIA_TYPE_VIDEO ] } + <MediaUpload + allowedTypes={ [ MEDIA_TYPE_VIDEO ] } onSelect={ this.onSelectMediaUploadOption } render={ ( { open, getMediaOptions } ) => { return ( @@ -193,13 +209,13 @@ class VideoEdit extends React.Component { /> </ToolbarGroup> ); - } } > - </MediaUpload> + } } + ></MediaUpload> ); if ( ! id ) { return ( - <View style={ { flex: 1 } } > + <View style={ { flex: 1 } }> <MediaPlaceholder allowedTypes={ [ MEDIA_TYPE_VIDEO ] } onSelect={ this.onSelectMediaUploadOption } @@ -217,10 +233,9 @@ class VideoEdit extends React.Component { disabled={ ! isSelected } > <View style={ { flex: 1 } }> - { ! this.state.isCaptionSelected && - <BlockControls> - { toolbarEditButton } - </BlockControls> } + { ! this.state.isCaptionSelected && ( + <BlockControls>{ toolbarEditButton }</BlockControls> + ) } <InspectorControls> <PanelBody title={ __( 'Video settings' ) }> <VideoCommonSettings @@ -231,14 +246,31 @@ class VideoEdit extends React.Component { </InspectorControls> <MediaUploadProgress mediaId={ id } - onFinishMediaUploadWithSuccess={ this.finishMediaUploadWithSuccess } - onFinishMediaUploadWithFailure={ this.finishMediaUploadWithFailure } + onFinishMediaUploadWithSuccess={ + this.finishMediaUploadWithSuccess + } + onFinishMediaUploadWithFailure={ + this.finishMediaUploadWithFailure + } onUpdateMediaProgress={ this.updateMediaProgress } onMediaUploadStateReset={ this.mediaUploadStateReset } - renderContent={ ( { isUploadInProgress, isUploadFailed, retryMessage } ) => { - const showVideo = isURL( src ) && ! isUploadInProgress && ! isUploadFailed; - const icon = this.getIcon( isUploadFailed ? ICON_TYPE.RETRY : ICON_TYPE.UPLOAD ); - const styleIconContainer = isUploadFailed ? style.modalIconRetry : style.modalIcon; + renderContent={ ( { + isUploadInProgress, + isUploadFailed, + retryMessage, + } ) => { + const showVideo = + isURL( src ) && + ! isUploadInProgress && + ! isUploadFailed; + const icon = this.getIcon( + isUploadFailed + ? ICON_TYPE.RETRY + : ICON_TYPE.UPLOAD + ); + const styleIconContainer = isUploadFailed + ? style.modalIconRetry + : style.modalIcon; const iconContainer = ( <View style={ styleIconContainer }> @@ -251,27 +283,54 @@ class VideoEdit extends React.Component { ...style.video, }; - const containerStyle = showVideo && isSelected ? style.containerFocused : style.container; + const containerStyle = + showVideo && isSelected + ? style.containerFocused + : style.container; return ( - <View onLayout={ this.onVideoContanerLayout } style={ containerStyle }> - { showVideo && + <View + onLayout={ this.onVideoContanerLayout } + style={ containerStyle } + > + { showVideo && ( <View style={ style.videoContainer }> <VideoPlayer - isSelected={ isSelected && ! this.state.isCaptionSelected } + isSelected={ + isSelected && + ! this.state + .isCaptionSelected + } style={ videoStyle } source={ { uri: src } } paused={ true } /> </View> - } - { ! showVideo && - <View style={ { height: videoContainerHeight, width: '100%', ...this.props.getStylesFromColorScheme( - style.placeholderContainer, style.placeholderContainerDark ) } }> - { videoContainerHeight > 0 && iconContainer } - { isUploadFailed && <Text style={ style.uploadFailedText }>{ retryMessage }</Text> } + ) } + { ! showVideo && ( + <View + style={ { + height: videoContainerHeight, + width: '100%', + ...this.props.getStylesFromColorScheme( + style.placeholderContainer, + style.placeholderContainerDark + ), + } } + > + { videoContainerHeight > 0 && + iconContainer } + { isUploadFailed && ( + <Text + style={ + style.uploadFailedText + } + > + { retryMessage } + </Text> + ) } </View> - } + ) } </View> ); } } @@ -279,13 +338,14 @@ class VideoEdit extends React.Component { <BlockCaption accessible={ true } accessibilityLabelCreator={ ( caption ) => - isEmpty( caption ) ? - /* translators: accessibility text. Empty video caption. */ - ( 'Video caption. Empty' ) : - sprintf( - /* translators: accessibility text. %s: video caption. */ - __( 'Video caption. %s' ), - caption ) + isEmpty( caption ) + ? /* translators: accessibility text. Empty video caption. */ + 'Video caption. Empty' + : sprintf( + /* translators: accessibility text. %s: video caption. */ + __( 'Video caption. %s' ), + caption + ) } clientId={ this.props.clientId } isSelected={ this.state.isCaptionSelected } diff --git a/packages/block-library/src/video/icon-retry.native.js b/packages/block-library/src/video/icon-retry.native.js index d56dff2cac124e..d9b3d82e796cb0 100644 --- a/packages/block-library/src/video/icon-retry.native.js +++ b/packages/block-library/src/video/icon-retry.native.js @@ -3,5 +3,9 @@ */ import { Path, SVG } from '@wordpress/components'; -export default <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" /><Path d="M0 0h24v24H0z" fill="none" /></SVG>; - +export default ( + <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> + <Path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" /> + <Path d="M0 0h24v24H0z" fill="none" /> + </SVG> +); diff --git a/packages/block-library/src/video/index.js b/packages/block-library/src/video/index.js index b7997c1f4438ef..7fcabbee16db06 100644 --- a/packages/block-library/src/video/index.js +++ b/packages/block-library/src/video/index.js @@ -18,7 +18,9 @@ export { metadata, name }; export const settings = { title: __( 'Video' ), - description: __( 'Embed a video from your media library or upload a new one.' ), + description: __( + 'Embed a video from your media library or upload a new one.' + ), icon, keywords: [ __( 'movie' ) ], transforms, diff --git a/packages/block-library/src/video/save.js b/packages/block-library/src/video/save.js index f2b621bcadb2da..a16c3b0fd387aa 100644 --- a/packages/block-library/src/video/save.js +++ b/packages/block-library/src/video/save.js @@ -4,7 +4,17 @@ import { RichText } from '@wordpress/block-editor'; export default function save( { attributes } ) { - const { autoplay, caption, controls, loop, muted, poster, preload, src, playsInline } = attributes; + const { + autoplay, + caption, + controls, + loop, + muted, + poster, + preload, + src, + playsInline, + } = attributes; return ( <figure> { src && ( diff --git a/packages/block-library/src/video/transforms.js b/packages/block-library/src/video/transforms.js index ca070a37855a75..246032422d643d 100644 --- a/packages/block-library/src/video/transforms.js +++ b/packages/block-library/src/video/transforms.js @@ -9,7 +9,10 @@ const transforms = { { type: 'files', isMatch( files ) { - return files.length === 1 && files[ 0 ].type.indexOf( 'video/' ) === 0; + return ( + files.length === 1 && + files[ 0 ].type.indexOf( 'video/' ) === 0 + ); }, transform( files ) { const file = files[ 0 ]; @@ -28,7 +31,9 @@ const transforms = { attributes: { src: { type: 'string', - shortcode: ( { named: { src, mp4, m4v, webm, ogv, flv } } ) => { + shortcode: ( { + named: { src, mp4, m4v, webm, ogv, flv }, + } ) => { return src || mp4 || m4v || webm || ogv || flv; }, }, diff --git a/packages/block-serialization-default-parser/src/index.js b/packages/block-serialization-default-parser/src/index.js index d92fcfc5fc18bb..cc6f61e5fe7352 100644 --- a/packages/block-serialization-default-parser/src/index.js +++ b/packages/block-serialization-default-parser/src/index.js @@ -167,7 +167,7 @@ function proceed() { const stackDepth = stack.length; // we may have some HTML soup before the next block - const leadingHtmlStart = ( startOffset > offset ) ? offset : null; + const leadingHtmlStart = startOffset > offset ? offset : null; switch ( tokenType ) { case 'no-more-tokens': @@ -202,7 +202,14 @@ function proceed() { // in the top-level of the document if ( 0 === stackDepth ) { if ( null !== leadingHtmlStart ) { - output.push( Freeform( document.substr( leadingHtmlStart, startOffset - leadingHtmlStart ) ) ); + output.push( + Freeform( + document.substr( + leadingHtmlStart, + startOffset - leadingHtmlStart + ) + ) + ); } output.push( Block( blockName, attrs, [], '', [] ) ); offset = startOffset + tokenLength; @@ -213,7 +220,7 @@ function proceed() { addInnerBlock( Block( blockName, attrs, [], '', [] ), startOffset, - tokenLength, + tokenLength ); offset = startOffset + tokenLength; return true; @@ -226,8 +233,8 @@ function proceed() { startOffset, tokenLength, startOffset + tokenLength, - leadingHtmlStart, - ), + leadingHtmlStart + ) ); offset = startOffset + tokenLength; return true; @@ -254,7 +261,10 @@ function proceed() { // otherwise we're nested and we have to close out the current // block and add it as a innerBlock to the parent const stackTop = stack.pop(); - const html = document.substr( stackTop.prevOffset, startOffset - stackTop.prevOffset ); + const html = document.substr( + stackTop.prevOffset, + startOffset - stackTop.prevOffset + ); stackTop.block.innerHTML += html; stackTop.block.innerContent.push( html ); stackTop.prevOffset = startOffset + tokenLength; @@ -263,7 +273,7 @@ function proceed() { stackTop.block, stackTop.tokenStart, stackTop.tokenLength, - startOffset + tokenLength, + startOffset + tokenLength ); offset = startOffset + tokenLength; return true; @@ -308,7 +318,15 @@ function nextToken() { } const startedAt = matches.index; - const [ match, closerMatch, namespaceMatch, nameMatch, attrsMatch, /* internal/unused */, voidMatch ] = matches; + const [ + match, + closerMatch, + namespaceMatch, + nameMatch, + attrsMatch /* internal/unused */, + , + voidMatch, + ] = matches; const length = match.length; const isCloser = !! closerMatch; @@ -349,7 +367,10 @@ function addFreeform( rawLength ) { function addInnerBlock( block, tokenStart, tokenLength, lastOffset ) { const parent = stack[ stack.length - 1 ]; parent.block.innerBlocks.push( block ); - const html = document.substr( parent.prevOffset, tokenStart - parent.prevOffset ); + const html = document.substr( + parent.prevOffset, + tokenStart - parent.prevOffset + ); if ( html ) { parent.block.innerHTML += html; @@ -363,7 +384,9 @@ function addInnerBlock( block, tokenStart, tokenLength, lastOffset ) { function addBlockFromStack( endOffset ) { const { block, leadingHtmlStart, prevOffset, tokenStart } = stack.pop(); - const html = endOffset ? document.substr( prevOffset, endOffset - prevOffset ) : document.substr( prevOffset ); + const html = endOffset + ? document.substr( prevOffset, endOffset - prevOffset ) + : document.substr( prevOffset ); if ( html ) { block.innerHTML += html; @@ -371,7 +394,14 @@ function addBlockFromStack( endOffset ) { } if ( null !== leadingHtmlStart ) { - output.push( Freeform( document.substr( leadingHtmlStart, tokenStart - leadingHtmlStart ) ) ); + output.push( + Freeform( + document.substr( + leadingHtmlStart, + tokenStart - leadingHtmlStart + ) + ) + ); } output.push( block ); diff --git a/packages/block-serialization-default-parser/test/index.js b/packages/block-serialization-default-parser/test/index.js index 33c494cf51ab56..0762fb62525a93 100644 --- a/packages/block-serialization-default-parser/test/index.js +++ b/packages/block-serialization-default-parser/test/index.js @@ -7,7 +7,10 @@ import path from 'path'; * WordPress dependencies */ // eslint-disable-next-line no-restricted-syntax -import { jsTester, phpTester } from '@wordpress/block-serialization-spec-parser/shared-tests'; +import { + jsTester, + phpTester, +} from '@wordpress/block-serialization-spec-parser/shared-tests'; /** * Internal dependencies @@ -16,4 +19,7 @@ import { parse } from '../src'; describe( 'block-serialization-default-parser-js', jsTester( parse ) ); // eslint-disable-line jest/valid-describe -phpTester( 'block-serialization-default-parser-php', path.join( __dirname, 'test-parser.php' ) ); +phpTester( + 'block-serialization-default-parser-php', + path.join( __dirname, 'test-parser.php' ) +); diff --git a/packages/block-serialization-spec-parser/bin/create-php-parser.js b/packages/block-serialization-spec-parser/bin/create-php-parser.js index c9e48a200fa48a..8152f098491989 100755 --- a/packages/block-serialization-spec-parser/bin/create-php-parser.js +++ b/packages/block-serialization-spec-parser/bin/create-php-parser.js @@ -5,21 +5,18 @@ const phpegjs = require( 'phpegjs' ); const fs = require( 'fs' ); const path = require( 'path' ); -const peg = fs.readFileSync( path.join( __dirname, '..', 'grammar.pegjs' ), 'utf8' ); - -const parser = pegjs.generate( - peg, - { - plugins: [ phpegjs ], - phpegjs: { - parserNamespace: null, - parserGlobalNamePrefix: 'Gutenberg_PEG_', - mbstringAllowed: false, - }, - } +const peg = fs.readFileSync( + path.join( __dirname, '..', 'grammar.pegjs' ), + 'utf8' ); -fs.writeFileSync( - path.join( __dirname, '..', 'parser.php' ), - parser -); +const parser = pegjs.generate( peg, { + plugins: [ phpegjs ], + phpegjs: { + parserNamespace: null, + parserGlobalNamePrefix: 'Gutenberg_PEG_', + mbstringAllowed: false, + }, +} ); + +fs.writeFileSync( path.join( __dirname, '..', 'parser.php' ), parser ); diff --git a/packages/block-serialization-spec-parser/shared-tests.js b/packages/block-serialization-spec-parser/shared-tests.js index 74c991b6bbd72f..80655fedb76c0c 100644 --- a/packages/block-serialization-spec-parser/shared-tests.js +++ b/packages/block-serialization-spec-parser/shared-tests.js @@ -3,68 +3,170 @@ export const jsTester = ( parse ) => () => { test( 'output is an array', () => { expect( parse( '' ) ).toEqual( expect.any( Array ) ); expect( parse( 'test' ) ).toEqual( expect.any( Array ) ); - expect( parse( '<!-- wp:void /-->' ) ).toEqual( expect.any( Array ) ); - expect( parse( '<!-- wp:block --><!-- wp:inner /--><!-- /wp:block -->' ) ).toEqual( expect.any( Array ) ); - expect( parse( '<!-- wp:first /--><!-- wp:second /-->' ) ).toEqual( expect.any( Array ) ); + expect( parse( '<!-- wp:void /-->' ) ).toEqual( + expect.any( Array ) + ); + expect( + parse( '<!-- wp:block --><!-- wp:inner /--><!-- /wp:block -->' ) + ).toEqual( expect.any( Array ) ); + expect( parse( '<!-- wp:first /--><!-- wp:second /-->' ) ).toEqual( + expect.any( Array ) + ); } ); test( 'parses blocks of various types', () => { - expect( parse( '<!-- wp:void /-->' )[ 0 ] ).toHaveProperty( 'blockName', 'core/void' ); - expect( parse( '<!-- wp:void {} /-->' )[ 0 ] ).toHaveProperty( 'blockName', 'core/void' ); - expect( parse( '<!-- wp:void {"value":true} /-->' )[ 0 ] ).toHaveProperty( 'blockName', 'core/void' ); - expect( parse( '<!-- wp:void {"a":{}} /-->' )[ 0 ] ).toHaveProperty( 'blockName', 'core/void' ); - expect( parse( '<!-- wp:void { "value" : true } /-->' )[ 0 ] ).toHaveProperty( 'blockName', 'core/void' ); - expect( parse( '<!-- wp:void {\n\t"value" : true\n} /-->' )[ 0 ] ).toHaveProperty( 'blockName', 'core/void' ); - expect( parse( '<!-- wp:block --><!-- /wp:block -->' )[ 0 ] ).toHaveProperty( 'blockName', 'core/block' ); - expect( parse( '<!-- wp:block {} --><!-- /wp:block -->' )[ 0 ] ).toHaveProperty( 'blockName', 'core/block' ); - expect( parse( '<!-- wp:block {"value":true} --><!-- /wp:block -->' )[ 0 ] ).toHaveProperty( 'blockName', 'core/block' ); - expect( parse( '<!-- wp:block {} -->inner<!-- /wp:block -->' )[ 0 ] ).toHaveProperty( 'blockName', 'core/block' ); - expect( parse( '<!-- wp:block {"value":{"a" : "true"}} -->inner<!-- /wp:block -->' )[ 0 ] ).toHaveProperty( 'blockName', 'core/block' ); + expect( parse( '<!-- wp:void /-->' )[ 0 ] ).toHaveProperty( + 'blockName', + 'core/void' + ); + expect( parse( '<!-- wp:void {} /-->' )[ 0 ] ).toHaveProperty( + 'blockName', + 'core/void' + ); + expect( + parse( '<!-- wp:void {"value":true} /-->' )[ 0 ] + ).toHaveProperty( 'blockName', 'core/void' ); + expect( parse( '<!-- wp:void {"a":{}} /-->' )[ 0 ] ).toHaveProperty( + 'blockName', + 'core/void' + ); + expect( + parse( '<!-- wp:void { "value" : true } /-->' )[ 0 ] + ).toHaveProperty( 'blockName', 'core/void' ); + expect( + parse( '<!-- wp:void {\n\t"value" : true\n} /-->' )[ 0 ] + ).toHaveProperty( 'blockName', 'core/void' ); + expect( + parse( '<!-- wp:block --><!-- /wp:block -->' )[ 0 ] + ).toHaveProperty( 'blockName', 'core/block' ); + expect( + parse( '<!-- wp:block {} --><!-- /wp:block -->' )[ 0 ] + ).toHaveProperty( 'blockName', 'core/block' ); + expect( + parse( + '<!-- wp:block {"value":true} --><!-- /wp:block -->' + )[ 0 ] + ).toHaveProperty( 'blockName', 'core/block' ); + expect( + parse( '<!-- wp:block {} -->inner<!-- /wp:block -->' )[ 0 ] + ).toHaveProperty( 'blockName', 'core/block' ); + expect( + parse( + '<!-- wp:block {"value":{"a" : "true"}} -->inner<!-- /wp:block -->' + )[ 0 ] + ).toHaveProperty( 'blockName', 'core/block' ); } ); test( 'blockName is namespaced string (except freeform)', () => { - expect( parse( 'freeform has null name' )[ 0 ] ).toHaveProperty( 'blockName', null ); - expect( parse( '<!-- wp:more /-->' )[ 0 ] ).toHaveProperty( 'blockName', 'core/more' ); - expect( parse( '<!-- wp:core/more /-->' )[ 0 ] ).toHaveProperty( 'blockName', 'core/more' ); - expect( parse( '<!-- wp:my/more /-->' )[ 0 ] ).toHaveProperty( 'blockName', 'my/more' ); + expect( parse( 'freeform has null name' )[ 0 ] ).toHaveProperty( + 'blockName', + null + ); + expect( parse( '<!-- wp:more /-->' )[ 0 ] ).toHaveProperty( + 'blockName', + 'core/more' + ); + expect( parse( '<!-- wp:core/more /-->' )[ 0 ] ).toHaveProperty( + 'blockName', + 'core/more' + ); + expect( parse( '<!-- wp:my/more /-->' )[ 0 ] ).toHaveProperty( + 'blockName', + 'my/more' + ); } ); test( 'JSON attributes are key/value object', () => { - expect( parse( 'freeform has empty attrs' )[ 0 ] ).toHaveProperty( 'attrs', {} ); - expect( parse( '<!-- wp:void /-->' )[ 0 ] ).toHaveProperty( 'attrs', {} ); - expect( parse( '<!-- wp:void {} /-->' )[ 0 ] ).toHaveProperty( 'blockName', 'core/void' ); - expect( parse( '<!-- wp:void {} /-->' )[ 0 ] ).toHaveProperty( 'attrs', {} ); - expect( parse( '<!-- wp:void {"key": "value"} /-->' )[ 0 ] ).toHaveProperty( 'attrs', { key: 'value' } ); - expect( parse( '<!-- wp:block --><!-- /wp:block -->' )[ 0 ] ).toHaveProperty( 'attrs', {} ); - expect( parse( '<!-- wp:block {} --><!-- /wp:block -->' )[ 0 ] ).toHaveProperty( 'attrs', {} ); - expect( parse( '<!-- wp:block {"key": "value"} --><!-- /wp:block -->' )[ 0 ] ).toHaveProperty( 'attrs', { key: 'value' } ); + expect( parse( 'freeform has empty attrs' )[ 0 ] ).toHaveProperty( + 'attrs', + {} + ); + expect( parse( '<!-- wp:void /-->' )[ 0 ] ).toHaveProperty( + 'attrs', + {} + ); + expect( parse( '<!-- wp:void {} /-->' )[ 0 ] ).toHaveProperty( + 'blockName', + 'core/void' + ); + expect( parse( '<!-- wp:void {} /-->' )[ 0 ] ).toHaveProperty( + 'attrs', + {} + ); + expect( + parse( '<!-- wp:void {"key": "value"} /-->' )[ 0 ] + ).toHaveProperty( 'attrs', { + key: 'value', + } ); + expect( + parse( '<!-- wp:block --><!-- /wp:block -->' )[ 0 ] + ).toHaveProperty( 'attrs', {} ); + expect( + parse( '<!-- wp:block {} --><!-- /wp:block -->' )[ 0 ] + ).toHaveProperty( 'attrs', {} ); + expect( + parse( + '<!-- wp:block {"key": "value"} --><!-- /wp:block -->' + )[ 0 ] + ).toHaveProperty( 'attrs', { key: 'value' } ); } ); test( 'innerBlocks is a list', () => { - expect( parse( 'freeform has empty innerBlocks' )[ 0 ] ).toHaveProperty( 'innerBlocks', [] ); - expect( parse( '<!-- wp:void /-->' )[ 0 ] ).toHaveProperty( 'innerBlocks', [] ); - expect( parse( '<!-- wp:block --><!-- /wp:block -->' )[ 0 ] ).toHaveProperty( 'innerBlocks', [] ); + expect( + parse( 'freeform has empty innerBlocks' )[ 0 ] + ).toHaveProperty( 'innerBlocks', [] ); + expect( parse( '<!-- wp:void /-->' )[ 0 ] ).toHaveProperty( + 'innerBlocks', + [] + ); + expect( + parse( '<!-- wp:block --><!-- /wp:block -->' )[ 0 ] + ).toHaveProperty( 'innerBlocks', [] ); - const withInner = parse( '<!-- wp:block --><!-- wp:inner /--><!-- /wp:block -->' )[ 0 ]; - expect( withInner ).toHaveProperty( 'innerBlocks', expect.any( Array ) ); + const withInner = parse( + '<!-- wp:block --><!-- wp:inner /--><!-- /wp:block -->' + )[ 0 ]; + expect( withInner ).toHaveProperty( + 'innerBlocks', + expect.any( Array ) + ); expect( withInner.innerBlocks ).toHaveLength( 1 ); - const withTwoInner = parse( '<!-- wp:block -->a<!-- wp:first /-->b<!-- wp:second /-->c<!-- /wp:block -->' )[ 0 ]; - expect( withTwoInner ).toHaveProperty( 'innerBlocks', expect.any( Array ) ); + const withTwoInner = parse( + '<!-- wp:block -->a<!-- wp:first /-->b<!-- wp:second /-->c<!-- /wp:block -->' + )[ 0 ]; + expect( withTwoInner ).toHaveProperty( + 'innerBlocks', + expect.any( Array ) + ); expect( withTwoInner.innerBlocks ).toHaveLength( 2 ); } ); test( 'innerHTML is a string', () => { - expect( parse( 'test' )[ 0 ] ).toHaveProperty( 'innerHTML', expect.any( String ) ); - expect( parse( '<!-- wp:test /-->' )[ 0 ] ).toHaveProperty( 'innerHTML', expect.any( String ) ); - expect( parse( '<!-- wp:test --><!-- /wp:test -->' )[ 0 ] ).toHaveProperty( 'innerHTML', expect.any( String ) ); - expect( parse( '<!-- wp:test -->test<!-- /wp:test -->' )[ 0 ] ).toHaveProperty( 'innerHTML', expect.any( String ) ); + expect( parse( 'test' )[ 0 ] ).toHaveProperty( + 'innerHTML', + expect.any( String ) + ); + expect( parse( '<!-- wp:test /-->' )[ 0 ] ).toHaveProperty( + 'innerHTML', + expect.any( String ) + ); + expect( + parse( '<!-- wp:test --><!-- /wp:test -->' )[ 0 ] + ).toHaveProperty( 'innerHTML', expect.any( String ) ); + expect( + parse( '<!-- wp:test -->test<!-- /wp:test -->' )[ 0 ] + ).toHaveProperty( 'innerHTML', expect.any( String ) ); } ); } ); describe( 'generic tests', () => { test( 'parse() accepts inputs with multiple Reusable blocks', () => { - expect( parse( '<!-- wp:block {"ref":313} /--><!-- wp:block {"ref":482} /-->' ) ).toEqual( [ + expect( + parse( + '<!-- wp:block {"ref":313} /--><!-- wp:block {"ref":482} /-->' + ) + ).toEqual( [ expect.objectContaining( { blockName: 'core/block', attrs: { ref: 313 }, @@ -77,72 +179,112 @@ export const jsTester = ( parse ) => () => { } ); test( 'treats void blocks and empty blocks identically', () => { - expect( parse( - '<!-- wp:block /-->' - ) ).toEqual( parse( - '<!-- wp:block --><!-- /wp:block -->' - ) ); + expect( parse( '<!-- wp:block /-->' ) ).toEqual( + parse( '<!-- wp:block --><!-- /wp:block -->' ) + ); - expect( parse( - '<!-- wp:my/bus { "is": "fast" } /-->' - ) ).toEqual( parse( - '<!-- wp:my/bus { "is": "fast" } --><!-- /wp:my/bus -->' - ) ); + expect( parse( '<!-- wp:my/bus { "is": "fast" } /-->' ) ).toEqual( + parse( + '<!-- wp:my/bus { "is": "fast" } --><!-- /wp:my/bus -->' + ) + ); } ); test( 'should grab HTML soup before block openers', () => { [ '<p>Break me</p><!-- wp:block /-->', '<p>Break me</p><!-- wp:block --><!-- /wp:block -->', - ].forEach( ( input ) => expect( parse( input ) ).toEqual( [ - expect.objectContaining( { innerHTML: '<p>Break me</p>' } ), - expect.objectContaining( { blockName: 'core/block', innerHTML: '' } ), - ] ) ); - } ); - - test( 'should grab HTML soup before inner block openers', () => [ - '<!-- wp:outer --><p>Break me</p><!-- wp:block /--><!-- /wp:outer -->', - '<!-- wp:outer --><p>Break me</p><!-- wp:block --><!-- /wp:block --><!-- /wp:outer -->', - ].forEach( ( input ) => expect( parse( input ) ).toEqual( [ - expect.objectContaining( { - innerBlocks: [ expect.objectContaining( { blockName: 'core/block', innerHTML: '' } ) ], - innerHTML: '<p>Break me</p>', - } ), - ] ) ) ); - - test( 'should grab HTML soup after blocks', () => [ - '<!-- wp:block /--><p>Break me</p>', - '<!-- wp:block --><!-- /wp:block --><p>Break me</p>', - ].forEach( ( input ) => expect( parse( input ) ).toEqual( [ - expect.objectContaining( { blockName: 'core/block', innerHTML: '' } ), - expect.objectContaining( { innerHTML: '<p>Break me</p>' } ), - ] ) ) ); + ].forEach( ( input ) => + expect( parse( input ) ).toEqual( [ + expect.objectContaining( { innerHTML: '<p>Break me</p>' } ), + expect.objectContaining( { + blockName: 'core/block', + innerHTML: '', + } ), + ] ) + ); + } ); + + test( 'should grab HTML soup before inner block openers', () => + [ + '<!-- wp:outer --><p>Break me</p><!-- wp:block /--><!-- /wp:outer -->', + '<!-- wp:outer --><p>Break me</p><!-- wp:block --><!-- /wp:block --><!-- /wp:outer -->', + ].forEach( ( input ) => + expect( parse( input ) ).toEqual( [ + expect.objectContaining( { + innerBlocks: [ + expect.objectContaining( { + blockName: 'core/block', + innerHTML: '', + } ), + ], + innerHTML: '<p>Break me</p>', + } ), + ] ) + ) ); + + test( 'should grab HTML soup after blocks', () => + [ + '<!-- wp:block /--><p>Break me</p>', + '<!-- wp:block --><!-- /wp:block --><p>Break me</p>', + ].forEach( ( input ) => + expect( parse( input ) ).toEqual( [ + expect.objectContaining( { + blockName: 'core/block', + innerHTML: '', + } ), + expect.objectContaining( { innerHTML: '<p>Break me</p>' } ), + ] ) + ) ); } ); describe( 'innerBlock placemarkers', () => { test( 'innerContent exists', () => { - expect( parse( 'test' )[ 0 ] ).toHaveProperty( 'innerContent', [ 'test' ] ); - expect( parse( '<!-- wp:void /-->' )[ 0 ] ).toHaveProperty( 'innerContent', [] ); + expect( parse( 'test' )[ 0 ] ).toHaveProperty( 'innerContent', [ + 'test', + ] ); + expect( parse( '<!-- wp:void /-->' )[ 0 ] ).toHaveProperty( + 'innerContent', + [] + ); } ); test( 'innerContent contains innerHTML', () => { - expect( parse( '<!-- wp:block -->Inner<!-- /wp:block -->' )[ 0 ] ).toHaveProperty( 'innerContent', [ 'Inner' ] ); + expect( + parse( '<!-- wp:block -->Inner<!-- /wp:block -->' )[ 0 ] + ).toHaveProperty( 'innerContent', [ 'Inner' ] ); } ); test( 'block locations become null', () => { - expect( parse( '<!-- wp:block --><!-- wp:void /--><!-- /wp:block -->' )[ 0 ] ).toHaveProperty( 'innerContent', [ null ] ); + expect( + parse( + '<!-- wp:block --><!-- wp:void /--><!-- /wp:block -->' + )[ 0 ] + ).toHaveProperty( 'innerContent', [ null ] ); } ); test( 'HTML soup appears after blocks', () => { - expect( parse( '<!-- wp:block --><!-- wp:void /-->After<!-- /wp:block -->' )[ 0 ] ).toHaveProperty( 'innerContent', [ null, 'After' ] ); + expect( + parse( + '<!-- wp:block --><!-- wp:void /-->After<!-- /wp:block -->' + )[ 0 ] + ).toHaveProperty( 'innerContent', [ null, 'After' ] ); } ); test( 'HTML soup appears before blocks', () => { - expect( parse( '<!-- wp:block -->Before<!-- wp:void /--><!-- /wp:block -->' )[ 0 ] ).toHaveProperty( 'innerContent', [ 'Before', null ] ); + expect( + parse( + '<!-- wp:block -->Before<!-- wp:void /--><!-- /wp:block -->' + )[ 0 ] + ).toHaveProperty( 'innerContent', [ 'Before', null ] ); } ); test( 'blocks follow each other', () => { - expect( parse( '<!-- wp:block --><!-- wp:void /--><!-- wp:void /--><!-- /wp:block -->' )[ 0 ] ).toHaveProperty( 'innerContent', [ null, null ] ); + expect( + parse( + '<!-- wp:block --><!-- wp:void /--><!-- wp:void /--><!-- /wp:block -->' + )[ 0 ] + ).toHaveProperty( 'innerContent', [ null, null ] ); } ); } ); @@ -152,7 +294,12 @@ export const jsTester = ( parse ) => () => { const as = 'a'.repeat( length ); let parsed; - expect( () => parsed = parse( `<!-- wp:fake {"a":"${ as }"} /-->` )[ 0 ] ).not.toThrow(); + expect( + () => + ( parsed = parse( + `<!-- wp:fake {"a":"${ as }"} /-->` + )[ 0 ] ) + ).not.toThrow(); expect( parsed.attrs.a ).toHaveLength( length ); } ); @@ -160,52 +307,76 @@ export const jsTester = ( parse ) => () => { test( 'extra space after void closer', () => { let parsed; - expect( () => parsed = parse( '<!-- wp:block / -->' )[ 0 ] ).not.toThrow(); + expect( + () => ( parsed = parse( '<!-- wp:block / -->' )[ 0 ] ) + ).not.toThrow(); expect( parsed.blockName ).toBeNull(); } ); } ); } ); }; -const hasPHP = 'test' === process.env.NODE_ENV ? ( () => { - const process = require( 'child_process' ).spawnSync( 'php', [ '-r', 'echo 1;' ], { encoding: 'utf8' } ); +const hasPHP = + 'test' === process.env.NODE_ENV + ? ( () => { + const process = require( 'child_process' ).spawnSync( + 'php', + [ '-r', 'echo 1;' ], + { + encoding: 'utf8', + } + ); - return process.status === 0 && process.stdout === '1'; -} )() : false; + return process.status === 0 && process.stdout === '1'; + } )() + : false; // skipping if `php` isn't available to us, such as in local dev without it // skipping preserves snapshots while commenting out or simply // not injecting the tests prompts `jest` to remove "obsolete snapshots" -// eslint-disable-next-line jest/no-disabled-tests, jest/valid-describe -const makeTest = hasPHP ? ( ...args ) => describe( ...args ) : ( ...args ) => describe.skip( ...args ); - -export const phpTester = ( name, filename ) => makeTest( - name, - 'test' === process.env.NODE_ENV ? jsTester( ( doc ) => { - const process = require( 'child_process' ).spawnSync( - 'php', - [ '-f', filename ], - { - input: doc, - encoding: 'utf8', - timeout: 30 * 1000, // abort after 30 seconds, that's too long anyway - } - ); - - if ( process.status !== 0 ) { - throw new Error( process.stderr || process.stdout ); - } - - try { - /* - * Due to an issue with PHP's json_encode() serializing an empty associative array - * as an empty list `[]` we're manually replacing the already-encoded bit here. - * - * This is an issue with the test runner, not with the parser. - */ - return JSON.parse( process.stdout.replace( /"attrs":\s*\[\]/g, '"attrs":{}' ) ); - } catch ( e ) { - throw new Error( 'failed to parse JSON:\n' + process.stdout ); - } - } ) : () => {} -); +const makeTest = hasPHP + ? // eslint-disable-next-line jest/valid-describe + ( ...args ) => describe( ...args ) + : // eslint-disable-next-line jest/no-disabled-tests, jest/valid-describe + ( ...args ) => describe.skip( ...args ); + +export const phpTester = ( name, filename ) => + makeTest( + name, + 'test' === process.env.NODE_ENV + ? jsTester( ( doc ) => { + const process = require( 'child_process' ).spawnSync( + 'php', + [ '-f', filename ], + { + input: doc, + encoding: 'utf8', + timeout: 30 * 1000, // abort after 30 seconds, that's too long anyway + } + ); + + if ( process.status !== 0 ) { + throw new Error( process.stderr || process.stdout ); + } + + try { + /* + * Due to an issue with PHP's json_encode() serializing an empty associative array + * as an empty list `[]` we're manually replacing the already-encoded bit here. + * + * This is an issue with the test runner, not with the parser. + */ + return JSON.parse( + process.stdout.replace( + /"attrs":\s*\[\]/g, + '"attrs":{}' + ) + ); + } catch ( e ) { + throw new Error( + 'failed to parse JSON:\n' + process.stdout + ); + } + } ) + : () => {} + ); diff --git a/packages/block-serialization-spec-parser/test/index.js b/packages/block-serialization-spec-parser/test/index.js index 2b60ce2c11f1e3..36eb13a6b7bc0d 100644 --- a/packages/block-serialization-spec-parser/test/index.js +++ b/packages/block-serialization-spec-parser/test/index.js @@ -11,4 +11,7 @@ import { jsTester, phpTester } from '../shared-tests'; describe( 'block-serialization-spec-parser-js', jsTester( parse ) ); // eslint-disable-line jest/valid-describe -phpTester( 'block-serialization-spec-parser-php', path.join( __dirname, 'test-parser.php' ) ); +phpTester( + 'block-serialization-spec-parser-php', + path.join( __dirname, 'test-parser.php' ) +); diff --git a/packages/blocks/src/api/children.js b/packages/blocks/src/api/children.js index 7cf70d2da78151..40f279c02d7268 100644 --- a/packages/blocks/src/api/children.js +++ b/packages/blocks/src/api/children.js @@ -64,10 +64,9 @@ export function concat( ...blockNodes ) { const blockNode = castArray( blockNodes[ i ] ); for ( let j = 0; j < blockNode.length; j++ ) { const child = blockNode[ j ]; - const canConcatToPreviousString = ( + const canConcatToPreviousString = typeof child === 'string' && - typeof result[ result.length - 1 ] === 'string' - ); + typeof result[ result.length - 1 ] === 'string'; if ( canConcatToPreviousString ) { result[ result.length - 1 ] += child; diff --git a/packages/blocks/src/api/factory.js b/packages/blocks/src/api/factory.js index e793800ecb1362..983bafd73caabe 100644 --- a/packages/blocks/src/api/factory.js +++ b/packages/blocks/src/api/factory.js @@ -26,7 +26,11 @@ import { createHooks, applyFilters } from '@wordpress/hooks'; /** * Internal dependencies */ -import { getBlockType, getBlockTypes, getGroupingBlockName } from './registration'; +import { + getBlockType, + getBlockTypes, + getGroupingBlockName, +} from './registration'; import { normalizeBlockType } from './utils'; /** @@ -44,27 +48,31 @@ export function createBlock( name, attributes = {}, innerBlocks = [] ) { // Ensure attributes contains only values defined by block type, and merge // default values for missing attributes. - const sanitizedAttributes = reduce( blockType.attributes, ( accumulator, schema, key ) => { - const value = attributes[ key ]; - - if ( undefined !== value ) { - accumulator[ key ] = value; - } else if ( schema.hasOwnProperty( 'default' ) ) { - accumulator[ key ] = schema.default; - } + const sanitizedAttributes = reduce( + blockType.attributes, + ( accumulator, schema, key ) => { + const value = attributes[ key ]; + + if ( undefined !== value ) { + accumulator[ key ] = value; + } else if ( schema.hasOwnProperty( 'default' ) ) { + accumulator[ key ] = schema.default; + } - if ( [ 'node', 'children' ].indexOf( schema.source ) !== -1 ) { - // Ensure value passed is always an array, which we're expecting in - // the RichText component to handle the deprecated value. - if ( typeof accumulator[ key ] === 'string' ) { - accumulator[ key ] = [ accumulator[ key ] ]; - } else if ( ! Array.isArray( accumulator[ key ] ) ) { - accumulator[ key ] = []; + if ( [ 'node', 'children' ].indexOf( schema.source ) !== -1 ) { + // Ensure value passed is always an array, which we're expecting in + // the RichText component to handle the deprecated value. + if ( typeof accumulator[ key ] === 'string' ) { + accumulator[ key ] = [ accumulator[ key ] ]; + } else if ( ! Array.isArray( accumulator[ key ] ) ) { + accumulator[ key ] = []; + } } - } - return accumulator; - }, {} ); + return accumulator; + }, + {} + ); const clientId = uuid(); @@ -99,7 +107,8 @@ export function cloneBlock( block, mergeAttributes = {}, newInnerBlocks ) { ...block.attributes, ...mergeAttributes, }, - innerBlocks: newInnerBlocks || + innerBlocks: + newInnerBlocks || block.innerBlocks.map( ( innerBlock ) => cloneBlock( innerBlock ) ), }; } @@ -123,14 +132,20 @@ const isPossibleTransformForSource = ( transform, direction, blocks ) => { // or wildcard transforms are allowed. const isMultiBlock = blocks.length > 1; const firstBlockName = first( blocks ).name; - const isValidForMultiBlocks = isWildcardBlockTransform( transform ) || ! isMultiBlock || transform.isMultiBlock; + const isValidForMultiBlocks = + isWildcardBlockTransform( transform ) || + ! isMultiBlock || + transform.isMultiBlock; if ( ! isValidForMultiBlocks ) { return false; } // Check non-wildcard transforms to ensure that transform is valid // for a block selection of multiple blocks of different types - if ( ! isWildcardBlockTransform( transform ) && ! every( blocks, { name: firstBlockName } ) ) { + if ( + ! isWildcardBlockTransform( transform ) && + ! every( blocks, { name: firstBlockName } ) + ) { return false; } @@ -143,20 +158,29 @@ const isPossibleTransformForSource = ( transform, direction, blocks ) => { // Check if the transform's block name matches the source block (or is a wildcard) // only if this is a transform 'from'. const sourceBlock = first( blocks ); - const hasMatchingName = direction !== 'from' || transform.blocks.indexOf( sourceBlock.name ) !== -1 || isWildcardBlockTransform( transform ); + const hasMatchingName = + direction !== 'from' || + transform.blocks.indexOf( sourceBlock.name ) !== -1 || + isWildcardBlockTransform( transform ); if ( ! hasMatchingName ) { return false; } // Don't allow single Grouping blocks to be transformed into // a Grouping block. - if ( ! isMultiBlock && isContainerGroupBlock( sourceBlock.name ) && isContainerGroupBlock( transform.blockName ) ) { + if ( + ! isMultiBlock && + isContainerGroupBlock( sourceBlock.name ) && + isContainerGroupBlock( transform.blockName ) + ) { return false; } // If the transform has a `isMatch` function specified, check that it returns true. if ( isFunction( transform.isMatch ) ) { - const attributes = transform.isMultiBlock ? blocks.map( ( block ) => block.attributes ) : sourceBlock.attributes; + const attributes = transform.isMultiBlock + ? blocks.map( ( block ) => block.attributes ) + : sourceBlock.attributes; if ( ! transform.isMatch( attributes ) ) { return false; } @@ -186,13 +210,14 @@ const getBlockTypesForPossibleFromTransforms = ( blocks ) => { ( blockType ) => { const fromTransforms = getBlockTransforms( 'from', blockType.name ); - return !! findTransform( - fromTransforms, - ( transform ) => { - return isPossibleTransformForSource( transform, 'from', blocks ); - } - ); - }, + return !! findTransform( fromTransforms, ( transform ) => { + return isPossibleTransformForSource( + transform, + 'from', + blocks + ); + } ); + } ); return blockTypesWithPossibleFromTransforms; @@ -216,12 +241,11 @@ const getBlockTypesForPossibleToTransforms = ( blocks ) => { const transformsTo = getBlockTransforms( 'to', blockType.name ); // filter all 'to' transforms to find those that are possible. - const possibleTransforms = filter( - transformsTo, - ( transform ) => { - return transform && isPossibleTransformForSource( transform, 'to', blocks ); - } - ); + const possibleTransforms = filter( transformsTo, ( transform ) => { + return ( + transform && isPossibleTransformForSource( transform, 'to', blocks ) + ); + } ); // Build a list of block names using the possible 'to' transforms. const blockNames = flatMap( @@ -242,7 +266,11 @@ const getBlockTypesForPossibleToTransforms = ( blocks ) => { * * @return {boolean} whether transform is a wildcard transform */ -export const isWildcardBlockTransform = ( t ) => t && t.type === 'block' && Array.isArray( t.blocks ) && t.blocks.includes( '*' ); +export const isWildcardBlockTransform = ( t ) => + t && + t.type === 'block' && + Array.isArray( t.blocks ) && + t.blocks.includes( '*' ); /** * Determines whether the given Block is the core Block which @@ -253,7 +281,8 @@ export const isWildcardBlockTransform = ( t ) => t && t.type === 'block' && Arra * * @return {boolean} whether or not the Block is the container Block type */ -export const isContainerGroupBlock = ( name ) => name === getGroupingBlockName(); +export const isContainerGroupBlock = ( name ) => + name === getGroupingBlockName(); /** * Determines whether the provided Blocks are of the same type @@ -285,8 +314,12 @@ export function getPossibleBlockTransformations( blocks ) { return []; } - const blockTypesForFromTransforms = getBlockTypesForPossibleFromTransforms( blocks ); - const blockTypesForToTransforms = getBlockTypesForPossibleToTransforms( blocks ); + const blockTypesForFromTransforms = getBlockTypesForPossibleFromTransforms( + blocks + ); + const blockTypesForToTransforms = getBlockTypesForPossibleToTransforms( + blocks + ); return uniq( [ ...blockTypesForFromTransforms, @@ -317,7 +350,7 @@ export function findTransform( transforms, predicate ) { hooks.addFilter( 'transform', 'transform/' + i.toString(), - ( result ) => result ? result : candidate, + ( result ) => ( result ? result : candidate ), candidate.priority ); } @@ -341,9 +374,8 @@ export function findTransform( transforms, predicate ) { export function getBlockTransforms( direction, blockTypeOrName ) { // When retrieving transforms for all block types, recurse into self. if ( blockTypeOrName === undefined ) { - return flatMap( - getBlockTypes(), - ( { name } ) => getBlockTransforms( direction, name ) + return flatMap( getBlockTypes(), ( { name } ) => + getBlockTransforms( direction, name ) ); } @@ -378,7 +410,11 @@ export function switchToBlockType( blocks, name ) { // Unless it's a Grouping Block then for multi block selections // check that all Blocks are of the same type otherwise // we can't run a conversion - if ( ! isContainerGroupBlock( name ) && isMultiBlock && ! isBlockSelectionOfSameType( blocksArray ) ) { + if ( + ! isContainerGroupBlock( name ) && + isMultiBlock && + ! isBlockSelectionOfSameType( blocksArray ) + ) { return null; } @@ -390,11 +426,19 @@ export function switchToBlockType( blocks, name ) { const transformation = findTransform( transformationsTo, - ( t ) => t.type === 'block' && ( ( isWildcardBlockTransform( t ) ) || t.blocks.indexOf( name ) !== -1 ) && ( ! isMultiBlock || t.isMultiBlock ) + ( t ) => + t.type === 'block' && + ( isWildcardBlockTransform( t ) || + t.blocks.indexOf( name ) !== -1 ) && + ( ! isMultiBlock || t.isMultiBlock ) ) || findTransform( transformationsFrom, - ( t ) => t.type === 'block' && ( ( isWildcardBlockTransform( t ) ) || t.blocks.indexOf( sourceName ) !== -1 ) && ( ! isMultiBlock || t.isMultiBlock ) + ( t ) => + t.type === 'block' && + ( isWildcardBlockTransform( t ) || + t.blocks.indexOf( sourceName ) !== -1 ) && + ( ! isMultiBlock || t.isMultiBlock ) ); // Stop if there is no valid transformation. @@ -406,17 +450,24 @@ export function switchToBlockType( blocks, name ) { if ( transformation.isMultiBlock ) { if ( has( transformation, '__experimentalConvert' ) ) { - transformationResults = transformation.__experimentalConvert( blocksArray ); + transformationResults = transformation.__experimentalConvert( + blocksArray + ); } else { transformationResults = transformation.transform( blocksArray.map( ( currentBlock ) => currentBlock.attributes ), - blocksArray.map( ( currentBlock ) => currentBlock.innerBlocks ), + blocksArray.map( ( currentBlock ) => currentBlock.innerBlocks ) ); } } else if ( has( transformation, '__experimentalConvert' ) ) { - transformationResults = transformation.__experimentalConvert( firstBlock ); + transformationResults = transformation.__experimentalConvert( + firstBlock + ); } else { - transformationResults = transformation.transform( firstBlock.attributes, firstBlock.innerBlocks ); + transformationResults = transformation.transform( + firstBlock.attributes, + firstBlock.innerBlocks + ); } // Ensure that the transformation function returned an object or an array @@ -431,11 +482,18 @@ export function switchToBlockType( blocks, name ) { // Ensure that every block object returned by the transformation has a // valid block type. - if ( transformationResults.some( ( result ) => ! getBlockType( result.name ) ) ) { + if ( + transformationResults.some( + ( result ) => ! getBlockType( result.name ) + ) + ) { return null; } - const firstSwitchedBlock = findIndex( transformationResults, ( result ) => result.name === name ); + const firstSwitchedBlock = findIndex( + transformationResults, + ( result ) => result.name === name + ); // Ensure that at least one block object returned by the transformation has // the expected "destination" block type. @@ -448,7 +506,10 @@ export function switchToBlockType( blocks, name ) { ...result, // The first transformed block whose type matches the "destination" // type gets to keep the existing client ID of the first block. - clientId: index === firstSwitchedBlock ? firstBlock.clientId : result.clientId, + clientId: + index === firstSwitchedBlock + ? firstBlock.clientId + : result.clientId, }; /** @@ -459,7 +520,11 @@ export function switchToBlockType( blocks, name ) { * @param {Object} transformedBlock The transformed block. * @param {Object[]} blocks Original blocks transformed. */ - return applyFilters( 'blocks.switchToBlockType.transformedBlock', transformedBlock, blocks ); + return applyFilters( + 'blocks.switchToBlockType.transformedBlock', + transformedBlock, + blocks + ); } ); } @@ -472,7 +537,11 @@ export function switchToBlockType( blocks, name ) { * @return {Object} block. */ export const getBlockFromExample = ( name, example ) => { - return createBlock( name, example.attributes, map( - example.innerBlocks, ( innerBlock ) => getBlockFromExample( innerBlock.name, innerBlock ) - ) ); + return createBlock( + name, + example.attributes, + map( example.innerBlocks, ( innerBlock ) => + getBlockFromExample( innerBlock.name, innerBlock ) + ) + ); }; diff --git a/packages/blocks/src/api/index.js b/packages/blocks/src/api/index.js index bd2ca2ec5f1711..131175c89568e4 100644 --- a/packages/blocks/src/api/index.js +++ b/packages/blocks/src/api/index.js @@ -12,7 +12,11 @@ export { getBlockAttributes, parseWithAttributeSchema, } from './parser'; -export { pasteHandler, rawHandler, getPhrasingContentSchema } from './raw-handling'; +export { + pasteHandler, + rawHandler, + getPhrasingContentSchema, +} from './raw-handling'; export { default as serialize, getBlockContent, @@ -22,11 +26,7 @@ export { getSaveContent, } from './serializer'; export { isValidBlockContent } from './validation'; -export { - getCategories, - setCategories, - updateCategory, -} from './categories'; +export { getCategories, setCategories, updateCategory } from './categories'; export { registerBlockType, registerBlockCollection, @@ -50,8 +50,8 @@ export { unstable__bootstrapServerSideBlockDefinitions, // eslint-disable-line camelcase registerBlockStyle, unregisterBlockStyle, - __experimentalRegisterBlockPattern, - __experimentalUnregisterBlockPattern, + __experimentalRegisterBlockVariation, + __experimentalUnregisterBlockVariation, } from './registration'; export { isUnmodifiedDefaultBlock, diff --git a/packages/blocks/src/api/node.js b/packages/blocks/src/api/node.js index 075715f1d84c4a..ad749132863de7 100644 --- a/packages/blocks/src/api/node.js +++ b/packages/blocks/src/api/node.js @@ -70,7 +70,7 @@ export function fromDOM( domNode ) { if ( domNode.nodeType !== ELEMENT_NODE ) { throw new TypeError( 'A block node can only be created from a node of type text or ' + - 'element.' + 'element.' ); } diff --git a/packages/blocks/src/api/parser.js b/packages/blocks/src/api/parser.js index 6300eac329f153..85b44d31468d8c 100644 --- a/packages/blocks/src/api/parser.js +++ b/packages/blocks/src/api/parser.js @@ -31,12 +31,7 @@ import { DEPRECATED_ENTRY_KEYS } from './constants'; * * @type {Set} */ -const STRING_SOURCES = new Set( [ - 'attribute', - 'html', - 'text', - 'tag', -] ); +const STRING_SOURCES = new Set( [ 'attribute', 'html', 'text', 'tag' ] ); /** * Higher-order hpq matcher which enhances an attribute matcher to return true @@ -49,23 +44,24 @@ const STRING_SOURCES = new Set( [ * * @return {Function} Enhanced hpq matcher. */ -export const toBooleanAttributeMatcher = ( matcher ) => flow( [ - matcher, - // Expected values from `attr( 'disabled' )`: - // - // <input> - // - Value: `undefined` - // - Transformed: `false` - // - // <input disabled> - // - Value: `''` - // - Transformed: `true` - // - // <input disabled="disabled"> - // - Value: `'disabled'` - // - Transformed: `true` - ( value ) => value !== undefined, -] ); +export const toBooleanAttributeMatcher = ( matcher ) => + flow( [ + matcher, + // Expected values from `attr( 'disabled' )`: + // + // <input> + // - Value: `undefined` + // - Transformed: `false` + // + // <input disabled> + // - Value: `''` + // - Transformed: `true` + // + // <input disabled="disabled"> + // - Value: `'disabled'` + // - Transformed: `true` + ( value ) => value !== undefined, + ] ); /** * Returns true if value is of the given JSON schema type, or false otherwise. @@ -195,12 +191,16 @@ export function matcherFromSource( sourceConfig ) { case 'node': return node( sourceConfig.selector ); case 'query': - const subMatchers = mapValues( sourceConfig.query, matcherFromSource ); + const subMatchers = mapValues( + sourceConfig.query, + matcherFromSource + ); return query( sourceConfig.selector, subMatchers ); case 'tag': return flow( [ prop( sourceConfig.selector, 'nodeName' ), - ( nodeName ) => nodeName ? nodeName.toLowerCase() : undefined, + ( nodeName ) => + nodeName ? nodeName.toLowerCase() : undefined, ] ); default: // eslint-disable-next-line no-console @@ -233,14 +233,21 @@ export function parseWithAttributeSchema( innerHTML, attributeSchema ) { * * @return {*} Attribute value. */ -export function getBlockAttribute( attributeKey, attributeSchema, innerHTML, commentAttributes ) { +export function getBlockAttribute( + attributeKey, + attributeSchema, + innerHTML, + commentAttributes +) { const { type, enum: enumSet } = attributeSchema; let value; switch ( attributeSchema.source ) { // undefined source means that it's an attribute serialized to the block's "comment" case undefined: - value = commentAttributes ? commentAttributes[ attributeKey ] : undefined; + value = commentAttributes + ? commentAttributes[ attributeKey ] + : undefined; break; case 'attribute': case 'property': @@ -276,11 +283,23 @@ export function getBlockAttribute( attributeKey, attributeSchema, innerHTML, com * * @return {Object} All block attributes. */ -export function getBlockAttributes( blockTypeOrName, innerHTML, attributes = {} ) { +export function getBlockAttributes( + blockTypeOrName, + innerHTML, + attributes = {} +) { const blockType = normalizeBlockType( blockTypeOrName ); - const blockAttributes = mapValues( blockType.attributes, ( attributeSchema, attributeKey ) => { - return getBlockAttribute( attributeKey, attributeSchema, innerHTML, attributes ); - } ); + const blockAttributes = mapValues( + blockType.attributes, + ( attributeSchema, attributeKey ) => { + return getBlockAttribute( + attributeKey, + attributeSchema, + innerHTML, + attributes + ); + } + ); return applyFilters( 'blocks.getBlockAttributes', @@ -359,10 +378,10 @@ export function getMigratedBlock( block, parsedAttributes ) { // inner blocks. const { migrate } = deprecatedBlockType; if ( migrate ) { - ( [ + [ migratedAttributes = parsedAttributes, migratedInnerBlocks = innerBlocks, - ] = castArray( migrate( migratedAttributes, innerBlocks ) ) ); + ] = castArray( migrate( migratedAttributes, innerBlocks ) ); } block = { @@ -385,14 +404,11 @@ export function getMigratedBlock( block, parsedAttributes ) { */ export function createBlockWithFallback( blockNode ) { const { blockName: originalName } = blockNode; - let { - attrs: attributes, - innerBlocks = [], - innerHTML, - } = blockNode; + let { attrs: attributes, innerBlocks = [], innerHTML } = blockNode; const { innerContent } = blockNode; const freeformContentFallbackBlock = getFreeformContentHandlerName(); - const unregisteredFallbackBlock = getUnregisteredTypeHandlerName() || freeformContentFallbackBlock; + const unregisteredFallbackBlock = + getUnregisteredTypeHandlerName() || freeformContentFallbackBlock; attributes = attributes || {}; @@ -439,15 +455,16 @@ export function createBlockWithFallback( blockNode ) { // carries the block's own HTML and not its nested blocks'. const originalUndelimitedContent = serializeBlockNode( reconstitutedBlockNode, - { isCommentDelimited: false } + { + isCommentDelimited: false, + } ); // Preserve full block content for use by the unregistered type // handler, block boundaries included. - const originalContent = serializeBlockNode( - reconstitutedBlockNode, - { isCommentDelimited: true } - ); + const originalContent = serializeBlockNode( reconstitutedBlockNode, { + isCommentDelimited: true, + } ); // If detected as a block which is not registered, preserve comment // delimiters in content of unregistered type handler. @@ -456,7 +473,11 @@ export function createBlockWithFallback( blockNode ) { } name = unregisteredFallbackBlock; - attributes = { originalName, originalContent, originalUndelimitedContent }; + attributes = { + originalName, + originalContent, + originalUndelimitedContent, + }; blockType = getBlockType( name ); } @@ -469,10 +490,9 @@ export function createBlockWithFallback( blockNode ) { // empty freeform block nodes. See https://github.com/WordPress/gutenberg/pull/17164. innerBlocks = innerBlocks.filter( ( innerBlock ) => innerBlock ); - const isFallbackBlock = ( + const isFallbackBlock = name === freeformContentFallbackBlock || - name === unregisteredFallbackBlock - ); + name === unregisteredFallbackBlock; // Include in set only if type was determined. if ( ! blockType || ( ! innerHTML && isFallbackBlock ) ) { @@ -490,7 +510,11 @@ export function createBlockWithFallback( blockNode ) { // provided source value with the serialized output before there are any modifications to // the block. When both match, the block is marked as valid. if ( ! isFallbackBlock ) { - const { isValid, validationIssues } = getBlockContentValidationResult( blockType, block.attributes, innerHTML ); + const { isValid, validationIssues } = getBlockContentValidationResult( + blockType, + block.attributes, + innerHTML + ); block.isValid = isValid; block.validationIssues = validationIssues; } @@ -512,7 +536,9 @@ export function createBlockWithFallback( blockNode ) { block.originalContent ); } else { - block.validationIssues.forEach( ( { log, args } ) => log( ...args ) ); + block.validationIssues.forEach( ( { log, args } ) => + log( ...args ) + ); } } @@ -542,19 +568,28 @@ export function createBlockWithFallback( blockNode ) { */ export function serializeBlockNode( blockNode, options = {} ) { const { isCommentDelimited = true } = options; - const { blockName, attrs = {}, innerBlocks = [], innerContent = [] } = blockNode; + const { + blockName, + attrs = {}, + innerBlocks = [], + innerContent = [], + } = blockNode; let childIndex = 0; - const content = innerContent.map( ( item ) => - // `null` denotes a nested block, otherwise we have an HTML fragment - item !== null ? - item : - serializeBlockNode( innerBlocks[ childIndex++ ], options ) - ).join( '\n' ).replace( /\n+/g, '\n' ).trim(); - - return isCommentDelimited ? - getCommentDelimitedContent( blockName, attrs, content ) : - content; + const content = innerContent + .map( ( item ) => + // `null` denotes a nested block, otherwise we have an HTML fragment + item !== null + ? item + : serializeBlockNode( innerBlocks[ childIndex++ ], options ) + ) + .join( '\n' ) + .replace( /\n+/g, '\n' ) + .trim(); + + return isCommentDelimited + ? getCommentDelimitedContent( blockName, attrs, content ) + : content; } /** @@ -564,8 +599,8 @@ export function serializeBlockNode( blockNode, options = {} ) { * * @return {Function} An implementation which parses the post content. */ -const createParse = ( parseImplementation ) => - ( content ) => parseImplementation( content ).reduce( ( accumulator, blockNode ) => { +const createParse = ( parseImplementation ) => ( content ) => + parseImplementation( content ).reduce( ( accumulator, blockNode ) => { const block = createBlockWithFallback( blockNode ); if ( block ) { accumulator.push( block ); diff --git a/packages/blocks/src/api/raw-handling/html-formatting-remover.js b/packages/blocks/src/api/raw-handling/html-formatting-remover.js index f0b80c4c22c317..b44fbe71deff1f 100644 --- a/packages/blocks/src/api/raw-handling/html-formatting-remover.js +++ b/packages/blocks/src/api/raw-handling/html-formatting-remover.js @@ -57,10 +57,8 @@ export default function( node ) { if ( ! nextSibling || nextSibling.nodeName === 'BR' || - ( - nextSibling.nodeType === nextSibling.TEXT_NODE && - isFormattingSpace( nextSibling.textContent[ 0 ] ) - ) + ( nextSibling.nodeType === nextSibling.TEXT_NODE && + isFormattingSpace( nextSibling.textContent[ 0 ] ) ) ) { newData = newData.slice( 0, -1 ); } diff --git a/packages/blocks/src/api/raw-handling/image-corrector.native.js b/packages/blocks/src/api/raw-handling/image-corrector.native.js index f25f6d3c8bc903..cf680670f1514f 100644 --- a/packages/blocks/src/api/raw-handling/image-corrector.native.js +++ b/packages/blocks/src/api/raw-handling/image-corrector.native.js @@ -1,4 +1,3 @@ - /** * This method check for copy pasted img elements to see if they don't have suspicious attributes. * diff --git a/packages/blocks/src/api/raw-handling/index.js b/packages/blocks/src/api/raw-handling/index.js index d69ade4dac94a3..3b869f7ecd15a7 100644 --- a/packages/blocks/src/api/raw-handling/index.js +++ b/packages/blocks/src/api/raw-handling/index.js @@ -14,10 +14,7 @@ import listReducer from './list-reducer'; import blockquoteNormaliser from './blockquote-normaliser'; import figureContentReducer from './figure-content-reducer'; import shortcodeConverter from './shortcode-converter'; -import { - deepFilterHTML, - getBlockContentSchema, -} from './utils'; +import { deepFilterHTML, getBlockContentSchema } from './utils'; import { getPhrasingContentSchema } from './phrasing-content'; @@ -25,13 +22,18 @@ export { pasteHandler } from './paste-handler'; export { getPhrasingContentSchema }; function getRawTransformations() { - return filter( getBlockTransforms( 'from' ), { type: 'raw' } ) - .map( ( transform ) => { - return transform.isMatch ? transform : { - ...transform, - isMatch: ( node ) => transform.selector && node.matches( transform.selector ), - }; - } ); + return filter( getBlockTransforms( 'from' ), { type: 'raw' } ).map( + ( transform ) => { + return transform.isMatch + ? transform + : { + ...transform, + isMatch: ( node ) => + transform.selector && + node.matches( transform.selector ), + }; + } + ); } /** @@ -51,16 +53,15 @@ function htmlToBlocks( { html, rawTransforms } ) { doc.body.innerHTML = html; return Array.from( doc.body.children ).map( ( node ) => { - const rawTransform = findTransform( rawTransforms, ( { isMatch } ) => isMatch( node ) ); + const rawTransform = findTransform( rawTransforms, ( { isMatch } ) => + isMatch( node ) + ); if ( ! rawTransform ) { return createBlock( // Should not be hardcoded. 'core/html', - getBlockAttributes( - 'core/html', - node.outerHTML - ) + getBlockAttributes( 'core/html', node.outerHTML ) ); } @@ -72,10 +73,7 @@ function htmlToBlocks( { html, rawTransforms } ) { return createBlock( blockName, - getBlockAttributes( - blockName, - node.outerHTML - ) + getBlockAttributes( blockName, node.outerHTML ) ); } ); } @@ -99,32 +97,37 @@ export function rawHandler( { HTML = '' } ) { const pieces = shortcodeConverter( HTML ); const rawTransforms = getRawTransformations(); const phrasingContentSchema = getPhrasingContentSchema(); - const blockContentSchema = getBlockContentSchema( rawTransforms, phrasingContentSchema ); - - return compact( flatMap( pieces, ( piece ) => { - // Already a block from shortcode. - if ( typeof piece !== 'string' ) { - return piece; - } - - // These filters are essential for some blocks to be able to transform - // from raw HTML. These filters move around some content or add - // additional tags, they do not remove any content. - const filters = [ - // Needed to adjust invalid lists. - listReducer, - // Needed to create more and nextpage blocks. - specialCommentConverter, - // Needed to create media blocks. - figureContentReducer, - // Needed to create the quote block, which cannot handle text - // without wrapper paragraphs. - blockquoteNormaliser, - ]; - - piece = deepFilterHTML( piece, filters, blockContentSchema ); - piece = normaliseBlocks( piece ); - - return htmlToBlocks( { html: piece, rawTransforms } ); - } ) ); + const blockContentSchema = getBlockContentSchema( + rawTransforms, + phrasingContentSchema + ); + + return compact( + flatMap( pieces, ( piece ) => { + // Already a block from shortcode. + if ( typeof piece !== 'string' ) { + return piece; + } + + // These filters are essential for some blocks to be able to transform + // from raw HTML. These filters move around some content or add + // additional tags, they do not remove any content. + const filters = [ + // Needed to adjust invalid lists. + listReducer, + // Needed to create more and nextpage blocks. + specialCommentConverter, + // Needed to create media blocks. + figureContentReducer, + // Needed to create the quote block, which cannot handle text + // without wrapper paragraphs. + blockquoteNormaliser, + ]; + + piece = deepFilterHTML( piece, filters, blockContentSchema ); + piece = normaliseBlocks( piece ); + + return htmlToBlocks( { html: piece, rawTransforms } ); + } ) + ); } diff --git a/packages/blocks/src/api/raw-handling/is-inline-content.js b/packages/blocks/src/api/raw-handling/is-inline-content.js index 241f5d2cdb1ceb..b74487785893f8 100644 --- a/packages/blocks/src/api/raw-handling/is-inline-content.js +++ b/packages/blocks/src/api/raw-handling/is-inline-content.js @@ -32,19 +32,25 @@ function isInline( node, contextTag ) { [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ], ]; - return inlineWhitelistTagGroups.some( ( tagGroup ) => - difference( [ tag, contextTag ], tagGroup ).length === 0 + return inlineWhitelistTagGroups.some( + ( tagGroup ) => difference( [ tag, contextTag ], tagGroup ).length === 0 ); } function deepCheck( nodes, contextTag ) { - return nodes.every( ( node ) => - isInline( node, contextTag ) && deepCheck( Array.from( node.children ), contextTag ) + return nodes.every( + ( node ) => + isInline( node, contextTag ) && + deepCheck( Array.from( node.children ), contextTag ) ); } function isDoubleBR( node ) { - return node.nodeName === 'BR' && node.previousSibling && node.previousSibling.nodeName === 'BR'; + return ( + node.nodeName === 'BR' && + node.previousSibling && + node.previousSibling.nodeName === 'BR' + ); } export default function( HTML, contextTag ) { diff --git a/packages/blocks/src/api/raw-handling/ms-list-converter.js b/packages/blocks/src/api/raw-handling/ms-list-converter.js index 0181517493f797..34555ed719f9a5 100644 --- a/packages/blocks/src/api/raw-handling/ms-list-converter.js +++ b/packages/blocks/src/api/raw-handling/ms-list-converter.js @@ -73,7 +73,9 @@ export default function( node, doc ) { // Make sure we append to a list. if ( ! isList( receivingNode ) ) { - receivingNode = receivingNode.appendChild( doc.createElement( listType ) ); + receivingNode = receivingNode.appendChild( + doc.createElement( listType ) + ); } // Append the list item to the list. diff --git a/packages/blocks/src/api/raw-handling/normalise-blocks.js b/packages/blocks/src/api/raw-handling/normalise-blocks.js index 52ca57c43e047d..5e0d17292d21ca 100644 --- a/packages/blocks/src/api/raw-handling/normalise-blocks.js +++ b/packages/blocks/src/api/raw-handling/normalise-blocks.js @@ -32,7 +32,7 @@ export default function( HTML ) { accu.lastChild.appendChild( node ); } - // Element nodes. + // Element nodes. } else if ( node.nodeType === ELEMENT_NODE ) { // BR nodes: create a new paragraph on double, or append to previous. if ( node.nodeName === 'BR' ) { diff --git a/packages/blocks/src/api/raw-handling/paste-handler.js b/packages/blocks/src/api/raw-handling/paste-handler.js index 5189cf6e84887f..50738ae0358ce8 100644 --- a/packages/blocks/src/api/raw-handling/paste-handler.js +++ b/packages/blocks/src/api/raw-handling/paste-handler.js @@ -49,8 +49,14 @@ const { console } = window; * @return {string} HTML only containing phrasing content. */ function filterInlineHTML( HTML ) { - HTML = deepFilterHTML( HTML, [ googleDocsUIDRemover, phrasingContentReducer, commentRemover ] ); - HTML = removeInvalidHTML( HTML, getPhrasingContentSchema( 'paste' ), { inline: true } ); + HTML = deepFilterHTML( HTML, [ + googleDocsUIDRemover, + phrasingContentReducer, + commentRemover, + ] ); + HTML = removeInvalidHTML( HTML, getPhrasingContentSchema( 'paste' ), { + inline: true, + } ); HTML = deepFilterHTML( HTML, [ htmlFormattingRemover, brRemover ] ); // Allows us to ask for this information when we get a report. @@ -60,13 +66,18 @@ function filterInlineHTML( HTML ) { } function getRawTransformations() { - return filter( getBlockTransforms( 'from' ), { type: 'raw' } ) - .map( ( transform ) => { - return transform.isMatch ? transform : { - ...transform, - isMatch: ( node ) => transform.selector && node.matches( transform.selector ), - }; - } ); + return filter( getBlockTransforms( 'from' ), { type: 'raw' } ).map( + ( transform ) => { + return transform.isMatch + ? transform + : { + ...transform, + isMatch: ( node ) => + transform.selector && + node.matches( transform.selector ), + }; + } + ); } /** @@ -86,16 +97,15 @@ function htmlToBlocks( { html, rawTransforms } ) { doc.body.innerHTML = html; return Array.from( doc.body.children ).map( ( node ) => { - const rawTransform = findTransform( rawTransforms, ( { isMatch } ) => isMatch( node ) ); + const rawTransform = findTransform( rawTransforms, ( { isMatch } ) => + isMatch( node ) + ); if ( ! rawTransform ) { return createBlock( // Should not be hardcoded. 'core/html', - getBlockAttributes( - 'core/html', - node.outerHTML - ) + getBlockAttributes( 'core/html', node.outerHTML ) ); } @@ -107,10 +117,7 @@ function htmlToBlocks( { html, rawTransforms } ) { return createBlock( blockName, - getBlockAttributes( - blockName, - node.outerHTML - ) + getBlockAttributes( blockName, node.outerHTML ) ); } ); } @@ -130,12 +137,24 @@ function htmlToBlocks( { html, rawTransforms } ) { * * @return {Array|string} A list of blocks or a string, depending on `handlerMode`. */ -export function pasteHandler( { HTML = '', plainText = '', mode = 'AUTO', tagName, canUserUseUnfilteredHTML = false } ) { +export function pasteHandler( { + HTML = '', + plainText = '', + mode = 'AUTO', + tagName, + canUserUseUnfilteredHTML = false, +} ) { // First of all, strip any meta tags. HTML = HTML.replace( /<meta[^>]+>/g, '' ); // Strip Windows markers. - HTML = HTML.replace( /^\s*<html[^>]*>\s*<body[^>]*>(?:\s*<!--\s*StartFragment\s*-->)?/i, '' ); - HTML = HTML.replace( /(?:<!--\s*EndFragment\s*-->\s*)?<\/body>\s*<\/html>\s*$/i, '' ); + HTML = HTML.replace( + /^\s*<html[^>]*>\s*<body[^>]*>(?:\s*<!--\s*StartFragment\s*-->)?/i, + '' + ); + HTML = HTML.replace( + /(?:<!--\s*EndFragment\s*-->\s*)?<\/body>\s*<\/html>\s*$/i, + '' + ); // If we detect block delimiters in HTML, parse entirely as blocks. if ( mode !== 'INLINE' ) { @@ -192,58 +211,68 @@ export function pasteHandler( { HTML = '', plainText = '', mode = 'AUTO', tagNam // empty HTML strings are included. const hasShortcodes = pieces.length > 1; - if ( mode === 'AUTO' && ! hasShortcodes && isInlineContent( HTML, tagName ) ) { + if ( + mode === 'AUTO' && + ! hasShortcodes && + isInlineContent( HTML, tagName ) + ) { return filterInlineHTML( HTML ); } const rawTransforms = getRawTransformations(); const phrasingContentSchema = getPhrasingContentSchema( 'paste' ); - const blockContentSchema = getBlockContentSchema( rawTransforms, phrasingContentSchema, true ); - - const blocks = compact( flatMap( pieces, ( piece ) => { - // Already a block from shortcode. - if ( typeof piece !== 'string' ) { - return piece; - } - - const filters = [ - googleDocsUIDRemover, - msListConverter, - headRemover, - listReducer, - imageCorrector, - phrasingContentReducer, - specialCommentConverter, - commentRemover, - figureContentReducer, - blockquoteNormaliser, - ]; - - if ( ! canUserUseUnfilteredHTML ) { - // Should run before `figureContentReducer`. - filters.unshift( iframeRemover ); - } - - const schema = { - ...blockContentSchema, - // Keep top-level phrasing content, normalised by `normaliseBlocks`. - ...phrasingContentSchema, - }; + const blockContentSchema = getBlockContentSchema( + rawTransforms, + phrasingContentSchema, + true + ); + + const blocks = compact( + flatMap( pieces, ( piece ) => { + // Already a block from shortcode. + if ( typeof piece !== 'string' ) { + return piece; + } + + const filters = [ + googleDocsUIDRemover, + msListConverter, + headRemover, + listReducer, + imageCorrector, + phrasingContentReducer, + specialCommentConverter, + commentRemover, + figureContentReducer, + blockquoteNormaliser, + ]; + + if ( ! canUserUseUnfilteredHTML ) { + // Should run before `figureContentReducer`. + filters.unshift( iframeRemover ); + } + + const schema = { + ...blockContentSchema, + // Keep top-level phrasing content, normalised by `normaliseBlocks`. + ...phrasingContentSchema, + }; - piece = deepFilterHTML( piece, filters, blockContentSchema ); - piece = removeInvalidHTML( piece, schema ); - piece = normaliseBlocks( piece ); - piece = deepFilterHTML( piece, [ - htmlFormattingRemover, - brRemover, - emptyParagraphRemover, - ], blockContentSchema ); + piece = deepFilterHTML( piece, filters, blockContentSchema ); + piece = removeInvalidHTML( piece, schema ); + piece = normaliseBlocks( piece ); + piece = deepFilterHTML( + piece, + [ htmlFormattingRemover, brRemover, emptyParagraphRemover ], + blockContentSchema + ); - // Allows us to ask for this information when we get a report. - console.log( 'Processed HTML piece:\n\n', piece ); + // Allows us to ask for this information when we get a report. + console.log( 'Processed HTML piece:\n\n', piece ); - return htmlToBlocks( { html: piece, rawTransforms } ); - } ) ); + return htmlToBlocks( { html: piece, rawTransforms } ); + } ) + ); // If we're allowed to return inline content, and there is only one inlineable block, // and the original plain text content does not have any line breaks, then @@ -255,7 +284,10 @@ export function pasteHandler( { HTML = '', plainText = '', mode = 'AUTO', tagNam ) { const trimmedPlainText = plainText.trim(); - if ( trimmedPlainText !== '' && trimmedPlainText.indexOf( '\n' ) === -1 ) { + if ( + trimmedPlainText !== '' && + trimmedPlainText.indexOf( '\n' ) === -1 + ) { return removeInvalidHTML( getBlockContent( blocks[ 0 ] ), phrasingContentSchema diff --git a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js index 4f3c3a2a810a99..f0712dfa72f575 100644 --- a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js +++ b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js @@ -31,7 +31,10 @@ export default function( node, doc ) { // Some DOM implementations (Safari, JSDom) don't support // style.textDecorationLine, so we check style.textDecoration as a // fallback. - if ( textDecorationLine === 'line-through' || includes( textDecoration, 'line-through' ) ) { + if ( + textDecorationLine === 'line-through' || + includes( textDecoration, 'line-through' ) + ) { wrap( doc.createElement( 's' ), node ); } diff --git a/packages/blocks/src/api/raw-handling/phrasing-content.js b/packages/blocks/src/api/raw-handling/phrasing-content.js index 82eba1764d8c4c..1b56c1a7b10839 100644 --- a/packages/blocks/src/api/raw-handling/phrasing-content.js +++ b/packages/blocks/src/api/raw-handling/phrasing-content.js @@ -62,13 +62,65 @@ without( Object.keys( textContentSchema ), '#text', 'br' ).forEach( ( tag ) => { * @see https://www.w3.org/TR/2011/WD-html5-20110525/content-models.html#embedded-content-0 */ const embeddedContentSchema = { - audio: { attributes: [ 'src', 'preload', 'autoplay', 'mediagroup', 'loop', 'muted' ] }, + audio: { + attributes: [ + 'src', + 'preload', + 'autoplay', + 'mediagroup', + 'loop', + 'muted', + ], + }, canvas: { attributes: [ 'width', 'height' ] }, embed: { attributes: [ 'src', 'type', 'width', 'height' ] }, - iframe: { attributes: [ 'src', 'srcdoc', 'name', 'sandbox', 'seamless', 'width', 'height' ] }, - img: { attributes: [ 'alt', 'src', 'srcset', 'usemap', 'ismap', 'width', 'height' ] }, - object: { attributes: [ 'data', 'type', 'name', 'usemap', 'form', 'width', 'height' ] }, - video: { attributes: [ 'src', 'poster', 'preload', 'autoplay', 'mediagroup', 'loop', 'muted', 'controls', 'width', 'height' ] }, + iframe: { + attributes: [ + 'src', + 'srcdoc', + 'name', + 'sandbox', + 'seamless', + 'width', + 'height', + ], + }, + img: { + attributes: [ + 'alt', + 'src', + 'srcset', + 'usemap', + 'ismap', + 'width', + 'height', + ], + }, + object: { + attributes: [ + 'data', + 'type', + 'name', + 'usemap', + 'form', + 'width', + 'height', + ], + }, + video: { + attributes: [ + 'src', + 'poster', + 'preload', + 'autoplay', + 'mediagroup', + 'loop', + 'muted', + 'controls', + 'width', + 'height', + ], + }, }; /** @@ -96,21 +148,24 @@ export function getPhrasingContentSchema( context ) { return phrasingContentSchema; } - return omit( { - ...phrasingContentSchema, - // We shouldn't paste potentially sensitive information which is not - // visible to the user when pasted, so strip the attributes. - ins: { children: phrasingContentSchema.ins.children }, - del: { children: phrasingContentSchema.del.children }, - }, [ - 'u', // Used to mark misspelling. Shouldn't be pasted. - 'abbr', // Invisible. - 'data', // Invisible. - 'time', // Invisible. - 'wbr', // Invisible. - 'bdi', // Invisible. - 'bdo', // Invisible. - ] ); + return omit( + { + ...phrasingContentSchema, + // We shouldn't paste potentially sensitive information which is not + // visible to the user when pasted, so strip the attributes. + ins: { children: phrasingContentSchema.ins.children }, + del: { children: phrasingContentSchema.del.children }, + }, + [ + 'u', // Used to mark misspelling. Shouldn't be pasted. + 'abbr', // Invisible. + 'data', // Invisible. + 'time', // Invisible. + 'wbr', // Invisible. + 'bdi', // Invisible. + 'bdo', // Invisible. + ] + ); } /** diff --git a/packages/blocks/src/api/raw-handling/shortcode-converter.js b/packages/blocks/src/api/raw-handling/shortcode-converter.js index c15b4cce3f5ad1..901bf311007b15 100644 --- a/packages/blocks/src/api/raw-handling/shortcode-converter.js +++ b/packages/blocks/src/api/raw-handling/shortcode-converter.js @@ -15,22 +15,32 @@ import { createBlock, getBlockTransforms, findTransform } from '../factory'; import { getBlockType } from '../registration'; import { getBlockAttributes } from '../parser'; -function segmentHTMLToShortcodeBlock( HTML, lastIndex = 0, excludedBlockNames = [] ) { +function segmentHTMLToShortcodeBlock( + HTML, + lastIndex = 0, + excludedBlockNames = [] +) { // Get all matches. const transformsFrom = getBlockTransforms( 'from' ); - const transformation = findTransform( transformsFrom, ( transform ) => ( - excludedBlockNames.indexOf( transform.blockName ) === -1 && - transform.type === 'shortcode' && - some( castArray( transform.tag ), ( tag ) => regexp( tag ).test( HTML ) ) - ) ); + const transformation = findTransform( + transformsFrom, + ( transform ) => + excludedBlockNames.indexOf( transform.blockName ) === -1 && + transform.type === 'shortcode' && + some( castArray( transform.tag ), ( tag ) => + regexp( tag ).test( HTML ) + ) + ); if ( ! transformation ) { return [ HTML ]; } const transformTags = castArray( transformation.tag ); - const transformTag = find( transformTags, ( tag ) => regexp( tag ).test( HTML ) ); + const transformTag = find( transformTags, ( tag ) => + regexp( tag ).test( HTML ) + ); let match; const previousIndex = lastIndex; @@ -62,12 +72,14 @@ function segmentHTMLToShortcodeBlock( HTML, lastIndex = 0, excludedBlockNames = // should skip that argument as a way to reset the exclusion state, so // that one `isMatch` fail in an HTML fragment doesn't prevent any // valid matches in subsequent fragments. - if ( transformation.isMatch && ! transformation.isMatch( match.shortcode.attrs ) ) { - return segmentHTMLToShortcodeBlock( - HTML, - previousIndex, - [ ...excludedBlockNames, transformation.blockName ], - ); + if ( + transformation.isMatch && + ! transformation.isMatch( match.shortcode.attrs ) + ) { + return segmentHTMLToShortcodeBlock( HTML, previousIndex, [ + ...excludedBlockNames, + transformation.blockName, + ] ); } const attributes = mapValues( @@ -76,7 +88,7 @@ function segmentHTMLToShortcodeBlock( HTML, lastIndex = 0, excludedBlockNames = // but shouldn't be too relied upon. // // See: https://github.com/WordPress/gutenberg/pull/3610#discussion_r152546926 - ( schema ) => schema.shortcode( match.shortcode.attrs, match ), + ( schema ) => schema.shortcode( match.shortcode.attrs, match ) ); const block = createBlock( @@ -87,7 +99,7 @@ function segmentHTMLToShortcodeBlock( HTML, lastIndex = 0, excludedBlockNames = attributes: transformation.attributes, }, match.shortcode.content, - attributes, + attributes ) ); diff --git a/packages/blocks/src/api/raw-handling/test/blockquote-normaliser.js b/packages/blocks/src/api/raw-handling/test/blockquote-normaliser.js index fa3f9660fd2d53..f7ac96a95b94f6 100644 --- a/packages/blocks/src/api/raw-handling/test/blockquote-normaliser.js +++ b/packages/blocks/src/api/raw-handling/test/blockquote-normaliser.js @@ -8,6 +8,8 @@ describe( 'blockquoteNormaliser', () => { it( 'should normalise blockquote', () => { const input = '<blockquote>test</blockquote>'; const output = '<blockquote><p>test</p></blockquote>'; - expect( deepFilterHTML( input, [ blockquoteNormaliser ] ) ).toEqual( output ); + expect( deepFilterHTML( input, [ blockquoteNormaliser ] ) ).toEqual( + output + ); } ); } ); diff --git a/packages/blocks/src/api/raw-handling/test/comment-remover.js b/packages/blocks/src/api/raw-handling/test/comment-remover.js index 6721e20bec23b6..b9d22a80249886 100644 --- a/packages/blocks/src/api/raw-handling/test/comment-remover.js +++ b/packages/blocks/src/api/raw-handling/test/comment-remover.js @@ -6,38 +6,34 @@ import { deepFilterHTML } from '../utils'; describe( 'commentRemover', () => { it( 'should remove a single comment', () => { - expect( deepFilterHTML( - '<!-- Comment -->', - [ commentRemover ] - ) ).toEqual( - '' - ); + expect( + deepFilterHTML( '<!-- Comment -->', [ commentRemover ] ) + ).toEqual( '' ); } ); it( 'should remove multiple comments', () => { - expect( deepFilterHTML( - '<!-- First comment --><p>First paragraph.</p><!-- Second comment --><p>Second paragraph.</p><!-- Third comment -->', - [ commentRemover ] - ) ).toEqual( - '<p>First paragraph.</p><p>Second paragraph.</p>' - ); + expect( + deepFilterHTML( + '<!-- First comment --><p>First paragraph.</p><!-- Second comment --><p>Second paragraph.</p><!-- Third comment -->', + [ commentRemover ] + ) + ).toEqual( '<p>First paragraph.</p><p>Second paragraph.</p>' ); } ); it( 'should remove nested comments', () => { - expect( deepFilterHTML( - '<p>Paragraph.<!-- Comment --></p>', - [ commentRemover ] - ) ).toEqual( - '<p>Paragraph.</p>' - ); + expect( + deepFilterHTML( '<p>Paragraph.<!-- Comment --></p>', [ + commentRemover, + ] ) + ).toEqual( '<p>Paragraph.</p>' ); } ); it( 'should remove multi-line comments', () => { - expect( deepFilterHTML( - `<p>First paragraph.</p><!-- + expect( + deepFilterHTML( + `<p>First paragraph.</p><!-- Multi-line comment --><p>Second paragraph.</p>`, - [ commentRemover ] - ) ).toEqual( - '<p>First paragraph.</p><p>Second paragraph.</p>' - ); + [ commentRemover ] + ) + ).toEqual( '<p>First paragraph.</p><p>Second paragraph.</p>' ); } ); } ); diff --git a/packages/blocks/src/api/raw-handling/test/figure-content-reducer.js b/packages/blocks/src/api/raw-handling/test/figure-content-reducer.js index cb3be95c28f061..5489bd2b28d6bd 100644 --- a/packages/blocks/src/api/raw-handling/test/figure-content-reducer.js +++ b/packages/blocks/src/api/raw-handling/test/figure-content-reducer.js @@ -22,66 +22,88 @@ describe( 'figureContentReducer', () => { const input = '<img>'; const output = '<figure><img></figure>'; - expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( output ); + expect( + deepFilterHTML( input, [ figureContentReducer ], schema ) + ).toEqual( output ); } ); it( 'should move lone embedded content from paragraph', () => { const input = '<p><img></p>'; const output = '<figure><img></figure><p></p>'; - expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( output ); + expect( + deepFilterHTML( input, [ figureContentReducer ], schema ) + ).toEqual( output ); } ); it( 'should move multiple lone embedded content from paragraph', () => { const input = '<p><img><img></p>'; const output = '<figure><img></figure><figure><img></figure><p></p>'; - expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( output ); + expect( + deepFilterHTML( input, [ figureContentReducer ], schema ) + ).toEqual( output ); } ); it( 'should move aligned embedded content from paragraph (1)', () => { const input = '<p><img class="alignright">test</p>'; const output = '<figure><img class="alignright"></figure><p>test</p>'; - expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( output ); + expect( + deepFilterHTML( input, [ figureContentReducer ], schema ) + ).toEqual( output ); } ); it( 'should move aligned embedded content from paragraph (2)', () => { const input = '<p>test<img class="alignright"></p>'; const output = '<figure><img class="alignright"></figure><p>test</p>'; - expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( output ); + expect( + deepFilterHTML( input, [ figureContentReducer ], schema ) + ).toEqual( output ); } ); it( 'should move aligned embedded content from paragraph (3)', () => { const input = '<p>test<img class="alignright">test</p>'; - const output = '<figure><img class="alignright"></figure><p>testtest</p>'; + const output = + '<figure><img class="alignright"></figure><p>testtest</p>'; - expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( output ); + expect( + deepFilterHTML( input, [ figureContentReducer ], schema ) + ).toEqual( output ); } ); it( 'should not move embedded content from paragraph (1)', () => { const input = '<p><img>test</p>'; - expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( input ); + expect( + deepFilterHTML( input, [ figureContentReducer ], schema ) + ).toEqual( input ); } ); it( 'should not move embedded content from paragraph (2)', () => { const input = '<p>test<img></p>'; - expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( input ); + expect( + deepFilterHTML( input, [ figureContentReducer ], schema ) + ).toEqual( input ); } ); it( 'should not move embedded content from paragraph (3)', () => { const input = '<p>test<img>test</p>'; - expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( input ); + expect( + deepFilterHTML( input, [ figureContentReducer ], schema ) + ).toEqual( input ); } ); it( 'should move an anchor with an image', () => { const input = '<p><a href="#"><img class="alignleft"></a>test</p>'; - const output = '<figure><a href="#"><img class="alignleft"></a></figure><p>test</p>'; + const output = + '<figure><a href="#"><img class="alignleft"></a></figure><p>test</p>'; - expect( deepFilterHTML( input, [ figureContentReducer ], schema ) ).toEqual( output ); + expect( + deepFilterHTML( input, [ figureContentReducer ], schema ) + ).toEqual( output ); } ); } ); diff --git a/packages/blocks/src/api/raw-handling/test/is-inline-content.js b/packages/blocks/src/api/raw-handling/test/is-inline-content.js index 09c80cbea8232e..04dc21dd67be6d 100644 --- a/packages/blocks/src/api/raw-handling/test/is-inline-content.js +++ b/packages/blocks/src/api/raw-handling/test/is-inline-content.js @@ -12,7 +12,9 @@ describe( 'isInlineContent', () => { it( 'should not be inline content', () => { expect( isInlineContent( '<div>test</div>' ) ).toBe( false ); - expect( isInlineContent( '<em>test</em><div>test</div>' ) ).toBe( false ); + expect( isInlineContent( '<em>test</em><div>test</div>' ) ).toBe( + false + ); expect( isInlineContent( 'test<br><br>test' ) ).toBe( false ); expect( isInlineContent( '<em><div>test</div></em>' ) ).toBe( false ); expect( isInlineContent( '<li>test</li>', 'p' ) ).toBe( false ); diff --git a/packages/blocks/src/api/raw-handling/test/list-reducer.js b/packages/blocks/src/api/raw-handling/test/list-reducer.js index 9d1b053627295d..cb89e5c45e6a48 100644 --- a/packages/blocks/src/api/raw-handling/test/list-reducer.js +++ b/packages/blocks/src/api/raw-handling/test/list-reducer.js @@ -18,7 +18,8 @@ describe( 'listReducer', () => { } ); it( 'should not merge lists if it has more than one item', () => { - const input = '<ul><li>one</li></ul><ul><li>two</li><li>three</li></ul>'; + const input = + '<ul><li>one</li></ul><ul><li>two</li><li>three</li></ul>'; expect( deepFilterHTML( input, [ listReducer ] ) ).toEqual( input ); } ); @@ -33,8 +34,10 @@ describe( 'listReducer', () => { } ); it( 'should merge list items if nested list parent has no content', () => { - const input = '<ul><li>1</li><li><ul><li>1.1</li><li>1.2</li></ul></li><li>2</li></ul>'; - const output = '<ul><li>1<ul><li>1.1</li><li>1.2</li></ul></li><li>2</li></ul>'; + const input = + '<ul><li>1</li><li><ul><li>1.1</li><li>1.2</li></ul></li><li>2</li></ul>'; + const output = + '<ul><li>1<ul><li>1.1</li><li>1.2</li></ul></li><li>2</li></ul>'; expect( deepFilterHTML( input, [ listReducer ] ) ).toEqual( output ); } ); diff --git a/packages/blocks/src/api/raw-handling/test/markdown-converter.js b/packages/blocks/src/api/raw-handling/test/markdown-converter.js index 32cc4c865fe19f..26c35049661267 100644 --- a/packages/blocks/src/api/raw-handling/test/markdown-converter.js +++ b/packages/blocks/src/api/raw-handling/test/markdown-converter.js @@ -24,7 +24,7 @@ describe( 'markdownConverter', () => { it( 'should not correct code with line breaks', () => { const input = '```js\ntest\n```'; - const output = '<pre><code class=\"js language-js\">test</code></pre>'; + const output = '<pre><code class="js language-js">test</code></pre>'; expect( markdownConverter( input ) ).toEqual( output ); } ); } ); diff --git a/packages/blocks/src/api/raw-handling/test/ms-list-converter.js b/packages/blocks/src/api/raw-handling/test/ms-list-converter.js index 5a64cab01c9f0e..a7c58dfa03010a 100644 --- a/packages/blocks/src/api/raw-handling/test/ms-list-converter.js +++ b/packages/blocks/src/api/raw-handling/test/ms-list-converter.js @@ -6,31 +6,52 @@ import { deepFilterHTML } from '../utils'; describe( 'msListConverter', () => { it( 'should convert unordered list', () => { - const input = '<p style="mso-list:l0 level1 lfo1"><span>* </span>test</p>'; + const input = + '<p style="mso-list:l0 level1 lfo1"><span>* </span>test</p>'; const output = '<ul><li>test</li></ul>'; - expect( deepFilterHTML( input, [ msListConverter ] ) ).toEqual( output ); + expect( deepFilterHTML( input, [ msListConverter ] ) ).toEqual( + output + ); } ); it( 'should convert ordered list', () => { - const input = '<p style="mso-list:l0 level1 lfo1"><span>1 </span>test</p>'; + const input = + '<p style="mso-list:l0 level1 lfo1"><span>1 </span>test</p>'; const output = '<ol type="1"><li>test</li></ol>'; - expect( deepFilterHTML( input, [ msListConverter ] ) ).toEqual( output ); + expect( deepFilterHTML( input, [ msListConverter ] ) ).toEqual( + output + ); } ); it( 'should convert indented list', () => { - const input1 = '<p style="mso-list:l0 level1 lfo1"><span>* </span>test</p>'; - const input2 = '<p style="mso-list:l0 level2 lfo1"><span>* </span>test</p>'; - const input3 = '<p style="mso-list:l0 level1 lfo1"><span>* </span>test</p>'; - const output = '<ul><li>test<ul><li>test</li></ul></li><li>test</li></ul>'; - expect( deepFilterHTML( input1 + input2 + input3, [ msListConverter ] ) ).toEqual( output ); + const input1 = + '<p style="mso-list:l0 level1 lfo1"><span>* </span>test</p>'; + const input2 = + '<p style="mso-list:l0 level2 lfo1"><span>* </span>test</p>'; + const input3 = + '<p style="mso-list:l0 level1 lfo1"><span>* </span>test</p>'; + const output = + '<ul><li>test<ul><li>test</li></ul></li><li>test</li></ul>'; + expect( + deepFilterHTML( input1 + input2 + input3, [ msListConverter ] ) + ).toEqual( output ); } ); it( 'should convert deep indented list', () => { - const input1 = '<p style="mso-list:l0 level1 lfo1"><span>* </span>test</p>'; - const input2 = '<p style="mso-list:l0 level2 lfo1"><span>* </span>test</p>'; - const input3 = '<p style="mso-list:l0 level3 lfo1"><span>* </span>test</p>'; - const input4 = '<p style="mso-list:l0 level1 lfo1"><span>* </span>test</p>'; - const output = '<ul><li>test<ul><li>test<ul><li>test</li></ul></li></ul></li><li>test</li></ul>'; - expect( deepFilterHTML( input1 + input2 + input3 + input4, [ msListConverter ] ) ).toEqual( output ); + const input1 = + '<p style="mso-list:l0 level1 lfo1"><span>* </span>test</p>'; + const input2 = + '<p style="mso-list:l0 level2 lfo1"><span>* </span>test</p>'; + const input3 = + '<p style="mso-list:l0 level3 lfo1"><span>* </span>test</p>'; + const input4 = + '<p style="mso-list:l0 level1 lfo1"><span>* </span>test</p>'; + const output = + '<ul><li>test<ul><li>test<ul><li>test</li></ul></li></ul></li><li>test</li></ul>'; + expect( + deepFilterHTML( input1 + input2 + input3 + input4, [ + msListConverter, + ] ) + ).toEqual( output ); } ); } ); diff --git a/packages/blocks/src/api/raw-handling/test/normalise-blocks.js b/packages/blocks/src/api/raw-handling/test/normalise-blocks.js index f5c9cba20f7060..e8fa918ec6211d 100644 --- a/packages/blocks/src/api/raw-handling/test/normalise-blocks.js +++ b/packages/blocks/src/api/raw-handling/test/normalise-blocks.js @@ -5,16 +5,24 @@ import normaliseBlocks from '../normalise-blocks'; describe( 'normaliseBlocks', () => { it( 'should convert double line breaks to paragraphs', () => { - expect( normaliseBlocks( 'test<br><br>test' ) ).toEqual( '<p>test</p><p>test</p>' ); + expect( normaliseBlocks( 'test<br><br>test' ) ).toEqual( + '<p>test</p><p>test</p>' + ); } ); it( 'should not convert single line break to paragraphs', () => { - expect( normaliseBlocks( 'test<br>test' ) ).toEqual( '<p>test<br>test</p>' ); + expect( normaliseBlocks( 'test<br>test' ) ).toEqual( + '<p>test<br>test</p>' + ); } ); it( 'should not add extra line at the start', () => { - expect( normaliseBlocks( 'test<br><br><br>test' ) ).toEqual( '<p>test</p><p>test</p>' ); - expect( normaliseBlocks( '<br>test<br><br>test' ) ).toEqual( '<p>test</p><p>test</p>' ); + expect( normaliseBlocks( 'test<br><br><br>test' ) ).toEqual( + '<p>test</p><p>test</p>' + ); + expect( normaliseBlocks( '<br>test<br><br>test' ) ).toEqual( + '<p>test</p><p>test</p>' + ); } ); it( 'should preserve non-inline content', () => { @@ -27,11 +35,19 @@ describe( 'normaliseBlocks', () => { } ); it( 'should wrap lose inline elements', () => { - expect( normaliseBlocks( '<a href="#">test</a>' ) ).toEqual( '<p><a href="#">test</a></p>' ); + expect( normaliseBlocks( '<a href="#">test</a>' ) ).toEqual( + '<p><a href="#">test</a></p>' + ); } ); it( 'should not break between inline siblings', () => { - expect( normaliseBlocks( '<strong>test</strong>&nbsp;is a test of&nbsp;<a href="#">test</a>&nbsp;using a&nbsp;<a href="#">test</a>.' ) ).toEqual( '<p><strong>test</strong>&nbsp;is a test of&nbsp;<a href="#">test</a>&nbsp;using a&nbsp;<a href="#">test</a>.</p>' ); + expect( + normaliseBlocks( + '<strong>test</strong>&nbsp;is a test of&nbsp;<a href="#">test</a>&nbsp;using a&nbsp;<a href="#">test</a>.' + ) + ).toEqual( + '<p><strong>test</strong>&nbsp;is a test of&nbsp;<a href="#">test</a>&nbsp;using a&nbsp;<a href="#">test</a>.</p>' + ); } ); it( 'should not append empty text nodes', () => { diff --git a/packages/blocks/src/api/raw-handling/test/phrasing-content-reducer.js b/packages/blocks/src/api/raw-handling/test/phrasing-content-reducer.js index 14a9d26984956b..d73935429d4615 100644 --- a/packages/blocks/src/api/raw-handling/test/phrasing-content-reducer.js +++ b/packages/blocks/src/api/raw-handling/test/phrasing-content-reducer.js @@ -6,36 +6,76 @@ import { deepFilterHTML } from '../utils'; describe( 'phrasingContentReducer', () => { it( 'should transform font weight', () => { - expect( deepFilterHTML( '<span style="font-weight:bold">test</span>', [ phrasingContentReducer ], {} ) ).toEqual( '<strong><span style="font-weight:bold">test</span></strong>' ); + expect( + deepFilterHTML( + '<span style="font-weight:bold">test</span>', + [ phrasingContentReducer ], + {} + ) + ).toEqual( + '<strong><span style="font-weight:bold">test</span></strong>' + ); } ); it( 'should transform numeric font weight', () => { - expect( deepFilterHTML( '<span style="font-weight:700">test</span>', [ phrasingContentReducer ], {} ) ).toEqual( '<strong><span style="font-weight:700">test</span></strong>' ); + expect( + deepFilterHTML( + '<span style="font-weight:700">test</span>', + [ phrasingContentReducer ], + {} + ) + ).toEqual( + '<strong><span style="font-weight:700">test</span></strong>' + ); } ); it( 'should transform font style', () => { - expect( deepFilterHTML( '<span style="font-style:italic">test</span>', [ phrasingContentReducer ], {} ) ).toEqual( '<em><span style="font-style:italic">test</span></em>' ); + expect( + deepFilterHTML( + '<span style="font-style:italic">test</span>', + [ phrasingContentReducer ], + {} + ) + ).toEqual( '<em><span style="font-style:italic">test</span></em>' ); } ); it( 'should transform nested formatting', () => { - expect( deepFilterHTML( '<span style="font-style:italic;font-weight:bold">test</span>', [ phrasingContentReducer ], {} ) ).toEqual( '<strong><em><span style="font-style:italic;font-weight:bold">test</span></em></strong>' ); + expect( + deepFilterHTML( + '<span style="font-style:italic;font-weight:bold">test</span>', + [ phrasingContentReducer ], + {} + ) + ).toEqual( + '<strong><em><span style="font-style:italic;font-weight:bold">test</span></em></strong>' + ); } ); it( 'should normalise the rel attribute', () => { - const input = '<a href="https://wordpress.org" target="_blank">WordPress</a>'; - const output = '<a href="https://wordpress.org" target="_blank" rel="noreferrer noopener">WordPress</a>'; - expect( deepFilterHTML( input, [ phrasingContentReducer ], {} ) ).toEqual( output ); + const input = + '<a href="https://wordpress.org" target="_blank">WordPress</a>'; + const output = + '<a href="https://wordpress.org" target="_blank" rel="noreferrer noopener">WordPress</a>'; + expect( + deepFilterHTML( input, [ phrasingContentReducer ], {} ) + ).toEqual( output ); } ); it( 'should only allow target="_blank"', () => { - const input = '<a href="https://wordpress.org" target="_self">WordPress</a>'; + const input = + '<a href="https://wordpress.org" target="_self">WordPress</a>'; const output = '<a href="https://wordpress.org">WordPress</a>'; - expect( deepFilterHTML( input, [ phrasingContentReducer ], {} ) ).toEqual( output ); + expect( + deepFilterHTML( input, [ phrasingContentReducer ], {} ) + ).toEqual( output ); } ); it( 'should remove the rel attribute when target is not set', () => { - const input = '<a href="https://wordpress.org" rel="noopener">WordPress</a>'; + const input = + '<a href="https://wordpress.org" rel="noopener">WordPress</a>'; const output = '<a href="https://wordpress.org">WordPress</a>'; - expect( deepFilterHTML( input, [ phrasingContentReducer ], {} ) ).toEqual( output ); + expect( + deepFilterHTML( input, [ phrasingContentReducer ], {} ) + ).toEqual( output ); } ); } ); diff --git a/packages/blocks/src/api/raw-handling/test/special-comment-converter.js b/packages/blocks/src/api/raw-handling/test/special-comment-converter.js index 528dd430d3607b..a6b4489b103c38 100644 --- a/packages/blocks/src/api/raw-handling/test/special-comment-converter.js +++ b/packages/blocks/src/api/raw-handling/test/special-comment-converter.js @@ -6,33 +6,33 @@ import { deepFilterHTML } from '../utils'; describe( 'specialCommentConverter', () => { it( 'should convert a single "more" comment into a basic block', () => { - expect( deepFilterHTML( - '<p><!--more--></p>', - [ specialCommentConverter ] - ) ).toEqual( - '<p></p><wp-block data-block="core/more"></wp-block>' - ); + expect( + deepFilterHTML( '<p><!--more--></p>', [ specialCommentConverter ] ) + ).toEqual( '<p></p><wp-block data-block="core/more"></wp-block>' ); } ); it( 'should convert a single "nextpage" comment into a basic block', () => { expect( - deepFilterHTML( '<p><!--nextpage--></p>', [ specialCommentConverter ] ) - ).toEqual( - '<p></p><wp-block data-block="core/nextpage"></wp-block>' - ); + deepFilterHTML( '<p><!--nextpage--></p>', [ + specialCommentConverter, + ] ) + ).toEqual( '<p></p><wp-block data-block="core/nextpage"></wp-block>' ); } ); it( 'should convert two comments into a block', () => { - expect( deepFilterHTML( - '<p><!--more--><!--noteaser--></p>', - [ specialCommentConverter ] - ) ).toEqual( + expect( + deepFilterHTML( '<p><!--more--><!--noteaser--></p>', [ + specialCommentConverter, + ] ) + ).toEqual( '<p></p><wp-block data-block="core/more" data-no-teaser=""></wp-block>' ); } ); it( 'should pass custom text to the block', () => { - expect( deepFilterHTML( - '<p><!--more Read all about it!--><!--noteaser--></p>', - [ specialCommentConverter ] - ) ).toEqual( + expect( + deepFilterHTML( + '<p><!--more Read all about it!--><!--noteaser--></p>', + [ specialCommentConverter ] + ) + ).toEqual( '<p></p><wp-block data-block="core/more" data-custom-text="Read all about it!" data-no-teaser=""></wp-block>' ); } ); diff --git a/packages/blocks/src/api/raw-handling/test/utils.js b/packages/blocks/src/api/raw-handling/test/utils.js index efaea872497ae9..c00940b3c80354 100644 --- a/packages/blocks/src/api/raw-handling/test/utils.js +++ b/packages/blocks/src/api/raw-handling/test/utils.js @@ -1,4 +1,3 @@ - /** * External dependencies */ @@ -8,7 +7,12 @@ import deepFreeze from 'deep-freeze'; * Internal dependencies */ import { getPhrasingContentSchema } from '../phrasing-content'; -import { getBlockContentSchema, isEmpty, isPlain, removeInvalidHTML } from '../utils'; +import { + getBlockContentSchema, + isEmpty, + isPlain, + removeInvalidHTML, +} from '../utils'; jest.mock( '@wordpress/data', () => { return { @@ -17,8 +21,10 @@ jest.mock( '@wordpress/data', () => { case 'core/blocks': { return { hasBlockSupport: ( blockName, supports ) => { - return blockName === 'core/paragraph' && - supports === 'anchor'; + return ( + blockName === 'core/paragraph' && + supports === 'anchor' + ); }, }; } @@ -178,11 +184,14 @@ describe( 'removeInvalidHTML', () => { it( 'should break up block content with phrasing schema', () => { const input = '<p>test</p><p>test</p>'; const output = 'test<br>test'; - expect( removeInvalidHTML( input, phrasingContentSchema, true ) ).toBe( output ); + expect( removeInvalidHTML( input, phrasingContentSchema, true ) ).toBe( + output + ); } ); it( 'should unwrap node that does not satisfy require', () => { - const input = '<figure><p>test</p><figcaption>test</figcaption></figure>'; + const input = + '<figure><p>test</p><figcaption>test</figcaption></figure>'; const output = '<p>test</p>test'; expect( removeInvalidHTML( input, schema ) ).toBe( output ); } ); @@ -201,16 +210,18 @@ describe( 'getBlockContentSchema', () => { }; it( 'should handle a single raw transform', () => { - const transforms = deepFreeze( [ { - blockName: 'core/paragraph', - type: 'raw', - selector: 'p', - schema: { - p: { - children: myContentSchema, + const transforms = deepFreeze( [ + { + blockName: 'core/paragraph', + type: 'raw', + selector: 'p', + schema: { + p: { + children: myContentSchema, + }, }, }, - } ] ); + ] ); const output = { p: { children: myContentSchema, @@ -218,33 +229,34 @@ describe( 'getBlockContentSchema', () => { isMatch: undefined, }, }; - expect( - getBlockContentSchema( transforms ) - ).toEqual( output ); + expect( getBlockContentSchema( transforms ) ).toEqual( output ); } ); it( 'should handle multiple raw transforms', () => { const preformattedIsMatch = ( input ) => { return input === 4; }; - const transforms = deepFreeze( [ { - blockName: 'core/paragraph', - type: 'raw', - schema: { - p: { - children: myContentSchema, + const transforms = deepFreeze( [ + { + blockName: 'core/paragraph', + type: 'raw', + schema: { + p: { + children: myContentSchema, + }, }, }, - }, { - blockName: 'core/preformatted', - type: 'raw', - isMatch: preformattedIsMatch, - schema: { - pre: { - children: myContentSchema, + { + blockName: 'core/preformatted', + type: 'raw', + isMatch: preformattedIsMatch, + schema: { + pre: { + children: myContentSchema, + }, }, }, - } ] ); + ] ); const output = { p: { children: myContentSchema, @@ -257,33 +269,34 @@ describe( 'getBlockContentSchema', () => { isMatch: preformattedIsMatch, }, }; - expect( - getBlockContentSchema( transforms ) - ).toEqual( output ); + expect( getBlockContentSchema( transforms ) ).toEqual( output ); } ); it( 'should correctly merge the children', () => { - const transforms = deepFreeze( [ { - blockName: 'my/preformatted', - type: 'raw', - schema: { - pre: { - children: { - sub: {}, - sup: {}, - strong: {}, + const transforms = deepFreeze( [ + { + blockName: 'my/preformatted', + type: 'raw', + schema: { + pre: { + children: { + sub: {}, + sup: {}, + strong: {}, + }, }, }, }, - }, { - blockName: 'core/preformatted', - type: 'raw', - schema: { - pre: { - children: myContentSchema, + { + blockName: 'core/preformatted', + type: 'raw', + schema: { + pre: { + children: myContentSchema, + }, }, }, - } ] ); + ] ); const output = { pre: { children: { @@ -294,39 +307,38 @@ describe( 'getBlockContentSchema', () => { }, }, }; - expect( - getBlockContentSchema( transforms ) - ).toEqual( output ); + expect( getBlockContentSchema( transforms ) ).toEqual( output ); } ); it( 'should correctly merge the attributes', () => { - const transforms = deepFreeze( [ { - blockName: 'my/preformatted', - type: 'raw', - schema: { - pre: { - attributes: [ 'data-chicken' ], - children: myContentSchema, + const transforms = deepFreeze( [ + { + blockName: 'my/preformatted', + type: 'raw', + schema: { + pre: { + attributes: [ 'data-chicken' ], + children: myContentSchema, + }, }, }, - }, { - blockName: 'core/preformatted', - type: 'raw', - schema: { - pre: { - attributes: [ 'data-ribs' ], - children: myContentSchema, + { + blockName: 'core/preformatted', + type: 'raw', + schema: { + pre: { + attributes: [ 'data-ribs' ], + children: myContentSchema, + }, }, }, - } ] ); + ] ); const output = { pre: { children: myContentSchema, attributes: [ 'data-chicken', 'data-ribs' ], }, }; - expect( - getBlockContentSchema( transforms ) - ).toEqual( output ); + expect( getBlockContentSchema( transforms ) ).toEqual( output ); } ); } ); diff --git a/packages/blocks/src/api/raw-handling/utils.js b/packages/blocks/src/api/raw-handling/utils.js index a70d78c3e34174..24dd405a3ca6dd 100644 --- a/packages/blocks/src/api/raw-handling/utils.js +++ b/packages/blocks/src/api/raw-handling/utils.js @@ -28,11 +28,17 @@ const { ELEMENT_NODE, TEXT_NODE } = window.Node; * * @return {Object} A complete block content schema. */ -export function getBlockContentSchema( transforms, phrasingContentSchema, isPaste ) { +export function getBlockContentSchema( + transforms, + phrasingContentSchema, + isPaste +) { const schemas = transforms.map( ( { isMatch, blockName, schema } ) => { const hasAnchorSupport = hasBlockSupport( blockName, 'anchor' ); - schema = isFunction( schema ) ? schema( { phrasingContentSchema, isPaste } ) : schema; + schema = isFunction( schema ) + ? schema( { phrasingContentSchema, isPaste } ) + : schema; // If the block does not has anchor support and the transform does not // provides an isMatch we can return the schema right away. @@ -208,7 +214,10 @@ function cleanNodeList( nodeList, doc, schema, inline ) { if ( node.hasAttributes() ) { // Strip invalid attributes. Array.from( node.attributes ).forEach( ( { name } ) => { - if ( name !== 'class' && ! includes( attributes, name ) ) { + if ( + name !== 'class' && + ! includes( attributes, name ) + ) { node.removeAttribute( name ); } } ); @@ -228,7 +237,11 @@ function cleanNodeList( nodeList, doc, schema, inline ) { } ); Array.from( node.classList ).forEach( ( name ) => { - if ( ! mattchers.some( ( isMatch ) => isMatch( name ) ) ) { + if ( + ! mattchers.some( ( isMatch ) => + isMatch( name ) + ) + ) { node.classList.remove( name ); } } ); @@ -249,25 +262,47 @@ function cleanNodeList( nodeList, doc, schema, inline ) { if ( children ) { // If a parent requires certain children, but it does // not have them, drop the parent and continue. - if ( require.length && ! node.querySelector( require.join( ',' ) ) ) { - cleanNodeList( node.childNodes, doc, schema, inline ); + if ( + require.length && + ! node.querySelector( require.join( ',' ) ) + ) { + cleanNodeList( + node.childNodes, + doc, + schema, + inline + ); unwrap( node ); - // If the node is at the top, phrasing content, and - // contains children that are block content, unwrap - // the node because it is invalid. + // If the node is at the top, phrasing content, and + // contains children that are block content, unwrap + // the node because it is invalid. } else if ( node.parentNode.nodeName === 'BODY' && isPhrasingContent( node ) ) { - cleanNodeList( node.childNodes, doc, schema, inline ); - - if ( Array.from( node.childNodes ).some( ( child ) => ! isPhrasingContent( child ) ) ) { + cleanNodeList( + node.childNodes, + doc, + schema, + inline + ); + + if ( + Array.from( node.childNodes ).some( + ( child ) => ! isPhrasingContent( child ) + ) + ) { unwrap( node ); } } else { - cleanNodeList( node.childNodes, doc, children, inline ); + cleanNodeList( + node.childNodes, + doc, + children, + inline + ); } - // Remove children if the node is not supposed to have any. + // Remove children if the node is not supposed to have any. } else { while ( node.firstChild ) { remove( node.firstChild ); @@ -275,13 +310,17 @@ function cleanNodeList( nodeList, doc, schema, inline ) { } } } - // Invalid child. Continue with schema at the same place and unwrap. + // Invalid child. Continue with schema at the same place and unwrap. } else { cleanNodeList( node.childNodes, doc, schema, inline ); // For inline mode, insert a line break when unwrapping nodes that // are not phrasing content. - if ( inline && ! isPhrasingContent( node ) && node.nextElementSibling ) { + if ( + inline && + ! isPhrasingContent( node ) && + node.nextElementSibling + ) { insertAfter( doc.createElement( 'br' ), node ); } diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 0fd01c1d4ff11e..eed76a144c459a 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -3,14 +3,7 @@ /** * External dependencies */ -import { - get, - omit, - pick, - isFunction, - isPlainObject, - some, -} from 'lodash'; +import { get, omit, pick, isFunction, isPlainObject, some } from 'lodash'; /** * WordPress dependencies @@ -65,31 +58,31 @@ import { DEPRECATED_ENTRY_KEYS } from './constants'; */ /** - * Named block pattern scopes. + * Named block variation scopes. * - * @typedef {'block'|'inserter'} WPBlockPatternScope + * @typedef {'block'|'inserter'} WPBlockVariationScope */ /** - * An object describing a pattern defined for the block type. + * An object describing a variation defined for the block type. * - * @typedef {Object} WPBlockPattern + * @typedef {Object} WPBlockVariation * - * @property {string} name The unique and machine-readable name. - * @property {string} title A human-readable pattern title. - * @property {string} description A detailed pattern description. - * @property {WPIcon} [icon] An icon helping to visualize the pattern. - * @property {boolean} [isDefault] Indicates whether the current pattern is - * the default one. Defaults to `false`. - * @property {Object} [attributes] Values which override block attributes. - * @property {Array[]} [innerBlocks] Initial configuration of nested blocks. - * @property {Object} [example] Example provides structured data for - * the block preview. You can set to - * `undefined` to disable the preview shown - * for the block type. - * @property {WPBlockPatternScope[]} [scope] The list of scopes where the pattern - * is applicable. When not provided, it - * assumes all available scopes. + * @property {string} name The unique and machine-readable name. + * @property {string} title A human-readable variation title. + * @property {string} description A detailed variation description. + * @property {WPIcon} [icon] An icon helping to visualize the variation. + * @property {boolean} [isDefault] Indicates whether the current variation is + * the default one. Defaults to `false`. + * @property {Object} [attributes] Values which override block attributes. + * @property {Array[]} [innerBlocks] Initial configuration of nested blocks. + * @property {Object} [example] Example provides structured data for + * the block preview. You can set to + * `undefined` to disable the preview shown + * for the block type. + * @property {WPBlockVariationScope[]} [scope] The list of scopes where the variation + * is applicable. When not provided, it + * assumes all available scopes. */ /** @@ -97,26 +90,26 @@ import { DEPRECATED_ENTRY_KEYS } from './constants'; * * @typedef {Object} WPBlock * - * @property {string} name Block type's namespaced name. - * @property {string} title Human-readable block type label. - * @property {string} description A detailed block type description. - * @property {string} category Block type category classification, - * used in search interfaces to arrange - * block types by category. - * @property {WPBlockTypeIcon} [icon] Block type icon. - * @property {string[]} [keywords] Additional keywords to produce block - * type as result in search interfaces. - * @property {Object} [attributes] Block type attributes. - * @property {WPComponent} [save] Optional component describing - * serialized markup structure of a - * block type. - * @property {WPComponent} edit Component rendering an element to - * manipulate the attributes of a block - * in the context of an editor. - * @property {WPBlockPattern[]} [patterns] The list of block patterns. - * @property {Object} [example] Example provides structured data for - * the block preview. When not defined - * then no preview is shown. + * @property {string} name Block type's namespaced name. + * @property {string} title Human-readable block type label. + * @property {string} description A detailed block type description. + * @property {string} category Block type category classification, + * used in search interfaces to arrange + * block types by category. + * @property {WPBlockTypeIcon} [icon] Block type icon. + * @property {string[]} [keywords] Additional keywords to produce block + * type as result in search interfaces. + * @property {Object} [attributes] Block type attributes. + * @property {WPComponent} [save] Optional component describing + * serialized markup structure of a + * block type. + * @property {WPComponent} edit Component rendering an element to + * manipulate the attributes of a block + * in the context of an editor. + * @property {WPBlockVariation[]} [variations] The list of block variations. + * @property {Object} [example] Example provides structured data for + * the block preview. When not defined + * then no preview is shown. */ /** @@ -138,7 +131,8 @@ export let serverSideBlockDefinitions = {}; * * @param {Object} definitions Server-side block definitions */ -export function unstable__bootstrapServerSideBlockDefinitions( definitions ) { // eslint-disable-line camelcase +// eslint-disable-next-line camelcase +export function unstable__bootstrapServerSideBlockDefinitions( definitions ) { serverSideBlockDefinitions = { ...serverSideBlockDefinitions, ...definitions, @@ -165,9 +159,7 @@ export function registerBlockType( name, settings ) { }; if ( typeof name !== 'string' ) { - console.error( - 'Block names must be strings.' - ); + console.error( 'Block names must be strings.' ); return; } if ( ! /^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/.test( name ) ) { @@ -177,9 +169,7 @@ export function registerBlockType( name, settings ) { return; } if ( select( 'core/blocks' ).getBlockType( name ) ) { - console.error( - 'Block "' + name + '" is already registered.' - ); + console.error( 'Block "' + name + '" is already registered.' ); return; } @@ -188,7 +178,8 @@ export function registerBlockType( name, settings ) { if ( settings.deprecated ) { settings.deprecated = settings.deprecated.map( ( deprecation ) => - pick( // Only keep valid deprecation keys. + pick( + // Only keep valid deprecation keys. applyFilters( 'blocks.registerBlockType', // Merge deprecation keys with pre-filter settings @@ -208,33 +199,27 @@ export function registerBlockType( name, settings ) { } if ( ! isPlainObject( settings ) ) { - console.error( - 'Block settings must be a valid object.' - ); + console.error( 'Block settings must be a valid object.' ); return; } if ( ! isFunction( settings.save ) ) { - console.error( - 'The "save" property must be a valid function.' - ); + console.error( 'The "save" property must be a valid function.' ); return; } if ( 'edit' in settings && ! isFunction( settings.edit ) ) { - console.error( - 'The "edit" property must be a valid function.' - ); + console.error( 'The "edit" property must be a valid function.' ); return; } if ( ! ( 'category' in settings ) ) { - console.error( - 'The block "' + name + '" must have a category.' - ); + console.error( 'The block "' + name + '" must have a category.' ); return; } if ( 'category' in settings && - ! some( select( 'core/blocks' ).getCategories(), { slug: settings.category } ) + ! some( select( 'core/blocks' ).getCategories(), { + slug: settings.category, + } ) ) { console.error( 'The block "' + name + '" must have a registered category.' @@ -242,15 +227,11 @@ export function registerBlockType( name, settings ) { return; } if ( ! ( 'title' in settings ) || settings.title === '' ) { - console.error( - 'The block "' + name + '" must have a title.' - ); + console.error( 'The block "' + name + '" must have a title.' ); return; } if ( typeof settings.title !== 'string' ) { - console.error( - 'Block titles must be strings.' - ); + console.error( 'Block titles must be strings.' ); return; } @@ -258,7 +239,7 @@ export function registerBlockType( name, settings ) { if ( ! isValidIcon( settings.icon.src ) ) { console.error( 'The icon passed is invalid. ' + - 'The icon should be a string, an element, a function, or an object following the specifications documented in https://developer.wordpress.org/block-editor/developers/block-api/block-registration/#icon-optional' + 'The icon should be a string, an element, a function, or an object following the specifications documented in https://developer.wordpress.org/block-editor/developers/block-api/block-registration/#icon-optional' ); return; } @@ -300,9 +281,7 @@ export function unregisterBlockCollection( namespace ) { export function unregisterBlockType( name ) { const oldBlock = select( 'core/blocks' ).getBlockType( name ); if ( ! oldBlock ) { - console.error( - 'Block "' + name + '" is not registered.' - ); + console.error( 'Block "' + name + '" is not registered.' ); return; } dispatch( 'core/blocks' ).removeBlockTypes( name ); @@ -414,7 +393,11 @@ export function getBlockTypes() { * @return {?*} Block support value */ export function getBlockSupport( nameOrType, feature, defaultSupports ) { - return select( 'core/blocks' ).getBlockSupport( nameOrType, feature, defaultSupports ); + return select( 'core/blocks' ).getBlockSupport( + nameOrType, + feature, + defaultSupports + ); } /** @@ -428,7 +411,11 @@ export function getBlockSupport( nameOrType, feature, defaultSupports ) { * @return {boolean} Whether block supports feature. */ export function hasBlockSupport( nameOrType, feature, defaultSupports ) { - return select( 'core/blocks' ).hasBlockSupport( nameOrType, feature, defaultSupports ); + return select( 'core/blocks' ).hasBlockSupport( + nameOrType, + feature, + defaultSupports + ); } /** @@ -475,7 +462,9 @@ export const hasChildBlocks = ( blockName ) => { * and false otherwise. */ export const hasChildBlocksWithInserterSupport = ( blockName ) => { - return select( 'core/blocks' ).hasChildBlocksWithInserterSupport( blockName ); + return select( 'core/blocks' ).hasChildBlocksWithInserterSupport( + blockName + ); }; /** @@ -495,25 +484,40 @@ export const registerBlockStyle = ( blockName, styleVariation ) => { * @param {string} styleVariationName Name of class applied to the block. */ export const unregisterBlockStyle = ( blockName, styleVariationName ) => { - dispatch( 'core/blocks' ).removeBlockStyles( blockName, styleVariationName ); + dispatch( 'core/blocks' ).removeBlockStyles( + blockName, + styleVariationName + ); }; /** - * Registers a new block pattern for the given block. + * Registers a new block variation for the given block. * - * @param {string} blockName Name of the block (example: “core/columns”). - * @param {WPBlockPattern} pattern Object describing a block pattern. + * @param {string} blockName Name of the block (example: “core/columns”). + * @param {WPBlockVariation} variation Object describing a block variation. */ -export const __experimentalRegisterBlockPattern = ( blockName, pattern ) => { - dispatch( 'core/blocks' ).__experimentalAddBlockPatterns( blockName, pattern ); +export const __experimentalRegisterBlockVariation = ( + blockName, + variation +) => { + dispatch( 'core/blocks' ).__experimentalAddBlockVariations( + blockName, + variation + ); }; /** - * Unregisters a block pattern defined for the given block. + * Unregisters a block variation defined for the given block. * - * @param {string} blockName Name of the block (example: “core/columns”). - * @param {string} patternName Name of the pattern defined for the block. + * @param {string} blockName Name of the block (example: “core/columns”). + * @param {string} variationName Name of the variation defined for the block. */ -export const __experimentalUnregisterBlockPattern = ( blockName, patternName ) => { - dispatch( 'core/blocks' ).__experimentalRemoveBlockPatterns( blockName, patternName ); +export const __experimentalUnregisterBlockVariation = ( + blockName, + variationName +) => { + dispatch( 'core/blocks' ).__experimentalRemoveBlockVariations( + blockName, + variationName + ); }; diff --git a/packages/blocks/src/api/serializer.js b/packages/blocks/src/api/serializer.js index d2dfbcab2d672b..394a212dd88e9e 100644 --- a/packages/blocks/src/api/serializer.js +++ b/packages/blocks/src/api/serializer.js @@ -37,9 +37,14 @@ import BlockContentProvider from '../block-content-provider'; export function getBlockDefaultClassName( blockName ) { // Generated HTML classes for blocks follow the `wp-block-{name}` nomenclature. // Blocks provided by WordPress drop the prefixes 'core/' or 'core-' (used in 'core-embed/'). - const className = 'wp-block-' + blockName.replace( /\//, '-' ).replace( /^core-/, '' ); + const className = + 'wp-block-' + blockName.replace( /\//, '-' ).replace( /^core-/, '' ); - return applyFilters( 'blocks.getBlockDefaultClassName', className, blockName ); + return applyFilters( + 'blocks.getBlockDefaultClassName', + className, + blockName + ); } /** @@ -52,9 +57,15 @@ export function getBlockDefaultClassName( blockName ) { export function getBlockMenuDefaultClassName( blockName ) { // Generated HTML classes for blocks follow the `editor-block-list-item-{name}` nomenclature. // Blocks provided by WordPress drop the prefixes 'core/' or 'core-' (used in 'core-embed/'). - const className = 'editor-block-list-item-' + blockName.replace( /\//, '-' ).replace( /^core-/, '' ); - - return applyFilters( 'blocks.getBlockMenuDefaultClassName', className, blockName ); + const className = + 'editor-block-list-item-' + + blockName.replace( /\//, '-' ).replace( /^core-/, '' ); + + return applyFilters( + 'blocks.getBlockMenuDefaultClassName', + className, + blockName + ); } /** @@ -67,7 +78,11 @@ export function getBlockMenuDefaultClassName( blockName ) { * * @return {Object|string} Save element or raw HTML string. */ -export function getSaveElement( blockTypeOrName, attributes, innerBlocks = [] ) { +export function getSaveElement( + blockTypeOrName, + attributes, + innerBlocks = [] +) { const blockType = normalizeBlockType( blockTypeOrName ); let { save } = blockType; @@ -81,7 +96,10 @@ export function getSaveElement( blockTypeOrName, attributes, innerBlocks = [] ) let element = save( { attributes, innerBlocks } ); - if ( isObject( element ) && hasFilter( 'blocks.getSaveContent.extraProps' ) ) { + if ( + isObject( element ) && + hasFilter( 'blocks.getSaveContent.extraProps' ) + ) { /** * Filters the props applied to the block save result element. * @@ -108,7 +126,12 @@ export function getSaveElement( blockTypeOrName, attributes, innerBlocks = [] ) * @param {WPBlock} blockType Block type definition. * @param {Object} attributes Block attributes. */ - element = applyFilters( 'blocks.getSaveElement', element, blockType, attributes ); + element = applyFilters( + 'blocks.getSaveElement', + element, + blockType, + attributes + ); return ( <BlockContentProvider innerBlocks={ innerBlocks }> @@ -130,7 +153,9 @@ export function getSaveElement( blockTypeOrName, attributes, innerBlocks = [] ) export function getSaveContent( blockTypeOrName, attributes, innerBlocks ) { const blockType = normalizeBlockType( blockTypeOrName ); - return renderToString( getSaveElement( blockType, attributes, innerBlocks ) ); + return renderToString( + getSaveElement( blockType, attributes, innerBlocks ) + ); } /** @@ -150,28 +175,35 @@ export function getSaveContent( blockTypeOrName, attributes, innerBlocks ) { * @return {Object<string,*>} Subset of attributes for comment serialization. */ export function getCommentAttributes( blockType, attributes ) { - return reduce( blockType.attributes, ( accumulator, attributeSchema, key ) => { - const value = attributes[ key ]; - // Ignore undefined values. - if ( undefined === value ) { - return accumulator; - } - - // Ignore all attributes but the ones with an "undefined" source - // "undefined" source refers to attributes saved in the block comment. - if ( attributeSchema.source !== undefined ) { + return reduce( + blockType.attributes, + ( accumulator, attributeSchema, key ) => { + const value = attributes[ key ]; + // Ignore undefined values. + if ( undefined === value ) { + return accumulator; + } + + // Ignore all attributes but the ones with an "undefined" source + // "undefined" source refers to attributes saved in the block comment. + if ( attributeSchema.source !== undefined ) { + return accumulator; + } + + // Ignore default value. + if ( + 'default' in attributeSchema && + attributeSchema.default === value + ) { + return accumulator; + } + + // Otherwise, include in comment set. + accumulator[ key ] = value; return accumulator; - } - - // Ignore default value. - if ( 'default' in attributeSchema && attributeSchema.default === value ) { - return accumulator; - } - - // Otherwise, include in comment set. - accumulator[ key ] = value; - return accumulator; - }, {} ); + }, + {} + ); } /** @@ -183,20 +215,22 @@ export function getCommentAttributes( blockType, attributes ) { * @return {string} Serialized attributes. */ export function serializeAttributes( attributes ) { - return JSON.stringify( attributes ) - // Don't break HTML comments. - .replace( /--/g, '\\u002d\\u002d' ) - - // Don't break non-standard-compliant tools. - .replace( /</g, '\\u003c' ) - .replace( />/g, '\\u003e' ) - .replace( /&/g, '\\u0026' ) - - // Bypass server stripslashes behavior which would unescape stringify's - // escaping of quotation mark. - // - // See: https://developer.wordpress.org/reference/functions/wp_kses_stripslashes/ - .replace( /\\"/g, '\\u0022' ); + return ( + JSON.stringify( attributes ) + // Don't break HTML comments. + .replace( /--/g, '\\u002d\\u002d' ) + + // Don't break non-standard-compliant tools. + .replace( /</g, '\\u003c' ) + .replace( />/g, '\\u003e' ) + .replace( /&/g, '\\u0026' ) + + // Bypass server stripslashes behavior which would unescape stringify's + // escaping of quotation mark. + // + // See: https://developer.wordpress.org/reference/functions/wp_kses_stripslashes/ + .replace( /\\"/g, '\\u0022' ) + ); } /** @@ -217,7 +251,11 @@ export function getBlockContent( block ) { let saveContent = block.originalContent; if ( block.isValid || block.innerBlocks.length ) { try { - saveContent = getSaveContent( block.name, block.attributes, block.innerBlocks ); + saveContent = getSaveContent( + block.name, + block.attributes, + block.innerBlocks + ); } catch ( error ) {} } @@ -233,15 +271,19 @@ export function getBlockContent( block ) { * * @return {string} Comment-delimited block content. */ -export function getCommentDelimitedContent( rawBlockName, attributes, content ) { - const serializedAttributes = ! isEmpty( attributes ) ? - serializeAttributes( attributes ) + ' ' : - ''; +export function getCommentDelimitedContent( + rawBlockName, + attributes, + content +) { + const serializedAttributes = ! isEmpty( attributes ) + ? serializeAttributes( attributes ) + ' ' + : ''; // Strip core blocks of their namespace prefix. - const blockName = startsWith( rawBlockName, 'core/' ) ? - rawBlockName.slice( 5 ) : - rawBlockName; + const blockName = startsWith( rawBlockName, 'core/' ) + ? rawBlockName.slice( 5 ) + : rawBlockName; // @todo make the `wp:` prefix potentially configurable. @@ -270,7 +312,7 @@ export function serializeBlock( block, { isInnerBlocks = false } = {} ) { const saveContent = getBlockContent( block ); if ( - ( blockName === getUnregisteredTypeHandlerName() ) || + blockName === getUnregisteredTypeHandlerName() || ( ! isInnerBlocks && blockName === getFreeformContentHandlerName() ) ) { return saveContent; diff --git a/packages/blocks/src/api/templates.js b/packages/blocks/src/api/templates.js index d09be49453e702..5d21e0bf5a5922 100644 --- a/packages/blocks/src/api/templates.js +++ b/packages/blocks/src/api/templates.js @@ -54,53 +54,64 @@ export function synchronizeBlocksWithTemplate( blocks = [], template ) { return blocks; } - return map( template, ( [ name, attributes, innerBlocksTemplate ], index ) => { - const block = blocks[ index ]; + return map( + template, + ( [ name, attributes, innerBlocksTemplate ], index ) => { + const block = blocks[ index ]; - if ( block && block.name === name ) { - const innerBlocks = synchronizeBlocksWithTemplate( block.innerBlocks, innerBlocksTemplate ); - return { ...block, innerBlocks }; - } + if ( block && block.name === name ) { + const innerBlocks = synchronizeBlocksWithTemplate( + block.innerBlocks, + innerBlocksTemplate + ); + return { ...block, innerBlocks }; + } - // To support old templates that were using the "children" format - // for the attributes using "html" strings now, we normalize the template attributes - // before creating the blocks. + // To support old templates that were using the "children" format + // for the attributes using "html" strings now, we normalize the template attributes + // before creating the blocks. - const blockType = getBlockType( name ); - const isHTMLAttribute = ( attributeDefinition ) => get( attributeDefinition, [ 'source' ] ) === 'html'; - const isQueryAttribute = ( attributeDefinition ) => get( attributeDefinition, [ 'source' ] ) === 'query'; + const blockType = getBlockType( name ); + const isHTMLAttribute = ( attributeDefinition ) => + get( attributeDefinition, [ 'source' ] ) === 'html'; + const isQueryAttribute = ( attributeDefinition ) => + get( attributeDefinition, [ 'source' ] ) === 'query'; - const normalizeAttributes = ( schema, values ) => { - return mapValues( values, ( value, key ) => { - return normalizeAttribute( schema[ key ], value ); - } ); - }; - const normalizeAttribute = ( definition, value ) => { - if ( isHTMLAttribute( definition ) && isArray( value ) ) { - // Introduce a deprecated call at this point - // When we're confident that "children" format should be removed from the templates. + const normalizeAttributes = ( schema, values ) => { + return mapValues( values, ( value, key ) => { + return normalizeAttribute( schema[ key ], value ); + } ); + }; + const normalizeAttribute = ( definition, value ) => { + if ( isHTMLAttribute( definition ) && isArray( value ) ) { + // Introduce a deprecated call at this point + // When we're confident that "children" format should be removed from the templates. - return renderToString( value ); - } + return renderToString( value ); + } - if ( isQueryAttribute( definition ) && value ) { - return value.map( ( subValues ) => { - return normalizeAttributes( definition.query, subValues ); - } ); - } + if ( isQueryAttribute( definition ) && value ) { + return value.map( ( subValues ) => { + return normalizeAttributes( + definition.query, + subValues + ); + } ); + } - return value; - }; + return value; + }; - const normalizedAttributes = normalizeAttributes( - get( blockType, [ 'attributes' ], {} ), - attributes - ); + const normalizedAttributes = normalizeAttributes( + get( blockType, [ 'attributes' ], {} ), + attributes + ); - return createBlock( - name, - normalizedAttributes, - synchronizeBlocksWithTemplate( [], innerBlocksTemplate ) - ); - } ); + return createBlock( + name, + normalizedAttributes, + synchronizeBlocksWithTemplate( [], innerBlocksTemplate ) + ); + } + ); } diff --git a/packages/blocks/src/api/test/children.js b/packages/blocks/src/api/test/children.js index cc3fe5d76d28ad..dbc066e585850c 100644 --- a/packages/blocks/src/api/test/children.js +++ b/packages/blocks/src/api/test/children.js @@ -33,7 +33,9 @@ describe( 'getSerializeCapableElement', () => { // since all that is cared about is that it can be serialized. const html = renderToString( element ); - expect( html ).toBe( 'This <strong class="is-extra-strong">is</strong> a test' ); + expect( html ).toBe( + 'This <strong class="is-extra-strong">is</strong> a test' + ); } ); } ); @@ -52,7 +54,7 @@ describe( 'concat', () => { props: { children: [ 'world' ], }, - }, + } ); expect( result ).toEqual( [ @@ -73,16 +75,12 @@ describe( 'concat', () => { } ); it( 'should merge adjacent strings', () => { - const result = concat( - 'Hello', - ' ', - { - type: 'strong', - props: { - children: [ 'World' ], - }, + const result = concat( 'Hello', ' ', { + type: 'strong', + props: { + children: [ 'World' ], }, - ); + } ); expect( result ).toEqual( [ 'Hello ', @@ -118,7 +116,8 @@ describe( 'toHTML', () => { describe( 'fromDOM', () => { it( 'should return an equivalent block children', () => { const node = document.createElement( 'div' ); - node.innerHTML = 'This <strong class="is-extra-strong">is</strong> a test'; + node.innerHTML = + 'This <strong class="is-extra-strong">is</strong> a test'; const blockNode = fromDOM( node.childNodes ); diff --git a/packages/blocks/src/api/test/factory.js b/packages/blocks/src/api/test/factory.js index bb34cb60cf2cd9..641745ddeeb3cc 100644 --- a/packages/blocks/src/api/test/factory.js +++ b/packages/blocks/src/api/test/factory.js @@ -69,11 +69,9 @@ describe( 'block factory', () => { category: 'common', title: 'test block', } ); - const block = createBlock( - 'core/test-block', - { align: 'left' }, - [ createBlock( 'core/test-block' ) ], - ); + const block = createBlock( 'core/test-block', { align: 'left' }, [ + createBlock( 'core/test-block' ), + ] ); expect( block.name ).toEqual( 'core/test-block' ); expect( block.attributes ).toEqual( { @@ -180,11 +178,9 @@ describe( 'block factory', () => { title: 'test block', } ); const block = deepFreeze( - createBlock( - 'core/test-block', - { align: 'left' }, - [ createBlock( 'core/test-block' ) ], - ) + createBlock( 'core/test-block', { align: 'left' }, [ + createBlock( 'core/test-block' ), + ] ) ); const clonedBlock = cloneBlock( block, { @@ -217,14 +213,10 @@ describe( 'block factory', () => { title: 'test block', } ); const block = deepFreeze( - createBlock( - 'core/test-block', - { align: 'left' }, - [ - createBlock( 'core/test-block', { align: 'right' } ), - createBlock( 'core/test-block', { align: 'left' } ), - ], - ) + createBlock( 'core/test-block', { align: 'left' }, [ + createBlock( 'core/test-block', { align: 'right' } ), + createBlock( 'core/test-block', { align: 'left' } ), + ] ) ); const clonedBlock = cloneBlock( block, undefined, [ @@ -232,7 +224,9 @@ describe( 'block factory', () => { ] ); expect( clonedBlock.innerBlocks ).toHaveLength( 1 ); - expect( clonedBlock.innerBlocks[ 0 ].attributes ).not.toHaveProperty( 'align' ); + expect( + clonedBlock.innerBlocks[ 0 ].attributes + ).not.toHaveProperty( 'align' ); } ); it( 'should clone innerBlocks if innerBlocks are not passed', () => { @@ -251,25 +245,33 @@ describe( 'block factory', () => { title: 'test block', } ); const block = deepFreeze( - createBlock( - 'core/test-block', - { align: 'left' }, - [ - createBlock( 'core/test-block', { align: 'right' } ), - createBlock( 'core/test-block', { align: 'left' } ), - ], - ) + createBlock( 'core/test-block', { align: 'left' }, [ + createBlock( 'core/test-block', { align: 'right' } ), + createBlock( 'core/test-block', { align: 'left' } ), + ] ) ); const clonedBlock = cloneBlock( block ); expect( clonedBlock.innerBlocks ).toHaveLength( 2 ); - expect( clonedBlock.innerBlocks[ 0 ].clientId ).not.toBe( block.innerBlocks[ 0 ].clientId ); - expect( clonedBlock.innerBlocks[ 0 ].attributes ).not.toBe( block.innerBlocks[ 0 ].attributes ); - expect( clonedBlock.innerBlocks[ 0 ].attributes ).toEqual( block.innerBlocks[ 0 ].attributes ); - expect( clonedBlock.innerBlocks[ 1 ].clientId ).not.toBe( block.innerBlocks[ 1 ].clientId ); - expect( clonedBlock.innerBlocks[ 1 ].attributes ).not.toBe( block.innerBlocks[ 1 ].attributes ); - expect( clonedBlock.innerBlocks[ 1 ].attributes ).toEqual( block.innerBlocks[ 1 ].attributes ); + expect( clonedBlock.innerBlocks[ 0 ].clientId ).not.toBe( + block.innerBlocks[ 0 ].clientId + ); + expect( clonedBlock.innerBlocks[ 0 ].attributes ).not.toBe( + block.innerBlocks[ 0 ].attributes + ); + expect( clonedBlock.innerBlocks[ 0 ].attributes ).toEqual( + block.innerBlocks[ 0 ].attributes + ); + expect( clonedBlock.innerBlocks[ 1 ].clientId ).not.toBe( + block.innerBlocks[ 1 ].clientId + ); + expect( clonedBlock.innerBlocks[ 1 ].attributes ).not.toBe( + block.innerBlocks[ 1 ].attributes + ); + expect( clonedBlock.innerBlocks[ 1 ].attributes ).toEqual( + block.innerBlocks[ 1 ].attributes + ); } ); } ); @@ -282,11 +284,13 @@ describe( 'block factory', () => { }, }, transforms: { - from: [ { - type: 'block', - blocks: [ 'core/text-block' ], - transform: noop, - } ], + from: [ + { + type: 'block', + blocks: [ 'core/text-block' ], + transform: noop, + }, + ], }, save: noop, category: 'common', @@ -298,10 +302,14 @@ describe( 'block factory', () => { value: 'chicken', } ); - const availableBlocks = getPossibleBlockTransformations( [ block ] ); + const availableBlocks = getPossibleBlockTransformations( [ + block, + ] ); expect( availableBlocks ).toHaveLength( 1 ); - expect( availableBlocks[ 0 ].name ).toBe( 'core/updated-text-block' ); + expect( availableBlocks[ 0 ].name ).toBe( + 'core/updated-text-block' + ); } ); it( 'should show as available a simple "to" transformation"', () => { @@ -312,11 +320,13 @@ describe( 'block factory', () => { }, }, transforms: { - to: [ { - type: 'block', - blocks: [ 'core/text-block' ], - transform: noop, - } ], + to: [ + { + type: 'block', + blocks: [ 'core/text-block' ], + transform: noop, + }, + ], }, save: noop, category: 'common', @@ -328,7 +338,9 @@ describe( 'block factory', () => { value: 'ribs', } ); - const availableBlocks = getPossibleBlockTransformations( [ block ] ); + const availableBlocks = getPossibleBlockTransformations( [ + block, + ] ); expect( availableBlocks ).toHaveLength( 1 ); expect( availableBlocks[ 0 ].name ).toBe( 'core/text-block' ); @@ -342,11 +354,13 @@ describe( 'block factory', () => { }, }, transforms: { - from: [ { - type: 'block', - blocks: [ 'core/text-block' ], - transform: noop, - } ], + from: [ + { + type: 'block', + blocks: [ 'core/text-block' ], + transform: noop, + }, + ], }, save: noop, category: 'common', @@ -362,7 +376,10 @@ describe( 'block factory', () => { value: 'ribs', } ); - const availableBlocks = getPossibleBlockTransformations( [ block1, block2 ] ); + const availableBlocks = getPossibleBlockTransformations( [ + block1, + block2, + ] ); expect( availableBlocks ).toEqual( [] ); } ); @@ -375,17 +392,22 @@ describe( 'block factory', () => { }, }, transforms: { - to: [ { - type: 'block', - blocks: [ 'core/updated-text-block' ], - transform: noop, - } ], + to: [ + { + type: 'block', + blocks: [ 'core/updated-text-block' ], + transform: noop, + }, + ], }, save: noop, category: 'common', title: 'updated text block', } ); - registerBlockType( 'core/updated-text-block', defaultBlockSettings ); + registerBlockType( + 'core/updated-text-block', + defaultBlockSettings + ); const block1 = createBlock( 'core/text-block', { value: 'chicken', @@ -395,7 +417,10 @@ describe( 'block factory', () => { value: 'ribs', } ); - const availableBlocks = getPossibleBlockTransformations( [ block1, block2 ] ); + const availableBlocks = getPossibleBlockTransformations( [ + block1, + block2, + ] ); expect( availableBlocks ).toEqual( [] ); } ); @@ -408,12 +433,14 @@ describe( 'block factory', () => { }, }, transforms: { - from: [ { - type: 'block', - blocks: [ 'core/text-block' ], - transform: noop, - isMultiBlock: true, - } ], + from: [ + { + type: 'block', + blocks: [ 'core/text-block' ], + transform: noop, + isMultiBlock: true, + }, + ], }, save: noop, category: 'common', @@ -429,10 +456,15 @@ describe( 'block factory', () => { value: 'ribs', } ); - const availableBlocks = getPossibleBlockTransformations( [ block1, block2 ] ); + const availableBlocks = getPossibleBlockTransformations( [ + block1, + block2, + ] ); expect( availableBlocks ).toHaveLength( 1 ); - expect( availableBlocks[ 0 ].name ).toBe( 'core/updated-text-block' ); + expect( availableBlocks[ 0 ].name ).toBe( + 'core/updated-text-block' + ); } ); it( 'should show a transformation as available if multiple blocks are passed and the transformation accepts multiple blocks (for a "to" transform)', () => { @@ -443,18 +475,23 @@ describe( 'block factory', () => { }, }, transforms: { - to: [ { - type: 'block', - blocks: [ 'core/updated-text-block' ], - transform: noop, - isMultiBlock: true, - } ], + to: [ + { + type: 'block', + blocks: [ 'core/updated-text-block' ], + transform: noop, + isMultiBlock: true, + }, + ], }, save: noop, category: 'common', title: 'updated text block', } ); - registerBlockType( 'core/updated-text-block', defaultBlockSettings ); + registerBlockType( + 'core/updated-text-block', + defaultBlockSettings + ); const block1 = createBlock( 'core/text-block', { value: 'chicken', @@ -464,10 +501,15 @@ describe( 'block factory', () => { value: 'ribs', } ); - const availableBlocks = getPossibleBlockTransformations( [ block1, block2 ] ); + const availableBlocks = getPossibleBlockTransformations( [ + block1, + block2, + ] ); expect( availableBlocks ).toHaveLength( 1 ); - expect( availableBlocks[ 0 ].name ).toBe( 'core/updated-text-block' ); + expect( availableBlocks[ 0 ].name ).toBe( + 'core/updated-text-block' + ); } ); it( 'should show multiple possible transformations', () => { @@ -478,34 +520,44 @@ describe( 'block factory', () => { }, }, transforms: { - to: [ { - type: 'block', - blocks: [ 'core/text-block' ], - transform: noop, - isMultiBlock: true, - }, { - type: 'block', - blocks: [ 'core/another-text-block' ], - transform: noop, - isMultiBlock: true, - } ], + to: [ + { + type: 'block', + blocks: [ 'core/text-block' ], + transform: noop, + isMultiBlock: true, + }, + { + type: 'block', + blocks: [ 'core/another-text-block' ], + transform: noop, + isMultiBlock: true, + }, + ], }, save: noop, category: 'common', title: 'updated text block', } ); registerBlockType( 'core/text-block', defaultBlockSettings ); - registerBlockType( 'core/another-text-block', defaultBlockSettings ); + registerBlockType( + 'core/another-text-block', + defaultBlockSettings + ); const block = createBlock( 'core/updated-text-block', { value: 'chicken', } ); - const availableBlocks = getPossibleBlockTransformations( [ block ] ); + const availableBlocks = getPossibleBlockTransformations( [ + block, + ] ); expect( availableBlocks ).toHaveLength( 2 ); expect( availableBlocks[ 0 ].name ).toBe( 'core/text-block' ); - expect( availableBlocks[ 1 ].name ).toBe( 'core/another-text-block' ); + expect( availableBlocks[ 1 ].name ).toBe( + 'core/another-text-block' + ); } ); it( 'should show multiple possible transformations when multiple blocks have a matching `from` transform', () => { @@ -516,12 +568,14 @@ describe( 'block factory', () => { }, }, transforms: { - from: [ { - type: 'block', - blocks: [ 'core/text-block' ], - transform: noop, - isMultiBlock: false, - } ], + from: [ + { + type: 'block', + blocks: [ 'core/text-block' ], + transform: noop, + isMultiBlock: false, + }, + ], }, save: noop, category: 'common', @@ -534,12 +588,14 @@ describe( 'block factory', () => { }, }, transforms: { - from: [ { - type: 'block', - blocks: [ 'core/text-block' ], - transform: noop, - isMultiBlock: true, - } ], + from: [ + { + type: 'block', + blocks: [ 'core/text-block' ], + transform: noop, + isMultiBlock: true, + }, + ], }, save: noop, category: 'common', @@ -551,11 +607,17 @@ describe( 'block factory', () => { value: 'chicken', } ); - const availableBlocks = getPossibleBlockTransformations( [ block ] ); + const availableBlocks = getPossibleBlockTransformations( [ + block, + ] ); expect( availableBlocks ).toHaveLength( 2 ); - expect( availableBlocks[ 0 ].name ).toBe( 'core/updated-text-block' ); - expect( availableBlocks[ 1 ].name ).toBe( 'core/another-text-block' ); + expect( availableBlocks[ 0 ].name ).toBe( + 'core/updated-text-block' + ); + expect( availableBlocks[ 1 ].name ).toBe( + 'core/another-text-block' + ); } ); it( 'should show multiple possible transformations for a single `to` transform object with multiple block names', () => { @@ -566,28 +628,40 @@ describe( 'block factory', () => { }, }, transforms: { - to: [ { - type: 'block', - blocks: [ 'core/text-block', 'core/another-text-block' ], - transform: noop, - } ], + to: [ + { + type: 'block', + blocks: [ + 'core/text-block', + 'core/another-text-block', + ], + transform: noop, + }, + ], }, save: noop, category: 'common', title: 'updated text block', } ); registerBlockType( 'core/text-block', defaultBlockSettings ); - registerBlockType( 'core/another-text-block', defaultBlockSettings ); + registerBlockType( + 'core/another-text-block', + defaultBlockSettings + ); const block = createBlock( 'core/updated-text-block', { value: 'chicken', } ); - const availableBlocks = getPossibleBlockTransformations( [ block ] ); + const availableBlocks = getPossibleBlockTransformations( [ + block, + ] ); expect( availableBlocks ).toHaveLength( 2 ); expect( availableBlocks[ 0 ].name ).toBe( 'core/text-block' ); - expect( availableBlocks[ 1 ].name ).toBe( 'core/another-text-block' ); + expect( availableBlocks[ 1 ].name ).toBe( + 'core/another-text-block' + ); } ); it( 'returns a single transformation for a "from" transform that has a `isMatch` function returning `true`', () => { @@ -598,12 +672,14 @@ describe( 'block factory', () => { }, }, transforms: { - from: [ { - type: 'block', - blocks: [ 'core/text-block' ], - transform: noop, - isMatch: () => true, - } ], + from: [ + { + type: 'block', + blocks: [ 'core/text-block' ], + transform: noop, + isMatch: () => true, + }, + ], }, save: noop, category: 'common', @@ -615,10 +691,14 @@ describe( 'block factory', () => { value: 'chicken', } ); - const availableBlocks = getPossibleBlockTransformations( [ block ] ); + const availableBlocks = getPossibleBlockTransformations( [ + block, + ] ); expect( availableBlocks ).toHaveLength( 1 ); - expect( availableBlocks[ 0 ].name ).toBe( 'core/updated-text-block' ); + expect( availableBlocks[ 0 ].name ).toBe( + 'core/updated-text-block' + ); } ); it( 'returns no transformations for a "from" transform with a `isMatch` function returning `false`', () => { @@ -629,12 +709,14 @@ describe( 'block factory', () => { }, }, transforms: { - from: [ { - type: 'block', - blocks: [ 'core/text-block' ], - transform: noop, - isMatch: () => false, - } ], + from: [ + { + type: 'block', + blocks: [ 'core/text-block' ], + transform: noop, + isMatch: () => false, + }, + ], }, save: noop, category: 'common', @@ -646,7 +728,9 @@ describe( 'block factory', () => { value: 'chicken', } ); - const availableBlocks = getPossibleBlockTransformations( [ block ] ); + const availableBlocks = getPossibleBlockTransformations( [ + block, + ] ); expect( availableBlocks ).toEqual( [] ); } ); @@ -659,12 +743,14 @@ describe( 'block factory', () => { }, }, transforms: { - to: [ { - type: 'block', - blocks: [ 'core/text-block' ], - transform: noop, - isMatch: () => true, - } ], + to: [ + { + type: 'block', + blocks: [ 'core/text-block' ], + transform: noop, + isMatch: () => true, + }, + ], }, save: noop, category: 'common', @@ -676,7 +762,9 @@ describe( 'block factory', () => { value: 'ribs', } ); - const availableBlocks = getPossibleBlockTransformations( [ block ] ); + const availableBlocks = getPossibleBlockTransformations( [ + block, + ] ); expect( availableBlocks ).toHaveLength( 1 ); expect( availableBlocks[ 0 ].name ).toBe( 'core/text-block' ); @@ -690,12 +778,14 @@ describe( 'block factory', () => { }, }, transforms: { - to: [ { - type: 'block', - blocks: [ 'core/text-block' ], - transform: noop, - isMatch: () => false, - } ], + to: [ + { + type: 'block', + blocks: [ 'core/text-block' ], + transform: noop, + isMatch: () => false, + }, + ], }, save: noop, category: 'common', @@ -707,7 +797,9 @@ describe( 'block factory', () => { value: 'ribs', } ); - const availableBlocks = getPossibleBlockTransformations( [ block ] ); + const availableBlocks = getPossibleBlockTransformations( [ + block, + ] ); expect( availableBlocks ).toEqual( [] ); } ); @@ -722,12 +814,14 @@ describe( 'block factory', () => { }, }, transforms: { - to: [ { - type: 'block', - blocks: [ 'core/text-block' ], - transform: noop, - isMatch, - } ], + to: [ + { + type: 'block', + blocks: [ 'core/text-block' ], + transform: noop, + isMatch, + }, + ], }, save: noop, category: 'common', @@ -754,13 +848,15 @@ describe( 'block factory', () => { }, }, transforms: { - to: [ { - type: 'block', - blocks: [ 'core/text-block' ], - transform: noop, - isMultiBlock: true, - isMatch, - } ], + to: [ + { + type: 'block', + blocks: [ 'core/text-block' ], + transform: noop, + isMultiBlock: true, + isMatch, + }, + ], }, save: noop, category: 'common', @@ -778,7 +874,10 @@ describe( 'block factory', () => { getPossibleBlockTransformations( [ meatBlock, cheeseBlock ] ); - expect( isMatch ).toHaveBeenCalledWith( [ { value: 'ribs' }, { value: 'halloumi' } ] ); + expect( isMatch ).toHaveBeenCalledWith( [ + { value: 'ribs' }, + { value: 'halloumi' }, + ] ); } ); describe( 'wildcard block transforms', () => { @@ -790,11 +889,13 @@ describe( 'block factory', () => { }, }, transforms: { - from: [ { - type: 'block', - blocks: [ '*' ], - transform: noop, - } ], + from: [ + { + type: 'block', + blocks: [ '*' ], + transform: noop, + }, + ], }, save: noop, category: 'common', @@ -812,7 +913,9 @@ describe( 'block factory', () => { } ); } ); - const availableBlocks = getPossibleBlockTransformations( textBlocks ); + const availableBlocks = getPossibleBlockTransformations( + textBlocks + ); expect( availableBlocks ).toHaveLength( 1 ); expect( availableBlocks[ 0 ].name ).toBe( 'core/group' ); @@ -834,7 +937,10 @@ describe( 'block factory', () => { } ); } ); - const availableBlocks = getPossibleBlockTransformations( [ ...textBlocks, ...imageBlocks ] ); + const availableBlocks = getPossibleBlockTransformations( [ + ...textBlocks, + ...imageBlocks, + ] ); expect( availableBlocks ).toHaveLength( 1 ); expect( availableBlocks[ 0 ].name ).toBe( 'core/group' ); @@ -849,7 +955,9 @@ describe( 'block factory', () => { } ); } ); - const availableBlocks = getPossibleBlockTransformations( blocks ); + const availableBlocks = getPossibleBlockTransformations( + blocks + ); expect( availableBlocks ).toHaveLength( 1 ); expect( availableBlocks[ 0 ].name ).toBe( 'core/group' ); @@ -866,15 +974,17 @@ describe( 'block factory', () => { }, }, transforms: { - from: [ { - type: 'block', - blocks: [ 'core/text-block' ], - transform: ( { value } ) => { - return createBlock( 'core/updated-text-block', { - value: 'chicken ' + value, - } ); + from: [ + { + type: 'block', + blocks: [ 'core/text-block' ], + transform: ( { value } ) => { + return createBlock( 'core/updated-text-block', { + value: 'chicken ' + value, + } ); + }, }, - } ], + ], }, save: noop, category: 'common', @@ -886,11 +996,16 @@ describe( 'block factory', () => { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( + block, + 'core/updated-text-block' + ); expect( transformedBlocks ).toHaveLength( 1 ); expect( transformedBlocks[ 0 ] ).toHaveProperty( 'clientId' ); - expect( transformedBlocks[ 0 ].name ).toBe( 'core/updated-text-block' ); + expect( transformedBlocks[ 0 ].name ).toBe( + 'core/updated-text-block' + ); expect( transformedBlocks[ 0 ].isValid ).toBe( true ); expect( transformedBlocks[ 0 ].attributes ).toEqual( { value: 'chicken ribs', @@ -898,7 +1013,10 @@ describe( 'block factory', () => { } ); it( 'should switch the blockType of a block using the "transform to"', () => { - registerBlockType( 'core/updated-text-block', defaultBlockSettings ); + registerBlockType( + 'core/updated-text-block', + defaultBlockSettings + ); registerBlockType( 'core/text-block', { attributes: { value: { @@ -906,15 +1024,17 @@ describe( 'block factory', () => { }, }, transforms: { - to: [ { - type: 'block', - blocks: [ 'core/updated-text-block' ], - transform: ( { value } ) => { - return createBlock( 'core/updated-text-block', { - value: 'chicken ' + value, - } ); + to: [ + { + type: 'block', + blocks: [ 'core/updated-text-block' ], + transform: ( { value } ) => { + return createBlock( 'core/updated-text-block', { + value: 'chicken ' + value, + } ); + }, }, - } ], + ], }, save: noop, category: 'common', @@ -925,11 +1045,16 @@ describe( 'block factory', () => { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( + block, + 'core/updated-text-block' + ); expect( transformedBlocks ).toHaveLength( 1 ); expect( transformedBlocks[ 0 ] ).toHaveProperty( 'clientId' ); - expect( transformedBlocks[ 0 ].name ).toBe( 'core/updated-text-block' ); + expect( transformedBlocks[ 0 ].name ).toBe( + 'core/updated-text-block' + ); expect( transformedBlocks[ 0 ].isValid ).toBe( true ); expect( transformedBlocks[ 0 ].attributes ).toEqual( { value: 'chicken ribs', @@ -937,14 +1062,20 @@ describe( 'block factory', () => { } ); it( 'should return null if no transformation is found', () => { - registerBlockType( 'core/updated-text-block', defaultBlockSettings ); + registerBlockType( + 'core/updated-text-block', + defaultBlockSettings + ); registerBlockType( 'core/text-block', defaultBlockSettings ); const block = createBlock( 'core/text-block', { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( + block, + 'core/updated-text-block' + ); expect( transformedBlocks ).toBeNull(); } ); @@ -957,10 +1088,12 @@ describe( 'block factory', () => { }, }, transforms: { - from: [ { - blocks: [ 'core/text-block' ], - transform: () => null, - } ], + from: [ + { + blocks: [ 'core/text-block' ], + transform: () => null, + }, + ], }, save: noop, category: 'common', @@ -972,7 +1105,10 @@ describe( 'block factory', () => { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( + block, + 'core/updated-text-block' + ); expect( transformedBlocks ).toBeNull(); } ); @@ -985,10 +1121,12 @@ describe( 'block factory', () => { }, }, transforms: { - from: [ { - blocks: [ 'core/text-block' ], - transform: () => [], - } ], + from: [ + { + blocks: [ 'core/text-block' ], + transform: () => [], + }, + ], }, save: noop, category: 'common', @@ -1000,7 +1138,10 @@ describe( 'block factory', () => { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( + block, + 'core/updated-text-block' + ); expect( transformedBlocks ).toBeNull(); } ); @@ -1013,16 +1154,18 @@ describe( 'block factory', () => { }, }, transforms: { - from: [ { - blocks: [ 'core/text-block' ], - transform: ( { value } ) => { - return { - attributes: { - value: 'chicken ' + value, - }, - }; + from: [ + { + blocks: [ 'core/text-block' ], + transform: ( { value } ) => { + return { + attributes: { + value: 'chicken ' + value, + }, + }; + }, }, - } ], + ], }, save: noop, category: 'common', @@ -1034,7 +1177,10 @@ describe( 'block factory', () => { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( + block, + 'core/updated-text-block' + ); expect( transformedBlocks ).toBeNull(); } ); @@ -1047,21 +1193,23 @@ describe( 'block factory', () => { }, }, transforms: { - from: [ { - blocks: [ 'core/text-block' ], - transform: ( { value } ) => { - return [ - createBlock( 'core/updated-text-block', { - value: 'chicken ' + value, - } ), - { - attributes: { - value: 'smoked ' + value, + from: [ + { + blocks: [ 'core/text-block' ], + transform: ( { value } ) => { + return [ + createBlock( 'core/updated-text-block', { + value: 'chicken ' + value, + } ), + { + attributes: { + value: 'smoked ' + value, + }, }, - }, - ]; + ]; + }, }, - } ], + ], }, save: noop, category: 'common', @@ -1073,13 +1221,19 @@ describe( 'block factory', () => { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( + block, + 'core/updated-text-block' + ); expect( transformedBlocks ).toBeNull(); } ); it( 'should reject single transformations with unexpected block types', () => { - registerBlockType( 'core/updated-text-block', defaultBlockSettings ); + registerBlockType( + 'core/updated-text-block', + defaultBlockSettings + ); registerBlockType( 'core/text-block', { attributes: { value: { @@ -1087,14 +1241,16 @@ describe( 'block factory', () => { }, }, transforms: { - to: [ { - blocks: [ 'core/updated-text-block' ], - transform: ( { value } ) => { - return createBlock( 'core/text-block', { - value: 'chicken ' + value, - } ); + to: [ + { + blocks: [ 'core/updated-text-block' ], + transform: ( { value } ) => { + return createBlock( 'core/text-block', { + value: 'chicken ' + value, + } ); + }, }, - } ], + ], }, save: noop, category: 'common', @@ -1105,13 +1261,19 @@ describe( 'block factory', () => { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( + block, + 'core/updated-text-block' + ); expect( transformedBlocks ).toBeNull(); } ); it( 'should reject array transformations with unexpected block types', () => { - registerBlockType( 'core/updated-text-block', defaultBlockSettings ); + registerBlockType( + 'core/updated-text-block', + defaultBlockSettings + ); registerBlockType( 'core/text-block', { attributes: { value: { @@ -1119,19 +1281,21 @@ describe( 'block factory', () => { }, }, transforms: { - to: [ { - blocks: [ 'core/updated-text-block' ], - transform: ( { value } ) => { - return [ - createBlock( 'core/text-block', { - value: 'chicken ' + value, - } ), - createBlock( 'core/text-block', { - value: 'smoked ' + value, - } ), - ]; + to: [ + { + blocks: [ 'core/updated-text-block' ], + transform: ( { value } ) => { + return [ + createBlock( 'core/text-block', { + value: 'chicken ' + value, + } ), + createBlock( 'core/text-block', { + value: 'smoked ' + value, + } ), + ]; + }, }, - } ], + ], }, save: noop, category: 'common', @@ -1142,13 +1306,19 @@ describe( 'block factory', () => { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( + block, + 'core/updated-text-block' + ); expect( transformedBlocks ).toEqual( null ); } ); it( 'should accept valid array transformations', () => { - registerBlockType( 'core/updated-text-block', defaultBlockSettings ); + registerBlockType( + 'core/updated-text-block', + defaultBlockSettings + ); registerBlockType( 'core/text-block', { attributes: { value: { @@ -1156,20 +1326,22 @@ describe( 'block factory', () => { }, }, transforms: { - to: [ { - type: 'block', - blocks: [ 'core/updated-text-block' ], - transform: ( { value } ) => { - return [ - createBlock( 'core/text-block', { - value: 'chicken ' + value, - } ), - createBlock( 'core/updated-text-block', { - value: 'smoked ' + value, - } ), - ]; + to: [ + { + type: 'block', + blocks: [ 'core/updated-text-block' ], + transform: ( { value } ) => { + return [ + createBlock( 'core/text-block', { + value: 'chicken ' + value, + } ), + createBlock( 'core/updated-text-block', { + value: 'smoked ' + value, + } ), + ]; + }, }, - } ], + ], }, save: noop, category: 'common', @@ -1180,14 +1352,19 @@ describe( 'block factory', () => { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( + block, + 'core/updated-text-block' + ); // Make sure the block client IDs are set as expected: the first // transformed block whose type matches the "destination" type gets // to keep the existing block's client ID. expect( transformedBlocks ).toHaveLength( 2 ); expect( transformedBlocks[ 0 ] ).toHaveProperty( 'clientId' ); - expect( transformedBlocks[ 0 ].clientId ).not.toBe( block.clientId ); + expect( transformedBlocks[ 0 ].clientId ).not.toBe( + block.clientId + ); expect( transformedBlocks[ 0 ].name ).toBe( 'core/text-block' ); expect( transformedBlocks[ 0 ].isValid ).toBe( true ); expect( transformedBlocks[ 0 ].attributes ).toEqual( { @@ -1195,7 +1372,9 @@ describe( 'block factory', () => { } ); expect( transformedBlocks[ 1 ].clientId ).toBe( block.clientId ); expect( transformedBlocks[ 1 ] ).toHaveProperty( 'clientId' ); - expect( transformedBlocks[ 1 ].name ).toBe( 'core/updated-text-block' ); + expect( transformedBlocks[ 1 ].name ).toBe( + 'core/updated-text-block' + ); expect( transformedBlocks[ 1 ].isValid ).toBe( true ); expect( transformedBlocks[ 1 ].attributes ).toEqual( { value: 'smoked ribs', @@ -1210,21 +1389,23 @@ describe( 'block factory', () => { }, }, transforms: { - from: [ { - type: 'block', - blocks: [ 'core/columns-block' ], - transform( attributes, innerBlocks ) { - return createBlock( - 'core/updated-columns-block', - attributes, - innerBlocks.map( ( innerBlock ) => { - return cloneBlock( innerBlock, { - value: 'after', - } ); - } ), - ); + from: [ + { + type: 'block', + blocks: [ 'core/columns-block' ], + transform( attributes, innerBlocks ) { + return createBlock( + 'core/updated-columns-block', + attributes, + innerBlocks.map( ( innerBlock ) => { + return cloneBlock( innerBlock, { + value: 'after', + } ); + } ) + ); + }, }, - } ], + ], }, save: noop, category: 'common', @@ -1233,17 +1414,20 @@ describe( 'block factory', () => { registerBlockType( 'core/columns-block', defaultBlockSettings ); registerBlockType( 'core/column-block', defaultBlockSettings ); - const block = createBlock( - 'core/columns-block', - {}, - [ createBlock( 'core/column-block', { value: 'before' } ) ] - ); + const block = createBlock( 'core/columns-block', {}, [ + createBlock( 'core/column-block', { value: 'before' } ), + ] ); - const transformedBlocks = switchToBlockType( block, 'core/updated-columns-block' ); + const transformedBlocks = switchToBlockType( + block, + 'core/updated-columns-block' + ); expect( transformedBlocks ).toHaveLength( 1 ); expect( transformedBlocks[ 0 ].innerBlocks ).toHaveLength( 1 ); - expect( transformedBlocks[ 0 ].innerBlocks[ 0 ].attributes.value ).toBe( 'after' ); + expect( + transformedBlocks[ 0 ].innerBlocks[ 0 ].attributes.value + ).toBe( 'after' ); } ); it( 'should pass through inner blocks to transform (multi)', () => { @@ -1254,24 +1438,33 @@ describe( 'block factory', () => { }, }, transforms: { - from: [ { - type: 'block', - blocks: [ 'core/columns-block' ], - isMultiBlock: true, - transform( blocksAttributes, blocksInnerBlocks ) { - return blocksAttributes.map( ( attributes, i ) => { - return createBlock( - 'core/updated-columns-block', - attributes, - blocksInnerBlocks[ i ].map( ( innerBlock ) => { - return cloneBlock( innerBlock, { - value: 'after' + i, - } ); - } ), + from: [ + { + type: 'block', + blocks: [ 'core/columns-block' ], + isMultiBlock: true, + transform( blocksAttributes, blocksInnerBlocks ) { + return blocksAttributes.map( + ( attributes, i ) => { + return createBlock( + 'core/updated-columns-block', + attributes, + blocksInnerBlocks[ i ].map( + ( innerBlock ) => { + return cloneBlock( + innerBlock, + { + value: 'after' + i, + } + ); + } + ) + ); + } ); - } ); + }, }, - } ], + ], }, save: noop, category: 'common', @@ -1281,25 +1474,28 @@ describe( 'block factory', () => { registerBlockType( 'core/column-block', defaultBlockSettings ); const blocks = [ - createBlock( - 'core/columns-block', - {}, - [ createBlock( 'core/column-block', { value: 'before' } ) ] - ), - createBlock( - 'core/columns-block', - {}, - [ createBlock( 'core/column-block', { value: 'before' } ) ] - ), + createBlock( 'core/columns-block', {}, [ + createBlock( 'core/column-block', { value: 'before' } ), + ] ), + createBlock( 'core/columns-block', {}, [ + createBlock( 'core/column-block', { value: 'before' } ), + ] ), ]; - const transformedBlocks = switchToBlockType( blocks, 'core/updated-columns-block' ); + const transformedBlocks = switchToBlockType( + blocks, + 'core/updated-columns-block' + ); expect( transformedBlocks ).toHaveLength( 2 ); expect( transformedBlocks[ 0 ].innerBlocks ).toHaveLength( 1 ); - expect( transformedBlocks[ 0 ].innerBlocks[ 0 ].attributes.value ).toBe( 'after0' ); + expect( + transformedBlocks[ 0 ].innerBlocks[ 0 ].attributes.value + ).toBe( 'after0' ); expect( transformedBlocks[ 1 ].innerBlocks ).toHaveLength( 1 ); - expect( transformedBlocks[ 1 ].innerBlocks[ 0 ].attributes.value ).toBe( 'after1' ); + expect( + transformedBlocks[ 1 ].innerBlocks[ 0 ].attributes.value + ).toBe( 'after1' ); } ); it( 'should pass entire block object(s) to the "__experimentalConvert" method if defined', () => { @@ -1310,18 +1506,30 @@ describe( 'block factory', () => { }, }, transforms: { - from: [ { - type: 'block', - blocks: [ '*' ], - isMultiBlock: true, - __experimentalConvert( blocks ) { - const groupInnerBlocks = blocks.map( ( { name, attributes, innerBlocks } ) => { - return createBlock( name, attributes, innerBlocks ); - } ); - - return createBlock( 'core/test-group-block', {}, groupInnerBlocks ); + from: [ + { + type: 'block', + blocks: [ '*' ], + isMultiBlock: true, + __experimentalConvert( blocks ) { + const groupInnerBlocks = blocks.map( + ( { name, attributes, innerBlocks } ) => { + return createBlock( + name, + attributes, + innerBlocks + ); + } + ); + + return createBlock( + 'core/test-group-block', + {}, + groupInnerBlocks + ); + }, }, - } ], + ], }, save: noop, category: 'common', @@ -1337,20 +1545,33 @@ describe( 'block factory', () => { } ); } ); - const transformedBlocks = switchToBlockType( blocks, 'core/test-group-block' ); + const transformedBlocks = switchToBlockType( + blocks, + 'core/test-group-block' + ); expect( transformedBlocks ).toHaveLength( 1 ); - expect( transformedBlocks[ 0 ].name ).toBe( 'core/test-group-block' ); - expect( transformedBlocks[ 0 ].innerBlocks ).toHaveLength( numOfBlocksToGroup ); + expect( transformedBlocks[ 0 ].name ).toBe( + 'core/test-group-block' + ); + expect( transformedBlocks[ 0 ].innerBlocks ).toHaveLength( + numOfBlocksToGroup + ); } ); it( 'should prefer "__experimentalConvert" method over "transform" method when running a transformation', () => { const convertSpy = jest.fn( ( blocks ) => { - const groupInnerBlocks = blocks.map( ( { name, attributes, innerBlocks } ) => { - return createBlock( name, attributes, innerBlocks ); - } ); - - return createBlock( 'core/test-group-block', {}, groupInnerBlocks ); + const groupInnerBlocks = blocks.map( + ( { name, attributes, innerBlocks } ) => { + return createBlock( name, attributes, innerBlocks ); + } + ); + + return createBlock( + 'core/test-group-block', + {}, + groupInnerBlocks + ); } ); const transformSpy = jest.fn(); @@ -1361,13 +1582,15 @@ describe( 'block factory', () => { }, }, transforms: { - from: [ { - type: 'block', - blocks: [ '*' ], - isMultiBlock: true, - __experimentalConvert: convertSpy, - transform: transformSpy, - } ], + from: [ + { + type: 'block', + blocks: [ '*' ], + isMultiBlock: true, + __experimentalConvert: convertSpy, + transform: transformSpy, + }, + ], }, save: noop, category: 'common', @@ -1383,7 +1606,10 @@ describe( 'block factory', () => { } ); } ); - const transformedBlocks = switchToBlockType( blocks, 'core/test-group-block' ); + const transformedBlocks = switchToBlockType( + blocks, + 'core/test-group-block' + ); expect( transformedBlocks ).toHaveLength( 1 ); expect( convertSpy.mock.calls ).toHaveLength( 1 ); @@ -1396,9 +1622,11 @@ describe( 'block factory', () => { registerBlockType( 'core/text-block', defaultBlockSettings ); registerBlockType( 'core/transform-from-text-block-1', { transforms: { - from: [ { - blocks: [ 'core/text-block' ], - } ], + from: [ + { + blocks: [ 'core/text-block' ], + }, + ], }, save: noop, category: 'common', @@ -1406,9 +1634,11 @@ describe( 'block factory', () => { } ); registerBlockType( 'core/transform-from-text-block-2', { transforms: { - from: [ { - blocks: [ 'core/text-block' ], - } ], + from: [ + { + blocks: [ 'core/text-block' ], + }, + ], }, save: noop, category: 'common', @@ -1438,13 +1668,19 @@ describe( 'block factory', () => { } ); it( 'should return empty array if no defined transforms', () => { - const transforms = getBlockTransforms( 'to', 'core/transform-from-text-block-1' ); + const transforms = getBlockTransforms( + 'to', + 'core/transform-from-text-block-1' + ); expect( transforms ).toEqual( [] ); } ); it( 'should return single block type transforms of direction', () => { - const transforms = getBlockTransforms( 'from', 'core/transform-from-text-block-1' ); + const transforms = getBlockTransforms( + 'from', + 'core/transform-from-text-block-1' + ); expect( transforms ).toEqual( [ { @@ -1515,32 +1751,33 @@ describe( 'block factory', () => { blockName: 'core/test-block', }; - expect( isWildcardBlockTransform( validWildcardBlockTransform ) ).toBe( true ); + expect( + isWildcardBlockTransform( validWildcardBlockTransform ) + ).toBe( true ); } ); it( 'should return false for transforms with a type which is not "block"', () => { const invalidWildcardBlockTransform = { type: 'file', - blocks: [ - '*', - ], + blocks: [ '*' ], blockName: 'core/test-block', }; - expect( isWildcardBlockTransform( invalidWildcardBlockTransform ) ).toBe( false ); + expect( + isWildcardBlockTransform( invalidWildcardBlockTransform ) + ).toBe( false ); } ); it( 'should return false for transforms which do not include "*" alias in "block" array', () => { const invalidWildcardBlockTransform = { type: 'block', - blocks: [ - 'core/some-block', - 'core/another-block', - ], + blocks: [ 'core/some-block', 'core/another-block' ], blockName: 'core/test-block', }; - expect( isWildcardBlockTransform( invalidWildcardBlockTransform ) ).toBe( false ); + expect( + isWildcardBlockTransform( invalidWildcardBlockTransform ) + ).toBe( false ); } ); it( 'should return false for transforms which do not provide an array as the "blocks" option', () => { @@ -1550,7 +1787,9 @@ describe( 'block factory', () => { blockName: 'core/test-block', }; - expect( isWildcardBlockTransform( invalidWildcardBlockTransform ) ).toBe( false ); + expect( + isWildcardBlockTransform( invalidWildcardBlockTransform ) + ).toBe( false ); } ); } ); @@ -1563,11 +1802,13 @@ describe( 'block factory', () => { }, }, transforms: { - from: [ { - type: 'block', - blocks: [ '*' ], - transform: noop, - } ], + from: [ + { + type: 'block', + blocks: [ '*' ], + transform: noop, + }, + ], }, save: noop, category: 'common', @@ -1577,7 +1818,9 @@ describe( 'block factory', () => { it( 'should return true when passed block name that matches the registered "Grouping" Block', () => { setGroupingBlockName( 'registered-grouping-block' ); - expect( isContainerGroupBlock( 'registered-grouping-block' ) ).toBe( true ); + expect( isContainerGroupBlock( 'registered-grouping-block' ) ).toBe( + true + ); } ); it( 'should return false when passed block name does not match the registered "Grouping" Block', () => { diff --git a/packages/blocks/src/api/test/matchers.js b/packages/blocks/src/api/test/matchers.js index 32d1246a2e9d6e..1e24069acaddd9 100644 --- a/packages/blocks/src/api/test/matchers.js +++ b/packages/blocks/src/api/test/matchers.js @@ -24,7 +24,8 @@ describe( 'matchers', () => { it( 'should return HTML equivalent WPElement of matched element', () => { // Assumption here is that we can cleanly convert back and forth // between a string and WPElement representation - const html = '<blockquote><p>A delicious sundae dessert</p></blockquote>'; + const html = + '<blockquote><p>A delicious sundae dessert</p></blockquote>'; const match = parse( html, sources.children() ); expect( renderToString( match ) ).toBe( html ); @@ -41,7 +42,8 @@ describe( 'matchers', () => { it( 'should return HTML equivalent WPElement of matched element', () => { // Assumption here is that we can cleanly convert back and forth // between a string and WPElement representation - const html = '<blockquote><p>A delicious sundae dessert</p></blockquote>'; + const html = + '<blockquote><p>A delicious sundae dessert</p></blockquote>'; const match = parse( html, sources.node() ); expect( renderToString( match ) ).toBe( `<body>${ html }</body>` ); diff --git a/packages/blocks/src/api/test/node.js b/packages/blocks/src/api/test/node.js index 447395a24ed125..8c627f95b3896d 100644 --- a/packages/blocks/src/api/test/node.js +++ b/packages/blocks/src/api/test/node.js @@ -1,16 +1,15 @@ /** * Internal dependencies */ -import { - getNamedNodeMapAsObject, - toHTML, - fromDOM, -} from '../node'; +import { getNamedNodeMapAsObject, toHTML, fromDOM } from '../node'; describe( 'getNamedNodeMapAsObject', () => { it( 'should return an object of node attributes', () => { const node = document.createElement( 'img' ); - node.setAttribute( 'src', 'https://s.w.org/style/images/wporg-logo.svg' ); + node.setAttribute( + 'src', + 'https://s.w.org/style/images/wporg-logo.svg' + ); const object = getNamedNodeMapAsObject( node.attributes ); expect( object ).toEqual( { @@ -31,7 +30,9 @@ describe( 'toHTML', () => { const html = toHTML( blockNode ); - expect( html ).toBe( '<strong class="is-extra-strong">This is a test</strong>' ); + expect( html ).toBe( + '<strong class="is-extra-strong">This is a test</strong>' + ); } ); } ); diff --git a/packages/blocks/src/api/test/parser.js b/packages/blocks/src/api/test/parser.js index 026e71ca2cc5d5..be377dd956da0a 100644 --- a/packages/blocks/src/api/test/parser.js +++ b/packages/blocks/src/api/test/parser.js @@ -182,40 +182,31 @@ describe( 'block parser', () => { describe( 'parseWithAttributeSchema', () => { it( 'should return the matcher’s attribute value', () => { - const value = parseWithAttributeSchema( - '<div>chicken</div>', - { - type: 'string', - source: 'text', - selector: 'div', - }, - ); + const value = parseWithAttributeSchema( '<div>chicken</div>', { + type: 'string', + source: 'text', + selector: 'div', + } ); expect( value ).toBe( 'chicken' ); } ); it( 'should return the matcher’s string attribute value', () => { - const value = parseWithAttributeSchema( - '<audio src="#" loop>', - { - type: 'string', - source: 'attribute', - selector: 'audio', - attribute: 'src', - }, - ); + const value = parseWithAttributeSchema( '<audio src="#" loop>', { + type: 'string', + source: 'attribute', + selector: 'audio', + attribute: 'src', + } ); expect( value ).toBe( '#' ); } ); it( 'should return the matcher’s true boolean attribute value', () => { - const value = parseWithAttributeSchema( - '<audio src="#" loop>', - { - type: 'boolean', - source: 'attribute', - selector: 'audio', - attribute: 'loop', - }, - ); + const value = parseWithAttributeSchema( '<audio src="#" loop>', { + type: 'boolean', + source: 'attribute', + selector: 'audio', + attribute: 'loop', + } ); expect( value ).toBe( true ); } ); @@ -227,7 +218,7 @@ describe( 'block parser', () => { source: 'attribute', selector: 'audio', attribute: 'loop', - }, + } ); expect( value ).toBe( true ); } ); @@ -240,32 +231,26 @@ describe( 'block parser', () => { source: 'attribute', selector: 'audio', attribute: 'loop', - }, + } ); expect( value ).toBe( false ); } ); describe( 'source: tag', () => { it( 'returns tag name of matching selector', () => { - const value = parseWithAttributeSchema( - '<div></div>', - { - source: 'tag', - selector: ':nth-child(1)', - } - ); + const value = parseWithAttributeSchema( '<div></div>', { + source: 'tag', + selector: ':nth-child(1)', + } ); expect( value ).toBe( 'div' ); } ); it( 'returns undefined when no element matches selector', () => { - const value = parseWithAttributeSchema( - '<div></div>', - { - source: 'tag', - selector: ':nth-child(2)', - } - ); + const value = parseWithAttributeSchema( '<div></div>', { + source: 'tag', + selector: ':nth-child(2)', + } ); expect( value ).toBe( undefined ); } ); @@ -418,12 +403,14 @@ describe( 'block parser', () => { const innerHTML = '<div data-number="10">Ribs</div>'; const attrs = { align: null, invalid: true }; - expect( getBlockAttributes( blockType, innerHTML, attrs ) ).toEqual( { - content: 'Ribs', - align: null, - topic: 'none', - undefAmbiguousStringWithDefault: 'ok', - } ); + expect( getBlockAttributes( blockType, innerHTML, attrs ) ).toEqual( + { + content: 'Ribs', + align: null, + topic: 'none', + undefAmbiguousStringWithDefault: 'ok', + } + ); } ); it( 'should work when block type is passed as string', () => { @@ -453,7 +440,8 @@ describe( 'block parser', () => { const block = deepFreeze( { name: 'core/test-block', attributes: parsedAttributes, - originalContent: '<span class="wp-block-test-block">Bananas</span>', + originalContent: + '<span class="wp-block-test-block">Bananas</span>', isValid: false, } ); registerBlockType( 'core/test-block', defaultBlockSettings ); @@ -468,7 +456,8 @@ describe( 'block parser', () => { const block = deepFreeze( { name: 'core/test-block', attributes: parsedAttributes, - originalContent: '<span class="wp-block-test-block">Bananas</span>', + originalContent: + '<span class="wp-block-test-block">Bananas</span>', isValid: false, } ); registerBlockType( 'core/test-block', { @@ -508,7 +497,9 @@ describe( 'block parser', () => { selector: 'span', }, }, - save: ( props ) => <span>{ props.attributes.fruit }</span>, + save: ( props ) => ( + <span>{ props.attributes.fruit }</span> + ), }, ], } ); @@ -538,14 +529,18 @@ describe( 'block parser', () => { selector: 'span', }, }, - save: ( props ) => <span>{ props.attributes.fruit }</span>, + save: ( props ) => ( + <span>{ props.attributes.fruit }</span> + ), migrate: ( attributes ) => { return [ { newFruit: attributes.fruit }, - [ { - name: 'core/test-block', - attributes: { aaa: 'bbb' }, - } ], + [ + { + name: 'core/test-block', + attributes: { aaa: 'bbb' }, + }, + ], ]; }, }, @@ -554,10 +549,16 @@ describe( 'block parser', () => { const migratedBlock = getMigratedBlock( block, parsedAttributes ); - expect( migratedBlock.attributes ).toEqual( { newFruit: 'Bananas' } ); + expect( migratedBlock.attributes ).toEqual( { + newFruit: 'Bananas', + } ); expect( migratedBlock.innerBlocks ).toHaveLength( 1 ); - expect( migratedBlock.innerBlocks[ 0 ].name ).toEqual( 'core/test-block' ); - expect( migratedBlock.innerBlocks[ 0 ].attributes ).toEqual( { aaa: 'bbb' } ); + expect( migratedBlock.innerBlocks[ 0 ].name ).toEqual( + 'core/test-block' + ); + expect( migratedBlock.innerBlocks[ 0 ].attributes ).toEqual( { + aaa: 'bbb', + } ); } ); it( 'should ignore valid uneligible blocks', () => { @@ -686,7 +687,10 @@ describe( 'block parser', () => { } ); it( 'should fall back to the unregistered type handler for unregistered blocks if present', () => { - registerBlockType( 'core/unregistered-block', unknownBlockSettings ); + registerBlockType( + 'core/unregistered-block', + unknownBlockSettings + ); setUnregisteredTypeHandlerName( 'core/unregistered-block' ); const block = createBlockWithFallback( { @@ -737,8 +741,12 @@ describe( 'block parser', () => { selector: 'span', }, }, - save: ( { attributes } ) => <span>{ attributes.fruit }</span>, - migrate: ( attributes ) => ( { fruit: 'Big ' + attributes.fruit } ), + save: ( { attributes } ) => ( + <span>{ attributes.fruit }</span> + ), + migrate: ( attributes ) => ( { + fruit: 'Big ' + attributes.fruit, + } ), }, ], } ); @@ -817,8 +825,11 @@ describe( 'block parser', () => { blockName: 'core/list', attrs: {}, innerBlocks: [], - innerHTML: '<ul><li>B</li><li>C</li></ul>', - innerContent: [ '<ul><li>B</li><li>C</li></ul>' ], + innerHTML: + '<ul><li>B</li><li>C</li></ul>', + innerContent: [ + '<ul><li>B</li><li>C</li></ul>', + ], }, { blockName: 'core/paragraph', @@ -828,13 +839,15 @@ describe( 'block parser', () => { innerContent: [ '<p>D</p>' ], }, ], - innerHTML: '<div class="wp-block-group"><div class="wp-block-group__inner-container"></div></div>', + innerHTML: + '<div class="wp-block-group"><div class="wp-block-group__inner-container"></div></div>', innerContent: [ '<div class="wp-block-group"><div class="wp-block-group__inner-container">', null, '', null, - '</div></div>' ], + '</div></div>', + ], }, ], innerHTML: '<div class="wp-block-column"></div>', @@ -885,8 +898,8 @@ describe( 'block parser', () => { const parsed = parse( `<!-- wp:core/test-block {"smoked":"yes","url":"http://google.com","chicken":"ribs & 'wings'"} -->` + - 'Brisket' + - '<!-- /wp:core/test-block -->' + 'Brisket' + + '<!-- /wp:core/test-block -->' ); expect( parsed ).toHaveLength( 1 ); @@ -921,8 +934,8 @@ describe( 'block parser', () => { const parsed = parse( '<!-- wp:core/test-block -->\nRibs\n<!-- /wp:core/test-block -->' + - '<p>Broccoli</p>' + - '<!-- wp:core/unknown-block -->Ribs<!-- /wp:core/unknown-block -->' + '<p>Broccoli</p>' + + '<!-- wp:core/unknown-block -->Ribs<!-- /wp:core/unknown-block -->' ); expect( parsed ).toHaveLength( 1 ); @@ -952,8 +965,8 @@ describe( 'block parser', () => { const parsed = parse( '<!-- wp:test-block {"fruit":"Bananas"} -->\nBananas\n<!-- /wp:test-block -->' + - '<p>Broccoli</p>' + - '<!-- wp:core/unknown/block -->Ribs<!-- /wp:core/unknown/block -->' + '<p>Broccoli</p>' + + '<!-- wp:core/unknown/block -->Ribs<!-- /wp:core/unknown/block -->' ); expect( parsed ).toHaveLength( 1 ); expect( parsed[ 0 ].name ).toBe( 'core/test-block' ); @@ -967,8 +980,8 @@ describe( 'block parser', () => { const parsed = parse( '<!-- wp:test-block {"fruit":"Bananas"} -->\nBananas\n<!-- /wp:test-block -->' + - '<p>Broccoli</p>' + - '<!-- wp:core/unknown-block -->Ribs<!-- /wp:core/unknown-block -->' + '<p>Broccoli</p>' + + '<!-- wp:core/unknown-block -->Ribs<!-- /wp:core/unknown-block -->' ); expect( parsed ).toHaveLength( 3 ); @@ -987,10 +1000,10 @@ describe( 'block parser', () => { const parsed = parse( '<p>Cauliflower</p>' + - '<!-- wp:test-block {"fruit":"Bananas"} -->\nBananas\n<!-- /wp:test-block -->' + - '\n<p>Broccoli</p>\n' + - '<!-- wp:test-block {"fruit":"Bananas"} -->\nBananas\n<!-- /wp:test-block -->' + - '<p>Romanesco</p>' + '<!-- wp:test-block {"fruit":"Bananas"} -->\nBananas\n<!-- /wp:test-block -->' + + '\n<p>Broccoli</p>\n' + + '<!-- wp:test-block {"fruit":"Bananas"} -->\nBananas\n<!-- /wp:test-block -->' + + '<p>Romanesco</p>' ); expect( parsed ).toHaveLength( 5 ); @@ -1001,9 +1014,15 @@ describe( 'block parser', () => { 'core/test-block', 'core/unknown-block', ] ); - expect( parsed[ 0 ].attributes.content ).toEqual( '<p>Cauliflower</p>' ); - expect( parsed[ 2 ].attributes.content ).toEqual( '<p>Broccoli</p>' ); - expect( parsed[ 4 ].attributes.content ).toEqual( '<p>Romanesco</p>' ); + expect( parsed[ 0 ].attributes.content ).toEqual( + '<p>Cauliflower</p>' + ); + expect( parsed[ 2 ].attributes.content ).toEqual( + '<p>Broccoli</p>' + ); + expect( parsed[ 4 ].attributes.content ).toEqual( + '<p>Romanesco</p>' + ); } ); it( 'should parse blocks with empty content', () => { @@ -1023,12 +1042,13 @@ describe( 'block parser', () => { registerBlockType( 'core/void-block', defaultBlockSettings ); const parsed = parse( '<!-- wp:core/test-block --><!-- /wp:core/test-block -->' + - '<!-- wp:core/void-block /-->' + '<!-- wp:core/void-block /-->' ); expect( parsed ).toHaveLength( 2 ); expect( parsed.map( ( { name } ) => name ) ).toEqual( [ - 'core/test-block', 'core/void-block', + 'core/test-block', + 'core/void-block', ] ); } ); @@ -1044,7 +1064,7 @@ describe( 'block parser', () => { save: ( { attributes } ) => attributes.content, } ); - const content = '$foo = "My \"escaped\" text.";'; + const content = '$foo = "My "escaped" text.";'; const block = createBlock( 'core/code', { content } ); const serialized = serialize( block ); const parsed = parse( serialized ); diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index 92aeab35ea6493..45419fb9b36d4a 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -39,7 +39,11 @@ import { import { DEPRECATED_ENTRY_KEYS } from '../constants'; describe( 'blocks', () => { - const defaultBlockSettings = { save: noop, category: 'common', title: 'block title' }; + const defaultBlockSettings = { + save: noop, + category: 'common', + title: 'block title', + }; beforeAll( () => { // Initialize the block store. @@ -59,42 +63,60 @@ describe( 'blocks', () => { describe( 'registerBlockType()', () => { it( 'should reject numbers', () => { const block = registerBlockType( 999 ); - expect( console ).toHaveErroredWith( 'Block names must be strings.' ); + expect( console ).toHaveErroredWith( + 'Block names must be strings.' + ); expect( block ).toBeUndefined(); } ); it( 'should reject blocks without a namespace', () => { const block = registerBlockType( 'doing-it-wrong' ); - expect( console ).toHaveErroredWith( 'Block names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-block' ); + expect( console ).toHaveErroredWith( + 'Block names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-block' + ); expect( block ).toBeUndefined(); } ); it( 'should reject blocks with too many namespaces', () => { const block = registerBlockType( 'doing/it/wrong' ); - expect( console ).toHaveErroredWith( 'Block names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-block' ); + expect( console ).toHaveErroredWith( + 'Block names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-block' + ); expect( block ).toBeUndefined(); } ); it( 'should reject blocks with invalid characters', () => { const block = registerBlockType( 'still/_doing_it_wrong' ); - expect( console ).toHaveErroredWith( 'Block names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-block' ); + expect( console ).toHaveErroredWith( + 'Block names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-block' + ); expect( block ).toBeUndefined(); } ); it( 'should reject blocks with uppercase characters', () => { const block = registerBlockType( 'Core/Paragraph' ); - expect( console ).toHaveErroredWith( 'Block names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-block' ); + expect( console ).toHaveErroredWith( + 'Block names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-block' + ); expect( block ).toBeUndefined(); } ); it( 'should reject blocks not starting with a letter', () => { - const block = registerBlockType( 'my-plugin/4-fancy-block', defaultBlockSettings ); - expect( console ).toHaveErroredWith( 'Block names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-block' ); + const block = registerBlockType( + 'my-plugin/4-fancy-block', + defaultBlockSettings + ); + expect( console ).toHaveErroredWith( + 'Block names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-block' + ); expect( block ).toBeUndefined(); } ); it( 'should accept valid block names', () => { - const block = registerBlockType( 'my-plugin/fancy-block-4', defaultBlockSettings ); + const block = registerBlockType( + 'my-plugin/fancy-block-4', + defaultBlockSettings + ); expect( console ).not.toHaveErrored(); expect( block ).toEqual( { name: 'my-plugin/fancy-block-4', @@ -111,8 +133,13 @@ describe( 'blocks', () => { it( 'should prohibit registering the same block twice', () => { registerBlockType( 'core/test-block', defaultBlockSettings ); - const block = registerBlockType( 'core/test-block', defaultBlockSettings ); - expect( console ).toHaveErroredWith( 'Block "core/test-block" is already registered.' ); + const block = registerBlockType( + 'core/test-block', + defaultBlockSettings + ); + expect( console ).toHaveErroredWith( + 'Block "core/test-block" is already registered.' + ); expect( block ).toBeUndefined(); } ); @@ -123,49 +150,108 @@ describe( 'blocks', () => { keywords: [], save: 'invalid', } ); - expect( console ).toHaveErroredWith( 'The "save" property must be a valid function.' ); + expect( console ).toHaveErroredWith( + 'The "save" property must be a valid function.' + ); expect( block ).toBeUndefined(); } ); it( 'should reject blocks with an invalid edit function', () => { - const blockType = { save: noop, edit: 'not-a-function', category: 'common', title: 'block title' }, - block = registerBlockType( 'my-plugin/fancy-block-6', blockType ); - expect( console ).toHaveErroredWith( 'The "edit" property must be a valid function.' ); + const blockType = { + save: noop, + edit: 'not-a-function', + category: 'common', + title: 'block title', + }, + block = registerBlockType( + 'my-plugin/fancy-block-6', + blockType + ); + expect( console ).toHaveErroredWith( + 'The "edit" property must be a valid function.' + ); expect( block ).toBeUndefined(); } ); it( 'should reject blocks without category', () => { - const blockType = { settingName: 'settingValue', save: noop, title: 'block title' }, - block = registerBlockType( 'my-plugin/fancy-block-8', blockType ); - expect( console ).toHaveErroredWith( 'The block "my-plugin/fancy-block-8" must have a category.' ); + const blockType = { + settingName: 'settingValue', + save: noop, + title: 'block title', + }, + block = registerBlockType( + 'my-plugin/fancy-block-8', + blockType + ); + expect( console ).toHaveErroredWith( + 'The block "my-plugin/fancy-block-8" must have a category.' + ); expect( block ).toBeUndefined(); } ); it( 'should reject blocks with non registered category.', () => { - const blockType = { save: noop, category: 'custom-category-slug', title: 'block title' }, - block = registerBlockType( 'my-plugin/fancy-block-9', blockType ); - expect( console ).toHaveErroredWith( 'The block "my-plugin/fancy-block-9" must have a registered category.' ); + const blockType = { + save: noop, + category: 'custom-category-slug', + title: 'block title', + }, + block = registerBlockType( + 'my-plugin/fancy-block-9', + blockType + ); + expect( console ).toHaveErroredWith( + 'The block "my-plugin/fancy-block-9" must have a registered category.' + ); expect( block ).toBeUndefined(); } ); it( 'should reject blocks without title', () => { - const blockType = { settingName: 'settingValue', save: noop, category: 'common' }, - block = registerBlockType( 'my-plugin/fancy-block-9', blockType ); - expect( console ).toHaveErroredWith( 'The block "my-plugin/fancy-block-9" must have a title.' ); + const blockType = { + settingName: 'settingValue', + save: noop, + category: 'common', + }, + block = registerBlockType( + 'my-plugin/fancy-block-9', + blockType + ); + expect( console ).toHaveErroredWith( + 'The block "my-plugin/fancy-block-9" must have a title.' + ); expect( block ).toBeUndefined(); } ); it( 'should reject blocks with empty titles', () => { - const blockType = { settingName: 'settingValue', save: noop, category: 'common', title: '' }, - block = registerBlockType( 'my-plugin/fancy-block-10', blockType ); - expect( console ).toHaveErroredWith( 'The block "my-plugin/fancy-block-10" must have a title.' ); + const blockType = { + settingName: 'settingValue', + save: noop, + category: 'common', + title: '', + }, + block = registerBlockType( + 'my-plugin/fancy-block-10', + blockType + ); + expect( console ).toHaveErroredWith( + 'The block "my-plugin/fancy-block-10" must have a title.' + ); expect( block ).toBeUndefined(); } ); it( 'should reject titles which are not strings', () => { - const blockType = { settingName: 'settingValue', save: noop, category: 'common', title: 12345 }, - block = registerBlockType( 'my-plugin/fancy-block-11', blockType ); - expect( console ).toHaveErroredWith( 'Block titles must be strings.' ); + const blockType = { + settingName: 'settingValue', + save: noop, + category: 'common', + title: 12345, + }, + block = registerBlockType( + 'my-plugin/fancy-block-11', + blockType + ); + expect( console ).toHaveErroredWith( + 'Block titles must be strings.' + ); expect( block ).toBeUndefined(); } ); @@ -194,24 +280,31 @@ describe( 'blocks', () => { 'core/test-block-with-attributes': { attributes }, } ); - const blockType = { settingName: 'settingValue', save: noop, category: 'common', title: 'block title' }; - registerBlockType( 'core/test-block-with-attributes', blockType ); - expect( getBlockType( 'core/test-block-with-attributes' ) ).toEqual( { - name: 'core/test-block-with-attributes', + const blockType = { settingName: 'settingValue', save: noop, category: 'common', title: 'block title', - icon: { - src: 'block-default', - }, - attributes: { - ok: { - type: 'boolean', + }; + registerBlockType( 'core/test-block-with-attributes', blockType ); + expect( getBlockType( 'core/test-block-with-attributes' ) ).toEqual( + { + name: 'core/test-block-with-attributes', + settingName: 'settingValue', + save: noop, + category: 'common', + title: 'block title', + icon: { + src: 'block-default', }, - }, - keywords: [], - } ); + attributes: { + ok: { + type: 'boolean', + }, + }, + keywords: [], + } + ); } ); it( 'should validate the icon', () => { @@ -221,7 +314,10 @@ describe( 'blocks', () => { title: 'block title', icon: { chicken: 'ribs' }, }; - const block = registerBlockType( 'core/test-block-icon-normalize-element', blockType ); + const block = registerBlockType( + 'core/test-block-icon-normalize-element', + blockType + ); expect( console ).toHaveErrored(); expect( block ).toBeUndefined(); } ); @@ -231,22 +327,43 @@ describe( 'blocks', () => { save: noop, category: 'common', title: 'block title', - icon: ( <svg width="20" height="20" viewBox="0 0 20 20"> - <circle cx="10" cy="10" r="10" - fill="red" stroke="blue" strokeWidth="10" /> - </svg> ), + icon: ( + <svg width="20" height="20" viewBox="0 0 20 20"> + <circle + cx="10" + cy="10" + r="10" + fill="red" + stroke="blue" + strokeWidth="10" + /> + </svg> + ), }; - registerBlockType( 'core/test-block-icon-normalize-element', blockType ); - expect( getBlockType( 'core/test-block-icon-normalize-element' ) ).toEqual( { + registerBlockType( + 'core/test-block-icon-normalize-element', + blockType + ); + expect( + getBlockType( 'core/test-block-icon-normalize-element' ) + ).toEqual( { name: 'core/test-block-icon-normalize-element', save: noop, category: 'common', title: 'block title', icon: { - src: ( <svg width="20" height="20" viewBox="0 0 20 20"> - <circle cx="10" cy="10" r="10" - fill="red" stroke="blue" strokeWidth="10" /> - </svg> ), + src: ( + <svg width="20" height="20" viewBox="0 0 20 20"> + <circle + cx="10" + cy="10" + r="10" + fill="red" + stroke="blue" + strokeWidth="10" + /> + </svg> + ), }, attributes: {}, keywords: [], @@ -260,8 +377,13 @@ describe( 'blocks', () => { title: 'block title', icon: 'foo', }; - registerBlockType( 'core/test-block-icon-normalize-string', blockType ); - expect( getBlockType( 'core/test-block-icon-normalize-string' ) ).toEqual( { + registerBlockType( + 'core/test-block-icon-normalize-string', + blockType + ); + expect( + getBlockType( 'core/test-block-icon-normalize-string' ) + ).toEqual( { name: 'core/test-block-icon-normalize-string', save: noop, category: 'common', @@ -276,10 +398,18 @@ describe( 'blocks', () => { it( 'should normalize the icon containing a function', () => { const MyTestIcon = () => { - return <svg width="20" height="20" viewBox="0 0 20 20"> - <circle cx="10" cy="10" r="10" - fill="red" stroke="blue" strokeWidth="10" /> - </svg>; + return ( + <svg width="20" height="20" viewBox="0 0 20 20"> + <circle + cx="10" + cy="10" + r="10" + fill="red" + stroke="blue" + strokeWidth="10" + /> + </svg> + ); }; const blockType = { save: noop, @@ -287,8 +417,13 @@ describe( 'blocks', () => { title: 'block title', icon: MyTestIcon, }; - registerBlockType( 'core/test-block-icon-normalize-function', blockType ); - expect( getBlockType( 'core/test-block-icon-normalize-function' ) ).toEqual( { + registerBlockType( + 'core/test-block-icon-normalize-function', + blockType + ); + expect( + getBlockType( 'core/test-block-icon-normalize-function' ) + ).toEqual( { name: 'core/test-block-icon-normalize-function', save: noop, category: 'common', @@ -308,14 +443,27 @@ describe( 'blocks', () => { title: 'block title', icon: { background: '#f00', - src: ( <svg width="20" height="20" viewBox="0 0 20 20"> - <circle cx="10" cy="10" r="10" - fill="red" stroke="blue" strokeWidth="10" /> - </svg> ), + src: ( + <svg width="20" height="20" viewBox="0 0 20 20"> + <circle + cx="10" + cy="10" + r="10" + fill="red" + stroke="blue" + strokeWidth="10" + /> + </svg> + ), }, }; - registerBlockType( 'core/test-block-icon-normalize-background', blockType ); - expect( getBlockType( 'core/test-block-icon-normalize-background' ) ).toEqual( { + registerBlockType( + 'core/test-block-icon-normalize-background', + blockType + ); + expect( + getBlockType( 'core/test-block-icon-normalize-background' ) + ).toEqual( { name: 'core/test-block-icon-normalize-background', save: noop, category: 'common', @@ -324,10 +472,18 @@ describe( 'blocks', () => { background: '#f00', foreground: '#191e23', shadowColor: 'rgba(255, 0, 0, 0.3)', - src: ( <svg width="20" height="20" viewBox="0 0 20 20"> - <circle cx="10" cy="10" r="10" - fill="red" stroke="blue" strokeWidth="10" /> - </svg> ), + src: ( + <svg width="20" height="20" viewBox="0 0 20 20"> + <circle + cx="10" + cy="10" + r="10" + fill="red" + stroke="blue" + strokeWidth="10" + /> + </svg> + ), }, attributes: {}, keywords: [], @@ -335,7 +491,12 @@ describe( 'blocks', () => { } ); it( 'should store a copy of block type', () => { - const blockType = { settingName: 'settingValue', save: noop, category: 'common', title: 'block title' }; + const blockType = { + settingName: 'settingValue', + save: noop, + category: 'common', + title: 'block title', + }; registerBlockType( 'core/test-block-with-settings', blockType ); blockType.mutated = true; expect( getBlockType( 'core/test-block-with-settings' ) ).toEqual( { @@ -358,23 +519,41 @@ describe( 'blocks', () => { } ); it( 'should reject valid blocks when they become invalid after executing filter', () => { - addFilter( 'blocks.registerBlockType', 'core/blocks/without-title', ( settings ) => { - return { - ...settings, - title: '', - }; - } ); - const block = registerBlockType( 'my-plugin/fancy-block-12', defaultBlockSettings ); - expect( console ).toHaveErroredWith( 'The block "my-plugin/fancy-block-12" must have a title.' ); + addFilter( + 'blocks.registerBlockType', + 'core/blocks/without-title', + ( settings ) => { + return { + ...settings, + title: '', + }; + } + ); + const block = registerBlockType( + 'my-plugin/fancy-block-12', + defaultBlockSettings + ); + expect( console ).toHaveErroredWith( + 'The block "my-plugin/fancy-block-12" must have a title.' + ); expect( block ).toBeUndefined(); } ); it( 'should reject blocks which become invalid after executing filter which does not return a plain object', () => { - addFilter( 'blocks.registerBlockType', 'core/blocks/without-save', ( settings ) => { - return [ settings ]; - } ); - const block = registerBlockType( 'my-plugin/fancy-block-13', defaultBlockSettings ); - expect( console ).toHaveErroredWith( 'Block settings must be a valid object.' ); + addFilter( + 'blocks.registerBlockType', + 'core/blocks/without-save', + ( settings ) => { + return [ settings ]; + } + ); + const block = registerBlockType( + 'my-plugin/fancy-block-13', + defaultBlockSettings + ); + expect( console ).toHaveErroredWith( + 'Block settings must be a valid object.' + ); expect( block ).toBeUndefined(); } ); @@ -397,43 +576,59 @@ describe( 'blocks', () => { }; let i = 0; - addFilter( 'blocks.registerBlockType', 'core/blocks/without-title', ( settings ) => { - // Verify that for deprecations, the filter is called with a merge of pre-filter - // settings with deprecation keys omitted and the deprecation entry. - if ( i > 0 ) { - expect( settings ).toEqual( { - ...omit( - { - name, - ...DEFAULT_BLOCK_TYPE_SETTINGS, - ...get( serverSideBlockDefinitions, name ), - ...blockSettingsWithDeprecations, + addFilter( + 'blocks.registerBlockType', + 'core/blocks/without-title', + ( settings ) => { + // Verify that for deprecations, the filter is called with a merge of pre-filter + // settings with deprecation keys omitted and the deprecation entry. + if ( i > 0 ) { + expect( settings ).toEqual( { + ...omit( + { + name, + ...DEFAULT_BLOCK_TYPE_SETTINGS, + ...get( + serverSideBlockDefinitions, + name + ), + ...blockSettingsWithDeprecations, + }, + DEPRECATED_ENTRY_KEYS + ), + ...blockSettingsWithDeprecations.deprecated[ + i - 1 + ], + } ); + } + i++; + + return { + ...settings, + attributes: { + ...settings.attributes, + id: { + type: 'string', }, - DEPRECATED_ENTRY_KEYS - ), - ...blockSettingsWithDeprecations.deprecated[ i - 1 ], - } ); - } - i++; - - return { - ...settings, - attributes: { - ...settings.attributes, - id: { - type: 'string', }, - }, - }; - } ); + }; + } + ); - const block = registerBlockType( name, blockSettingsWithDeprecations ); + const block = registerBlockType( + name, + blockSettingsWithDeprecations + ); expect( block.attributes.id ).toEqual( { type: 'string' } ); block.deprecated.forEach( ( deprecation ) => { - expect( deprecation.attributes.id ).toEqual( { type: 'string' } ); + expect( deprecation.attributes.id ).toEqual( { + type: 'string', + } ); // Verify that the deprecation's keys are a subset of deprecation keys. - expect( deprecation ).toEqual( pick( deprecation, DEPRECATED_ENTRY_KEYS ) ); + expect( deprecation ).toEqual( + pick( deprecation, DEPRECATED_ENTRY_KEYS ) + ); } ); } ); } ); @@ -443,7 +638,9 @@ describe( 'blocks', () => { it( 'creates a new block collection', () => { registerBlockCollection( 'core', { title: 'Core' } ); - expect( select( 'core/blocks' ).getCollections() ).toEqual( { core: { title: 'Core', icon: undefined } } ); + expect( select( 'core/blocks' ).getCollections() ).toEqual( { + core: { title: 'Core', icon: undefined }, + } ); } ); } ); @@ -453,14 +650,18 @@ describe( 'blocks', () => { registerBlockCollection( 'core2', { title: 'Core2' } ); unregisterBlockCollection( 'core' ); - expect( select( 'core/blocks' ).getCollections() ).toEqual( { core2: { title: 'Core2', icon: undefined } } ); + expect( select( 'core/blocks' ).getCollections() ).toEqual( { + core2: { title: 'Core2', icon: undefined }, + } ); } ); } ); describe( 'unregisterBlockType()', () => { it( 'should fail if a block is not registered', () => { const oldBlock = unregisterBlockType( 'core/test-block' ); - expect( console ).toHaveErroredWith( 'Block "core/test-block" is not registered.' ); + expect( console ).toHaveErroredWith( + 'Block "core/test-block" is not registered.' + ); expect( oldBlock ).toBeUndefined(); } ); @@ -477,7 +678,7 @@ describe( 'blocks', () => { }, attributes: {}, keywords: [], - patterns: [], + variations: [], }, ] ); const oldBlock = unregisterBlockType( 'core/test-block' ); @@ -515,7 +716,9 @@ describe( 'blocks', () => { it( 'assigns unknown type handler', () => { setUnregisteredTypeHandlerName( 'core/test-block' ); - expect( getUnregisteredTypeHandlerName() ).toBe( 'core/test-block' ); + expect( getUnregisteredTypeHandlerName() ).toBe( + 'core/test-block' + ); } ); } ); @@ -570,7 +773,12 @@ describe( 'blocks', () => { } ); it( 'should return all block type elements', () => { - const blockType = { settingName: 'settingValue', save: noop, category: 'common', title: 'block title' }; + const blockType = { + settingName: 'settingValue', + save: noop, + category: 'common', + title: 'block title', + }; registerBlockType( 'core/test-block-with-settings', blockType ); expect( getBlockType( 'core/test-block-with-settings' ) ).toEqual( { name: 'core/test-block-with-settings', @@ -594,7 +802,12 @@ describe( 'blocks', () => { it( 'should return all registered blocks', () => { registerBlockType( 'core/test-block', defaultBlockSettings ); - const blockType = { settingName: 'settingValue', save: noop, category: 'common', title: 'block title' }; + const blockType = { + settingName: 'settingValue', + save: noop, + category: 'common', + title: 'block title', + }; registerBlockType( 'core/test-block-with-settings', blockType ); expect( getBlockTypes() ).toEqual( [ { @@ -607,7 +820,7 @@ describe( 'blocks', () => { }, attributes: {}, keywords: [], - patterns: [], + variations: [], }, { name: 'core/test-block-with-settings', @@ -620,7 +833,7 @@ describe( 'blocks', () => { }, attributes: {}, keywords: [], - patterns: [], + variations: [], }, ] ); } ); @@ -635,7 +848,9 @@ describe( 'blocks', () => { }, } ); - expect( getBlockSupport( 'core/test-block', 'foo' ) ).toBe( undefined ); + expect( getBlockSupport( 'core/test-block', 'foo' ) ).toBe( + undefined + ); } ); it( 'should return block supports value', () => { @@ -657,7 +872,9 @@ describe( 'blocks', () => { }, } ); - expect( getBlockSupport( 'core/test-block', 'foo', true ) ).toBe( true ); + expect( getBlockSupport( 'core/test-block', 'foo', true ) ).toBe( + true + ); } ); } ); @@ -687,7 +904,9 @@ describe( 'blocks', () => { }, } ); - expect( hasBlockSupport( 'core/test-block', 'foo', true ) ).toBe( true ); + expect( hasBlockSupport( 'core/test-block', 'foo', true ) ).toBe( + true + ); } ); it( 'should return true if block type supports', () => { diff --git a/packages/blocks/src/api/test/serializer.js b/packages/blocks/src/api/test/serializer.js index 28281b42ea8c6d..6156a8e06d2c15 100644 --- a/packages/blocks/src/api/test/serializer.js +++ b/packages/blocks/src/api/test/serializer.js @@ -39,7 +39,8 @@ describe( 'block serializer', () => { describe( 'getSaveContent()', () => { describe( 'function save', () => { - const fruitBlockSave = ( { attributes } ) => createElement( 'div', null, attributes.fruit ); + const fruitBlockSave = ( { attributes } ) => + createElement( 'div', null, attributes.fruit ); it( 'should return element as string if save returns element', () => { const saved = getSaveContent( @@ -60,10 +61,9 @@ describe( 'block serializer', () => { save: fruitBlockSave, } ); - const saved = getSaveContent( - 'core/fruit', - { fruit: 'Bananas' } - ); + const saved = getSaveContent( 'core/fruit', { + fruit: 'Bananas', + } ); expect( saved ).toBe( '<div>Bananas</div>' ); } ); @@ -75,7 +75,11 @@ describe( 'block serializer', () => { { save: class extends Component { render() { - return createElement( 'div', null, this.props.attributes.fruit ); + return createElement( + 'div', + null, + this.props.attributes.fruit + ); } }, name: 'core/fruit', @@ -148,23 +152,33 @@ describe( 'block serializer', () => { describe( 'serializeAttributes()', () => { it( 'should not break HTML comments', () => { - expect( serializeAttributes( { a: '-- and --' } ) ).toBe( '{"a":"\\u002d\\u002d and \\u002d\\u002d"}' ); + expect( serializeAttributes( { a: '-- and --' } ) ).toBe( + '{"a":"\\u002d\\u002d and \\u002d\\u002d"}' + ); } ); it( 'should not break standard-non-compliant tools for "<"', () => { - expect( serializeAttributes( { a: '< and <' } ) ).toBe( '{"a":"\\u003c and \\u003c"}' ); + expect( serializeAttributes( { a: '< and <' } ) ).toBe( + '{"a":"\\u003c and \\u003c"}' + ); } ); it( 'should not break standard-non-compliant tools for ">"', () => { - expect( serializeAttributes( { a: '> and >' } ) ).toBe( '{"a":"\\u003e and \\u003e"}' ); + expect( serializeAttributes( { a: '> and >' } ) ).toBe( + '{"a":"\\u003e and \\u003e"}' + ); } ); it( 'should not break standard-non-compliant tools for "&"', () => { - expect( serializeAttributes( { a: '& and &' } ) ).toBe( '{"a":"\\u0026 and \\u0026"}' ); + expect( serializeAttributes( { a: '& and &' } ) ).toBe( + '{"a":"\\u0026 and \\u0026"}' + ); } ); it( 'should replace quotation marks', () => { - expect( serializeAttributes( { a: '" and "' } ) ).toBe( '{"a":"\\u0022 and \\u0022"}' ); + expect( serializeAttributes( { a: '" and "' } ) ).toBe( + '{"a":"\\u0022 and \\u0022"}' + ); } ); } ); @@ -186,7 +200,9 @@ describe( 'block serializer', () => { '' ); - expect( content ).toBe( '<!-- wp:my-wonderful-namespace/test-block /-->' ); + expect( content ).toBe( + '<!-- wp:my-wonderful-namespace/test-block /-->' + ); } ); it( 'should generate empty attributes non-void', () => { @@ -196,7 +212,9 @@ describe( 'block serializer', () => { 'Delicious' ); - expect( content ).toBe( '<!-- wp:test-block -->\nDelicious\n<!-- /wp:test-block -->' ); + expect( content ).toBe( + '<!-- wp:test-block -->\nDelicious\n<!-- /wp:test-block -->' + ); } ); it( 'should generate non-empty attributes void', () => { @@ -237,7 +255,9 @@ describe( 'block serializer', () => { save: ( { attributes } ) => attributes.fruit, } ); setFreeformContentHandlerName( 'core/freeform-block' ); - const block = createBlock( 'core/freeform-block', { fruit: 'Bananas' } ); + const block = createBlock( 'core/freeform-block', { + fruit: 'Bananas', + } ); const content = serializeBlock( block ); @@ -255,14 +275,16 @@ describe( 'block serializer', () => { save: ( { attributes } ) => attributes.fruit, } ); setFreeformContentHandlerName( 'core/freeform-block' ); - const block = createBlock( 'core/freeform-block', { fruit: 'Bananas' } ); + const block = createBlock( 'core/freeform-block', { + fruit: 'Bananas', + } ); const content = serializeBlock( block, { isInnerBlocks: true } ); expect( content ).toBe( - '<!-- wp:freeform-block {\"fruit\":\"Bananas\"} -->\n' + - 'Bananas\n' + - '<!-- /wp:freeform-block -->' + '<!-- wp:freeform-block {"fruit":"Bananas"} -->\n' + + 'Bananas\n' + + '<!-- /wp:freeform-block -->' ); } ); it( 'serializes the unregistered fallback block without comment delimiters', () => { @@ -277,7 +299,9 @@ describe( 'block serializer', () => { save: ( { attributes } ) => attributes.fruit, } ); setUnregisteredTypeHandlerName( 'core/unregistered-block' ); - const block = createBlock( 'core/unregistered-block', { fruit: 'Bananas' } ); + const block = createBlock( 'core/unregistered-block', { + fruit: 'Bananas', + } ); const content = serializeBlock( block ); @@ -309,11 +333,7 @@ describe( 'block serializer', () => { throw new Error(); } - return ( - <p> - { attributes.content } - </p> - ); + return <p>{ attributes.content }</p>; }, category: 'common', title: 'block title', @@ -326,7 +346,8 @@ describe( 'block serializer', () => { content: 'Ribs & Chicken', stuff: 'left & right -- but <not>', } ); - const expectedPostContent = '<!-- wp:test-block {"stuff":"left \\u0026 right \\u002d\\u002d but \\u003cnot\\u003e"} -->\n<p>Ribs &amp; Chicken</p>\n<!-- /wp:test-block -->'; + const expectedPostContent = + '<!-- wp:test-block {"stuff":"left \\u0026 right \\u002d\\u002d but \\u003cnot\\u003e"} -->\n<p>Ribs &amp; Chicken</p>\n<!-- /wp:test-block -->'; expect( serialize( [ block ] ) ).toEqual( expectedPostContent ); expect( serialize( block ) ).toEqual( expectedPostContent ); @@ -375,7 +396,7 @@ describe( 'block serializer', () => { title: 'block title', }; registerBlockType( 'core/chicken', blockType ); - const block = { + const block = { name: 'core/chicken', attributes: { content: 'chicken', diff --git a/packages/blocks/src/api/test/templates.js b/packages/blocks/src/api/test/templates.js index 12730f75197eb9..80350ebcb968d5 100644 --- a/packages/blocks/src/api/test/templates.js +++ b/packages/blocks/src/api/test/templates.js @@ -7,8 +7,15 @@ import { noop } from 'lodash'; * Internal dependencies */ import { createBlock } from '../factory'; -import { getBlockTypes, unregisterBlockType, registerBlockType } from '../registration'; -import { doBlocksMatchTemplate, synchronizeBlocksWithTemplate } from '../templates'; +import { + getBlockTypes, + unregisterBlockType, + registerBlockType, +} from '../registration'; +import { + doBlocksMatchTemplate, + synchronizeBlocksWithTemplate, +} from '../templates'; describe( 'templates', () => { beforeAll( () => { @@ -60,46 +67,47 @@ describe( 'templates', () => { it( 'return true if the template matches the blocks with nested blocks', () => { const template = [ [ 'core/test-block' ], - [ 'core/test-block-2', {}, [ - [ 'core/test-block' ], - ] ], + [ 'core/test-block-2', {}, [ [ 'core/test-block' ] ] ], [ 'core/test-block-2' ], ]; const blockList = [ createBlock( 'core/test-block' ), - createBlock( 'core/test-block-2', {}, [ createBlock( 'core/test-block' ) ] ), + createBlock( 'core/test-block-2', {}, [ + createBlock( 'core/test-block' ), + ] ), createBlock( 'core/test-block-2' ), ]; expect( doBlocksMatchTemplate( blockList, template ) ).toBe( true ); } ); it( "return false if the template length doesn't match the blocks length", () => { - const template = [ - [ 'core/test-block' ], - [ 'core/test-block-2' ], - ]; + const template = [ [ 'core/test-block' ], [ 'core/test-block-2' ] ]; const blockList = [ createBlock( 'core/test-block' ), createBlock( 'core/test-block-2' ), createBlock( 'core/test-block-2' ), ]; - expect( doBlocksMatchTemplate( blockList, template ) ).toBe( false ); + expect( doBlocksMatchTemplate( blockList, template ) ).toBe( + false + ); } ); it( "return false if the nested template doesn't match the blocks", () => { const template = [ [ 'core/test-block' ], - [ 'core/test-block-2', {}, [ - [ 'core/test-block' ], - ] ], + [ 'core/test-block-2', {}, [ [ 'core/test-block' ] ] ], [ 'core/test-block-2' ], ]; const blockList = [ createBlock( 'core/test-block' ), - createBlock( 'core/test-block-2', {}, [ createBlock( 'core/test-block-2' ) ] ), + createBlock( 'core/test-block-2', {}, [ + createBlock( 'core/test-block-2' ), + ] ), createBlock( 'core/test-block-2' ), ]; - expect( doBlocksMatchTemplate( blockList, template ) ).toBe( false ); + expect( doBlocksMatchTemplate( blockList, template ) ).toBe( + false + ); } ); } ); @@ -111,7 +119,9 @@ describe( 'templates', () => { [ 'core/test-block-2' ], ]; const blockList = []; - expect( synchronizeBlocksWithTemplate( blockList, template ) ).toMatchObject( [ + expect( + synchronizeBlocksWithTemplate( blockList, template ) + ).toMatchObject( [ { name: 'core/test-block' }, { name: 'core/test-block-2' }, { name: 'core/test-block-2' }, @@ -120,15 +130,16 @@ describe( 'templates', () => { it( 'should create nested blocks', () => { const template = [ - [ 'core/test-block', {}, [ - [ 'core/test-block-2' ], - ] ], + [ 'core/test-block', {}, [ [ 'core/test-block-2' ] ] ], ]; const blockList = []; - expect( synchronizeBlocksWithTemplate( blockList, template ) ).toMatchObject( [ - { name: 'core/test-block', innerBlocks: [ - { name: 'core/test-block-2' }, - ] }, + expect( + synchronizeBlocksWithTemplate( blockList, template ) + ).toMatchObject( [ + { + name: 'core/test-block', + innerBlocks: [ { name: 'core/test-block-2' } ], + }, ] ); } ); @@ -142,7 +153,9 @@ describe( 'templates', () => { const block1 = createBlock( 'core/test-block' ); const block2 = createBlock( 'core/test-block-2' ); const blockList = [ block1, block2 ]; - expect( synchronizeBlocksWithTemplate( blockList, template ) ).toMatchObject( [ + expect( + synchronizeBlocksWithTemplate( blockList, template ) + ).toMatchObject( [ block1, block2, { name: 'core/test-block-2' }, @@ -159,7 +172,9 @@ describe( 'templates', () => { const block1 = createBlock( 'core/test-block' ); const block2 = createBlock( 'core/test-block' ); const blockList = [ block1, block2 ]; - expect( synchronizeBlocksWithTemplate( blockList, template ) ).toMatchObject( [ + expect( + synchronizeBlocksWithTemplate( blockList, template ) + ).toMatchObject( [ block1, { name: 'core/test-block-2' }, { name: 'core/test-block-2' }, @@ -171,20 +186,20 @@ describe( 'templates', () => { const block1 = createBlock( 'core/test-block' ); const block2 = createBlock( 'core/test-block' ); const blockList = [ block1, block2 ]; - expect( synchronizeBlocksWithTemplate( blockList, template ) ).toBe( blockList ); + expect( synchronizeBlocksWithTemplate( blockList, template ) ).toBe( + blockList + ); } ); it( 'should remove blocks if extra blocks are found', () => { - const template = [ - [ 'core/test-block' ], - ]; + const template = [ [ 'core/test-block' ] ]; const block1 = createBlock( 'core/test-block' ); const block2 = createBlock( 'core/test-block' ); const blockList = [ block1, block2 ]; - expect( synchronizeBlocksWithTemplate( blockList, template ) ).toEqual( [ - block1, - ] ); + expect( + synchronizeBlocksWithTemplate( blockList, template ) + ).toEqual( [ block1 ] ); } ); } ); } ); diff --git a/packages/blocks/src/api/test/utils.js b/packages/blocks/src/api/test/utils.js index 53c01260f6f51e..c00f22cf28b6ea 100644 --- a/packages/blocks/src/api/test/utils.js +++ b/packages/blocks/src/api/test/utils.js @@ -7,7 +7,12 @@ import { noop } from 'lodash'; * Internal dependencies */ import { createBlock } from '../factory'; -import { getBlockTypes, unregisterBlockType, registerBlockType, setDefaultBlockName } from '../registration'; +import { + getBlockTypes, + unregisterBlockType, + registerBlockType, + setDefaultBlockName, +} from '../registration'; import { isUnmodifiedDefaultBlock, getAccessibleBlockLabel, @@ -96,7 +101,9 @@ describe( 'block helpers', () => { setDefaultBlockName( 'core/test-block1' ); isUnmodifiedDefaultBlock( createBlock( 'core/test-block1' ) ); setDefaultBlockName( 'core/test-block2' ); - expect( isUnmodifiedDefaultBlock( createBlock( 'core/test-block2' ) ) ).toBe( true ); + expect( + isUnmodifiedDefaultBlock( createBlock( 'core/test-block2' ) ) + ).toBe( true ); } ); } ); } ); @@ -117,15 +124,23 @@ describe( 'getBlockLabel', () => { } ); it( 'returns the block title with the label when the `getLabel` function returns a value', () => { - const blockType = { title: 'Recipe', __experimentalLabel: ( { heading } ) => heading }; + const blockType = { + title: 'Recipe', + __experimentalLabel: ( { heading } ) => heading, + }; const attributes = { heading: 'Cupcakes!' }; expect( getBlockLabel( blockType, attributes ) ).toBe( 'Cupcakes!' ); } ); it( 'removes any html elements from the output of the `getLabel` function', () => { - const blockType = { title: 'Recipe', __experimentalLabel: ( { heading } ) => heading }; - const attributes = { heading: '<b><span class="my-class">Cupcakes!</span></b>' }; + const blockType = { + title: 'Recipe', + __experimentalLabel: ( { heading } ) => heading, + }; + const attributes = { + heading: '<b><span class="my-class">Cupcakes!</span></b>', + }; expect( getBlockLabel( blockType, attributes ) ).toBe( 'Cupcakes!' ); } ); @@ -136,42 +151,64 @@ describe( 'getAccessibleBlockLabel', () => { const blockType = { title: 'Recipe' }; const attributes = {}; - expect( getAccessibleBlockLabel( blockType, attributes ) ).toBe( 'Recipe Block' ); + expect( getAccessibleBlockLabel( blockType, attributes ) ).toBe( + 'Recipe Block' + ); } ); it( 'returns only the block title when the block has a `getLabel` function, but it returns a falsey value', () => { const blockType = { title: 'Recipe', __experimentalLabel: () => '' }; const attributes = {}; - expect( getAccessibleBlockLabel( blockType, attributes ) ).toBe( 'Recipe Block' ); + expect( getAccessibleBlockLabel( blockType, attributes ) ).toBe( + 'Recipe Block' + ); } ); it( 'returns the block title with the label when the `getLabel` function returns a value', () => { - const blockType = { title: 'Recipe', __experimentalLabel: ( { heading } ) => heading }; + const blockType = { + title: 'Recipe', + __experimentalLabel: ( { heading } ) => heading, + }; const attributes = { heading: 'Cupcakes!' }; - expect( getAccessibleBlockLabel( blockType, attributes ) ).toBe( 'Recipe Block. Cupcakes!' ); + expect( getAccessibleBlockLabel( blockType, attributes ) ).toBe( + 'Recipe Block. Cupcakes!' + ); } ); it( 'removes any html elements from the output of the `getLabel` function', () => { - const blockType = { title: 'Recipe', __experimentalLabel: ( { heading } ) => heading }; - const attributes = { heading: '<b><span class="my-class">Cupcakes!</span></b>' }; - - expect( getAccessibleBlockLabel( blockType, attributes ) ).toBe( 'Recipe Block. Cupcakes!' ); + const blockType = { + title: 'Recipe', + __experimentalLabel: ( { heading } ) => heading, + }; + const attributes = { + heading: '<b><span class="my-class">Cupcakes!</span></b>', + }; + + expect( getAccessibleBlockLabel( blockType, attributes ) ).toBe( + 'Recipe Block. Cupcakes!' + ); } ); it( 'outputs the block title and label with a row number indicating the position of the block, when the optional third parameter is provided', () => { - const blockType = { title: 'Recipe', __experimentalLabel: ( { heading } ) => heading }; + const blockType = { + title: 'Recipe', + __experimentalLabel: ( { heading } ) => heading, + }; const attributes = { heading: 'Cupcakes!' }; - expect( getAccessibleBlockLabel( blockType, attributes, 3 ) ).toBe( 'Recipe Block. Row 3. Cupcakes!' ); + expect( getAccessibleBlockLabel( blockType, attributes, 3 ) ).toBe( + 'Recipe Block. Row 3. Cupcakes!' + ); } ); it( 'outputs just the block title and row number when there no label is available for the block', () => { const blockType = { title: 'Recipe' }; const attributes = {}; - expect( getAccessibleBlockLabel( blockType, attributes, 3 ) ).toBe( 'Recipe Block. Row 3' ); + expect( getAccessibleBlockLabel( blockType, attributes, 3 ) ).toBe( + 'Recipe Block. Row 3' + ); } ); } ); - diff --git a/packages/blocks/src/api/test/validation.js b/packages/blocks/src/api/test/validation.js index ed974072644146..d2dbd8915563e8 100644 --- a/packages/blocks/src/api/test/validation.js +++ b/packages/blocks/src/api/test/validation.js @@ -62,7 +62,9 @@ describe( 'validation', () => { } ); it( 'returns false for an invalid character reference', () => { - const result = isValidCharacterReference( ' Test</h2><h2>Test &amp' ); + const result = isValidCharacterReference( + ' Test</h2><h2>Test &amp' + ); expect( result ).toBe( false ); } ); @@ -70,7 +72,9 @@ describe( 'validation', () => { describe( 'DecodeEntityParser', () => { it( 'can be constructed', () => { - expect( new DecodeEntityParser() instanceof DecodeEntityParser ).toBe( true ); + expect( + new DecodeEntityParser() instanceof DecodeEntityParser + ).toBe( true ); } ); it( 'returns parse as decoded value', () => { @@ -140,7 +144,7 @@ describe( 'validation', () => { it( 'should return false if not equal with collapsed whitespace', () => { const isEqual = isEquivalentTextTokens( { chars: ' a \t b \n c' }, - { chars: 'a \n c \t b ' }, + { chars: 'a \n c \t b ' } ); expect( console ).toHaveWarned(); @@ -150,7 +154,7 @@ describe( 'validation', () => { it( 'should return true if equal with collapsed whitespace', () => { const isEqual = isEquivalentTextTokens( { chars: ' a \t b \n c' }, - { chars: 'a \n b \t c ' }, + { chars: 'a \n b \t c ' } ); expect( isEqual ).toBe( true ); @@ -163,7 +167,9 @@ describe( 'validation', () => { 'url( "https://wordpress.org/img.png" )' ); - expect( normalizedValue ).toBe( 'url(https://wordpress.org/img.png)' ); + expect( normalizedValue ).toBe( + 'url(https://wordpress.org/img.png)' + ); } ); } ); @@ -185,7 +191,7 @@ describe( 'validation', () => { it( 'ignores ordering', () => { const isEqual = isEqualAttributesOfName.class( 'a b c', - 'b a c', + 'b a c' ); expect( isEqual ).toBe( true ); @@ -194,7 +200,7 @@ describe( 'validation', () => { it( 'ignores whitespace', () => { const isEqual = isEqualAttributesOfName.class( 'a b c', - 'b a c', + 'b a c' ); expect( isEqual ).toBe( true ); @@ -203,7 +209,7 @@ describe( 'validation', () => { it( 'returns false if not equal', () => { const isEqual = isEqualAttributesOfName.class( 'a b c', - 'b a c d', + 'b a c d' ); expect( isEqual ).toBe( false ); @@ -232,10 +238,7 @@ describe( 'validation', () => { describe( 'boolean attributes', () => { it( 'returns true if both present', () => { - const isEqual = isEqualAttributesOfName.controls( - 'true', - '' - ); + const isEqual = isEqualAttributesOfName.controls( 'true', '' ); expect( isEqual ).toBe( true ); } ); @@ -245,12 +248,13 @@ describe( 'validation', () => { describe( 'isEqualTagAttributePairs()', () => { it( 'returns false if not equal pairs', () => { const isEqual = isEqualTagAttributePairs( - [ - [ 'class', 'b a c' ], - ], + [ [ 'class', 'b a c' ] ], [ [ 'class', 'c a b' ], - [ 'style', 'background-image: url( "https://wordpress.org/img.png" ); color: red;' ], + [ + 'style', + 'background-image: url( "https://wordpress.org/img.png" ); color: red;', + ], ] ); @@ -262,12 +266,18 @@ describe( 'validation', () => { const isEqual = isEqualTagAttributePairs( [ [ 'class', 'b a c' ], - [ 'style', 'color: red; background-image: url( "https://wordpress.org/img.png" );' ], + [ + 'style', + 'color: red; background-image: url( "https://wordpress.org/img.png" );', + ], [ 'controls', '' ], ], [ [ 'class', 'c a b' ], - [ 'style', 'background-image: url( "https://wordpress.org/img.png" ); color: red;' ], + [ + 'style', + 'background-image: url( "https://wordpress.org/img.png" ); color: red;', + ], [ 'controls', 'true' ], ] ); @@ -309,15 +319,16 @@ describe( 'validation', () => { const isEqual = isEqualTokensOfType.StartTag( { tagName: 'p', - attributes: [ - [ 'class', 'b a c' ], - ], + attributes: [ [ 'class', 'b a c' ] ], }, { tagName: 'p', attributes: [ [ 'class', 'c a b' ], - [ 'style', 'background-image: url( "https://wordpress.org/img.png" ); color: red;' ], + [ + 'style', + 'background-image: url( "https://wordpress.org/img.png" ); color: red;', + ], ], } ); @@ -332,14 +343,20 @@ describe( 'validation', () => { tagName: 'p', attributes: [ [ 'class', 'b a c' ], - [ 'style', 'color: red; background-image: url( "https://wordpress.org/img.png" );' ], + [ + 'style', + 'color: red; background-image: url( "https://wordpress.org/img.png" );', + ], ], }, { tagName: 'p', attributes: [ [ 'class', 'c a b' ], - [ 'style', 'background-image: url( "https://wordpress.org/img.png" ); color: red;' ], + [ + 'style', + 'background-image: url( "https://wordpress.org/img.png" ); color: red;', + ], ], } ); @@ -384,9 +401,7 @@ describe( 'validation', () => { } ); it( 'returns undefined if token options exhausted', () => { - const tokens = [ - { type: 'Chars', chars: ' \n\t' }, - ]; + const tokens = [ { type: 'Chars', chars: ' \n\t' } ]; const token = getNextNonWhitespaceToken( tokens ); @@ -399,7 +414,7 @@ describe( 'validation', () => { it( 'should return true if self-closed token is closed by an end token', () => { const isClosed = isClosedByToken( { type: 'StartTag', tagName: 'div', selfClosing: true }, - { type: 'EndTag', tagName: 'div' }, + { type: 'EndTag', tagName: 'div' } ); expect( isClosed ).toBe( true ); @@ -408,7 +423,7 @@ describe( 'validation', () => { it( 'should return false if open token is not closed by an end token', () => { const isClosed = isClosedByToken( { type: 'StartTag', tagName: 'div', selfClosing: false }, - { type: 'EndTag', tagName: 'div' }, + { type: 'EndTag', tagName: 'div' } ); expect( isClosed ).toBe( false ); @@ -417,7 +432,7 @@ describe( 'validation', () => { it( 'should return false if self-closed token has a different name to the end token', () => { const isClosed = isClosedByToken( { type: 'StartTag', tagName: 'div', selfClosing: true }, - { type: 'EndTag', tagName: 'span' }, + { type: 'EndTag', tagName: 'span' } ); expect( isClosed ).toBe( false ); @@ -426,7 +441,7 @@ describe( 'validation', () => { it( 'should return false if self-closed token is not closed by a start token', () => { const isClosed = isClosedByToken( { type: 'StartTag', tagName: 'div', selfClosing: true }, - { type: 'StartTag', tagName: 'div' }, + { type: 'StartTag', tagName: 'div' } ); expect( isClosed ).toBe( false ); @@ -435,7 +450,7 @@ describe( 'validation', () => { it( 'should return false if self-closed token is not closed by an undefined token', () => { const isClosed = isClosedByToken( { type: 'StartTag', tagName: 'div', selfClosing: true }, - undefined, + undefined ); expect( isClosed ).toBe( false ); @@ -544,10 +559,7 @@ describe( 'validation', () => { } ); it( 'should return false when difference of data- attribute', () => { - const isEquivalent = isEquivalentHTML( - '<div data-foo>', - '<div>' - ); + const isEquivalent = isEquivalentHTML( '<div data-foo>', '<div>' ); expect( console ).toHaveWarned(); expect( isEquivalent ).toBe( false ); @@ -582,10 +594,7 @@ describe( 'validation', () => { } ); it( 'should return true when comparing empty strings', () => { - const isEquivalent = isEquivalentHTML( - '', - '', - ); + const isEquivalent = isEquivalentHTML( '', '' ); expect( isEquivalent ).toBe( true ); } ); @@ -593,7 +602,7 @@ describe( 'validation', () => { it( 'should return false if supplied malformed HTML', () => { const isEquivalent = isEquivalentHTML( '<blockquote class="wp-block-quote">fsdfsdfsd<p>fdsfsdfsdd</pfd fd fd></blockquote>', - '<blockquote class="wp-block-quote">fsdfsdfsd<p>fdsfsdfsdd</p></blockquote>', + '<blockquote class="wp-block-quote">fsdfsdfsd<p>fdsfsdfsdd</p></blockquote>' ); expect( console ).toHaveWarned(); @@ -603,7 +612,7 @@ describe( 'validation', () => { it( 'should return false if supplied two sets of malformed HTML', () => { const isEquivalent = isEquivalentHTML( '<div>fsdfsdfsd<p>fdsfsdfsdd</pfd fd fd></div>', - '<blockquote>fsdfsdfsd<p>fdsfsdfsdd</p a></blockquote>', + '<blockquote>fsdfsdfsd<p>fdsfsdfsdd</p a></blockquote>' ); expect( console ).toHaveWarned(); diff --git a/packages/blocks/src/api/utils.js b/packages/blocks/src/api/utils.js index 3ee762b28fe0fc..11b333d8bdef23 100644 --- a/packages/blocks/src/api/utils.js +++ b/packages/blocks/src/api/utils.js @@ -52,8 +52,10 @@ export function isUnmodifiedDefaultBlock( block ) { const newDefaultBlock = isUnmodifiedDefaultBlock.block; const blockType = getBlockType( defaultBlockName ); - return every( blockType.attributes, ( value, key ) => - newDefaultBlock.attributes[ key ] === block.attributes[ key ] + return every( + blockType.attributes, + ( value, key ) => + newDefaultBlock.attributes[ key ] === block.attributes[ key ] ); } @@ -66,11 +68,12 @@ export function isUnmodifiedDefaultBlock( block ) { */ export function isValidIcon( icon ) { - return !! icon && ( - isString( icon ) || - isValidElement( icon ) || - isFunction( icon ) || - icon instanceof Component + return ( + !! icon && + ( isString( icon ) || + isValidElement( icon ) || + isFunction( icon ) || + icon instanceof Component ) ); } @@ -95,11 +98,13 @@ export function normalizeIconObject( icon ) { return { ...icon, - foreground: icon.foreground ? icon.foreground : mostReadable( - tinyBgColor, - ICON_COLORS, - { includeFallbackColors: true, level: 'AA', size: 'large' } - ).toHexString(), + foreground: icon.foreground + ? icon.foreground + : mostReadable( tinyBgColor, ICON_COLORS, { + includeFallbackColors: true, + level: 'AA', + size: 'large', + } ).toHexString(), shadowColor: tinyBgColor.setAlpha( 0.3 ).toRgbString(), }; } @@ -135,10 +140,7 @@ export function normalizeBlockType( blockTypeOrName ) { * @return {string} The block label. */ export function getBlockLabel( blockType, attributes, context = 'visual' ) { - const { - __experimentalLabel: getLabel, - title, - } = blockType; + const { __experimentalLabel: getLabel, title } = blockType; const label = getLabel && getLabel( attributes, { context } ); @@ -162,7 +164,12 @@ export function getBlockLabel( blockType, attributes, context = 'visual' ) { * * @return {string} The block label. */ -export function getAccessibleBlockLabel( blockType, attributes, position, direction = 'vertical' ) { +export function getAccessibleBlockLabel( + blockType, + attributes, + position, + direction = 'vertical' +) { // `title` is already localized, `label` is a user-supplied value. const { title } = blockType; const label = getBlockLabel( blockType, attributes, 'accessibility' ); @@ -189,7 +196,7 @@ export function getAccessibleBlockLabel( blockType, attributes, position, direct /* translators: accessibility text. %s: The block title, %d The block row number. */ __( '%s Block. Row %d' ), title, - position, + position ); } else if ( hasPosition && direction === 'horizontal' ) { if ( hasLabel ) { @@ -206,7 +213,7 @@ export function getAccessibleBlockLabel( blockType, attributes, position, direct /* translators: accessibility text. %s: The block title, %d The block column number. */ __( '%s Block. Column %d' ), title, - position, + position ); } diff --git a/packages/blocks/src/api/validation/index.js b/packages/blocks/src/api/validation/index.js index aab7517307e30b..ec5d2da4535214 100644 --- a/packages/blocks/src/api/validation/index.js +++ b/packages/blocks/src/api/validation/index.js @@ -2,14 +2,7 @@ * External dependencies */ import { Tokenizer } from 'simple-html-tokenizer'; -import { - identity, - xor, - fromPairs, - isEqual, - includes, - stubTrue, -} from 'lodash'; +import { identity, xor, fromPairs, isEqual, includes, stubTrue } from 'lodash'; /** * WordPress dependencies @@ -149,10 +142,7 @@ const MEANINGFUL_ATTRIBUTES = [ * * @type {Array} */ -const TEXT_NORMALIZATIONS = [ - identity, - getTextWithCollapsedWhitespace, -]; +const TEXT_NORMALIZATIONS = [ identity, getTextWithCollapsedWhitespace ]; /** * Regular expression matching a named character reference. In lieu of bundling @@ -303,7 +293,11 @@ export function getMeaningfulAttributePairs( token ) { * * @return {boolean} Whether two text tokens are equivalent. */ -export function isEquivalentTextTokens( actual, expected, logger = createLogger() ) { +export function isEquivalentTextTokens( + actual, + expected, + logger = createLogger() +) { // This function is intentionally written as syntactically "ugly" as a hot // path optimization. Text is progressively normalized in order from least- // to-most operationally expensive, until the earliest point at which text @@ -322,7 +316,11 @@ export function isEquivalentTextTokens( actual, expected, logger = createLogger( } } - logger.warning( 'Expected text `%s`, saw `%s`.', expected.chars, actual.chars ); + logger.warning( + 'Expected text `%s`, saw `%s`.', + expected.chars, + actual.chars + ); return false; } @@ -336,9 +334,11 @@ export function isEquivalentTextTokens( actual, expected, logger = createLogger( * @return {string} Normalized style value. */ export function getNormalizedStyleValue( value ) { - return value - // Normalize URL type to omit whitespace or quotes - .replace( REGEXP_STYLE_URL_TYPE, 'url($1)' ); + return ( + value + // Normalize URL type to omit whitespace or quotes + .replace( REGEXP_STYLE_URL_TYPE, 'url($1)' ) + ); } /** @@ -360,10 +360,7 @@ export function getStyleProperties( text ) { const [ key, ...valueParts ] = style.split( ':' ); const value = valueParts.join( ':' ); - return [ - key.trim(), - getNormalizedStyleValue( value.trim() ), - ]; + return [ key.trim(), getNormalizedStyleValue( value.trim() ) ]; } ); return fromPairs( pairs ); @@ -378,14 +375,18 @@ export const isEqualAttributesOfName = { class: ( actual, expected ) => { // Class matches if members are the same, even if out of order or // superfluous whitespace between. - return ! xor( ...[ actual, expected ].map( getTextPiecesSplitOnWhitespace ) ).length; + return ! xor( + ...[ actual, expected ].map( getTextPiecesSplitOnWhitespace ) + ).length; }, style: ( actual, expected ) => { return isEqual( ...[ actual, expected ].map( getStyleProperties ) ); }, // For each boolean attribute, mere presence of attribute in both is enough // to assume equivalence. - ...fromPairs( BOOLEAN_ATTRIBUTES.map( ( attribute ) => [ attribute, stubTrue ] ) ), + ...fromPairs( + BOOLEAN_ATTRIBUTES.map( ( attribute ) => [ attribute, stubTrue ] ) + ), }; /** @@ -398,12 +399,20 @@ export const isEqualAttributesOfName = { * * @return {boolean} Whether attributes are equivalent. */ -export function isEqualTagAttributePairs( actual, expected, logger = createLogger() ) { +export function isEqualTagAttributePairs( + actual, + expected, + logger = createLogger() +) { // Attributes is tokenized as tuples. Their lengths should match. This also // avoids us needing to check both attributes sets, since if A has any keys // which do not exist in B, we know the sets to be different. if ( actual.length !== expected.length ) { - logger.warning( 'Expected attributes %o, instead saw %o.', expected, actual ); + logger.warning( + 'Expected attributes %o, instead saw %o.', + expected, + actual + ); return false; } @@ -412,7 +421,8 @@ export function isEqualTagAttributePairs( actual, expected, logger = createLogge // an object, for lookup by key. const expectedAttributes = {}; for ( let i = 0; i < expected.length; i++ ) { - expectedAttributes[ expected[ i ][ 0 ].toLowerCase() ] = expected[ i ][ 1 ]; + expectedAttributes[ expected[ i ][ 0 ].toLowerCase() ] = + expected[ i ][ 1 ]; } for ( let i = 0; i < actual.length; i++ ) { @@ -431,12 +441,22 @@ export function isEqualTagAttributePairs( actual, expected, logger = createLogge if ( isEqualAttributes ) { // Defer custom attribute equality handling if ( ! isEqualAttributes( actualValue, expectedValue ) ) { - logger.warning( 'Expected attribute `%s` of value `%s`, saw `%s`.', name, expectedValue, actualValue ); + logger.warning( + 'Expected attribute `%s` of value `%s`, saw `%s`.', + name, + expectedValue, + actualValue + ); return false; } } else if ( actualValue !== expectedValue ) { // Otherwise strict inequality should bail - logger.warning( 'Expected attribute `%s` of value `%s`, saw `%s`.', name, expectedValue, actualValue ); + logger.warning( + 'Expected attribute `%s` of value `%s`, saw `%s`.', + name, + expectedValue, + actualValue + ); return false; } } @@ -458,7 +478,11 @@ export const isEqualTokensOfType = { // have exactly equal tag names. actual.tagName.toLowerCase() !== expected.tagName.toLowerCase() ) { - logger.warning( 'Expected tag name `%s`, instead saw `%s`.', expected.tagName, actual.tagName ); + logger.warning( + 'Expected tag name `%s`, instead saw `%s`.', + expected.tagName, + actual.tagName + ); return false; } @@ -528,7 +552,11 @@ export function isClosedByToken( currentToken, nextToken ) { } // Check token names and determine if nextToken is the closing tag for currentToken - if ( nextToken && nextToken.tagName === currentToken.tagName && nextToken.type === 'EndTag' ) { + if ( + nextToken && + nextToken.tagName === currentToken.tagName && + nextToken.type === 'EndTag' + ) { return true; } @@ -548,9 +576,10 @@ export function isClosedByToken( currentToken, nextToken ) { */ export function isEquivalentHTML( actual, expected, logger = createLogger() ) { // Tokenize input content and reserialized save content - const [ actualTokens, expectedTokens ] = [ actual, expected ].map( - ( html ) => getHTMLTokens( html, logger ) - ); + const [ actualTokens, expectedTokens ] = [ + actual, + expected, + ].map( ( html ) => getHTMLTokens( html, logger ) ); // If either is malformed then stop comparing - the strings are not equivalent if ( ! actualTokens || ! expectedTokens ) { @@ -563,20 +592,32 @@ export function isEquivalentHTML( actual, expected, logger = createLogger() ) { // Inequal if exhausted all expected tokens if ( ! expectedToken ) { - logger.warning( 'Expected end of content, instead saw %o.', actualToken ); + logger.warning( + 'Expected end of content, instead saw %o.', + actualToken + ); return false; } // Inequal if next non-whitespace token of each set are not same type if ( actualToken.type !== expectedToken.type ) { - logger.warning( 'Expected token of type `%s` (%o), instead saw `%s` (%o).', expectedToken.type, expectedToken, actualToken.type, actualToken ); + logger.warning( + 'Expected token of type `%s` (%o), instead saw `%s` (%o).', + expectedToken.type, + expectedToken, + actualToken.type, + actualToken + ); return false; } // Defer custom token type equality handling, otherwise continue and // assume as equal const isEqualTokens = isEqualTokensOfType[ actualToken.type ]; - if ( isEqualTokens && ! isEqualTokens( actualToken, expectedToken, logger ) ) { + if ( + isEqualTokens && + ! isEqualTokens( actualToken, expectedToken, logger ) + ) { return false; } @@ -596,7 +637,10 @@ export function isEquivalentHTML( actual, expected, logger = createLogger() ) { if ( ( expectedToken = getNextNonWhitespaceToken( expectedTokens ) ) ) { // If any non-whitespace tokens remain in expected token set, this // indicates inequality - logger.warning( 'Expected %o, instead saw end of content.', expectedToken ); + logger.warning( + 'Expected %o, instead saw end of content.', + expectedToken + ); return false; } @@ -616,13 +660,21 @@ export function isEquivalentHTML( actual, expected, logger = createLogger() ) { * * @return {Object} Whether block is valid and contains validation messages. */ -export function getBlockContentValidationResult( blockTypeOrName, attributes, originalBlockContent, logger = createQueuedLogger() ) { +export function getBlockContentValidationResult( + blockTypeOrName, + attributes, + originalBlockContent, + logger = createQueuedLogger() +) { const blockType = normalizeBlockType( blockTypeOrName ); let generatedBlockContent; try { generatedBlockContent = getSaveContent( blockType, attributes ); } catch ( error ) { - logger.error( 'Block validation failed because an error occurred while generating block content:\n\n%s', error.toString() ); + logger.error( + 'Block validation failed because an error occurred while generating block content:\n\n%s', + error.toString() + ); return { isValid: false, @@ -630,7 +682,11 @@ export function getBlockContentValidationResult( blockTypeOrName, attributes, or }; } - const isValid = isEquivalentHTML( originalBlockContent, generatedBlockContent, logger ); + const isValid = isEquivalentHTML( + originalBlockContent, + generatedBlockContent, + logger + ); if ( ! isValid ) { logger.error( 'Block validation failed for `%s` (%o).\n\nContent generated by `save` function:\n\n%s\n\nContent retrieved from post body:\n\n%s', @@ -660,8 +716,17 @@ export function getBlockContentValidationResult( blockTypeOrName, attributes, or * * @return {boolean} Whether block is valid. */ -export function isValidBlockContent( blockTypeOrName, attributes, originalBlockContent ) { - const { isValid } = getBlockContentValidationResult( blockTypeOrName, attributes, originalBlockContent, createLogger() ); +export function isValidBlockContent( + blockTypeOrName, + attributes, + originalBlockContent +) { + const { isValid } = getBlockContentValidationResult( + blockTypeOrName, + attributes, + originalBlockContent, + createLogger() + ); return isValid; } diff --git a/packages/blocks/src/api/validation/logger.js b/packages/blocks/src/api/validation/logger.js index ce7f3f6ea22c4d..8ab769f991d819 100644 --- a/packages/blocks/src/api/validation/logger.js +++ b/packages/blocks/src/api/validation/logger.js @@ -7,15 +7,17 @@ export function createLogger() { * @return {Function} Augmented logger function. */ function createLogHandler( logger ) { - let log = ( message, ...args ) => logger( 'Block validation: ' + message, ...args ); + let log = ( message, ...args ) => + logger( 'Block validation: ' + message, ...args ); // In test environments, pre-process the sprintf message to improve // readability of error messages. We'd prefer to avoid pulling in this // dependency in runtime environments, and it can be dropped by a combo // of Webpack env substitution + UglifyJS dead code elimination. if ( process.env.NODE_ENV === 'test' ) { - // eslint-disable-next-line import/no-extraneous-dependencies - log = ( ...args ) => logger( require( 'sprintf-js' ).sprintf( ...args ) ); + log = ( ...args ) => + // eslint-disable-next-line import/no-extraneous-dependencies + logger( require( 'sprintf-js' ).sprintf( ...args ) ); } return log; diff --git a/packages/blocks/src/block-content-provider/index.js b/packages/blocks/src/block-content-provider/index.js index 96b9a4d31bcd1b..505fd4adeeacdd 100644 --- a/packages/blocks/src/block-content-provider/index.js +++ b/packages/blocks/src/block-content-provider/index.js @@ -37,11 +37,7 @@ const BlockContentProvider = ( { children, innerBlocks } ) => { return <RawHTML>{ html }</RawHTML>; }; - return ( - <Provider value={ BlockContent }> - { children } - </Provider> - ); + return <Provider value={ BlockContent }>{ children }</Provider>; }; /** @@ -50,17 +46,17 @@ const BlockContentProvider = ( { children, innerBlocks } ) => { * * @return {WPComponent} Enhanced component with injected BlockContent as prop. */ -export const withBlockContentContext = createHigherOrderComponent( ( OriginalComponent ) => { - return ( props ) => ( - <Consumer> - { ( context ) => ( - <OriginalComponent - { ...props } - BlockContent={ context } - /> - ) } - </Consumer> - ); -}, 'withBlockContentContext' ); +export const withBlockContentContext = createHigherOrderComponent( + ( OriginalComponent ) => { + return ( props ) => ( + <Consumer> + { ( context ) => ( + <OriginalComponent { ...props } BlockContent={ context } /> + ) } + </Consumer> + ); + }, + 'withBlockContentContext' +); export default BlockContentProvider; diff --git a/packages/blocks/src/store/actions.js b/packages/blocks/src/store/actions.js index 6d8521f5f8eee6..0e9038ea6a406d 100644 --- a/packages/blocks/src/store/actions.js +++ b/packages/blocks/src/store/actions.js @@ -3,6 +3,8 @@ */ import { castArray } from 'lodash'; +/** @typedef {import('../api/registration').WPBlockVariation} WPBlockVariation */ + /** * Returns an action object used in signalling that block types have been added. * @@ -64,33 +66,36 @@ export function removeBlockStyles( blockName, styleNames ) { } /** - * Returns an action object used in signalling that new block patterns have been added. + * Returns an action object used in signalling that new block variations have been added. * - * @param {string} blockName Block name. - * @param {WPBlockPattern|WPBlockPattern[]} patterns Block patterns. + * @param {string} blockName Block name. + * @param {WPBlockVariation|WPBlockVariation[]} variations Block variations. * * @return {Object} Action object. */ -export function __experimentalAddBlockPatterns( blockName, patterns ) { +export function __experimentalAddBlockVariations( blockName, variations ) { return { - type: 'ADD_BLOCK_PATTERNS', - patterns: castArray( patterns ), + type: 'ADD_BLOCK_VARIATIONS', + variations: castArray( variations ), blockName, }; } /** - * Returns an action object used in signalling that block patterns have been removed. + * Returns an action object used in signalling that block variations have been removed. * - * @param {string} blockName Block name. - * @param {string|string[]} patternNames Block pattern names. + * @param {string} blockName Block name. + * @param {string|string[]} variationNames Block variation names. * * @return {Object} Action object. */ -export function __experimentalRemoveBlockPatterns( blockName, patternNames ) { +export function __experimentalRemoveBlockVariations( + blockName, + variationNames +) { return { - type: 'REMOVE_BLOCK_PATTERNS', - patternNames: castArray( patternNames ), + type: 'REMOVE_BLOCK_VARIATIONS', + variationNames: castArray( variationNames ), blockName, }; } diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index 027748ba700035..e746eb80b5949a 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -45,7 +45,9 @@ export function blockTypes( state = {}, action ) { return { ...state, ...keyBy( - map( action.blockTypes, ( blockType ) => omit( blockType, 'styles ' ) ), + map( action.blockTypes, ( blockType ) => + omit( blockType, 'styles ' ) + ), 'name' ), }; @@ -69,27 +71,36 @@ export function blockStyles( state = {}, action ) { case 'ADD_BLOCK_TYPES': return { ...state, - ...mapValues( keyBy( action.blockTypes, 'name' ), ( blockType ) => { - return uniqBy( [ - ...get( blockType, [ 'styles' ], [] ), - ...get( state, [ blockType.name ], [] ), - ], ( style ) => style.name ); - } ), + ...mapValues( + keyBy( action.blockTypes, 'name' ), + ( blockType ) => { + return uniqBy( + [ + ...get( blockType, [ 'styles' ], [] ), + ...get( state, [ blockType.name ], [] ), + ], + ( style ) => style.name + ); + } + ), }; case 'ADD_BLOCK_STYLES': return { ...state, - [ action.blockName ]: uniqBy( [ - ...get( state, [ action.blockName ], [] ), - ...( action.styles ), - ], ( style ) => style.name ), + [ action.blockName ]: uniqBy( + [ + ...get( state, [ action.blockName ], [] ), + ...action.styles, + ], + ( style ) => style.name + ), }; case 'REMOVE_BLOCK_STYLES': return { ...state, [ action.blockName ]: filter( get( state, [ action.blockName ], [] ), - ( style ) => action.styleNames.indexOf( style.name ) === -1, + ( style ) => action.styleNames.indexOf( style.name ) === -1 ), }; } @@ -98,39 +109,49 @@ export function blockStyles( state = {}, action ) { } /** - * Reducer managing the block patterns. + * Reducer managing the block variations. * * @param {Object} state Current state. * @param {Object} action Dispatched action. * * @return {Object} Updated state. */ -export function blockPatterns( state = {}, action ) { +export function blockVariations( state = {}, action ) { switch ( action.type ) { case 'ADD_BLOCK_TYPES': return { ...state, - ...mapValues( keyBy( action.blockTypes, 'name' ), ( blockType ) => { - return uniqBy( [ - ...get( blockType, [ 'patterns' ], [] ), - ...get( state, [ blockType.name ], [] ), - ], ( pattern ) => pattern.name ); - } ), + ...mapValues( + keyBy( action.blockTypes, 'name' ), + ( blockType ) => { + return uniqBy( + [ + ...get( blockType, [ 'variations' ], [] ), + ...get( state, [ blockType.name ], [] ), + ], + ( variation ) => variation.name + ); + } + ), }; - case 'ADD_BLOCK_PATTERNS': + case 'ADD_BLOCK_VARIATIONS': return { ...state, - [ action.blockName ]: uniqBy( [ - ...get( state, [ action.blockName ], [] ), - ...( action.patterns ), - ], ( pattern ) => pattern.name ), + [ action.blockName ]: uniqBy( + [ + ...get( state, [ action.blockName ], [] ), + ...action.variations, + ], + ( variation ) => variation.name + ), }; - case 'REMOVE_BLOCK_PATTERNS': + case 'REMOVE_BLOCK_VARIATIONS': return { ...state, [ action.blockName ]: filter( get( state, [ action.blockName ], [] ), - ( pattern ) => action.patternNames.indexOf( pattern.name ) === -1, + ( variation ) => + action.variationNames.indexOf( variation.name ) === -1 ), }; } @@ -162,10 +183,18 @@ export function createBlockNameSetterReducer( setActionType ) { }; } -export const defaultBlockName = createBlockNameSetterReducer( 'SET_DEFAULT_BLOCK_NAME' ); -export const freeformFallbackBlockName = createBlockNameSetterReducer( 'SET_FREEFORM_FALLBACK_BLOCK_NAME' ); -export const unregisteredFallbackBlockName = createBlockNameSetterReducer( 'SET_UNREGISTERED_FALLBACK_BLOCK_NAME' ); -export const groupingBlockName = createBlockNameSetterReducer( 'SET_GROUPING_BLOCK_NAME' ); +export const defaultBlockName = createBlockNameSetterReducer( + 'SET_DEFAULT_BLOCK_NAME' +); +export const freeformFallbackBlockName = createBlockNameSetterReducer( + 'SET_FREEFORM_FALLBACK_BLOCK_NAME' +); +export const unregisteredFallbackBlockName = createBlockNameSetterReducer( + 'SET_UNREGISTERED_FALLBACK_BLOCK_NAME' +); +export const groupingBlockName = createBlockNameSetterReducer( + 'SET_GROUPING_BLOCK_NAME' +); /** * Reducer managing the categories @@ -219,7 +248,7 @@ export function collections( state = {}, action ) { export default combineReducers( { blockTypes, blockStyles, - blockPatterns, + blockVariations, defaultBlockName, freeformFallbackBlockName, unregisteredFallbackBlockName, diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index 2e81931d6ef29c..d8117af5d8f272 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -14,7 +14,9 @@ import { some, } from 'lodash'; -/** @typedef {import('../api/registration').WPBlockPatternScope} WPBlockPatternScope */ +/** @typedef {import('../api/registration').WPBlockVariation} WPBlockVariation */ + +/** @typedef {import('../api/registration').WPBlockVariationScope} WPBlockVariationScope */ /** * Given a block name or block type object, returns the corresponding @@ -25,11 +27,10 @@ import { * * @return {Object} Block type object. */ -const getNormalizedBlockType = ( state, nameOrType ) => ( - 'string' === typeof nameOrType ? - getBlockType( state, nameOrType ) : - nameOrType -); +const getNormalizedBlockType = ( state, nameOrType ) => + 'string' === typeof nameOrType + ? getBlockType( state, nameOrType ) + : nameOrType; /** * Returns all the available block types. @@ -43,14 +44,14 @@ export const getBlockTypes = createSelector( return Object.values( state.blockTypes ).map( ( blockType ) => { return { ...blockType, - patterns: __experimentalGetBlockPatterns( state, blockType.name ), + variations: __experimentalGetBlockVariations( + state, + blockType.name + ), }; } ); }, - ( state ) => [ - state.blockTypes, - state.blockPatterns, - ] + ( state ) => [ state.blockTypes, state.blockVariations ] ); /** @@ -78,40 +79,48 @@ export function getBlockStyles( state, name ) { } /** - * Returns block patterns by block name. + * Returns block variations by block name. * - * @param {Object} state Data state. - * @param {string} blockName Block type name. - * @param {WPBlockPatternScope} [scope] Block pattern scope name. + * @param {Object} state Data state. + * @param {string} blockName Block type name. + * @param {WPBlockVariationScope} [scope] Block variation scope name. * - * @return {(WPBlockPattern[]|void)} Block patterns. + * @return {(WPBlockVariation[]|void)} Block variations. */ -export function __experimentalGetBlockPatterns( state, blockName, scope ) { - const patterns = state.blockPatterns[ blockName ]; - if ( ! patterns || ! scope ) { - return patterns; +export function __experimentalGetBlockVariations( state, blockName, scope ) { + const variations = state.blockVariations[ blockName ]; + if ( ! variations || ! scope ) { + return variations; } - return patterns.filter( ( pattern ) => { - return ! pattern.scope || pattern.scope.includes( scope ); + return variations.filter( ( variation ) => { + return ! variation.scope || variation.scope.includes( scope ); } ); } /** - * Returns the default block pattern for the given block type. - * When there are multiple patterns annotated as the default one, + * Returns the default block variation for the given block type. + * When there are multiple variations annotated as the default one, * the last added item is picked. This simplifies registering overrides. - * When there is no default pattern set, it returns the first item. + * When there is no default variation set, it returns the first item. * - * @param {Object} state Data state. - * @param {string} blockName Block type name. - * @param {WPBlockPatternScope} [scope] Block pattern scope name. + * @param {Object} state Data state. + * @param {string} blockName Block type name. + * @param {WPBlockVariationScope} [scope] Block variation scope name. * - * @return {?WPBlockPattern} The default block pattern. + * @return {?WPBlockVariation} The default block variation. */ -export function __experimentalGetDefaultBlockPattern( state, blockName, scope ) { - const patterns = __experimentalGetBlockPatterns( state, blockName, scope ); +export function __experimentalGetDefaultBlockVariation( + state, + blockName, + scope +) { + const variations = __experimentalGetBlockVariations( + state, + blockName, + scope + ); - return findLast( patterns, 'isDefault' ) || first( patterns ); + return findLast( variations, 'isDefault' ) || first( variations ); } /** @@ -197,9 +206,7 @@ export const getChildBlockNames = createSelector( ( { name } ) => name ); }, - ( state ) => [ - state.blockTypes, - ] + ( state ) => [ state.blockTypes ] ); /** @@ -213,13 +220,15 @@ export const getChildBlockNames = createSelector( * * @return {?*} Block support value */ -export const getBlockSupport = ( state, nameOrType, feature, defaultSupports ) => { +export const getBlockSupport = ( + state, + nameOrType, + feature, + defaultSupports +) => { const blockType = getNormalizedBlockType( state, nameOrType ); - return get( blockType, [ - 'supports', - feature, - ], defaultSupports ); + return get( blockType, [ 'supports', feature ], defaultSupports ); }; /** @@ -268,10 +277,8 @@ export function isMatchingSearchTerm( state, nameOrType, searchTerm ) { const isSearchMatch = flow( [ getNormalizedSearchTerm, - ( normalizedCandidate ) => includes( - normalizedCandidate, - normalizedSearchTerm - ), + ( normalizedCandidate ) => + includes( normalizedCandidate, normalizedSearchTerm ), ] ); return ( diff --git a/packages/blocks/src/store/test/actions.js b/packages/blocks/src/store/test/actions.js index 35ece735f44868..c439fa98272381 100644 --- a/packages/blocks/src/store/test/actions.js +++ b/packages/blocks/src/store/test/actions.js @@ -2,40 +2,42 @@ * Internal dependencies */ import { - __experimentalAddBlockPatterns, - __experimentalRemoveBlockPatterns, + __experimentalAddBlockVariations, + __experimentalRemoveBlockVariations, } from '../actions'; describe( 'actions', () => { - describe( 'addBlockPatterns', () => { + describe( 'addBlockVariations', () => { const blockName = 'block/name'; - const patternName = 'my-pattern'; + const variationName = 'my-variation'; - it( 'should return the ADD_BLOCK_PATTERNS action', () => { - const pattern = { - name: patternName, - title: 'My Pattern', + it( 'should return the ADD_BLOCK_VARIATIONS action', () => { + const variation = { + name: variationName, + title: 'My Variation', attributes: { example: 'foo', }, }; - const result = __experimentalAddBlockPatterns( blockName, pattern ); + const result = __experimentalAddBlockVariations( + blockName, + variation + ); expect( result ).toEqual( { - type: 'ADD_BLOCK_PATTERNS', - patterns: [ - pattern, - ], + type: 'ADD_BLOCK_VARIATIONS', + variations: [ variation ], blockName, } ); } ); - it( 'should return the REMOVE_BLOCK_PATTERNS action', () => { - const result = __experimentalRemoveBlockPatterns( blockName, patternName ); + it( 'should return the REMOVE_BLOCK_VARIATIONS action', () => { + const result = __experimentalRemoveBlockVariations( + blockName, + variationName + ); expect( result ).toEqual( { - type: 'REMOVE_BLOCK_PATTERNS', - patternNames: [ - patternName, - ], + type: 'REMOVE_BLOCK_VARIATIONS', + variationNames: [ variationName ], blockName, } ); } ); diff --git a/packages/blocks/src/store/test/reducer.js b/packages/blocks/src/store/test/reducer.js index 2567903962d14f..3316964743812e 100644 --- a/packages/blocks/src/store/test/reducer.js +++ b/packages/blocks/src/store/test/reducer.js @@ -7,12 +7,12 @@ import deepFreeze from 'deep-freeze'; * Internal dependencies */ import { - __experimentalAddBlockPatterns, + __experimentalAddBlockVariations, addBlockTypes, - __experimentalRemoveBlockPatterns, + __experimentalRemoveBlockVariations, } from '../actions'; import { - blockPatterns, + blockVariations, blockStyles, blockTypes, categories, @@ -76,9 +76,7 @@ describe( 'blockStyles', () => { } ); expect( state ).toEqual( { - 'core/image': [ - { name: 'fancy' }, - ], + 'core/image': [ { name: 'fancy' } ], } ); state = blockStyles( state, { @@ -88,44 +86,33 @@ describe( 'blockStyles', () => { } ); expect( state ).toEqual( { - 'core/image': [ - { name: 'fancy' }, - { name: 'lightbox' }, - ], + 'core/image': [ { name: 'fancy' }, { name: 'lightbox' } ], } ); } ); it( 'should add block styles when adding a block', () => { const original = deepFreeze( { - 'core/image': [ - { name: 'fancy' }, - ], + 'core/image': [ { name: 'fancy' } ], } ); const state = blockStyles( original, { type: 'ADD_BLOCK_TYPES', - blockTypes: [ { - name: 'core/image', - styles: [ - { name: 'original' }, - ], - } ], + blockTypes: [ + { + name: 'core/image', + styles: [ { name: 'original' } ], + }, + ], } ); expect( state ).toEqual( { - 'core/image': [ - { name: 'original' }, - { name: 'fancy' }, - ], + 'core/image': [ { name: 'original' }, { name: 'fancy' } ], } ); } ); it( 'should remove block styles', () => { const original = deepFreeze( { - 'core/image': [ - { name: 'fancy' }, - { name: 'lightbox' }, - ], + 'core/image': [ { name: 'fancy' }, { name: 'lightbox' } ], } ); const state = blockStyles( original, { @@ -135,111 +122,90 @@ describe( 'blockStyles', () => { } ); expect( state ).toEqual( { - 'core/image': [ - { name: 'lightbox' }, - ], + 'core/image': [ { name: 'lightbox' } ], } ); } ); } ); -describe( 'blockPatterns', () => { +describe( 'blockVariations', () => { const blockName = 'block/name'; - const blockPatternName = 'pattern-name'; - const blockPattern = { - name: blockPatternName, - label: 'My pattern', + const blockVariationName = 'variation-name'; + const blockVariation = { + name: blockVariationName, + label: 'My variation', }; - const secondBlockPatternName = 'second-pattern-name'; - const secondBlockPattern = { - name: secondBlockPatternName, - label: 'My Second Pattern', + const secondBlockVariationName = 'second-variation-name'; + const secondBlockVariation = { + name: secondBlockVariationName, + label: 'My Second Variation', }; it( 'should return an empty object as default state', () => { - const state = blockPatterns( undefined, {} ); + const state = blockVariations( undefined, {} ); expect( state ).toEqual( {} ); } ); - it( 'should add a new block pattern when no pattern register', () => { + it( 'should add a new block variation when no variation register', () => { const initialState = deepFreeze( {} ); - const state = blockPatterns( + const state = blockVariations( initialState, - __experimentalAddBlockPatterns( blockName, blockPattern ) + __experimentalAddBlockVariations( blockName, blockVariation ) ); expect( state ).toEqual( { - [ blockName ]: [ - blockPattern, - ], + [ blockName ]: [ blockVariation ], } ); } ); - it( 'should add another pattern when a block pattern already present for the block', () => { + it( 'should add another variation when a block variation already present for the block', () => { const initialState = deepFreeze( { - [ blockName ]: [ - blockPattern, - ], + [ blockName ]: [ blockVariation ], } ); - const state = blockPatterns( + const state = blockVariations( initialState, - __experimentalAddBlockPatterns( blockName, secondBlockPattern ), + __experimentalAddBlockVariations( blockName, secondBlockVariation ) ); expect( state ).toEqual( { - [ blockName ]: [ - blockPattern, - secondBlockPattern, - ], + [ blockName ]: [ blockVariation, secondBlockVariation ], } ); } ); - it( 'should prepend block patterns added when adding a block', () => { + it( 'should prepend block variations added when adding a block', () => { const initialState = deepFreeze( { - [ blockName ]: [ - secondBlockPattern, - ], + [ blockName ]: [ secondBlockVariation ], } ); - const state = blockPatterns( + const state = blockVariations( initialState, addBlockTypes( { name: blockName, - patterns: [ - blockPattern, - ], + variations: [ blockVariation ], } ) ); expect( state ).toEqual( { - [ blockName ]: [ - blockPattern, - secondBlockPattern, - ], + [ blockName ]: [ blockVariation, secondBlockVariation ], } ); } ); - it( 'should remove a block pattern', () => { + it( 'should remove a block variation', () => { const initialState = deepFreeze( { - [ blockName ]: [ - blockPattern, - secondBlockPattern, - ], + [ blockName ]: [ blockVariation, secondBlockVariation ], } ); - const state = blockPatterns( + const state = blockVariations( initialState, - __experimentalRemoveBlockPatterns( blockName, blockPatternName ) + __experimentalRemoveBlockVariations( blockName, blockVariationName ) ); expect( state ).toEqual( { - [ blockName ]: [ - secondBlockPattern, - ], + [ blockName ]: [ secondBlockVariation ], } ); } ); } ); @@ -355,16 +321,16 @@ describe( 'categories', () => { categories: [ { slug: 'wings', title: 'Wings' } ], } ); - expect( state ).toEqual( [ - { slug: 'wings', title: 'Wings' }, - ] ); + expect( state ).toEqual( [ { slug: 'wings', title: 'Wings' } ] ); } ); it( 'should add the category icon', () => { - const original = deepFreeze( [ { - slug: 'chicken', - title: 'Chicken', - } ] ); + const original = deepFreeze( [ + { + slug: 'chicken', + title: 'Chicken', + }, + ] ); const state = categories( original, { type: 'UPDATE_CATEGORY', @@ -374,23 +340,28 @@ describe( 'categories', () => { }, } ); - expect( state ).toEqual( [ { - slug: 'chicken', - title: 'Chicken', - icon: 'new-icon', - } ] ); + expect( state ).toEqual( [ + { + slug: 'chicken', + title: 'Chicken', + icon: 'new-icon', + }, + ] ); } ); it( 'should update the category icon', () => { - const original = deepFreeze( [ { - slug: 'chicken', - title: 'Chicken', - icon: 'old-icon', - }, { - slug: 'wings', - title: 'Wings', - icon: 'old-icon', - } ] ); + const original = deepFreeze( [ + { + slug: 'chicken', + title: 'Chicken', + icon: 'old-icon', + }, + { + slug: 'wings', + title: 'Wings', + icon: 'old-icon', + }, + ] ); const state = categories( original, { type: 'UPDATE_CATEGORY', @@ -400,27 +371,33 @@ describe( 'categories', () => { }, } ); - expect( state ).toEqual( [ { - slug: 'chicken', - title: 'Chicken', - icon: 'new-icon', - }, { - slug: 'wings', - title: 'Wings', - icon: 'old-icon', - } ] ); + expect( state ).toEqual( [ + { + slug: 'chicken', + title: 'Chicken', + icon: 'new-icon', + }, + { + slug: 'wings', + title: 'Wings', + icon: 'old-icon', + }, + ] ); } ); it( 'should update multiple category properties', () => { - const original = deepFreeze( [ { - slug: 'chicken', - title: 'Chicken', - icon: 'old-icon', - }, { - slug: 'wings', - title: 'Wings', - icon: 'old-icon', - } ] ); + const original = deepFreeze( [ + { + slug: 'chicken', + title: 'Chicken', + icon: 'old-icon', + }, + { + slug: 'wings', + title: 'Wings', + icon: 'old-icon', + }, + ] ); const state = categories( original, { type: 'UPDATE_CATEGORY', @@ -431,15 +408,18 @@ describe( 'categories', () => { }, } ); - expect( state ).toEqual( [ { - slug: 'chicken', - title: 'Chicken', - icon: 'old-icon', - }, { - slug: 'wings', - title: 'New Wings', - chicken: 'ribs', - icon: 'old-icon', - } ] ); + expect( state ).toEqual( [ + { + slug: 'chicken', + title: 'Chicken', + icon: 'old-icon', + }, + { + slug: 'wings', + title: 'New Wings', + chicken: 'ribs', + icon: 'old-icon', + }, + ] ); } ); } ); diff --git a/packages/blocks/src/store/test/selectors.js b/packages/blocks/src/store/test/selectors.js index a7833648bda515..72bcf0c9ecd7e0 100644 --- a/packages/blocks/src/store/test/selectors.js +++ b/packages/blocks/src/store/test/selectors.js @@ -8,7 +8,7 @@ import deepFreeze from 'deep-freeze'; */ import { getChildBlockNames, - __experimentalGetDefaultBlockPattern, + __experimentalGetDefaultBlockVariation, getGroupingBlockName, isMatchingSearchTerm, } from '../selectors'; @@ -84,7 +84,10 @@ describe( 'selectors', () => { ], }; - expect( getChildBlockNames( state, 'parent1' ) ).toEqual( [ 'child1', 'child3' ] ); + expect( getChildBlockNames( state, 'parent1' ) ).toEqual( [ + 'child1', + 'child3', + ] ); } ); it( 'should return an array with the child block names even if only one child exists', () => { @@ -110,7 +113,9 @@ describe( 'selectors', () => { ], }; - expect( getChildBlockNames( state, 'parent1' ) ).toEqual( [ 'child1' ] ); + expect( getChildBlockNames( state, 'parent1' ) ).toEqual( [ + 'child1', + ] ); } ); it( 'should return an array with the child block names even if children have multiple parents', () => { @@ -140,78 +145,93 @@ describe( 'selectors', () => { ], }; - expect( getChildBlockNames( state, 'parent1' ) ).toEqual( [ 'child1', 'child2', 'child3' ] ); - expect( getChildBlockNames( state, 'parent2' ) ).toEqual( [ 'child2' ] ); + expect( getChildBlockNames( state, 'parent1' ) ).toEqual( [ + 'child1', + 'child2', + 'child3', + ] ); + expect( getChildBlockNames( state, 'parent2' ) ).toEqual( [ + 'child2', + ] ); } ); } ); - describe( '__experimentalGetDefaultBlockPattern', () => { + describe( '__experimentalGetDefaultBlockVariation', () => { const blockName = 'block/name'; - const createBlockPatternsState = ( patterns ) => { + const createBlockVariationsState = ( variations ) => { return deepFreeze( { - blockPatterns: { - [ blockName ]: patterns, + blockVariations: { + [ blockName ]: variations, }, } ); }; - const firstBlockPattern = { - name: 'first-block-pattern', + const firstBlockVariation = { + name: 'first-block-variation', }; - const secondBlockPattern = { - name: 'second-block-pattern', + const secondBlockVariation = { + name: 'second-block-variation', }; - const thirdBlockPattern = { - name: 'third-block-pattern', + const thirdBlockVariation = { + name: 'third-block-variation', }; - it( 'should return the default pattern when set', () => { - const defaultBlockPattern = { - ...secondBlockPattern, + it( 'should return the default variation when set', () => { + const defaultBlockVariation = { + ...secondBlockVariation, isDefault: true, }; - const state = createBlockPatternsState( [ - firstBlockPattern, - defaultBlockPattern, - thirdBlockPattern, + const state = createBlockVariationsState( [ + firstBlockVariation, + defaultBlockVariation, + thirdBlockVariation, ] ); - const result = __experimentalGetDefaultBlockPattern( state, blockName ); + const result = __experimentalGetDefaultBlockVariation( + state, + blockName + ); - expect( result ).toEqual( defaultBlockPattern ); + expect( result ).toEqual( defaultBlockVariation ); } ); - it( 'should return the last pattern when multiple default patterns added', () => { - const defaultBlockPattern = { - ...thirdBlockPattern, + it( 'should return the last variation when multiple default variations added', () => { + const defaultBlockVariation = { + ...thirdBlockVariation, isDefault: true, }; - const state = createBlockPatternsState( [ + const state = createBlockVariationsState( [ { - ...firstBlockPattern, + ...firstBlockVariation, isDefault: true, }, { - ...secondBlockPattern, + ...secondBlockVariation, isDefault: true, }, - defaultBlockPattern, + defaultBlockVariation, ] ); - const result = __experimentalGetDefaultBlockPattern( state, blockName ); + const result = __experimentalGetDefaultBlockVariation( + state, + blockName + ); - expect( result ).toEqual( defaultBlockPattern ); + expect( result ).toEqual( defaultBlockVariation ); } ); - it( 'should return the first pattern when no default pattern set', () => { - const state = createBlockPatternsState( [ - firstBlockPattern, - secondBlockPattern, - thirdBlockPattern, + it( 'should return the first variation when no default variation set', () => { + const state = createBlockVariationsState( [ + firstBlockVariation, + secondBlockVariation, + thirdBlockVariation, ] ); - const result = __experimentalGetDefaultBlockPattern( state, blockName ); + const result = __experimentalGetDefaultBlockVariation( + state, + blockName + ); - expect( result ).toEqual( firstBlockPattern ); + expect( result ).toEqual( firstBlockVariation ); } ); } ); @@ -234,43 +254,71 @@ describe( 'selectors', () => { [ 'block type', blockType ], ] )( 'by %s', ( label, nameOrType ) => { it( 'should return false if not match', () => { - const result = isMatchingSearchTerm( state, nameOrType, 'Quote' ); + const result = isMatchingSearchTerm( + state, + nameOrType, + 'Quote' + ); expect( result ).toBe( false ); } ); it( 'should return true if match by title', () => { - const result = isMatchingSearchTerm( state, nameOrType, 'Paragraph' ); + const result = isMatchingSearchTerm( + state, + nameOrType, + 'Paragraph' + ); expect( result ).toBe( true ); } ); it( 'should return true if match ignoring case', () => { - const result = isMatchingSearchTerm( state, nameOrType, 'PARAGRAPH' ); + const result = isMatchingSearchTerm( + state, + nameOrType, + 'PARAGRAPH' + ); expect( result ).toBe( true ); } ); it( 'should return true if match ignoring diacritics', () => { - const result = isMatchingSearchTerm( state, nameOrType, 'PÁRAGRAPH' ); + const result = isMatchingSearchTerm( + state, + nameOrType, + 'PÁRAGRAPH' + ); expect( result ).toBe( true ); } ); it( 'should return true if match ignoring whitespace', () => { - const result = isMatchingSearchTerm( state, nameOrType, ' PARAGRAPH ' ); + const result = isMatchingSearchTerm( + state, + nameOrType, + ' PARAGRAPH ' + ); expect( result ).toBe( true ); } ); it( 'should return true if match using the keywords', () => { - const result = isMatchingSearchTerm( state, nameOrType, 'TEXT' ); + const result = isMatchingSearchTerm( + state, + nameOrType, + 'TEXT' + ); expect( result ).toBe( true ); } ); it( 'should return true if match using the categories', () => { - const result = isMatchingSearchTerm( state, nameOrType, 'COMMON' ); + const result = isMatchingSearchTerm( + state, + nameOrType, + 'COMMON' + ); expect( result ).toBe( true ); } ); diff --git a/packages/components/src/angle-picker/index.js b/packages/components/src/angle-picker/index.js index a46d11170328b9..d1acd3b0ecddf4 100644 --- a/packages/components/src/angle-picker/index.js +++ b/packages/components/src/angle-picker/index.js @@ -2,7 +2,10 @@ * WordPress dependencies */ import { useRef } from '@wordpress/element'; -import { useInstanceId, __experimentalUseDragging as useDragging } from '@wordpress/compose'; +import { + useInstanceId, + __experimentalUseDragging as useDragging, +} from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; /** @@ -29,8 +32,8 @@ const AngleCircle = ( { value, onChange, ...props } ) => { const setAngleCircleCenter = () => { const rect = angleCircleRef.current.getBoundingClientRect(); angleCircleCenter.current = { - x: rect.x + ( rect.width / 2 ), - y: rect.y + ( rect.height / 2 ), + x: rect.x + rect.width / 2, + y: rect.y + rect.height / 2, }; }; @@ -47,7 +50,7 @@ const AngleCircle = ( { value, onChange, ...props } ) => { onDragMove: changeAngleToPosition, onDragEnd: changeAngleToPosition, } ); - return ( + return ( /* eslint-disable jsx-a11y/no-static-element-interactions */ <div ref={ angleCircleRef } @@ -57,7 +60,9 @@ const AngleCircle = ( { value, onChange, ...props } ) => { { ...props } > <div - style={ value ? { transform: `rotate(${ value }deg)` } : undefined } + style={ + value ? { transform: `rotate(${ value }deg)` } : undefined + } className="components-angle-picker__angle-circle-indicator-wrapper" > <span className="components-angle-picker__angle-circle-indicator" /> @@ -67,7 +72,11 @@ const AngleCircle = ( { value, onChange, ...props } ) => { ); }; -export default function AnglePicker( { value, onChange, label = __( 'Angle' ) } ) { +export default function AnglePicker( { + value, + onChange, + label = __( 'Angle' ), +} ) { const instanceId = useInstanceId( AnglePicker ); const inputId = `components-angle-picker__input-${ instanceId }`; return ( @@ -87,9 +96,10 @@ export default function AnglePicker( { value, onChange, label = __( 'Angle' ) } id={ inputId } onChange={ ( event ) => { const unprocessedValue = event.target.value; - const inputValue = unprocessedValue !== '' ? - parseInt( event.target.value, 10 ) : - 0; + const inputValue = + unprocessedValue !== '' + ? parseInt( event.target.value, 10 ) + : 0; onChange( inputValue ); } } value={ value } @@ -100,4 +110,3 @@ export default function AnglePicker( { value, onChange, label = __( 'Angle' ) } </BaseControl> ); } - diff --git a/packages/components/src/angle-picker/stories/index.js b/packages/components/src/angle-picker/stories/index.js index 99fe93c00dd30f..b3ab46f11c172b 100644 --- a/packages/components/src/angle-picker/stories/index.js +++ b/packages/components/src/angle-picker/stories/index.js @@ -1,4 +1,3 @@ - /** * WordPress dependencies */ @@ -13,9 +12,7 @@ export default { title: 'Components|AnglePicker', component: AnglePicker }; const AnglePickerWithState = () => { const [ angle, setAngle ] = useState(); - return ( - <AnglePicker value={ angle } onChange={ setAngle } /> - ); + return <AnglePicker value={ angle } onChange={ setAngle } />; }; export const _default = () => { diff --git a/packages/components/src/animate/index.js b/packages/components/src/animate/index.js index ee6ca3298e006f..a7cfc8d8cfa7d2 100644 --- a/packages/components/src/animate/index.js +++ b/packages/components/src/animate/index.js @@ -9,13 +9,10 @@ function Animate( { type, options = {}, children } ) { const [ yAxis, xAxis = 'center' ] = origin.split( ' ' ); return children( { - className: classnames( - 'components-animate__appear', - { - [ 'is-from-' + xAxis ]: xAxis !== 'center', - [ 'is-from-' + yAxis ]: yAxis !== 'middle', - }, - ), + className: classnames( 'components-animate__appear', { + [ 'is-from-' + xAxis ]: xAxis !== 'center', + [ 'is-from-' + yAxis ]: yAxis !== 'middle', + } ), } ); } @@ -25,16 +22,14 @@ function Animate( { type, options = {}, children } ) { return children( { className: classnames( 'components-animate__slide-in', - 'is-from-' + origin, + 'is-from-' + origin ), } ); } if ( type === 'loading' ) { return children( { - className: classnames( - 'components-animate__loading', - ), + className: classnames( 'components-animate__loading' ), } ); } diff --git a/packages/components/src/autocomplete/index.js b/packages/components/src/autocomplete/index.js index b95bdf641252c3..6466328cabd836 100644 --- a/packages/components/src/autocomplete/index.js +++ b/packages/components/src/autocomplete/index.js @@ -8,7 +8,15 @@ import { escapeRegExp, find, map, debounce, deburr } from 'lodash'; * WordPress dependencies */ import { Component, renderToString } from '@wordpress/element'; -import { ENTER, ESCAPE, UP, DOWN, LEFT, RIGHT, SPACE } from '@wordpress/keycodes'; +import { + ENTER, + ESCAPE, + UP, + DOWN, + LEFT, + RIGHT, + SPACE, +} from '@wordpress/keycodes'; import { __, _n, sprintf } from '@wordpress/i18n'; import { withInstanceId, compose } from '@wordpress/compose'; import { @@ -113,7 +121,9 @@ function filterOptions( search, options = [], maxResults = 10 ) { keywords = [ ...keywords, option.label ]; } - const isMatch = keywords.some( ( keyword ) => search.test( deburr( keyword ) ) ); + const isMatch = keywords.some( ( keyword ) => + search.test( deburr( keyword ) ) + ); if ( ! isMatch ) { continue; } @@ -181,9 +191,10 @@ export class Autocomplete extends Component { const completion = getOptionCompletion( option.value, query ); const { action, value } = - ( undefined === completion.action || undefined === completion.value ) ? - { action: 'insert-at-caret', value: completion } : - completion; + undefined === completion.action || + undefined === completion.value + ? { action: 'insert-at-caret', value: completion } + : completion; if ( 'replace' === action ) { onReplace( [ value ] ); @@ -214,11 +225,17 @@ export class Autocomplete extends Component { return; } if ( !! filteredOptions.length ) { - debouncedSpeak( sprintf( _n( - '%d result found, use up and down arrow keys to navigate.', - '%d results found, use up and down arrow keys to navigate.', - filteredOptions.length - ), filteredOptions.length ), 'assertive' ); + debouncedSpeak( + sprintf( + _n( + '%d result found, use up and down arrow keys to navigate.', + '%d results found, use up and down arrow keys to navigate.', + filteredOptions.length + ), + filteredOptions.length + ), + 'assertive' + ); } else { debouncedSpeak( __( 'No results.' ), 'assertive' ); } @@ -244,7 +261,7 @@ export class Autocomplete extends Component { * `activePromise` in the state would result in it actually being in `this.state` * before the promise resolves and we check to see if this is the active promise or not. */ - const promise = this.activePromise = Promise.resolve( + const promise = ( this.activePromise = Promise.resolve( typeof options === 'function' ? options( query ) : options ).then( ( optionsData ) => { if ( promise !== this.activePromise ) { @@ -252,23 +269,35 @@ export class Autocomplete extends Component { // or else we might end triggering a race condition updating the state. return; } - const keyedOptions = optionsData.map( ( optionData, optionIndex ) => ( { - key: `${ completer.idx }-${ optionIndex }`, - value: optionData, - label: completer.getOptionLabel( optionData ), - keywords: completer.getOptionKeywords ? completer.getOptionKeywords( optionData ) : [], - isDisabled: completer.isOptionDisabled ? completer.isOptionDisabled( optionData ) : false, - } ) ); - - const filteredOptions = filterOptions( this.state.search, keyedOptions ); - const selectedIndex = filteredOptions.length === this.state.filteredOptions.length ? this.state.selectedIndex : 0; + const keyedOptions = optionsData.map( + ( optionData, optionIndex ) => ( { + key: `${ completer.idx }-${ optionIndex }`, + value: optionData, + label: completer.getOptionLabel( optionData ), + keywords: completer.getOptionKeywords + ? completer.getOptionKeywords( optionData ) + : [], + isDisabled: completer.isOptionDisabled + ? completer.isOptionDisabled( optionData ) + : false, + } ) + ); + + const filteredOptions = filterOptions( + this.state.search, + keyedOptions + ); + const selectedIndex = + filteredOptions.length === this.state.filteredOptions.length + ? this.state.selectedIndex + : 0; this.setState( { [ 'options_' + completer.idx ]: keyedOptions, filteredOptions, selectedIndex, } ); this.announce( filteredOptions ); - } ); + } ) ); } handleKeyDown( event ) { @@ -303,12 +332,16 @@ export class Autocomplete extends Component { let nextSelectedIndex; switch ( event.keyCode ) { case UP: - nextSelectedIndex = ( selectedIndex === 0 ? filteredOptions.length : selectedIndex ) - 1; + nextSelectedIndex = + ( selectedIndex === 0 + ? filteredOptions.length + : selectedIndex ) - 1; this.setState( { selectedIndex: nextSelectedIndex } ); break; case DOWN: - nextSelectedIndex = ( selectedIndex + 1 ) % filteredOptions.length; + nextSelectedIndex = + ( selectedIndex + 1 ) % filteredOptions.length; this.setState( { selectedIndex: nextSelectedIndex } ); break; @@ -343,21 +376,37 @@ export class Autocomplete extends Component { const prevText = deburr( getTextContent( slice( prevRecord, 0 ) ) ); if ( text !== prevText ) { - const textAfterSelection = getTextContent( slice( record, undefined, getTextContent( record ).length ) ); - const allCompleters = map( completers, ( completer, idx ) => ( { ...completer, idx } ) ); - const open = find( allCompleters, ( { triggerPrefix, allowContext } ) => { - const index = text.lastIndexOf( triggerPrefix ); - - if ( index === -1 ) { - return false; + const textAfterSelection = getTextContent( + slice( record, undefined, getTextContent( record ).length ) + ); + const allCompleters = map( completers, ( completer, idx ) => ( { + ...completer, + idx, + } ) ); + const open = find( + allCompleters, + ( { triggerPrefix, allowContext } ) => { + const index = text.lastIndexOf( triggerPrefix ); + + if ( index === -1 ) { + return false; + } + + if ( + allowContext && + ! allowContext( + text.slice( 0, index ), + textAfterSelection + ) + ) { + return false; + } + + return /^\S*$/.test( + text.slice( index + triggerPrefix.length ) + ); } - - if ( allowContext && ! allowContext( text.slice( 0, index ), textAfterSelection ) ) { - return false; - } - - return /^\S*$/.test( text.slice( index + triggerPrefix.length ) ); - } ); + ); if ( ! open ) { this.reset(); @@ -365,11 +414,22 @@ export class Autocomplete extends Component { } const safeTrigger = escapeRegExp( open.triggerPrefix ); - const match = text.match( new RegExp( `${ safeTrigger }(\\S*)$` ) ); + const match = text.match( + new RegExp( `${ safeTrigger }(\\S*)$` ) + ); const query = match && match[ 1 ]; - const { open: wasOpen, suppress: wasSuppress, query: wasQuery } = this.state; - - if ( open && ( ! wasOpen || open.idx !== wasOpen.idx || query !== wasQuery ) ) { + const { + open: wasOpen, + suppress: wasSuppress, + query: wasQuery, + } = this.state; + + if ( + open && + ( ! wasOpen || + open.idx !== wasOpen.idx || + query !== wasQuery ) + ) { if ( open.isDebounced ) { this.debouncedLoadOptions( open, query ); } else { @@ -377,14 +437,29 @@ export class Autocomplete extends Component { } } // create a regular expression to filter the options - const search = open ? new RegExp( '(?:\\b|\\s|^)' + escapeRegExp( query ), 'i' ) : /./; + const search = open + ? new RegExp( '(?:\\b|\\s|^)' + escapeRegExp( query ), 'i' ) + : /./; // filter the options we already have - const filteredOptions = open ? filterOptions( search, this.state[ 'options_' + open.idx ] ) : []; + const filteredOptions = open + ? filterOptions( + search, + this.state[ 'options_' + open.idx ] + ) + : []; // check if we should still suppress the popover - const suppress = ( open && wasSuppress === open.idx ) ? wasSuppress : undefined; + const suppress = + open && wasSuppress === open.idx ? wasSuppress : undefined; // update the state if ( wasOpen || open ) { - this.setState( { selectedIndex: 0, filteredOptions, suppress, search, open, query } ); + this.setState( { + selectedIndex: 0, + filteredOptions, + suppress, + search, + open, + query, + } ); } // announce the count of filtered options but only if they have loaded if ( open && this.state[ 'options_' + open.idx ] ) { @@ -401,11 +476,16 @@ export class Autocomplete extends Component { render() { const { children, instanceId, isSelected } = this.props; const { open, suppress, selectedIndex, filteredOptions } = this.state; - const { key: selectedKey = '' } = filteredOptions[ selectedIndex ] || {}; + const { key: selectedKey = '' } = + filteredOptions[ selectedIndex ] || {}; const { className, idx } = open || {}; const isExpanded = suppress !== idx && filteredOptions.length > 0; - const listBoxId = isExpanded ? `components-autocomplete-listbox-${ instanceId }` : null; - const activeId = isExpanded ? `components-autocomplete-item-${ instanceId }-${ selectedKey }` : null; + const listBoxId = isExpanded + ? `components-autocomplete-listbox-${ instanceId }` + : null; + const activeId = isExpanded + ? `components-autocomplete-item-${ instanceId }-${ selectedKey }` + : null; return ( <> @@ -428,21 +508,29 @@ export class Autocomplete extends Component { role="listbox" className="components-autocomplete__results" > - { isExpanded && map( filteredOptions, ( option, index ) => ( - <Button - key={ option.key } - id={ `components-autocomplete-item-${ instanceId }-${ option.key }` } - role="option" - aria-selected={ index === selectedIndex } - disabled={ option.isDisabled } - className={ classnames( 'components-autocomplete__result', className, { - 'is-selected': index === selectedIndex, - } ) } - onClick={ () => this.select( option ) } - > - { option.label } - </Button> - ) ) } + { isExpanded && + map( filteredOptions, ( option, index ) => ( + <Button + key={ option.key } + id={ `components-autocomplete-item-${ instanceId }-${ option.key }` } + role="option" + aria-selected={ + index === selectedIndex + } + disabled={ option.isDisabled } + className={ classnames( + 'components-autocomplete__result', + className, + { + 'is-selected': + index === selectedIndex, + } + ) } + onClick={ () => this.select( option ) } + > + { option.label } + </Button> + ) ) } </div> </Popover> ) } @@ -451,7 +539,6 @@ export class Autocomplete extends Component { } } -export default compose( [ - withSpokenMessages, - withInstanceId, -] )( Autocomplete ); +export default compose( [ withSpokenMessages, withInstanceId ] )( + Autocomplete +); diff --git a/packages/components/src/base-control/index.js b/packages/components/src/base-control/index.js index 689cc98f424205..00c24d7a4d1f4c 100644 --- a/packages/components/src/base-control/index.js +++ b/packages/components/src/base-control/index.js @@ -8,37 +8,57 @@ import classnames from 'classnames'; */ import VisuallyHidden from '../visually-hidden'; -function BaseControl( { id, label, hideLabelFromVision, help, className, children } ) { +function BaseControl( { + id, + label, + hideLabelFromVision, + help, + className, + children, +} ) { return ( <div className={ classnames( 'components-base-control', className ) }> <div className="components-base-control__field"> - { label && id && ( hideLabelFromVision ? - <VisuallyHidden - as="label" - htmlFor={ id }>{ label }</VisuallyHidden> : - <label - className="components-base-control__label" - htmlFor={ id }>{ label }</label> - ) } - { label && ! id && ( hideLabelFromVision ? - <VisuallyHidden - as="label">{ label }</VisuallyHidden> : - <BaseControl.VisualLabel>{ label }</BaseControl.VisualLabel> - ) } + { label && + id && + ( hideLabelFromVision ? ( + <VisuallyHidden as="label" htmlFor={ id }> + { label } + </VisuallyHidden> + ) : ( + <label + className="components-base-control__label" + htmlFor={ id } + > + { label } + </label> + ) ) } + { label && + ! id && + ( hideLabelFromVision ? ( + <VisuallyHidden as="label">{ label }</VisuallyHidden> + ) : ( + <BaseControl.VisualLabel> + { label } + </BaseControl.VisualLabel> + ) ) } { children } </div> - { !! help && <p id={ id + '__help' } className="components-base-control__help">{ help }</p> } + { !! help && ( + <p + id={ id + '__help' } + className="components-base-control__help" + > + { help } + </p> + ) } </div> ); } BaseControl.VisualLabel = ( { className, children } ) => { className = classnames( 'components-base-control__label', className ); - return ( - <span className={ className }> - { children } - </span> - ); + return <span className={ className }>{ children }</span>; }; export default BaseControl; diff --git a/packages/components/src/base-control/index.native.js b/packages/components/src/base-control/index.native.js index ddf4067054d832..1bf653768e7532 100644 --- a/packages/components/src/base-control/index.native.js +++ b/packages/components/src/base-control/index.native.js @@ -5,10 +5,7 @@ import { Text, View } from 'react-native'; export default function BaseControl( { label, help, children } ) { return ( - <View - accessible={ true } - accessibilityLabel={ label } - > + <View accessible={ true } accessibilityLabel={ label }> { label && <Text>{ label }</Text> } { children } { help && <Text>{ help }</Text> } diff --git a/packages/components/src/base-control/stories/index.js b/packages/components/src/base-control/stories/index.js index f71877b5522434..ba2540d15cf4fd 100644 --- a/packages/components/src/base-control/stories/index.js +++ b/packages/components/src/base-control/stories/index.js @@ -25,9 +25,7 @@ export const _default = () => { hideLabelFromVision={ hideLabelFromVision } className={ className } > - <textarea - id={ id } - /> + <textarea id={ id } /> </BaseControl> ); }; diff --git a/packages/components/src/button-group/index.js b/packages/components/src/button-group/index.js index b864ff7ccc7014..6f4fb672c8c6ef 100644 --- a/packages/components/src/button-group/index.js +++ b/packages/components/src/button-group/index.js @@ -6,9 +6,7 @@ import classnames from 'classnames'; function ButtonGroup( { className, ...props } ) { const classes = classnames( 'components-button-group', className ); - return ( - <div { ...props } className={ classes } role="group" /> - ); + return <div { ...props } className={ classes } role="group" />; } export default ButtonGroup; diff --git a/packages/components/src/button-group/stories/index.js b/packages/components/src/button-group/stories/index.js index 604ffa61a58fbb..50332eede746ac 100644 --- a/packages/components/src/button-group/stories/index.js +++ b/packages/components/src/button-group/stories/index.js @@ -10,8 +10,12 @@ export const _default = () => { const style = { margin: '0 4px' }; return ( <ButtonGroup> - <Button isPrimary style={ style }>Button 1</Button> - <Button isPrimary style={ style }>Button 2</Button> + <Button isPrimary style={ style }> + Button 1 + </Button> + <Button isPrimary style={ style }> + Button 2 + </Button> </ButtonGroup> ); }; diff --git a/packages/components/src/button/deprecated.js b/packages/components/src/button/deprecated.js index ea7fc7ab5ab1c2..6c676d00215811 100644 --- a/packages/components/src/button/deprecated.js +++ b/packages/components/src/button/deprecated.js @@ -9,13 +9,7 @@ import { forwardRef } from '@wordpress/element'; */ import Button from '../button'; -function IconButton( { - labelPosition, - size, - tooltip, - label, - ...props -}, ref ) { +function IconButton( { labelPosition, size, tooltip, label, ...props }, ref ) { deprecated( 'wp.components.IconButton', { alternative: 'wp.components.Button', } ); diff --git a/packages/components/src/button/index.js b/packages/components/src/button/index.js index 2094b358983ef7..7e5acfb6f2ccf6 100644 --- a/packages/components/src/button/index.js +++ b/packages/components/src/button/index.js @@ -16,10 +16,7 @@ import { forwardRef } from '@wordpress/element'; import Tooltip from '../tooltip'; import Icon from '../icon'; -const disabledEventsOnDisabledButton = [ - 'onMouseDown', - 'onClick', -]; +const disabledEventsOnDisabledButton = [ 'onMouseDown', 'onClick' ]; export function Button( props, ref ) { const { @@ -70,9 +67,14 @@ export function Button( props, ref ) { const trulyDisabled = disabled && ! isFocusable; const Tag = href !== undefined && ! trulyDisabled ? 'a' : 'button'; - const tagProps = Tag === 'a' ? - { href, target } : - { type: 'button', disabled: trulyDisabled, 'aria-pressed': isPressed }; + const tagProps = + Tag === 'a' + ? { href, target } + : { + type: 'button', + disabled: trulyDisabled, + 'aria-pressed': isPressed, + }; if ( disabled && isFocusable ) { // In this case, the button will be disabled, but still focusable and @@ -88,20 +90,19 @@ export function Button( props, ref ) { } // Should show the tooltip if... - const shouldShowTooltip = ! trulyDisabled && ( + const shouldShowTooltip = + ! trulyDisabled && // an explicit tooltip is passed or... - ( showTooltip && label ) || - // there's a shortcut or... - shortcut || - ( + ( ( showTooltip && label ) || + // there's a shortcut or... + shortcut || // there's a label and... - !! label && - // the children are empty and... - ( ! children || ( isArray( children ) && ! children.length ) ) && - // the tooltip is not explicitly disabled. - false !== showTooltip - ) - ); + ( !! label && + // the children are empty and... + ( ! children || + ( isArray( children ) && ! children.length ) ) && + // the tooltip is not explicitly disabled. + false !== showTooltip ) ); const element = ( <Tag @@ -121,7 +122,11 @@ export function Button( props, ref ) { } return ( - <Tooltip text={ label } shortcut={ shortcut } position={ tooltipPosition }> + <Tooltip + text={ label } + shortcut={ shortcut } + position={ tooltipPosition } + > { element } </Tooltip> ); diff --git a/packages/components/src/button/index.native.js b/packages/components/src/button/index.native.js index 33cbfa3ca44583..d77c3ef40d4fd8 100644 --- a/packages/components/src/button/index.native.js +++ b/packages/components/src/button/index.native.js @@ -1,7 +1,13 @@ /** * External dependencies */ -import { StyleSheet, TouchableOpacity, Text, View, Platform } from 'react-native'; +import { + StyleSheet, + TouchableOpacity, + Text, + View, + Platform, +} from 'react-native'; import { isArray } from 'lodash'; /** @@ -104,30 +110,41 @@ export function Button( props ) { states.push( 'disabled' ); } - const subscriptInactive = getStylesFromColorScheme( styles.subscriptInactive, styles.subscriptInactiveDark ); + const subscriptInactive = getStylesFromColorScheme( + styles.subscriptInactive, + styles.subscriptInactiveDark + ); const newChildren = Children.map( children, ( child ) => { - return child ? cloneElement( child, { colorScheme: props.preferredColorScheme, isPressed } ) : child; + return child + ? cloneElement( child, { + colorScheme: props.preferredColorScheme, + isPressed, + } ) + : child; } ); // Should show the tooltip if... - const shouldShowTooltip = ! isDisabled && ( + const shouldShowTooltip = + ! isDisabled && // an explicit tooltip is passed or... - ( showTooltip && label ) || - // there's a shortcut or... - shortcut || - ( + ( ( showTooltip && label ) || + // there's a shortcut or... + shortcut || // there's a label and... - !! label && - // the children are empty and... - ( ! children || ( isArray( children ) && ! children.length ) ) && - // the tooltip is not explicitly disabled. - false !== showTooltip - ) - ); - - const newIcon = icon ? cloneElement( ( <Icon icon={ icon } size={ iconSize } /> ), - { colorScheme: props.preferredColorScheme, isPressed } ) : null; + ( !! label && + // the children are empty and... + ( ! children || + ( isArray( children ) && ! children.length ) ) && + // the tooltip is not explicitly disabled. + false !== showTooltip ) ); + + const newIcon = icon + ? cloneElement( <Icon icon={ icon } size={ iconSize } />, { + colorScheme: props.preferredColorScheme, + isPressed, + } ) + : null; const element = ( <TouchableOpacity @@ -147,7 +164,17 @@ export function Button( props ) { <View style={ { flexDirection: 'row' } }> { newIcon } { newChildren } - { subscript && ( <Text style={ isPressed ? styles.subscriptActive : subscriptInactive }>{ subscript }</Text> ) } + { subscript && ( + <Text + style={ + isPressed + ? styles.subscriptActive + : subscriptInactive + } + > + { subscript } + </Text> + ) } </View> </View> </TouchableOpacity> @@ -158,7 +185,11 @@ export function Button( props ) { } return ( - <Tooltip text={ label } shortcut={ shortcut } position={ tooltipPosition }> + <Tooltip + text={ label } + shortcut={ shortcut } + position={ tooltipPosition } + > { element } </Tooltip> ); diff --git a/packages/components/src/button/stories/index.js b/packages/components/src/button/stories/index.js index 4784b24a661e49..2b6219e078fa13 100644 --- a/packages/components/src/button/stories/index.js +++ b/packages/components/src/button/stories/index.js @@ -14,57 +14,43 @@ export default { title: 'Components/Button', component: Button }; export const _default = () => { const label = text( 'Label', 'Default Button' ); - return ( - <Button>{ label }</Button> - ); + return <Button>{ label }</Button>; }; export const primary = () => { const label = text( 'Label', 'Primary Button' ); - return ( - <Button isPrimary>{ label }</Button> - ); + return <Button isPrimary>{ label }</Button>; }; export const secondary = () => { const label = text( 'Label', 'Secondary Button' ); - return ( - <Button isSecondary>{ label }</Button> - ); + return <Button isSecondary>{ label }</Button>; }; export const tertiary = () => { const label = text( 'Label', 'Tertiary Button' ); - return ( - <Button isTertiary>{ label }</Button> - ); + return <Button isTertiary>{ label }</Button>; }; export const small = () => { const label = text( 'Label', 'Small Button' ); - return ( - <Button isSmall>{ label }</Button> - ); + return <Button isSmall>{ label }</Button>; }; export const pressed = () => { const label = text( 'Label', 'Pressed Button' ); - return ( - <Button isPressed>{ label }</Button> - ); + return <Button isPressed>{ label }</Button>; }; export const disabled = () => { const label = text( 'Label', 'Disabled Button' ); - return ( - <Button disabled>{ label }</Button> - ); + return <Button disabled>{ label }</Button>; }; export const disabledFocusable = () => { @@ -102,13 +88,7 @@ export const icon = () => { const label = text( 'Label', 'More' ); const size = number( 'Size' ); - return ( - <Button - icon={ usedIcon } - label={ label } - iconSize={ size } - /> - ); + return <Button icon={ usedIcon } label={ label } iconSize={ size } />; }; export const disabledFocusableIcon = () => { @@ -147,14 +127,22 @@ export const buttons = () => { <h2>Small Buttons</h2> <div className="story-buttons-container"> <Button isSmall>Button</Button> - <Button isPrimary isSmall>Primary Button</Button> - <Button isSecondary isSmall>Secondary Button</Button> - <Button isTertiary isSmall>Tertiary Button</Button> + <Button isPrimary isSmall> + Primary Button + </Button> + <Button isSecondary isSmall> + Secondary Button + </Button> + <Button isTertiary isSmall> + Tertiary Button + </Button> <Button isSmall icon="ellipsis" /> <Button isSmall isPrimary icon="ellipsis" /> <Button isSmall isSecondary icon="ellipsis" /> <Button isSmall isTertiary icon="ellipsis" /> - <Button isSmall isPrimary icon="ellipsis">Icon & Text</Button> + <Button isSmall isPrimary icon="ellipsis"> + Icon & Text + </Button> </div> <h2>Regular Buttons</h2> @@ -167,7 +155,9 @@ export const buttons = () => { <Button isPrimary icon="ellipsis" /> <Button isSecondary icon="ellipsis" /> <Button isTertiary icon="ellipsis" /> - <Button isPrimary icon="ellipsis">Icon & Text</Button> + <Button isPrimary icon="ellipsis"> + Icon & Text + </Button> </div> </div> ); diff --git a/packages/components/src/button/test/index.js b/packages/components/src/button/test/index.js index 211f24ceb00726..beaf19bc2cf7f6 100644 --- a/packages/components/src/button/test/index.js +++ b/packages/components/src/button/test/index.js @@ -60,7 +60,9 @@ describe( 'Button', () => { } ); it( 'should add only aria-disabled attribute when disabled and isFocusable are true', () => { - const button = shallow( <Button disabled __experimentalIsFocusable /> ); + const button = shallow( + <Button disabled __experimentalIsFocusable /> + ); expect( button.prop( 'disabled' ) ).toBe( false ); expect( button.prop( 'aria-disabled' ) ).toBe( true ); } ); @@ -90,20 +92,46 @@ describe( 'Button', () => { it( 'should render a Dashicon component matching the wordpress icon', () => { const iconButton = shallow( <Button icon="wordpress" /> ); - expect( iconButton.find( 'Icon' ).dive().shallow().hasClass( 'dashicons-wordpress' ) ).toBe( true ); + expect( + iconButton + .find( 'Icon' ) + .dive() + .shallow() + .hasClass( 'dashicons-wordpress' ) + ).toBe( true ); } ); it( 'should render child elements and icon', () => { - const iconButton = shallow( <Button icon="wordpress" children={ <p className="test">Test</p> } /> ); - expect( iconButton.find( 'Icon' ).dive().shallow().hasClass( 'dashicons-wordpress' ) ).toBe( true ); - expect( iconButton.find( '.test' ).shallow().text() ).toBe( 'Test' ); + const iconButton = shallow( + <Button + icon="wordpress" + children={ <p className="test">Test</p> } + /> + ); + expect( + iconButton + .find( 'Icon' ) + .dive() + .shallow() + .hasClass( 'dashicons-wordpress' ) + ).toBe( true ); + expect( + iconButton + .find( '.test' ) + .shallow() + .text() + ).toBe( 'Test' ); } ); it( 'should add an aria-label when the label property is used, with Tooltip wrapper', () => { - const iconButton = shallow( <Button icon="WordPress" label="WordPress" /> ); + const iconButton = shallow( + <Button icon="WordPress" label="WordPress" /> + ); expect( iconButton.name() ).toBe( 'Tooltip' ); expect( iconButton.prop( 'text' ) ).toBe( 'WordPress' ); - expect( iconButton.find( 'button' ).prop( 'aria-label' ) ).toBe( 'WordPress' ); + expect( iconButton.find( 'button' ).prop( 'aria-label' ) ).toBe( + 'WordPress' + ); } ); it( 'should support explicit aria-label override', () => { @@ -112,24 +140,40 @@ describe( 'Button', () => { } ); it( 'should allow tooltip disable', () => { - const iconButton = shallow( <Button icon="WordPress" label="WordPress" showTooltip={ false } /> ); + const iconButton = shallow( + <Button + icon="WordPress" + label="WordPress" + showTooltip={ false } + /> + ); expect( iconButton.name() ).toBe( 'button' ); expect( iconButton.prop( 'aria-label' ) ).toBe( 'WordPress' ); } ); it( 'should show the tooltip for empty children', () => { - const iconButton = shallow( <Button icon="WordPress" label="WordPress" children={ [] } /> ); + const iconButton = shallow( + <Button icon="WordPress" label="WordPress" children={ [] } /> + ); expect( iconButton.name() ).toBe( 'Tooltip' ); expect( iconButton.prop( 'text' ) ).toBe( 'WordPress' ); } ); it( 'should not show the tooltip when icon and children defined', () => { - const iconButton = shallow( <Button icon="WordPress" label="WordPress">Children</Button> ); + const iconButton = shallow( + <Button icon="WordPress" label="WordPress"> + Children + </Button> + ); expect( iconButton.name() ).toBe( 'button' ); } ); it( 'should force showing the tooltip even if icon and children defined', () => { - const iconButton = shallow( <Button icon="WordPress" label="WordPress" showTooltip>Children</Button> ); + const iconButton = shallow( + <Button icon="WordPress" label="WordPress" showTooltip> + Children + </Button> + ); expect( iconButton.name() ).toBe( 'Tooltip' ); } ); } ); @@ -143,13 +187,17 @@ describe( 'Button', () => { } ); it( 'should allow for the passing of the target prop when a link is created', () => { - const button = shallow( <Button href="https://wordpress.org/" target="_blank" /> ); + const button = shallow( + <Button href="https://wordpress.org/" target="_blank" /> + ); expect( button.prop( 'target' ) ).toBe( '_blank' ); } ); it( 'should become a button again when disabled is supplied', () => { - const button = shallow( <Button href="https://wordpress.org/" disabled /> ); + const button = shallow( + <Button href="https://wordpress.org/" disabled /> + ); expect( button.type() ).toBe( 'button' ); } ); @@ -159,7 +207,9 @@ describe( 'Button', () => { it( 'should enable access to DOM element', () => { const ref = createRef(); - TestUtils.renderIntoDocument( <ButtonWithForwardedRef ref={ ref } /> ); + TestUtils.renderIntoDocument( + <ButtonWithForwardedRef ref={ ref } /> + ); expect( ref.current.type ).toBe( 'button' ); } ); } ); diff --git a/packages/components/src/card/stories/media.js b/packages/components/src/card/stories/media.js index d06eb5197fcfc6..a172278117e6f0 100644 --- a/packages/components/src/card/stories/media.js +++ b/packages/components/src/card/stories/media.js @@ -122,11 +122,11 @@ const HorizontallyAlignedCard = styled( Card )` flex-direction: row-reverse; } - ${ StyledCardBody } { + ${StyledCardBody} { flex: 1; } - ${ StyledCardMedia } { + ${StyledCardMedia} { &.is-left { border-radius: 3px 0 0 3px; } diff --git a/packages/components/src/card/styles/card-styles.js b/packages/components/src/card/styles/card-styles.js index 95375baf3198e4..1afba2ab42f639 100644 --- a/packages/components/src/card/styles/card-styles.js +++ b/packages/components/src/card/styles/card-styles.js @@ -22,33 +22,33 @@ export const styleProps = { const { borderColor, borderRadius, backgroundShady } = styleProps; export const CardUI = styled.div` - background: ${ color( 'white' ) }; + background: ${color( 'white' )}; box-sizing: border-box; - border-radius: ${ borderRadius }; - border: 1px solid ${ borderColor }; + border-radius: ${borderRadius}; + border: 1px solid ${borderColor}; - ${ handleBorderless }; + ${handleBorderless}; &.is-elevated { - box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2), - 0px 1px 1px 0px rgba(0, 0, 0, 0.14), - 0px 2px 1px -1px rgba(0, 0, 0, 0.12); + box-shadow: 0px 1px 3px 0px rgba( 0, 0, 0, 0.2 ), + 0px 1px 1px 0px rgba( 0, 0, 0, 0.14 ), + 0px 2px 1px -1px rgba( 0, 0, 0, 0.12 ); } `; export const HeaderUI = styled.div` - border-bottom: 1px solid ${ borderColor }; - border-top-left-radius: ${ borderRadius }; - border-top-right-radius: ${ borderRadius }; + border-bottom: 1px solid ${borderColor}; + border-top-left-radius: ${borderRadius}; + border-top-right-radius: ${borderRadius}; box-sizing: border-box; &:last-child { border-bottom: none; } - ${ headerFooterSizes }; - ${ handleBorderless }; - ${ handleShady }; + ${headerFooterSizes}; + ${handleBorderless}; + ${handleShady}; `; export const MediaUI = styled.div` @@ -64,41 +64,41 @@ export const MediaUI = styled.div` } &:first-of-type { - border-top-left-radius: ${ borderRadius }; - border-top-right-radius: ${ borderRadius }; + border-top-left-radius: ${borderRadius}; + border-top-right-radius: ${borderRadius}; } &:last-of-type { - border-bottom-left-radius: ${ borderRadius }; - border-bottom-right-radius: ${ borderRadius }; + border-bottom-left-radius: ${borderRadius}; + border-bottom-right-radius: ${borderRadius}; } `; export const BodyUI = styled.div` box-sizing: border-box; - ${ bodySize }; - ${ handleShady }; + ${bodySize}; + ${handleShady}; `; export const FooterUI = styled.div` - border-top: 1px solid ${ borderColor }; - border-bottom-left-radius: ${ borderRadius }; - border-bottom-right-radius: ${ borderRadius }; + border-top: 1px solid ${borderColor}; + border-bottom-left-radius: ${borderRadius}; + border-bottom-right-radius: ${borderRadius}; box-sizing: border-box; &:first-of-type { border-top: none; } - ${ headerFooterSizes }; - ${ handleBorderless }; - ${ handleShady }; + ${headerFooterSizes}; + ${handleBorderless}; + ${handleShady}; `; export const DividerUI = styled( HorizontalRule )` all: unset; - border-top: 1px solid ${ borderColor }; + border-top: 1px solid ${borderColor}; box-sizing: border-box; display: block; height: 0; diff --git a/packages/components/src/checkbox-control/index.js b/packages/components/src/checkbox-control/index.js index 301f0953d16d1d..e6abcb5fadbfc1 100644 --- a/packages/components/src/checkbox-control/index.js +++ b/packages/components/src/checkbox-control/index.js @@ -9,13 +9,26 @@ import { Icon, check } from '@wordpress/icons'; */ import BaseControl from '../base-control'; -export default function CheckboxControl( { label, className, heading, checked, help, onChange, ...props } ) { +export default function CheckboxControl( { + label, + className, + heading, + checked, + help, + onChange, + ...props +} ) { const instanceId = useInstanceId( CheckboxControl ); const id = `inspector-checkbox-control-${ instanceId }`; const onChangeValue = ( event ) => onChange( event.target.checked ); return ( - <BaseControl label={ heading } id={ id } help={ help } className={ className }> + <BaseControl + label={ heading } + id={ id } + help={ help } + className={ className } + > <span className="components-checkbox-control__input-container"> <input id={ id } @@ -27,9 +40,18 @@ export default function CheckboxControl( { label, className, heading, checked, h aria-describedby={ !! help ? id + '__help' : undefined } { ...props } /> - { checked ? <Icon icon={ check } className="components-checkbox-control__checked" role="presentation" /> : null } + { checked ? ( + <Icon + icon={ check } + className="components-checkbox-control__checked" + role="presentation" + /> + ) : null } </span> - <label className="components-checkbox-control__label" htmlFor={ id }> + <label + className="components-checkbox-control__label" + htmlFor={ id } + > { label } </label> </BaseControl> diff --git a/packages/components/src/checkbox-control/stories/index.js b/packages/components/src/checkbox-control/stories/index.js index 30f1e5af765d7e..b0819664eb8b03 100644 --- a/packages/components/src/checkbox-control/stories/index.js +++ b/packages/components/src/checkbox-control/stories/index.js @@ -13,7 +13,10 @@ import { useState } from '@wordpress/element'; */ import CheckboxControl from '../'; -export default { title: 'Components/CheckboxControl', component: CheckboxControl }; +export default { + title: 'Components/CheckboxControl', + component: CheckboxControl, +}; const CheckboxControlWithState = ( { checked, ...props } ) => { const [ isChecked, setChecked ] = useState( checked ); @@ -30,12 +33,7 @@ const CheckboxControlWithState = ( { checked, ...props } ) => { export const _default = () => { const label = text( 'Label', 'Is author' ); - return ( - <CheckboxControlWithState - label={ label } - checked - /> - ); + return <CheckboxControlWithState label={ label } checked />; }; export const all = () => { diff --git a/packages/components/src/circular-option-picker/index.js b/packages/components/src/circular-option-picker/index.js index 53b4c3016c957d..b8878ad32633a1 100644 --- a/packages/components/src/circular-option-picker/index.js +++ b/packages/components/src/circular-option-picker/index.js @@ -15,28 +15,24 @@ import Button from '../button'; import Dropdown from '../dropdown'; import Tooltip from '../tooltip'; -function Option( { - className, - isSelected, - tooltipText, - ...additionalProps -} ) { +function Option( { className, isSelected, tooltipText, ...additionalProps } ) { const optionButton = ( <Button isPressed={ isSelected } className={ classnames( className, - 'components-circular-option-picker__option', + 'components-circular-option-picker__option' ) } { ...additionalProps } /> ); return ( <div className="components-circular-option-picker__option-wrapper"> - { tooltipText ? - ( <Tooltip text={ tooltipText }>{ optionButton }</Tooltip> ) : + { tooltipText ? ( + <Tooltip text={ tooltipText }>{ optionButton }</Tooltip> + ) : ( optionButton - } + ) } { isSelected && <Icon icon={ check } /> } </div> ); @@ -69,11 +65,7 @@ function DropdownLinkAction( { ); } -function ButtonAction( { - className, - children, - ...additionalProps -} ) { +function ButtonAction( { className, children, ...additionalProps } ) { return ( <Button className={ classnames( @@ -96,7 +88,12 @@ export default function CircularOptionPicker( { children, } ) { return ( - <div className={ classnames( 'components-circular-option-picker', className ) }> + <div + className={ classnames( + 'components-circular-option-picker', + className + ) } + > { options } { children } { actions && ( diff --git a/packages/components/src/clipboard-button/index.js b/packages/components/src/clipboard-button/index.js index 091b3a26df7e95..80741a18b19ed4 100644 --- a/packages/components/src/clipboard-button/index.js +++ b/packages/components/src/clipboard-button/index.js @@ -27,7 +27,7 @@ class ClipboardButton extends Component { const { getText, onCopy } = this; const container = this.containerRef.current; - this.clipboard = new Clipboard( container.firstChild, { + this.clipboard = new Clipboard( container.firstChild, { text: getText, container, } ); @@ -71,9 +71,18 @@ class ClipboardButton extends Component { } render() { - // Disable reason: Exclude from spread props passed to Button - // eslint-disable-next-line no-unused-vars - const { className, children, onCopy, onFinishCopy, text, ...buttonProps } = this.props; + const { + className, + children, + // Disable reason: Exclude from spread props passed to Button + // eslint-disable-next-line no-unused-vars + onCopy, + // eslint-disable-next-line no-unused-vars + onFinishCopy, + // eslint-disable-next-line no-unused-vars + text, + ...buttonProps + } = this.props; const classes = classnames( 'components-clipboard-button', className ); // Workaround for inconsistent behavior in Safari, where <textarea> is not diff --git a/packages/components/src/clipboard-button/stories/index.js b/packages/components/src/clipboard-button/stories/index.js index d6adbaf0b9203a..67052bbeab7cf1 100644 --- a/packages/components/src/clipboard-button/stories/index.js +++ b/packages/components/src/clipboard-button/stories/index.js @@ -36,5 +36,7 @@ export const _default = () => { const isPrimary = boolean( 'Is primary', true ); const copyText = text( 'Text', 'Text' ); - return <ClipboardButtonWithState isPrimary={ isPrimary } text={ copyText } />; + return ( + <ClipboardButtonWithState isPrimary={ isPrimary } text={ copyText } /> + ); }; diff --git a/packages/components/src/color-indicator/stories/index.js b/packages/components/src/color-indicator/stories/index.js index 13ccee622f7d00..f01d534ad03353 100644 --- a/packages/components/src/color-indicator/stories/index.js +++ b/packages/components/src/color-indicator/stories/index.js @@ -15,7 +15,5 @@ export default { export const _default = () => { const color = text( 'Color', '#0073aa' ); - return ( - <ColorIndicator colorValue={ color } /> - ); + return <ColorIndicator colorValue={ color } />; }; diff --git a/packages/components/src/color-indicator/test/index.js b/packages/components/src/color-indicator/test/index.js index 74cc582858b17e..dfa595ff2b4092 100644 --- a/packages/components/src/color-indicator/test/index.js +++ b/packages/components/src/color-indicator/test/index.js @@ -10,7 +10,9 @@ import ColorIndicator from '../'; describe( 'ColorIndicator', () => { it( 'matches the snapshot', () => { - const wrapper = shallow( <ColorIndicator aria-label="sample label" colorValue="#fff" /> ); + const wrapper = shallow( + <ColorIndicator aria-label="sample label" colorValue="#fff" /> + ); expect( wrapper ).toMatchSnapshot(); } ); diff --git a/packages/components/src/color-palette/index.js b/packages/components/src/color-palette/index.js index 9a0b1a1af4ab0f..0a3d1f0ad17483 100644 --- a/packages/components/src/color-palette/index.js +++ b/packages/components/src/color-palette/index.js @@ -23,33 +23,31 @@ export default function ColorPalette( { onChange, value, } ) { - const clearColor = useCallback( - () => onChange( undefined ), - [ onChange ] - ); - const colorOptions = useMemo( - () => { - return map( colors, ( { color, name } ) => ( - <CircularOptionPicker.Option - key={ color } - isSelected={ value === color } - tooltipText={ name || - // translators: %s: color hex code e.g: "#f00". - sprintf( __( 'Color code: %s' ), color ) - } - style={ { color } } - onClick={ value === color ? clearColor : () => onChange( color ) } - aria-label={ name ? - // translators: %s: The name of the color e.g: "vivid red". - sprintf( __( 'Color: %s' ), name ) : - // translators: %s: color hex code e.g: "#f00". - sprintf( __( 'Color code: %s' ), color ) - } - /> - ) ); - }, - [ colors, value, onChange, clearColor ] - ); + const clearColor = useCallback( () => onChange( undefined ), [ onChange ] ); + const colorOptions = useMemo( () => { + return map( colors, ( { color, name } ) => ( + <CircularOptionPicker.Option + key={ color } + isSelected={ value === color } + tooltipText={ + name || + // translators: %s: color hex code e.g: "#f00". + sprintf( __( 'Color code: %s' ), color ) + } + style={ { color } } + onClick={ + value === color ? clearColor : () => onChange( color ) + } + aria-label={ + name + ? // translators: %s: The name of the color e.g: "vivid red". + sprintf( __( 'Color: %s' ), name ) + : // translators: %s: color hex code e.g: "#f00". + sprintf( __( 'Color code: %s' ), color ) + } + /> + ) ); + }, [ colors, value, onChange, clearColor ] ); const renderCustomColorPicker = useCallback( () => ( <ColorPicker @@ -65,13 +63,14 @@ export default function ColorPalette( { <CircularOptionPicker className={ className } options={ colorOptions } - actions={ ( + actions={ <> { ! disableCustomColors && ( <CircularOptionPicker.DropdownLinkAction dropdownProps={ { renderContent: renderCustomColorPicker, - contentClassName: 'components-color-palette__picker', + contentClassName: + 'components-color-palette__picker', } } buttonProps={ { 'aria-label': __( 'Custom color picker' ), @@ -80,12 +79,14 @@ export default function ColorPalette( { /> ) } { !! clearable && ( - <CircularOptionPicker.ButtonAction onClick={ clearColor }> + <CircularOptionPicker.ButtonAction + onClick={ clearColor } + > { __( 'Clear' ) } </CircularOptionPicker.ButtonAction> ) } </> - ) } + } /> ); } diff --git a/packages/components/src/color-palette/test/index.js b/packages/components/src/color-palette/test/index.js index c4022ba48c4c7b..22d28ed762b4f7 100644 --- a/packages/components/src/color-palette/test/index.js +++ b/packages/components/src/color-palette/test/index.js @@ -9,11 +9,21 @@ import { mount, shallow } from 'enzyme'; import ColorPalette from '../'; describe( 'ColorPalette', () => { - const colors = [ { name: 'red', color: '#f00' }, { name: 'white', color: '#fff' }, { name: 'blue', color: '#00f' } ]; + const colors = [ + { name: 'red', color: '#f00' }, + { name: 'white', color: '#fff' }, + { name: 'blue', color: '#00f' }, + ]; const currentColor = '#f00'; const onChange = jest.fn(); - const wrapper = mount( <ColorPalette colors={ colors } value={ currentColor } onChange={ onChange } /> ); + const wrapper = mount( + <ColorPalette + colors={ colors } + value={ currentColor } + onChange={ onChange } + /> + ); const buttons = wrapper.find( 'Option button' ); beforeEach( () => { @@ -29,7 +39,9 @@ describe( 'ColorPalette', () => { } ); test( 'should call onClick on an active button with undefined', () => { - const activeButton = buttons.findWhere( ( button ) => button.hasClass( 'is-pressed' ) ); + const activeButton = buttons.findWhere( ( button ) => + button.hasClass( 'is-pressed' ) + ); activeButton.simulate( 'click' ); expect( onChange ).toHaveBeenCalledTimes( 1 ); @@ -37,7 +49,9 @@ describe( 'ColorPalette', () => { } ); test( 'should call onClick on an inactive button', () => { - const inactiveButton = buttons.findWhere( ( button ) => ! button.hasClass( 'is-pressed' ) ).first(); + const inactiveButton = buttons + .findWhere( ( button ) => ! button.hasClass( 'is-pressed' ) ) + .first(); inactiveButton.simulate( 'click' ); expect( onChange ).toHaveBeenCalledTimes( 1 ); @@ -55,7 +69,16 @@ describe( 'ColorPalette', () => { } ); test( 'should allow disabling custom color picker', () => { - expect( shallow( <ColorPalette colors={ colors } disableCustomColors={ true } value={ currentColor } onChange={ onChange } /> ) ).toMatchSnapshot(); + expect( + shallow( + <ColorPalette + colors={ colors } + disableCustomColors={ true } + value={ currentColor } + onChange={ onChange } + /> + ) + ).toMatchSnapshot(); } ); describe( 'Dropdown', () => { @@ -69,7 +92,9 @@ describe( 'ColorPalette', () => { const isOpen = true; const onToggle = jest.fn(); - const renderedToggleButton = shallow( dropdown.props().renderToggle( { isOpen, onToggle } ) ); + const renderedToggleButton = shallow( + dropdown.props().renderToggle( { isOpen, onToggle } ) + ); test( 'should render dropdown content', () => { expect( renderedToggleButton ).toMatchSnapshot(); diff --git a/packages/components/src/color-picker/alpha.js b/packages/components/src/color-picker/alpha.js index eb6e889e7ca470..5a550d975a1983 100644 --- a/packages/components/src/color-picker/alpha.js +++ b/packages/components/src/color-picker/alpha.js @@ -75,7 +75,8 @@ export class Alpha extends Component { decrease( amount = 0.01 ) { const { hsl, onChange = noop } = this.props; - const intValue = parseInt( hsl.a * 100, 10 ) - parseInt( amount * 100, 10 ); + const intValue = + parseInt( hsl.a * 100, 10 ) - parseInt( amount * 100, 10 ); const change = { h: hsl.h, s: hsl.s, @@ -88,7 +89,11 @@ export class Alpha extends Component { handleChange( e ) { const { onChange = noop } = this.props; - const change = calculateAlphaChange( e, this.props, this.container.current ); + const change = calculateAlphaChange( + e, + this.props, + this.container.current + ); if ( change ) { onChange( change, e ); } @@ -152,7 +157,8 @@ export class Alpha extends Component { ref={ this.container } onMouseDown={ this.handleMouseDown } onTouchMove={ this.handleChange } - onTouchStart={ this.handleChange }> + onTouchStart={ this.handleChange } + > <div tabIndex="0" role="slider" diff --git a/packages/components/src/color-picker/hue.js b/packages/components/src/color-picker/hue.js index 0c97020d89efdc..7261b729a29477 100644 --- a/packages/components/src/color-picker/hue.js +++ b/packages/components/src/color-picker/hue.js @@ -87,7 +87,11 @@ export class Hue extends Component { handleChange( e ) { const { onChange = noop } = this.props; - const change = calculateHueChange( e, this.props, this.container.current ); + const change = calculateHueChange( + e, + this.props, + this.container.current + ); if ( change ) { onChange( change, e ); } @@ -144,7 +148,8 @@ export class Hue extends Component { ref={ this.container } onMouseDown={ this.handleMouseDown } onTouchMove={ this.handleChange } - onTouchStart={ this.handleChange }> + onTouchStart={ this.handleChange } + > <div tabIndex="0" role="slider" @@ -152,16 +157,21 @@ export class Hue extends Component { aria-valuemin="359" aria-valuenow={ hsl.h } aria-orientation="horizontal" - aria-label={ __( 'Hue value in degrees, from 0 to 359.' ) } + aria-label={ __( + 'Hue value in degrees, from 0 to 359.' + ) } aria-describedby={ `components-color-picker__hue-description-${ instanceId }` } className="components-color-picker__hue-pointer" style={ pointerLocation } onKeyDown={ this.preventKeyEvents } /> - <VisuallyHidden as="p" + <VisuallyHidden + as="p" id={ `components-color-picker__hue-description-${ instanceId }` } > - { __( 'Move the arrow left or right to change hue.' ) } + { __( + 'Move the arrow left or right to change hue.' + ) } </VisuallyHidden> </div> { /* eslint-enable jsx-a11y/no-static-element-interactions */ } @@ -171,7 +181,4 @@ export class Hue extends Component { } } -export default compose( - pure, - withInstanceId -)( Hue ); +export default compose( pure, withInstanceId )( Hue ); diff --git a/packages/components/src/color-picker/index.js b/packages/components/src/color-picker/index.js index 74c42f3cee8f8e..957a4e578e1f59 100644 --- a/packages/components/src/color-picker/index.js +++ b/packages/components/src/color-picker/index.js @@ -49,20 +49,23 @@ const toLowerCase = ( value ) => String( value ).toLowerCase(); const isValueEmpty = ( data ) => { if ( data.source === 'hex' && ! data.hex ) { return true; - } else if ( data.source === 'hsl' && ( ! data.h || ! data.s || ! data.l ) ) { + } else if ( + data.source === 'hsl' && + ( ! data.h || ! data.s || ! data.l ) + ) { return true; - } else if ( data.source === 'rgb' && ( + } else if ( + data.source === 'rgb' && ( ! data.r || ! data.g || ! data.b ) && ( ! data.h || ! data.s || ! data.v || ! data.a ) && ( ! data.h || ! data.s || ! data.l || ! data.a ) - ) ) { + ) { return true; } return false; }; -const isValidColor = ( colors ) => colors.hex ? - isValidHex( colors.hex ) : - simpleCheckForValidColor( colors ); +const isValidColor = ( colors ) => + colors.hex ? isValidHex( colors.hex ) : simpleCheckForValidColor( colors ); /** * Function that creates the new color object @@ -126,13 +129,14 @@ export default class ColorPicker extends Component { if ( isValidColor( data ) ) { const colors = colorToState( data, data.h || oldHue ); - this.setState( { - ...colors, - draftHex: toLowerCase( colors.hex ), - draftHsl: colors.hsl, - draftRgb: colors.rgb, - }, - debounce( partial( onChangeComplete, colors ), 100 ) + this.setState( + { + ...colors, + draftHex: toLowerCase( colors.hex ), + draftHsl: colors.hsl, + draftRgb: colors.rgb, + }, + debounce( partial( onChangeComplete, colors ), 100 ) ); } } @@ -209,7 +213,8 @@ export default class ColorPicker extends Component { <div className="components-color-picker__active" style={ { - backgroundColor: color && color.toRgbString(), + backgroundColor: + color && color.toRgbString(), } } /> </div> diff --git a/packages/components/src/color-picker/saturation.js b/packages/components/src/color-picker/saturation.js index d2e495923b617d..3b9666d734588f 100644 --- a/packages/components/src/color-picker/saturation.js +++ b/packages/components/src/color-picker/saturation.js @@ -87,11 +87,7 @@ export class Saturation extends Component { brighten( amount = 0.01 ) { const { hsv, onChange = noop } = this.props; - const intValue = clamp( - hsv.v + Math.round( amount * 100 ), - 0, - 100 - ); + const intValue = clamp( hsv.v + Math.round( amount * 100 ), 0, 100 ); const change = { h: hsv.h, s: hsv.s, @@ -105,7 +101,11 @@ export class Saturation extends Component { handleChange( e ) { const { onChange = noop } = this.props; - const change = calculateSaturationChange( e, this.props, this.container.current ); + const change = calculateSaturationChange( + e, + this.props, + this.container.current + ); this.throttle( onChange, change, e ); } @@ -134,7 +134,7 @@ export class Saturation extends Component { render() { const { hsv, hsl, instanceId } = this.props; const pointerLocation = { - top: `${ -( hsv.v ) + 100 }%`, + top: `${ -hsv.v + 100 }%`, left: `${ hsv.s }%`, }; const shortcuts = { @@ -174,7 +174,8 @@ export class Saturation extends Component { onKeyDown={ this.preventKeyEvents } /> <VisuallyHidden - id={ `color-picker-saturation-${ instanceId }` }> + id={ `color-picker-saturation-${ instanceId }` } + > { __( 'Use your arrow keys to change the base color. Move up to lighten the color, down to darken, left to decrease saturation, and right to increase saturation.' ) } @@ -186,7 +187,4 @@ export class Saturation extends Component { } } -export default compose( - pure, - withInstanceId -)( Saturation ); +export default compose( pure, withInstanceId )( Saturation ); diff --git a/packages/components/src/color-picker/stories/index.js b/packages/components/src/color-picker/stories/index.js index 3442c1b42f745d..67bbc93d10c1be 100644 --- a/packages/components/src/color-picker/stories/index.js +++ b/packages/components/src/color-picker/stories/index.js @@ -1,4 +1,3 @@ - /** * WordPress dependencies */ @@ -23,17 +22,9 @@ const ColorPickerWithState = ( { ...props } ) => { }; export const _default = () => { - return ( - <ColorPickerWithState - disableAlpha - /> - ); + return <ColorPickerWithState disableAlpha />; }; export const alphaEnabled = () => { - return ( - <ColorPickerWithState - disableAlpha={ false } - /> - ); + return <ColorPickerWithState disableAlpha={ false } />; }; diff --git a/packages/components/src/color-picker/test/index.js b/packages/components/src/color-picker/test/index.js index 57e512bd65b8e6..82e9d6e1d01c1c 100644 --- a/packages/components/src/color-picker/test/index.js +++ b/packages/components/src/color-picker/test/index.js @@ -37,7 +37,9 @@ describe( 'ColorPicker', () => { disableAlpha /> ); - testRenderer.root.findByType( 'input' ).props.onChange( { target: { value: '#ABC' } } ); + testRenderer.root + .findByType( 'input' ) + .props.onChange( { target: { value: '#ABC' } } ); expect( testRenderer.toJSON() ).toMatchSnapshot(); } ); @@ -50,7 +52,9 @@ describe( 'ColorPicker', () => { disableAlpha /> ); - testRenderer.root.findByType( 'input' ).props.onChange( { target: { value: '#ABC' } } ); + testRenderer.root + .findByType( 'input' ) + .props.onChange( { target: { value: '#ABC' } } ); testRenderer.root.findByType( 'input' ).props.onBlur(); expect( testRenderer.toJSON() ).toMatchSnapshot(); } ); @@ -64,8 +68,12 @@ describe( 'ColorPicker', () => { disableAlpha /> ); - testRenderer.root.findByType( 'input' ).props.onChange( { target: { value: '#ABC' } } ); - testRenderer.root.findByType( 'input' ).props.onKeyDown( { keyCode: UP } ); + testRenderer.root + .findByType( 'input' ) + .props.onChange( { target: { value: '#ABC' } } ); + testRenderer.root + .findByType( 'input' ) + .props.onKeyDown( { keyCode: UP } ); expect( testRenderer.toJSON() ).toMatchSnapshot(); } ); @@ -78,8 +86,12 @@ describe( 'ColorPicker', () => { disableAlpha /> ); - testRenderer.root.findByType( 'input' ).props.onChange( { target: { value: '#ABC' } } ); - testRenderer.root.findByType( 'input' ).props.onKeyDown( { keyCode: DOWN } ); + testRenderer.root + .findByType( 'input' ) + .props.onChange( { target: { value: '#ABC' } } ); + testRenderer.root + .findByType( 'input' ) + .props.onKeyDown( { keyCode: DOWN } ); expect( testRenderer.toJSON() ).toMatchSnapshot(); } ); @@ -92,8 +104,12 @@ describe( 'ColorPicker', () => { disableAlpha /> ); - testRenderer.root.findByType( 'input' ).props.onChange( { target: { value: '#ABC' } } ); - testRenderer.root.findByType( 'input' ).props.onKeyDown( { keyCode: ENTER } ); + testRenderer.root + .findByType( 'input' ) + .props.onChange( { target: { value: '#ABC' } } ); + testRenderer.root + .findByType( 'input' ) + .props.onKeyDown( { keyCode: ENTER } ); expect( testRenderer.toJSON() ).toMatchSnapshot(); } ); } ); diff --git a/packages/components/src/color-picker/test/input.js b/packages/components/src/color-picker/test/input.js index 8e4bbd8f78d3c8..6ff7d8d3f90385 100644 --- a/packages/components/src/color-picker/test/input.js +++ b/packages/components/src/color-picker/test/input.js @@ -26,7 +26,9 @@ describe( 'Input ', () => { onChange={ onChange } /> ).root; - testInstance.findByType( 'input' ).props.onKeyDown( { keyCode: ENTER } ); + testInstance + .findByType( 'input' ) + .props.onKeyDown( { keyCode: ENTER } ); expect( onChange ).toHaveBeenCalledTimes( 1 ); expect( onChange ).toHaveBeenCalledWith( { source: 'rgb', @@ -47,7 +49,9 @@ describe( 'Input ', () => { onChange={ onChange } /> ).root; - testInstance.findByType( 'input' ).props.onKeyDown( { keyCode: UP } ); + testInstance + .findByType( 'input' ) + .props.onKeyDown( { keyCode: UP } ); expect( onChange ).toHaveBeenCalledTimes( 1 ); expect( onChange ).toHaveBeenCalledWith( { source: 'rgb', @@ -68,7 +72,9 @@ describe( 'Input ', () => { onChange={ onChange } /> ).root; - testInstance.findByType( 'input' ).props.onKeyDown( { keyCode: DOWN } ); + testInstance + .findByType( 'input' ) + .props.onKeyDown( { keyCode: DOWN } ); expect( onChange ).toHaveBeenCalledTimes( 1 ); expect( onChange ).toHaveBeenCalledWith( { source: 'rgb', @@ -89,7 +95,9 @@ describe( 'Input ', () => { onChange={ onChange } /> ).root; - testInstance.findByType( 'input' ).props.onChange( { target: { value: '#aaaaaa' } } ); + testInstance + .findByType( 'input' ) + .props.onChange( { target: { value: '#aaaaaa' } } ); expect( onChange ).toHaveBeenCalledTimes( 1 ); expect( onChange ).toHaveBeenCalledWith( { source: 'rgb', @@ -133,7 +141,9 @@ describe( 'Input ', () => { onChange={ onChange } /> ).root; - testInstance.findByType( 'input' ).props.onChange( { target: { value: '#aaaaa' } } ); + testInstance + .findByType( 'input' ) + .props.onChange( { target: { value: '#aaaaa' } } ); expect( onChange ).toHaveBeenCalledTimes( 1 ); expect( onChange ).toHaveBeenCalledWith( { source: 'rgb', @@ -155,7 +165,9 @@ describe( 'Input ', () => { onChange={ onChange } /> ).root; - testInstance.findByType( 'input' ).props.onKeyDown( { keyCode: SPACE } ); + testInstance + .findByType( 'input' ) + .props.onKeyDown( { keyCode: SPACE } ); expect( onChange ).not.toHaveBeenCalled(); } ); } ); diff --git a/packages/components/src/color-picker/utils.js b/packages/components/src/color-picker/utils.js index dada93c31c16ef..03f2ccc9efd575 100644 --- a/packages/components/src/color-picker/utils.js +++ b/packages/components/src/color-picker/utils.js @@ -110,7 +110,9 @@ export function isValidHex( hex ) { // disable hex4 and hex8 const lh = String( hex ).charAt( 0 ) === '#' ? 1 : 0; return ( - hex.length !== 4 + lh && hex.length < 7 + lh && tinycolor( hex ).isValid() + hex.length !== 4 + lh && + hex.length < 7 + lh && + tinycolor( hex ).isValid() ); } diff --git a/packages/components/src/custom-gradient-picker/constants.js b/packages/components/src/custom-gradient-picker/constants.js index 234fd270874c32..4a2c509753b363 100644 --- a/packages/components/src/custom-gradient-picker/constants.js +++ b/packages/components/src/custom-gradient-picker/constants.js @@ -1,10 +1,12 @@ export const INSERT_POINT_WIDTH = 23; export const GRADIENT_MARKERS_WIDTH = 18; -export const MINIMUM_DISTANCE_BETWEEN_INSERTER_AND_MARKER = ( INSERT_POINT_WIDTH + GRADIENT_MARKERS_WIDTH ) / 2; +export const MINIMUM_DISTANCE_BETWEEN_INSERTER_AND_MARKER = + ( INSERT_POINT_WIDTH + GRADIENT_MARKERS_WIDTH ) / 2; export const MINIMUM_ABSOLUTE_LEFT_POSITION = 5; export const MINIMUM_DISTANCE_BETWEEN_POINTS = 9; export const MINIMUM_SIGNIFICANT_MOVE = 5; -export const DEFAULT_GRADIENT = 'linear-gradient(135deg, rgba(6, 147, 227, 1) 0%, rgb(155, 81, 224) 100%)'; +export const DEFAULT_GRADIENT = + 'linear-gradient(135deg, rgba(6, 147, 227, 1) 0%, rgb(155, 81, 224) 100%)'; export const COLOR_POPOVER_PROPS = { className: 'components-custom-gradient-picker__color-picker-popover', position: 'top', diff --git a/packages/components/src/custom-gradient-picker/control-points.js b/packages/components/src/custom-gradient-picker/control-points.js index 5d6baafe9f46fb..4b114d73174398 100644 --- a/packages/components/src/custom-gradient-picker/control-points.js +++ b/packages/components/src/custom-gradient-picker/control-points.js @@ -1,4 +1,3 @@ - /** * External dependencies */ @@ -48,7 +47,12 @@ class ControlPointKeyboardMove extends Component { // to another editor area. event.stopPropagation(); const { gradientIndex, onChange, gradientAST } = this.props; - onChange( getGradientWithPositionAtIndexIncreased( gradientAST, gradientIndex ) ); + onChange( + getGradientWithPositionAtIndexIncreased( + gradientAST, + gradientIndex + ) + ); } decrease( event ) { @@ -56,7 +60,12 @@ class ControlPointKeyboardMove extends Component { // to another editor area. event.stopPropagation(); const { gradientIndex, onChange, gradientAST } = this.props; - onChange( getGradientWithPositionAtIndexDecreased( gradientAST, gradientIndex ) ); + onChange( + getGradientWithPositionAtIndexDecreased( + gradientAST, + gradientIndex + ) + ); } render() { const { children } = this.props; @@ -86,30 +95,28 @@ function ControlPointButton( { gradientAST={ gradientAST } > <Button - aria-label={ - sprintf( - // translators: %1$s: gradient position e.g: 70%, %2$s: gradient color code e.g: rgb(52,121,151). - __( 'Gradient control point at position %1$s with color code %2$s.' ), - position, - color - ) - } + aria-label={ sprintf( + // translators: %1$s: gradient position e.g: 70%, %2$s: gradient color code e.g: rgb(52,121,151). + __( + 'Gradient control point at position %1$s with color code %2$s.' + ), + position, + color + ) } aria-describedby={ descriptionId } aria-expanded={ isOpen } - className={ - classnames( - 'components-custom-gradient-picker__control-point-button', - { 'is-active': isOpen } - ) - } + className={ classnames( + 'components-custom-gradient-picker__control-point-button', + { + 'is-active': isOpen, + } + ) } style={ { left: position, } } { ...additionalProps } /> - <div - className="screen-reader-text" - id={ descriptionId }> + <div className="screen-reader-text" id={ descriptionId }> { __( 'Use your left or right arrow keys or drag and drop with the mouse to change the gradient position. Press the button to change the color or remove the control point.' ) } @@ -133,7 +140,7 @@ export default function ControlPoints( { const relativePosition = getHorizontalRelativeGradientPosition( event.clientX, gradientPickerDomRef.current, - GRADIENT_MARKERS_WIDTH, + GRADIENT_MARKERS_WIDTH ); const { gradientAST: referenceGradientAST, @@ -141,23 +148,39 @@ export default function ControlPoints( { significantMoveHappened, } = controlPointMoveState.current; if ( ! significantMoveHappened ) { - const initialPosition = referenceGradientAST.colorStops[ position ].length.value; - if ( Math.abs( initialPosition - relativePosition ) >= MINIMUM_SIGNIFICANT_MOVE ) { + const initialPosition = + referenceGradientAST.colorStops[ position ].length.value; + if ( + Math.abs( initialPosition - relativePosition ) >= + MINIMUM_SIGNIFICANT_MOVE + ) { controlPointMoveState.current.significantMoveHappened = true; } } - if ( ! isControlPointOverlapping( referenceGradientAST, relativePosition, position ) ) { + if ( + ! isControlPointOverlapping( + referenceGradientAST, + relativePosition, + position + ) + ) { onChange( - getGradientWithPositionAtIndexChanged( referenceGradientAST, position, relativePosition ) + getGradientWithPositionAtIndexChanged( + referenceGradientAST, + position, + relativePosition + ) ); } }; const cleanEventListeners = () => { if ( - window && window.removeEventListener && - controlPointMoveState.current && controlPointMoveState.current.listenersActivated + window && + window.removeEventListener && + controlPointMoveState.current && + controlPointMoveState.current.listenersActivated ) { window.removeEventListener( 'mousemove', onMouseMove ); window.removeEventListener( 'mouseup', cleanEventListeners ); @@ -173,8 +196,9 @@ export default function ControlPoints( { }, [] ); return markerPoints.map( - ( point, index ) => ( - point && ignoreMarkerPosition !== point.positionValue && ( + ( point, index ) => + point && + ignoreMarkerPosition !== point.positionValue && ( <Dropdown key={ index } onClose={ onStopControlPointChange } @@ -184,7 +208,8 @@ export default function ControlPoints( { onClick={ () => { if ( controlPointMoveState.current && - controlPointMoveState.current.significantMoveHappened + controlPointMoveState.current + .significantMoveHappened ) { return; } @@ -200,8 +225,14 @@ export default function ControlPoints( { listenersActivated: true, }; onStartControlPointChange(); - window.addEventListener( 'mousemove', onMouseMove ); - window.addEventListener( 'mouseup', cleanEventListeners ); + window.addEventListener( + 'mousemove', + onMouseMove + ); + window.addEventListener( + 'mouseup', + cleanEventListeners + ); } } } isOpen={ isOpen } @@ -218,7 +249,11 @@ export default function ControlPoints( { color={ point.color } onChangeComplete={ ( { rgb } ) => { onChange( - getGradientWithColorAtIndexChanged( gradientAST, index, rgb ) + getGradientWithColorAtIndexChanged( + gradientAST, + index, + rgb + ) ); } } /> @@ -226,7 +261,10 @@ export default function ControlPoints( { className="components-custom-gradient-picker__remove-control-point" onClick={ () => { onChange( - getGradientWithControlPointRemoved( gradientAST, index ) + getGradientWithControlPointRemoved( + gradientAST, + index + ) ); onClose(); } } @@ -239,6 +277,5 @@ export default function ControlPoints( { popoverProps={ COLOR_POPOVER_PROPS } /> ) - ) ); } diff --git a/packages/components/src/custom-gradient-picker/index.js b/packages/components/src/custom-gradient-picker/index.js index f5d511a7308157..9fb32453807e0f 100644 --- a/packages/components/src/custom-gradient-picker/index.js +++ b/packages/components/src/custom-gradient-picker/index.js @@ -1,4 +1,3 @@ - /** * External dependencies */ @@ -58,7 +57,10 @@ function InsertPoint( { className="components-custom-gradient-picker__insert-point" icon={ plusCircle } style={ { - left: insertPosition !== null ? `${ insertPosition }%` : undefined, + left: + insertPosition !== null + ? `${ insertPosition }%` + : undefined, } } /> ) } @@ -67,9 +69,17 @@ function InsertPoint( { onChangeComplete={ ( { rgb } ) => { let newGradient; if ( alreadyInsertedPoint ) { - newGradient = getGradientWithColorAtPositionChanged( gradientAST, insertPosition, rgb ); + newGradient = getGradientWithColorAtPositionChanged( + gradientAST, + insertPosition, + rgb + ); } else { - newGradient = getGradientWithColorStopAdded( gradientAST, insertPosition, rgb ); + newGradient = getGradientWithColorStopAdded( + gradientAST, + insertPosition, + rgb + ); setAlreadyInsertedPoint( true ); } onChange( newGradient ); @@ -162,16 +172,18 @@ export default function CustomGradientPicker( { value, onChange } ) { const insertPosition = getHorizontalRelativeGradientPosition( event.clientX, gradientPickerDomRef.current, - INSERT_POINT_WIDTH, + INSERT_POINT_WIDTH ); // If the insert point is close to an existing control point don't show it. - if ( some( - markerPoints, - ( { positionValue } ) => { - return Math.abs( insertPosition - positionValue ) < MINIMUM_DISTANCE_BETWEEN_POINTS; - } - ) ) { + if ( + some( markerPoints, ( { positionValue } ) => { + return ( + Math.abs( insertPosition - positionValue ) < + MINIMUM_DISTANCE_BETWEEN_POINTS + ); + } ) + ) { if ( gradientBarState.id === 'MOVING_INSERTER' ) { gradientBarStateDispatch( { type: 'STOP_INSERTER_MOVE' } ); } @@ -186,15 +198,15 @@ export default function CustomGradientPicker( { value, onChange } ) { }; const isMovingInserter = gradientBarState.id === 'MOVING_INSERTER'; - const isInsertingControlPoint = gradientBarState.id === 'INSERTING_CONTROL_POINT'; + const isInsertingControlPoint = + gradientBarState.id === 'INSERTING_CONTROL_POINT'; return ( <div ref={ gradientPickerDomRef } - className={ classnames( - 'components-custom-gradient-picker', - { 'has-gradient': hasGradient } - ) } + className={ classnames( 'components-custom-gradient-picker', { + 'has-gradient': hasGradient, + } ) } onMouseEnter={ onMouseEnterAndMove } onMouseMove={ onMouseEnterAndMove } style={ { @@ -209,24 +221,36 @@ export default function CustomGradientPicker( { value, onChange } ) { onChange={ onGradientStructureChange } gradientAST={ gradientAST } onOpenInserter={ () => { - gradientBarStateDispatch( { type: 'OPEN_INSERTER' } ); + gradientBarStateDispatch( { + type: 'OPEN_INSERTER', + } ); } } onCloseInserter={ () => { - gradientBarStateDispatch( { type: 'CLOSE_INSERTER' } ); + gradientBarStateDispatch( { + type: 'CLOSE_INSERTER', + } ); } } /> ) } <ControlPoints gradientPickerDomRef={ gradientPickerDomRef } - ignoreMarkerPosition={ isInsertingControlPoint ? gradientBarState.insertPosition : undefined } + ignoreMarkerPosition={ + isInsertingControlPoint + ? gradientBarState.insertPosition + : undefined + } markerPoints={ markerPoints } onChange={ onGradientStructureChange } gradientAST={ gradientAST } onStartControlPointChange={ () => { - gradientBarStateDispatch( { type: 'START_CONTROL_CHANGE' } ); + gradientBarStateDispatch( { + type: 'START_CONTROL_CHANGE', + } ); } } onStopControlPointChange={ () => { - gradientBarStateDispatch( { type: 'STOP_CONTROL_CHANGE' } ); + gradientBarStateDispatch( { + type: 'STOP_CONTROL_CHANGE', + } ); } } /> </div> diff --git a/packages/components/src/custom-gradient-picker/serializer.js b/packages/components/src/custom-gradient-picker/serializer.js index a76191e7ec4101..1714d12af1efca 100644 --- a/packages/components/src/custom-gradient-picker/serializer.js +++ b/packages/components/src/custom-gradient-picker/serializer.js @@ -12,7 +12,10 @@ export function serializeGradientPosition( { type, value } ) { } export function serializeGradientColorStop( { type, value, length } ) { - return `${ serializeGradientColor( { type, value } ) } ${ serializeGradientPosition( length ) }`; + return `${ serializeGradientColor( { + type, + value, + } ) } ${ serializeGradientPosition( length ) }`; } export function serializeGradientOrientation( orientation ) { @@ -24,8 +27,16 @@ export function serializeGradientOrientation( orientation ) { export function serializeGradient( { type, orientation, colorStops } ) { const serializedOrientation = serializeGradientOrientation( orientation ); - const serializedColorStops = colorStops.sort( ( colorStop1, colorStop2 ) => { - return get( colorStop1, [ 'length', 'value' ], 0 ) - get( colorStop2, [ 'length', 'value' ], 0 ); - } ).map( serializeGradientColorStop ); - return `${ type }(${ compact( [ serializedOrientation, ...serializedColorStops ] ).join( ',' ) })`; + const serializedColorStops = colorStops + .sort( ( colorStop1, colorStop2 ) => { + return ( + get( colorStop1, [ 'length', 'value' ], 0 ) - + get( colorStop2, [ 'length', 'value' ], 0 ) + ); + } ) + .map( serializeGradientColorStop ); + return `${ type }(${ compact( [ + serializedOrientation, + ...serializedColorStops, + ] ).join( ',' ) })`; } diff --git a/packages/components/src/custom-gradient-picker/test/serializer.js b/packages/components/src/custom-gradient-picker/test/serializer.js index fd2ab6f6bd421f..26fd7f6db4ca59 100644 --- a/packages/components/src/custom-gradient-picker/test/serializer.js +++ b/packages/components/src/custom-gradient-picker/test/serializer.js @@ -11,86 +11,134 @@ import { describe( 'It should serialize a gradient', () => { test( 'serializeGradientColor', () => { - expect( serializeGradientColor( - { type: 'rgba', value: [ 1, 2, 3, 0.5 ] } - ) ).toBe( 'rgba(1,2,3,0.5)' ); + expect( + serializeGradientColor( { type: 'rgba', value: [ 1, 2, 3, 0.5 ] } ) + ).toBe( 'rgba(1,2,3,0.5)' ); - expect( serializeGradientColor( - { type: 'rgb', value: [ 255, 0, 0 ] } - ) ).toBe( 'rgb(255,0,0)' ); + expect( + serializeGradientColor( { type: 'rgb', value: [ 255, 0, 0 ] } ) + ).toBe( 'rgb(255,0,0)' ); } ); test( 'serializeGradientPosition', () => { - expect( serializeGradientPosition( - { type: '%', value: 70 } - ) ).toBe( '70%' ); + expect( serializeGradientPosition( { type: '%', value: 70 } ) ).toBe( + '70%' + ); - expect( serializeGradientPosition( - { type: '%', value: 0 } - ) ).toBe( '0%' ); + expect( serializeGradientPosition( { type: '%', value: 0 } ) ).toBe( + '0%' + ); - expect( serializeGradientPosition( - { type: 'px', value: 4 } - ) ).toBe( '4px' ); + expect( serializeGradientPosition( { type: 'px', value: 4 } ) ).toBe( + '4px' + ); } ); test( 'serializeGradientColorStop', () => { - expect( serializeGradientColorStop( - { type: 'rgba', value: [ 1, 2, 3, 0.5 ], length: { type: '%', value: 70 } } - ) ).toBe( 'rgba(1,2,3,0.5) 70%' ); + expect( + serializeGradientColorStop( { + type: 'rgba', + value: [ 1, 2, 3, 0.5 ], + length: { type: '%', value: 70 }, + } ) + ).toBe( 'rgba(1,2,3,0.5) 70%' ); - expect( serializeGradientColorStop( - { type: 'rgb', value: [ 255, 0, 0 ], length: { type: '%', value: 0 } } - ) ).toBe( 'rgb(255,0,0) 0%' ); + expect( + serializeGradientColorStop( { + type: 'rgb', + value: [ 255, 0, 0 ], + length: { type: '%', value: 0 }, + } ) + ).toBe( 'rgb(255,0,0) 0%' ); - expect( serializeGradientColorStop( - { type: 'rgba', value: [ 1, 2, 3, 0.5 ], length: { type: 'px', value: 100 } } - ) ).toBe( 'rgba(1,2,3,0.5) 100px' ); + expect( + serializeGradientColorStop( { + type: 'rgba', + value: [ 1, 2, 3, 0.5 ], + length: { type: 'px', value: 100 }, + } ) + ).toBe( 'rgba(1,2,3,0.5) 100px' ); } ); test( 'serializeGradientOrientation', () => { - expect( serializeGradientOrientation( - { type: 'angular', value: 40 } - ) ).toBe( '40deg' ); + expect( + serializeGradientOrientation( { type: 'angular', value: 40 } ) + ).toBe( '40deg' ); - expect( serializeGradientOrientation( - { type: 'angular', value: 0 } - ) ).toBe( '0deg' ); + expect( + serializeGradientOrientation( { type: 'angular', value: 0 } ) + ).toBe( '0deg' ); } ); test( 'serializeGradient', () => { - expect( serializeGradient( - { + expect( + serializeGradient( { type: 'linear-gradient', orientation: { type: 'angular', value: 40 }, colorStops: [ - { type: 'rgba', value: [ 1, 2, 3, 0.5 ], length: { type: '%', value: 70 } }, - { type: 'rgba', value: [ 255, 1, 1, 0.9 ], length: { type: '%', value: 40 } }, + { + type: 'rgba', + value: [ 1, 2, 3, 0.5 ], + length: { type: '%', value: 70 }, + }, + { + type: 'rgba', + value: [ 255, 1, 1, 0.9 ], + length: { type: '%', value: 40 }, + }, ], - } - ) ).toBe( 'linear-gradient(40deg,rgba(255,1,1,0.9) 40%,rgba(1,2,3,0.5) 70%)' ); + } ) + ).toBe( + 'linear-gradient(40deg,rgba(255,1,1,0.9) 40%,rgba(1,2,3,0.5) 70%)' + ); - expect( serializeGradient( - { + expect( + serializeGradient( { type: 'linear-gradient', colorStops: [ - { type: 'rgba', value: [ 1, 2, 3, 0.5 ], length: { type: '%', value: 70 } }, - { type: 'rgba', value: [ 255, 1, 1, 0.9 ], length: { type: '%', value: 40 } }, + { + type: 'rgba', + value: [ 1, 2, 3, 0.5 ], + length: { type: '%', value: 70 }, + }, + { + type: 'rgba', + value: [ 255, 1, 1, 0.9 ], + length: { type: '%', value: 40 }, + }, ], - } - ) ).toBe( 'linear-gradient(rgba(255,1,1,0.9) 40%,rgba(1,2,3,0.5) 70%)' ); + } ) + ).toBe( 'linear-gradient(rgba(255,1,1,0.9) 40%,rgba(1,2,3,0.5) 70%)' ); - expect( serializeGradient( - { + expect( + serializeGradient( { type: 'linear-gradient', orientation: { type: 'angular', value: 0 }, colorStops: [ - { type: 'rgba', value: [ 1, 2, 3, 0.5 ], length: { type: '%', value: 0 } }, - { type: 'rgba', value: [ 255, 1, 1, 0.9 ], length: { type: '%', value: 40 } }, - { type: 'rgba', value: [ 1, 2, 3, 0.5 ], length: { type: '%', value: 100 } }, - { type: 'rgba', value: [ 10, 20, 30, 0.5 ], length: { type: '%', value: 20 } }, + { + type: 'rgba', + value: [ 1, 2, 3, 0.5 ], + length: { type: '%', value: 0 }, + }, + { + type: 'rgba', + value: [ 255, 1, 1, 0.9 ], + length: { type: '%', value: 40 }, + }, + { + type: 'rgba', + value: [ 1, 2, 3, 0.5 ], + length: { type: '%', value: 100 }, + }, + { + type: 'rgba', + value: [ 10, 20, 30, 0.5 ], + length: { type: '%', value: 20 }, + }, ], - } - ) ).toBe( 'linear-gradient(0deg,rgba(1,2,3,0.5) 0%,rgba(10,20,30,0.5) 20%,rgba(255,1,1,0.9) 40%,rgba(1,2,3,0.5) 100%)' ); + } ) + ).toBe( + 'linear-gradient(0deg,rgba(1,2,3,0.5) 0%,rgba(10,20,30,0.5) 20%,rgba(255,1,1,0.9) 40%,rgba(1,2,3,0.5) 100%)' + ); } ); } ); diff --git a/packages/components/src/custom-gradient-picker/utils.js b/packages/components/src/custom-gradient-picker/utils.js index 0ea49d4b311e63..cc2e7d087c7fce 100644 --- a/packages/components/src/custom-gradient-picker/utils.js +++ b/packages/components/src/custom-gradient-picker/utils.js @@ -29,7 +29,11 @@ function tinyColorRgbToGradientColorStop( { r, g, b, a } ) { }; } -export function getGradientWithColorStopAdded( gradientAST, relativePosition, rgbaColor ) { +export function getGradientWithColorStopAdded( + gradientAST, + relativePosition, + rgbaColor +) { const colorStop = tinyColorRgbToGradientColorStop( rgbaColor ); colorStop.length = { type: '%', @@ -37,14 +41,15 @@ export function getGradientWithColorStopAdded( gradientAST, relativePosition, rg }; return { ...gradientAST, - colorStops: [ - ...gradientAST.colorStops, - colorStop, - ], + colorStops: [ ...gradientAST.colorStops, colorStop ], }; } -export function getGradientWithPositionAtIndexChanged( gradientAST, index, relativePosition ) { +export function getGradientWithPositionAtIndexChanged( + gradientAST, + index, + relativePosition +) { return { ...gradientAST, colorStops: gradientAST.colorStops.map( @@ -64,41 +69,69 @@ export function getGradientWithPositionAtIndexChanged( gradientAST, index, relat }; } -export function isControlPointOverlapping( gradientAST, position, initialIndex ) { - const initialPosition = parseInt( gradientAST.colorStops[ initialIndex ].length.value ); +export function isControlPointOverlapping( + gradientAST, + position, + initialIndex +) { + const initialPosition = parseInt( + gradientAST.colorStops[ initialIndex ].length.value + ); const minPosition = Math.min( initialPosition, position ); const maxPosition = Math.max( initialPosition, position ); - return some( - gradientAST.colorStops, - ( { length }, index ) => { - const itemPosition = parseInt( length.value ); - return index !== initialIndex && ( - Math.abs( itemPosition - position ) < MINIMUM_DISTANCE_BETWEEN_POINTS || - ( minPosition < itemPosition && itemPosition < maxPosition ) - ); - } - ); + return some( gradientAST.colorStops, ( { length }, index ) => { + const itemPosition = parseInt( length.value ); + return ( + index !== initialIndex && + ( Math.abs( itemPosition - position ) < + MINIMUM_DISTANCE_BETWEEN_POINTS || + ( minPosition < itemPosition && itemPosition < maxPosition ) ) + ); + } ); } -function getGradientWithPositionAtIndexSummed( gradientAST, index, valueToSum ) { +function getGradientWithPositionAtIndexSummed( + gradientAST, + index, + valueToSum +) { const currentPosition = gradientAST.colorStops[ index ].length.value; - const newPosition = Math.max( 0, Math.min( 100, parseInt( currentPosition ) + valueToSum ) ); + const newPosition = Math.max( + 0, + Math.min( 100, parseInt( currentPosition ) + valueToSum ) + ); if ( isControlPointOverlapping( gradientAST, newPosition, index ) ) { return gradientAST; } - return getGradientWithPositionAtIndexChanged( gradientAST, index, newPosition ); + return getGradientWithPositionAtIndexChanged( + gradientAST, + index, + newPosition + ); } export function getGradientWithPositionAtIndexIncreased( gradientAST, index ) { - return getGradientWithPositionAtIndexSummed( gradientAST, index, MINIMUM_DISTANCE_BETWEEN_POINTS ); + return getGradientWithPositionAtIndexSummed( + gradientAST, + index, + MINIMUM_DISTANCE_BETWEEN_POINTS + ); } export function getGradientWithPositionAtIndexDecreased( gradientAST, index ) { - return getGradientWithPositionAtIndexSummed( gradientAST, index, -MINIMUM_DISTANCE_BETWEEN_POINTS ); + return getGradientWithPositionAtIndexSummed( + gradientAST, + index, + -MINIMUM_DISTANCE_BETWEEN_POINTS + ); } -export function getGradientWithColorAtIndexChanged( gradientAST, index, rgbaColor ) { +export function getGradientWithColorAtIndexChanged( + gradientAST, + index, + rgbaColor +) { return { ...gradientAST, colorStops: gradientAST.colorStops.map( @@ -115,7 +148,11 @@ export function getGradientWithColorAtIndexChanged( gradientAST, index, rgbaColo }; } -export function getGradientWithColorAtPositionChanged( gradientAST, relativePositionValue, rgbaColor ) { +export function getGradientWithColorAtPositionChanged( + gradientAST, + relativePositionValue, + rgbaColor +) { const index = findIndex( gradientAST.colorStops, ( colorStop ) => { return ( colorStop && @@ -136,15 +173,27 @@ export function getGradientWithControlPointRemoved( gradientAST, index ) { }; } -export function getHorizontalRelativeGradientPosition( mouseXCoordinate, containerElement, positionedElementWidth ) { +export function getHorizontalRelativeGradientPosition( + mouseXCoordinate, + containerElement, + positionedElementWidth +) { if ( ! containerElement ) { return; } const { x, width } = containerElement.getBoundingClientRect(); - const absolutePositionValue = mouseXCoordinate - x - MINIMUM_ABSOLUTE_LEFT_POSITION - ( positionedElementWidth / 2 ); - const availableWidth = width - MINIMUM_ABSOLUTE_LEFT_POSITION - INSERT_POINT_WIDTH; + const absolutePositionValue = + mouseXCoordinate - + x - + MINIMUM_ABSOLUTE_LEFT_POSITION - + positionedElementWidth / 2; + const availableWidth = + width - MINIMUM_ABSOLUTE_LEFT_POSITION - INSERT_POINT_WIDTH; return Math.round( - Math.min( Math.max( ( absolutePositionValue * 100 ) / availableWidth, 0 ), 100 ) + Math.min( + Math.max( ( absolutePositionValue * 100 ) / availableWidth, 0 ), + 100 + ) ); } @@ -164,7 +213,11 @@ export function getMarkerPoints( gradientAST ) { return []; } return map( gradientAST.colorStops, ( colorStop ) => { - if ( ! colorStop || ! colorStop.length || colorStop.length.type !== '%' ) { + if ( + ! colorStop || + ! colorStop.length || + colorStop.length.type !== '%' + ) { return null; } return { diff --git a/packages/components/src/custom-select-control/index.js b/packages/components/src/custom-select-control/index.js index 370ea443230e90..19d3abbed8b122 100644 --- a/packages/components/src/custom-select-control/index.js +++ b/packages/components/src/custom-select-control/index.js @@ -30,7 +30,10 @@ const stateReducer = ( selectedItem: items[ selectedItem - ? Math.min( items.indexOf( selectedItem ) + 1, items.length - 1 ) + ? Math.min( + items.indexOf( selectedItem ) + 1, + items.length - 1 + ) : 0 ], }; @@ -81,19 +84,29 @@ export default function CustomSelectControl( { // fully ARIA compliant. if ( menuProps[ 'aria-activedescendant' ] && - menuProps[ 'aria-activedescendant' ].slice( 0, 'downshift-null'.length ) === - 'downshift-null' + menuProps[ 'aria-activedescendant' ].slice( + 0, + 'downshift-null'.length + ) === 'downshift-null' ) { delete menuProps[ 'aria-activedescendant' ]; } return ( - <div className={ classnames( 'components-custom-select-control', className ) }> + <div + className={ classnames( + 'components-custom-select-control', + className + ) } + > { /* eslint-disable-next-line jsx-a11y/label-has-associated-control, jsx-a11y/label-has-for */ } <label { ...getLabelProps( { - className: classnames( 'components-custom-select-control__label', { - 'screen-reader-text': hideLabelFromVision, - } ), + className: classnames( + 'components-custom-select-control__label', + { + 'screen-reader-text': hideLabelFromVision, + } + ), } ) } > { label } @@ -125,7 +138,8 @@ export default function CustomSelectControl( { className: classnames( 'components-custom-select-control__item', { - 'is-highlighted': index === highlightedIndex, + 'is-highlighted': + index === highlightedIndex, } ), style: item.style, diff --git a/packages/components/src/custom-select-control/stories/index.js b/packages/components/src/custom-select-control/stories/index.js index 35df01f555797e..ec148009b16c97 100644 --- a/packages/components/src/custom-select-control/stories/index.js +++ b/packages/components/src/custom-select-control/stories/index.js @@ -3,7 +3,10 @@ */ import CustomSelectControl from '../'; -export default { title: 'Components/CustomSelectControl', component: CustomSelectControl }; +export default { + title: 'Components/CustomSelectControl', + component: CustomSelectControl, +}; const options = [ { diff --git a/packages/components/src/dashicon/index.js b/packages/components/src/dashicon/index.js index cb75d1747130ba..12a3235ac7397c 100644 --- a/packages/components/src/dashicon/index.js +++ b/packages/components/src/dashicon/index.js @@ -18,67 +18,87 @@ export default class Dashicon extends Component { switch ( icon ) { case 'admin-appearance': - path = 'M14.48 11.06L7.41 3.99l1.5-1.5c.5-.56 2.3-.47 3.51.32 1.21.8 1.43 1.28 2.91 2.1 1.18.64 2.45 1.26 4.45.85zm-.71.71L6.7 4.7 4.93 6.47c-.39.39-.39 1.02 0 1.41l1.06 1.06c.39.39.39 1.03 0 1.42-.6.6-1.43 1.11-2.21 1.69-.35.26-.7.53-1.01.84C1.43 14.23.4 16.08 1.4 17.07c.99 1 2.84-.03 4.18-1.36.31-.31.58-.66.85-1.02.57-.78 1.08-1.61 1.69-2.21.39-.39 1.02-.39 1.41 0l1.06 1.06c.39.39 1.02.39 1.41 0z'; + path = + 'M14.48 11.06L7.41 3.99l1.5-1.5c.5-.56 2.3-.47 3.51.32 1.21.8 1.43 1.28 2.91 2.1 1.18.64 2.45 1.26 4.45.85zm-.71.71L6.7 4.7 4.93 6.47c-.39.39-.39 1.02 0 1.41l1.06 1.06c.39.39.39 1.03 0 1.42-.6.6-1.43 1.11-2.21 1.69-.35.26-.7.53-1.01.84C1.43 14.23.4 16.08 1.4 17.07c.99 1 2.84-.03 4.18-1.36.31-.31.58-.66.85-1.02.57-.78 1.08-1.61 1.69-2.21.39-.39 1.02-.39 1.41 0l1.06 1.06c.39.39 1.02.39 1.41 0z'; break; case 'admin-collapse': - path = 'M10 2.16c4.33 0 7.84 3.51 7.84 7.84s-3.51 7.84-7.84 7.84S2.16 14.33 2.16 10 5.71 2.16 10 2.16zm2 11.72V6.12L6.18 9.97z'; + path = + 'M10 2.16c4.33 0 7.84 3.51 7.84 7.84s-3.51 7.84-7.84 7.84S2.16 14.33 2.16 10 5.71 2.16 10 2.16zm2 11.72V6.12L6.18 9.97z'; break; case 'admin-comments': - path = 'M5 2h9c1.1 0 2 .9 2 2v7c0 1.1-.9 2-2 2h-2l-5 5v-5H5c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2z'; + path = + 'M5 2h9c1.1 0 2 .9 2 2v7c0 1.1-.9 2-2 2h-2l-5 5v-5H5c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2z'; break; case 'admin-customizer': - path = 'M18.33 3.57s.27-.8-.31-1.36c-.53-.52-1.22-.24-1.22-.24-.61.3-5.76 3.47-7.67 5.57-.86.96-2.06 3.79-1.09 4.82.92.98 3.96-.17 4.79-1 2.06-2.06 5.21-7.17 5.5-7.79zM1.4 17.65c2.37-1.56 1.46-3.41 3.23-4.64.93-.65 2.22-.62 3.08.29.63.67.8 2.57-.16 3.46-1.57 1.45-4 1.55-6.15.89z'; + path = + 'M18.33 3.57s.27-.8-.31-1.36c-.53-.52-1.22-.24-1.22-.24-.61.3-5.76 3.47-7.67 5.57-.86.96-2.06 3.79-1.09 4.82.92.98 3.96-.17 4.79-1 2.06-2.06 5.21-7.17 5.5-7.79zM1.4 17.65c2.37-1.56 1.46-3.41 3.23-4.64.93-.65 2.22-.62 3.08.29.63.67.8 2.57-.16 3.46-1.57 1.45-4 1.55-6.15.89z'; break; case 'admin-generic': - path = 'M18 12h-2.18c-.17.7-.44 1.35-.81 1.93l1.54 1.54-2.1 2.1-1.54-1.54c-.58.36-1.23.63-1.91.79V19H8v-2.18c-.68-.16-1.33-.43-1.91-.79l-1.54 1.54-2.12-2.12 1.54-1.54c-.36-.58-.63-1.23-.79-1.91H1V9.03h2.17c.16-.7.44-1.35.8-1.94L2.43 5.55l2.1-2.1 1.54 1.54c.58-.37 1.24-.64 1.93-.81V2h3v2.18c.68.16 1.33.43 1.91.79l1.54-1.54 2.12 2.12-1.54 1.54c.36.59.64 1.24.8 1.94H18V12zm-8.5 1.5c1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3 1.34 3 3 3z'; + path = + 'M18 12h-2.18c-.17.7-.44 1.35-.81 1.93l1.54 1.54-2.1 2.1-1.54-1.54c-.58.36-1.23.63-1.91.79V19H8v-2.18c-.68-.16-1.33-.43-1.91-.79l-1.54 1.54-2.12-2.12 1.54-1.54c-.36-.58-.63-1.23-.79-1.91H1V9.03h2.17c.16-.7.44-1.35.8-1.94L2.43 5.55l2.1-2.1 1.54 1.54c.58-.37 1.24-.64 1.93-.81V2h3v2.18c.68.16 1.33.43 1.91.79l1.54-1.54 2.12 2.12-1.54 1.54c.36.59.64 1.24.8 1.94H18V12zm-8.5 1.5c1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3 1.34 3 3 3z'; break; case 'admin-home': - path = 'M16 8.5l1.53 1.53-1.06 1.06L10 4.62l-6.47 6.47-1.06-1.06L10 2.5l4 4v-2h2v4zm-6-2.46l6 5.99V18H4v-5.97zM12 17v-5H8v5h4z'; + path = + 'M16 8.5l1.53 1.53-1.06 1.06L10 4.62l-6.47 6.47-1.06-1.06L10 2.5l4 4v-2h2v4zm-6-2.46l6 5.99V18H4v-5.97zM12 17v-5H8v5h4z'; break; case 'admin-links': - path = 'M17.74 2.76c1.68 1.69 1.68 4.41 0 6.1l-1.53 1.52c-1.12 1.12-2.7 1.47-4.14 1.09l2.62-2.61.76-.77.76-.76c.84-.84.84-2.2 0-3.04-.84-.85-2.2-.85-3.04 0l-.77.76-3.38 3.38c-.37-1.44-.02-3.02 1.1-4.14l1.52-1.53c1.69-1.68 4.42-1.68 6.1 0zM8.59 13.43l5.34-5.34c.42-.42.42-1.1 0-1.52-.44-.43-1.13-.39-1.53 0l-5.33 5.34c-.42.42-.42 1.1 0 1.52.44.43 1.13.39 1.52 0zm-.76 2.29l4.14-4.15c.38 1.44.03 3.02-1.09 4.14l-1.52 1.53c-1.69 1.68-4.41 1.68-6.1 0-1.68-1.68-1.68-4.42 0-6.1l1.53-1.52c1.12-1.12 2.7-1.47 4.14-1.1l-4.14 4.15c-.85.84-.85 2.2 0 3.05.84.84 2.2.84 3.04 0z'; + path = + 'M17.74 2.76c1.68 1.69 1.68 4.41 0 6.1l-1.53 1.52c-1.12 1.12-2.7 1.47-4.14 1.09l2.62-2.61.76-.77.76-.76c.84-.84.84-2.2 0-3.04-.84-.85-2.2-.85-3.04 0l-.77.76-3.38 3.38c-.37-1.44-.02-3.02 1.1-4.14l1.52-1.53c1.69-1.68 4.42-1.68 6.1 0zM8.59 13.43l5.34-5.34c.42-.42.42-1.1 0-1.52-.44-.43-1.13-.39-1.53 0l-5.33 5.34c-.42.42-.42 1.1 0 1.52.44.43 1.13.39 1.52 0zm-.76 2.29l4.14-4.15c.38 1.44.03 3.02-1.09 4.14l-1.52 1.53c-1.69 1.68-4.41 1.68-6.1 0-1.68-1.68-1.68-4.42 0-6.1l1.53-1.52c1.12-1.12 2.7-1.47 4.14-1.1l-4.14 4.15c-.85.84-.85 2.2 0 3.05.84.84 2.2.84 3.04 0z'; break; case 'admin-media': - path = 'M13 11V4c0-.55-.45-1-1-1h-1.67L9 1H5L3.67 3H2c-.55 0-1 .45-1 1v7c0 .55.45 1 1 1h10c.55 0 1-.45 1-1zM7 4.5c1.38 0 2.5 1.12 2.5 2.5S8.38 9.5 7 9.5 4.5 8.38 4.5 7 5.62 4.5 7 4.5zM14 6h5v10.5c0 1.38-1.12 2.5-2.5 2.5S14 17.88 14 16.5s1.12-2.5 2.5-2.5c.17 0 .34.02.5.05V9h-3V6zm-4 8.05V13h2v3.5c0 1.38-1.12 2.5-2.5 2.5S7 17.88 7 16.5 8.12 14 9.5 14c.17 0 .34.02.5.05z'; + path = + 'M13 11V4c0-.55-.45-1-1-1h-1.67L9 1H5L3.67 3H2c-.55 0-1 .45-1 1v7c0 .55.45 1 1 1h10c.55 0 1-.45 1-1zM7 4.5c1.38 0 2.5 1.12 2.5 2.5S8.38 9.5 7 9.5 4.5 8.38 4.5 7 5.62 4.5 7 4.5zM14 6h5v10.5c0 1.38-1.12 2.5-2.5 2.5S14 17.88 14 16.5s1.12-2.5 2.5-2.5c.17 0 .34.02.5.05V9h-3V6zm-4 8.05V13h2v3.5c0 1.38-1.12 2.5-2.5 2.5S7 17.88 7 16.5 8.12 14 9.5 14c.17 0 .34.02.5.05z'; break; case 'admin-multisite': - path = 'M14.27 6.87L10 3.14 5.73 6.87 5 6.14l5-4.38 5 4.38zM14 8.42l-4.05 3.43L6 8.38v-.74l4-3.5 4 3.5v.78zM11 9.7V8H9v1.7h2zm-1.73 4.03L5 10 .73 13.73 0 13l5-4.38L10 13zm10 0L15 10l-4.27 3.73L10 13l5-4.38L20 13zM5 11l4 3.5V18H1v-3.5zm10 0l4 3.5V18h-8v-3.5zm-9 6v-2H4v2h2zm10 0v-2h-2v2h2z'; + path = + 'M14.27 6.87L10 3.14 5.73 6.87 5 6.14l5-4.38 5 4.38zM14 8.42l-4.05 3.43L6 8.38v-.74l4-3.5 4 3.5v.78zM11 9.7V8H9v1.7h2zm-1.73 4.03L5 10 .73 13.73 0 13l5-4.38L10 13zm10 0L15 10l-4.27 3.73L10 13l5-4.38L20 13zM5 11l4 3.5V18H1v-3.5zm10 0l4 3.5V18h-8v-3.5zm-9 6v-2H4v2h2zm10 0v-2h-2v2h2z'; break; case 'admin-network': - path = 'M16.95 2.58c1.96 1.95 1.96 5.12 0 7.07-1.51 1.51-3.75 1.84-5.59 1.01l-1.87 3.31-2.99.31L5 18H2l-1-2 7.95-7.69c-.92-1.87-.62-4.18.93-5.73 1.95-1.96 5.12-1.96 7.07 0zm-2.51 3.79c.74 0 1.33-.6 1.33-1.34 0-.73-.59-1.33-1.33-1.33-.73 0-1.33.6-1.33 1.33 0 .74.6 1.34 1.33 1.34z'; + path = + 'M16.95 2.58c1.96 1.95 1.96 5.12 0 7.07-1.51 1.51-3.75 1.84-5.59 1.01l-1.87 3.31-2.99.31L5 18H2l-1-2 7.95-7.69c-.92-1.87-.62-4.18.93-5.73 1.95-1.96 5.12-1.96 7.07 0zm-2.51 3.79c.74 0 1.33-.6 1.33-1.34 0-.73-.59-1.33-1.33-1.33-.73 0-1.33.6-1.33 1.33 0 .74.6 1.34 1.33 1.34z'; break; case 'admin-page': path = 'M6 15V2h10v13H6zm-1 1h8v2H3V5h2v11z'; break; case 'admin-plugins': - path = 'M13.11 4.36L9.87 7.6 8 5.73l3.24-3.24c.35-.34 1.05-.2 1.56.32.52.51.66 1.21.31 1.55zm-8 1.77l.91-1.12 9.01 9.01-1.19.84c-.71.71-2.63 1.16-3.82 1.16H6.14L4.9 17.26c-.59.59-1.54.59-2.12 0-.59-.58-.59-1.53 0-2.12l1.24-1.24v-3.88c0-1.13.4-3.19 1.09-3.89zm7.26 3.97l3.24-3.24c.34-.35 1.04-.21 1.55.31.52.51.66 1.21.31 1.55l-3.24 3.25z'; + path = + 'M13.11 4.36L9.87 7.6 8 5.73l3.24-3.24c.35-.34 1.05-.2 1.56.32.52.51.66 1.21.31 1.55zm-8 1.77l.91-1.12 9.01 9.01-1.19.84c-.71.71-2.63 1.16-3.82 1.16H6.14L4.9 17.26c-.59.59-1.54.59-2.12 0-.59-.58-.59-1.53 0-2.12l1.24-1.24v-3.88c0-1.13.4-3.19 1.09-3.89zm7.26 3.97l3.24-3.24c.34-.35 1.04-.21 1.55.31.52.51.66 1.21.31 1.55l-3.24 3.25z'; break; case 'admin-post': - path = 'M10.44 3.02l1.82-1.82 6.36 6.35-1.83 1.82c-1.05-.68-2.48-.57-3.41.36l-.75.75c-.92.93-1.04 2.35-.35 3.41l-1.83 1.82-2.41-2.41-2.8 2.79c-.42.42-3.38 2.71-3.8 2.29s1.86-3.39 2.28-3.81l2.79-2.79L4.1 9.36l1.83-1.82c1.05.69 2.48.57 3.4-.36l.75-.75c.93-.92 1.05-2.35.36-3.41z'; + path = + 'M10.44 3.02l1.82-1.82 6.36 6.35-1.83 1.82c-1.05-.68-2.48-.57-3.41.36l-.75.75c-.92.93-1.04 2.35-.35 3.41l-1.83 1.82-2.41-2.41-2.8 2.79c-.42.42-3.38 2.71-3.8 2.29s1.86-3.39 2.28-3.81l2.79-2.79L4.1 9.36l1.83-1.82c1.05.69 2.48.57 3.4-.36l.75-.75c.93-.92 1.05-2.35.36-3.41z'; break; case 'admin-settings': - path = 'M18 16V4c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h13c.55 0 1-.45 1-1zM8 11h1c.55 0 1 .45 1 1s-.45 1-1 1H8v1.5c0 .28-.22.5-.5.5s-.5-.22-.5-.5V13H6c-.55 0-1-.45-1-1s.45-1 1-1h1V5.5c0-.28.22-.5.5-.5s.5.22.5.5V11zm5-2h-1c-.55 0-1-.45-1-1s.45-1 1-1h1V5.5c0-.28.22-.5.5-.5s.5.22.5.5V7h1c.55 0 1 .45 1 1s-.45 1-1 1h-1v5.5c0 .28-.22.5-.5.5s-.5-.22-.5-.5V9z'; + path = + 'M18 16V4c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h13c.55 0 1-.45 1-1zM8 11h1c.55 0 1 .45 1 1s-.45 1-1 1H8v1.5c0 .28-.22.5-.5.5s-.5-.22-.5-.5V13H6c-.55 0-1-.45-1-1s.45-1 1-1h1V5.5c0-.28.22-.5.5-.5s.5.22.5.5V11zm5-2h-1c-.55 0-1-.45-1-1s.45-1 1-1h1V5.5c0-.28.22-.5.5-.5s.5.22.5.5V7h1c.55 0 1 .45 1 1s-.45 1-1 1h-1v5.5c0 .28-.22.5-.5.5s-.5-.22-.5-.5V9z'; break; case 'admin-site-alt': - path = 'M9 0C4.03 0 0 4.03 0 9s4.03 9 9 9 9-4.03 9-9-4.03-9-9-9zm7.5 6.48c-.274.896-.908 1.64-1.75 2.05-.45-1.69-1.658-3.074-3.27-3.75.13-.444.41-.83.79-1.09-.43-.28-1-.42-1.34.07-.53.69 0 1.61.21 2v.14c-.555-.337-.99-.84-1.24-1.44-.966-.03-1.922.208-2.76.69-.087-.565-.032-1.142.16-1.68.733.07 1.453-.23 1.92-.8.46-.52-.13-1.18-.59-1.58h.36c1.36-.01 2.702.335 3.89 1 1.36 1.005 2.194 2.57 2.27 4.26.24 0 .7-.55.91-.92.172.34.32.69.44 1.05zM9 16.84c-2.05-2.08.25-3.75-1-5.24-.92-.85-2.29-.26-3.11-1.23-.282-1.473.267-2.982 1.43-3.93.52-.44 4-1 5.42.22.83.715 1.415 1.674 1.67 2.74.46.035.918-.066 1.32-.29.41 2.98-3.15 6.74-5.73 7.73zM5.15 2.09c.786-.3 1.676-.028 2.16.66-.42.38-.94.63-1.5.72.02-.294.085-.584.19-.86l-.85-.52z'; + path = + 'M9 0C4.03 0 0 4.03 0 9s4.03 9 9 9 9-4.03 9-9-4.03-9-9-9zm7.5 6.48c-.274.896-.908 1.64-1.75 2.05-.45-1.69-1.658-3.074-3.27-3.75.13-.444.41-.83.79-1.09-.43-.28-1-.42-1.34.07-.53.69 0 1.61.21 2v.14c-.555-.337-.99-.84-1.24-1.44-.966-.03-1.922.208-2.76.69-.087-.565-.032-1.142.16-1.68.733.07 1.453-.23 1.92-.8.46-.52-.13-1.18-.59-1.58h.36c1.36-.01 2.702.335 3.89 1 1.36 1.005 2.194 2.57 2.27 4.26.24 0 .7-.55.91-.92.172.34.32.69.44 1.05zM9 16.84c-2.05-2.08.25-3.75-1-5.24-.92-.85-2.29-.26-3.11-1.23-.282-1.473.267-2.982 1.43-3.93.52-.44 4-1 5.42.22.83.715 1.415 1.674 1.67 2.74.46.035.918-.066 1.32-.29.41 2.98-3.15 6.74-5.73 7.73zM5.15 2.09c.786-.3 1.676-.028 2.16.66-.42.38-.94.63-1.5.72.02-.294.085-.584.19-.86l-.85-.52z'; break; case 'admin-site-alt2': - path = 'M9 0C4.03 0 0 4.03 0 9s4.03 9 9 9 9-4.03 9-9-4.03-9-9-9zm2.92 12.34c0 .35.14.63.36.66.22.03.47-.22.58-.6l.2.08c.718.384 1.07 1.22.84 2-.15.69-.743 1.198-1.45 1.24-.49-1.21-2.11.06-3.56-.22-.612-.154-1.11-.6-1.33-1.19 1.19-.11 2.85-1.73 4.36-1.97zM8 11.27c.918 0 1.695-.68 1.82-1.59.44.54.41 1.324-.07 1.83-.255.223-.594.325-.93.28-.335-.047-.635-.236-.82-.52zm3-.76c.41.39 3-.06 3.52 1.09-.95-.2-2.95.61-3.47-1.08l-.05-.01zM9.73 5.45v.27c-.65-.77-1.33-1.07-1.61-.57-.28.5 1 1.11.76 1.88-.24.77-1.27.56-1.88 1.61-.61 1.05-.49 2.42 1.24 3.67-1.192-.132-2.19-.962-2.54-2.11-.4-1.2-.09-2.26-.78-2.46C4 7.46 3 8.71 3 9.8c-1.26-1.26.05-2.86-1.2-4.18C3.5 1.998 7.644.223 11.44 1.49c-1.1 1.02-1.722 2.458-1.71 3.96z'; + path = + 'M9 0C4.03 0 0 4.03 0 9s4.03 9 9 9 9-4.03 9-9-4.03-9-9-9zm2.92 12.34c0 .35.14.63.36.66.22.03.47-.22.58-.6l.2.08c.718.384 1.07 1.22.84 2-.15.69-.743 1.198-1.45 1.24-.49-1.21-2.11.06-3.56-.22-.612-.154-1.11-.6-1.33-1.19 1.19-.11 2.85-1.73 4.36-1.97zM8 11.27c.918 0 1.695-.68 1.82-1.59.44.54.41 1.324-.07 1.83-.255.223-.594.325-.93.28-.335-.047-.635-.236-.82-.52zm3-.76c.41.39 3-.06 3.52 1.09-.95-.2-2.95.61-3.47-1.08l-.05-.01zM9.73 5.45v.27c-.65-.77-1.33-1.07-1.61-.57-.28.5 1 1.11.76 1.88-.24.77-1.27.56-1.88 1.61-.61 1.05-.49 2.42 1.24 3.67-1.192-.132-2.19-.962-2.54-2.11-.4-1.2-.09-2.26-.78-2.46C4 7.46 3 8.71 3 9.8c-1.26-1.26.05-2.86-1.2-4.18C3.5 1.998 7.644.223 11.44 1.49c-1.1 1.02-1.722 2.458-1.71 3.96z'; break; case 'admin-site-alt3': - path = 'M9 0C4.03 0 0 4.03 0 9s4.03 9 9 9 9-4.03 9-9-4.03-9-9-9zM1.11 9.68h2.51c.04.91.167 1.814.38 2.7H1.84c-.403-.85-.65-1.764-.73-2.7zm8.57-5.4V1.19c.964.366 1.756 1.08 2.22 2 .205.347.386.708.54 1.08l-2.76.01zm3.22 1.35c.232.883.37 1.788.41 2.7H9.68v-2.7h3.22zM8.32 1.19v3.09H5.56c.154-.372.335-.733.54-1.08.462-.924 1.255-1.64 2.22-2.01zm0 4.44v2.7H4.7c.04-.912.178-1.817.41-2.7h3.21zm-4.7 2.69H1.11c.08-.936.327-1.85.73-2.7H4c-.213.886-.34 1.79-.38 2.7zM4.7 9.68h3.62v2.7H5.11c-.232-.883-.37-1.788-.41-2.7zm3.63 4v3.09c-.964-.366-1.756-1.08-2.22-2-.205-.347-.386-.708-.54-1.08l2.76-.01zm1.35 3.09v-3.04h2.76c-.154.372-.335.733-.54 1.08-.464.92-1.256 1.634-2.22 2v-.04zm0-4.44v-2.7h3.62c-.04.912-.178 1.817-.41 2.7H9.68zm4.71-2.7h2.51c-.08.936-.327 1.85-.73 2.7H14c.21-.87.337-1.757.38-2.65l.01-.05zm0-1.35c-.046-.894-.176-1.78-.39-2.65h2.16c.403.85.65 1.764.73 2.7l-2.5-.05zm1-4H13.6c-.324-.91-.793-1.76-1.39-2.52 1.244.56 2.325 1.426 3.14 2.52h.04zm-9.6-2.52c-.597.76-1.066 1.61-1.39 2.52H2.65c.815-1.094 1.896-1.96 3.14-2.52zm-3.15 12H4.4c.324.91.793 1.76 1.39 2.52-1.248-.567-2.33-1.445-3.14-2.55l-.01.03zm9.56 2.52c.597-.76 1.066-1.61 1.39-2.52h1.76c-.82 1.08-1.9 1.933-3.14 2.48l-.01.04z'; + path = + 'M9 0C4.03 0 0 4.03 0 9s4.03 9 9 9 9-4.03 9-9-4.03-9-9-9zM1.11 9.68h2.51c.04.91.167 1.814.38 2.7H1.84c-.403-.85-.65-1.764-.73-2.7zm8.57-5.4V1.19c.964.366 1.756 1.08 2.22 2 .205.347.386.708.54 1.08l-2.76.01zm3.22 1.35c.232.883.37 1.788.41 2.7H9.68v-2.7h3.22zM8.32 1.19v3.09H5.56c.154-.372.335-.733.54-1.08.462-.924 1.255-1.64 2.22-2.01zm0 4.44v2.7H4.7c.04-.912.178-1.817.41-2.7h3.21zm-4.7 2.69H1.11c.08-.936.327-1.85.73-2.7H4c-.213.886-.34 1.79-.38 2.7zM4.7 9.68h3.62v2.7H5.11c-.232-.883-.37-1.788-.41-2.7zm3.63 4v3.09c-.964-.366-1.756-1.08-2.22-2-.205-.347-.386-.708-.54-1.08l2.76-.01zm1.35 3.09v-3.04h2.76c-.154.372-.335.733-.54 1.08-.464.92-1.256 1.634-2.22 2v-.04zm0-4.44v-2.7h3.62c-.04.912-.178 1.817-.41 2.7H9.68zm4.71-2.7h2.51c-.08.936-.327 1.85-.73 2.7H14c.21-.87.337-1.757.38-2.65l.01-.05zm0-1.35c-.046-.894-.176-1.78-.39-2.65h2.16c.403.85.65 1.764.73 2.7l-2.5-.05zm1-4H13.6c-.324-.91-.793-1.76-1.39-2.52 1.244.56 2.325 1.426 3.14 2.52h.04zm-9.6-2.52c-.597.76-1.066 1.61-1.39 2.52H2.65c.815-1.094 1.896-1.96 3.14-2.52zm-3.15 12H4.4c.324.91.793 1.76 1.39 2.52-1.248-.567-2.33-1.445-3.14-2.55l-.01.03zm9.56 2.52c.597-.76 1.066-1.61 1.39-2.52h1.76c-.82 1.08-1.9 1.933-3.14 2.48l-.01.04z'; break; case 'admin-site': - path = 'M9 0C4.03 0 0 4.03 0 9s4.03 9 9 9 9-4.03 9-9-4.03-9-9-9zm3.46 11.95c0 1.47-.8 3.3-4.06 4.7.3-4.17-2.52-3.69-3.2-5 .126-1.1.804-2.063 1.8-2.55-1.552-.266-3-.96-4.18-2 .05.47.28.904.64 1.21-.782-.295-1.458-.817-1.94-1.5.977-3.225 3.883-5.482 7.25-5.63-.84 1.38-1.5 4.13 0 5.57C7.23 7 6.26 5 5.41 5.79c-1.13 1.06.33 2.51 3.42 3.08 3.29.59 3.66 1.58 3.63 3.08zm1.34-4c-.32-1.11.62-2.23 1.69-3.14 1.356 1.955 1.67 4.45.84 6.68-.77-1.89-2.17-2.32-2.53-3.57v.03z'; + path = + 'M9 0C4.03 0 0 4.03 0 9s4.03 9 9 9 9-4.03 9-9-4.03-9-9-9zm3.46 11.95c0 1.47-.8 3.3-4.06 4.7.3-4.17-2.52-3.69-3.2-5 .126-1.1.804-2.063 1.8-2.55-1.552-.266-3-.96-4.18-2 .05.47.28.904.64 1.21-.782-.295-1.458-.817-1.94-1.5.977-3.225 3.883-5.482 7.25-5.63-.84 1.38-1.5 4.13 0 5.57C7.23 7 6.26 5 5.41 5.79c-1.13 1.06.33 2.51 3.42 3.08 3.29.59 3.66 1.58 3.63 3.08zm1.34-4c-.32-1.11.62-2.23 1.69-3.14 1.356 1.955 1.67 4.45.84 6.68-.77-1.89-2.17-2.32-2.53-3.57v.03z'; break; case 'admin-tools': - path = 'M16.68 9.77c-1.34 1.34-3.3 1.67-4.95.99l-5.41 6.52c-.99.99-2.59.99-3.58 0s-.99-2.59 0-3.57l6.52-5.42c-.68-1.65-.35-3.61.99-4.95 1.28-1.28 3.12-1.62 4.72-1.06l-2.89 2.89 2.82 2.82 2.86-2.87c.53 1.58.18 3.39-1.08 4.65zM3.81 16.21c.4.39 1.04.39 1.43 0 .4-.4.4-1.04 0-1.43-.39-.4-1.03-.4-1.43 0-.39.39-.39 1.03 0 1.43z'; + path = + 'M16.68 9.77c-1.34 1.34-3.3 1.67-4.95.99l-5.41 6.52c-.99.99-2.59.99-3.58 0s-.99-2.59 0-3.57l6.52-5.42c-.68-1.65-.35-3.61.99-4.95 1.28-1.28 3.12-1.62 4.72-1.06l-2.89 2.89 2.82 2.82 2.86-2.87c.53 1.58.18 3.39-1.08 4.65zM3.81 16.21c.4.39 1.04.39 1.43 0 .4-.4.4-1.04 0-1.43-.39-.4-1.03-.4-1.43 0-.39.39-.39 1.03 0 1.43z'; break; case 'admin-users': - path = 'M10 9.25c-2.27 0-2.73-3.44-2.73-3.44C7 4.02 7.82 2 9.97 2c2.16 0 2.98 2.02 2.71 3.81 0 0-.41 3.44-2.68 3.44zm0 2.57L12.72 10c2.39 0 4.52 2.33 4.52 4.53v2.49s-3.65 1.13-7.24 1.13c-3.65 0-7.24-1.13-7.24-1.13v-2.49c0-2.25 1.94-4.48 4.47-4.48z'; + path = + 'M10 9.25c-2.27 0-2.73-3.44-2.73-3.44C7 4.02 7.82 2 9.97 2c2.16 0 2.98 2.02 2.71 3.81 0 0-.41 3.44-2.68 3.44zm0 2.57L12.72 10c2.39 0 4.52 2.33 4.52 4.53v2.49s-3.65 1.13-7.24 1.13c-3.65 0-7.24-1.13-7.24-1.13v-2.49c0-2.25 1.94-4.48 4.47-4.48z'; break; case 'album': - path = 'M0 18h10v-.26c1.52.4 3.17.35 4.76-.24 4.14-1.52 6.27-6.12 4.75-10.26-1.43-3.89-5.58-6-9.51-4.98V2H0v16zM9 3v14H1V3h8zm5.45 8.22c-.68 1.35-2.32 1.9-3.67 1.23-.31-.15-.57-.35-.78-.59V8.13c.8-.86 2.11-1.13 3.22-.58 1.35.68 1.9 2.32 1.23 3.67zm-2.75-.82c.22.16.53.12.7-.1.16-.22.12-.53-.1-.7s-.53-.12-.7.1c-.16.21-.12.53.1.7zm3.01 3.67c-1.17.78-2.56.99-3.83.69-.27-.06-.44-.34-.37-.61s.34-.43.62-.36l.17.04c.96.17 1.98-.01 2.86-.59.47-.32.86-.72 1.14-1.18.15-.23.45-.3.69-.16.23.15.3.46.16.69-.36.57-.84 1.08-1.44 1.48zm1.05 1.57c-1.48.99-3.21 1.32-4.84 1.06-.28-.05-.47-.32-.41-.6.05-.27.32-.45.61-.39l.22.04c1.31.15 2.68-.14 3.87-.94.71-.47 1.27-1.07 1.7-1.74.14-.24.45-.31.68-.16.24.14.31.45.16.69-.49.79-1.16 1.49-1.99 2.04z'; + path = + 'M0 18h10v-.26c1.52.4 3.17.35 4.76-.24 4.14-1.52 6.27-6.12 4.75-10.26-1.43-3.89-5.58-6-9.51-4.98V2H0v16zM9 3v14H1V3h8zm5.45 8.22c-.68 1.35-2.32 1.9-3.67 1.23-.31-.15-.57-.35-.78-.59V8.13c.8-.86 2.11-1.13 3.22-.58 1.35.68 1.9 2.32 1.23 3.67zm-2.75-.82c.22.16.53.12.7-.1.16-.22.12-.53-.1-.7s-.53-.12-.7.1c-.16.21-.12.53.1.7zm3.01 3.67c-1.17.78-2.56.99-3.83.69-.27-.06-.44-.34-.37-.61s.34-.43.62-.36l.17.04c.96.17 1.98-.01 2.86-.59.47-.32.86-.72 1.14-1.18.15-.23.45-.3.69-.16.23.15.3.46.16.69-.36.57-.84 1.08-1.44 1.48zm1.05 1.57c-1.48.99-3.21 1.32-4.84 1.06-.28-.05-.47-.32-.41-.6.05-.27.32-.45.61-.39l.22.04c1.31.15 2.68-.14 3.87-.94.71-.47 1.27-1.07 1.7-1.74.14-.24.45-.31.68-.16.24.14.31.45.16.69-.49.79-1.16 1.49-1.99 2.04z'; break; case 'align-center': path = 'M3 5h14V3H3v2zm12 8V7H5v6h10zM3 17h14v-2H3v2z'; @@ -87,7 +107,8 @@ export default class Dashicon extends Component { path = 'M17 13V3H3v10h14zM5 17h10v-2H5v2z'; break; case 'align-left': - path = 'M3 5h14V3H3v2zm9 8V7H3v6h9zm2-4h3V7h-3v2zm0 4h3v-2h-3v2zM3 17h14v-2H3v2z'; + path = + 'M3 5h14V3H3v2zm9 8V7H3v6h9zm2-4h3V7h-3v2zm0 4h3v-2h-3v2zM3 17h14v-2H3v2z'; break; case 'align-none': path = 'M3 5h14V3H3v2zm10 8V7H3v6h10zM3 17h14v-2H3v2z'; @@ -99,13 +120,15 @@ export default class Dashicon extends Component { path = 'M17 16V4h-6v12h6zM9 7H3v2h6V7zm0 4H3v2h6v-2z'; break; case 'align-right': - path = 'M3 5h14V3H3v2zm0 4h3V7H3v2zm14 4V7H8v6h9zM3 13h3v-2H3v2zm0 4h14v-2H3v2z'; + path = + 'M3 5h14V3H3v2zm0 4h3V7H3v2zm14 4V7H8v6h9zM3 13h3v-2H3v2zm0 4h14v-2H3v2z'; break; case 'align-wide': path = 'M5 5h10V3H5v2zm12 8V7H3v6h14zM5 17h10v-2H5v2z'; break; case 'analytics': - path = 'M18 18V2H2v16h16zM16 5H4V4h12v1zM7 7v3h3c0 1.66-1.34 3-3 3s-3-1.34-3-3 1.34-3 3-3zm1 2V7c1.1 0 2 .9 2 2H8zm8-1h-4V7h4v1zm0 3h-4V9h4v2zm0 2h-4v-1h4v1zm0 3H4v-1h12v1z'; + path = + 'M18 18V2H2v16h16zM16 5H4V4h12v1zM7 7v3h3c0 1.66-1.34 3-3 3s-3-1.34-3-3 1.34-3 3-3zm1 2V7c1.1 0 2 .9 2 2H8zm8-1h-4V7h4v1zm0 3h-4V9h4v2zm0 2h-4v-1h4v1zm0 3H4v-1h12v1z'; break; case 'archive': path = 'M19 4v2H1V4h18zM2 7h16v10H2V7zm11 3V9H7v1h6z'; @@ -147,109 +170,142 @@ export default class Dashicon extends Component { path = 'M7 13l4.03-6L15 13H7z'; break; case 'art': - path = 'M8.55 3.06c1.01.34-1.95 2.01-.1 3.13 1.04.63 3.31-2.22 4.45-2.86.97-.54 2.67-.65 3.53 1.23 1.09 2.38.14 8.57-3.79 11.06-3.97 2.5-8.97 1.23-10.7-2.66-2.01-4.53 3.12-11.09 6.61-9.9zm1.21 6.45c.73 1.64 4.7-.5 3.79-2.8-.59-1.49-4.48 1.25-3.79 2.8z'; + path = + 'M8.55 3.06c1.01.34-1.95 2.01-.1 3.13 1.04.63 3.31-2.22 4.45-2.86.97-.54 2.67-.65 3.53 1.23 1.09 2.38.14 8.57-3.79 11.06-3.97 2.5-8.97 1.23-10.7-2.66-2.01-4.53 3.12-11.09 6.61-9.9zm1.21 6.45c.73 1.64 4.7-.5 3.79-2.8-.59-1.49-4.48 1.25-3.79 2.8z'; break; case 'awards': - path = 'M4.46 5.16L5 7.46l-.54 2.29 2.01 1.24L7.7 13l2.3-.54 2.3.54 1.23-2.01 2.01-1.24L15 7.46l.54-2.3-2-1.24-1.24-2.01-2.3.55-2.29-.54-1.25 2zm5.55 6.34C7.79 11.5 6 9.71 6 7.49c0-2.2 1.79-3.99 4.01-3.99 2.2 0 3.99 1.79 3.99 3.99 0 2.22-1.79 4.01-3.99 4.01zm-.02-1C8.33 10.5 7 9.16 7 7.5c0-1.65 1.33-3 2.99-3S13 5.85 13 7.5c0 1.66-1.35 3-3.01 3zm3.84 1.1l-1.28 2.24-2.08-.47L13 19.2l1.4-2.2h2.5zm-7.7.07l1.25 2.25 2.13-.51L7 19.2 5.6 17H3.1z'; + path = + 'M4.46 5.16L5 7.46l-.54 2.29 2.01 1.24L7.7 13l2.3-.54 2.3.54 1.23-2.01 2.01-1.24L15 7.46l.54-2.3-2-1.24-1.24-2.01-2.3.55-2.29-.54-1.25 2zm5.55 6.34C7.79 11.5 6 9.71 6 7.49c0-2.2 1.79-3.99 4.01-3.99 2.2 0 3.99 1.79 3.99 3.99 0 2.22-1.79 4.01-3.99 4.01zm-.02-1C8.33 10.5 7 9.16 7 7.5c0-1.65 1.33-3 2.99-3S13 5.85 13 7.5c0 1.66-1.35 3-3.01 3zm3.84 1.1l-1.28 2.24-2.08-.47L13 19.2l1.4-2.2h2.5zm-7.7.07l1.25 2.25 2.13-.51L7 19.2 5.6 17H3.1z'; break; case 'backup': - path = 'M13.65 2.88c3.93 2.01 5.48 6.84 3.47 10.77s-6.83 5.48-10.77 3.47c-1.87-.96-3.2-2.56-3.86-4.4l1.64-1.03c.45 1.57 1.52 2.95 3.08 3.76 3.01 1.54 6.69.35 8.23-2.66 1.55-3.01.36-6.69-2.65-8.24C9.78 3.01 6.1 4.2 4.56 7.21l1.88.97-4.95 3.08-.39-5.82 1.78.91C4.9 2.4 9.75.89 13.65 2.88zm-4.36 7.83C9.11 10.53 9 10.28 9 10c0-.07.03-.12.04-.19h-.01L10 5l.97 4.81L14 13l-4.5-2.12.02-.02c-.08-.04-.16-.09-.23-.15z'; + path = + 'M13.65 2.88c3.93 2.01 5.48 6.84 3.47 10.77s-6.83 5.48-10.77 3.47c-1.87-.96-3.2-2.56-3.86-4.4l1.64-1.03c.45 1.57 1.52 2.95 3.08 3.76 3.01 1.54 6.69.35 8.23-2.66 1.55-3.01.36-6.69-2.65-8.24C9.78 3.01 6.1 4.2 4.56 7.21l1.88.97-4.95 3.08-.39-5.82 1.78.91C4.9 2.4 9.75.89 13.65 2.88zm-4.36 7.83C9.11 10.53 9 10.28 9 10c0-.07.03-.12.04-.19h-.01L10 5l.97 4.81L14 13l-4.5-2.12.02-.02c-.08-.04-.16-.09-.23-.15z'; break; case 'block-default': - path = 'M15 6V4h-3v2H8V4H5v2H4c-.6 0-1 .4-1 1v8h14V7c0-.6-.4-1-1-1h-1z'; + path = + 'M15 6V4h-3v2H8V4H5v2H4c-.6 0-1 .4-1 1v8h14V7c0-.6-.4-1-1-1h-1z'; break; case 'book-alt': - path = 'M5 17h13v2H5c-1.66 0-3-1.34-3-3V4c0-1.66 1.34-3 3-3h13v14H5c-.55 0-1 .45-1 1s.45 1 1 1zm2-3.5v-11c0-.28-.22-.5-.5-.5s-.5.22-.5.5v11c0 .28.22.5.5.5s.5-.22.5-.5z'; + path = + 'M5 17h13v2H5c-1.66 0-3-1.34-3-3V4c0-1.66 1.34-3 3-3h13v14H5c-.55 0-1 .45-1 1s.45 1 1 1zm2-3.5v-11c0-.28-.22-.5-.5-.5s-.5.22-.5.5v11c0 .28.22.5.5.5s.5-.22.5-.5z'; break; case 'book': - path = 'M16 3h2v16H5c-1.66 0-3-1.34-3-3V4c0-1.66 1.34-3 3-3h9v14H5c-.55 0-1 .45-1 1s.45 1 1 1h11V3z'; + path = + 'M16 3h2v16H5c-1.66 0-3-1.34-3-3V4c0-1.66 1.34-3 3-3h9v14H5c-.55 0-1 .45-1 1s.45 1 1 1h11V3z'; break; case 'buddicons-activity': - path = 'M8 1v7h2V6c0-1.52 1.45-3 3-3v.86c.55-.52 1.26-.86 2-.86v3h1c1.1 0 2 .9 2 2s-.9 2-2 2h-1v6c0 .55-.45 1-1 1s-1-.45-1-1v-2.18c-.31.11-.65.18-1 .18v2c0 .55-.45 1-1 1s-1-.45-1-1v-2H8v2c0 .55-.45 1-1 1s-1-.45-1-1v-2c-.35 0-.69-.07-1-.18V16c0 .55-.45 1-1 1s-1-.45-1-1v-4H2v-1c0-1.66 1.34-3 3-3h2V1h1zm5 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1z'; + path = + 'M8 1v7h2V6c0-1.52 1.45-3 3-3v.86c.55-.52 1.26-.86 2-.86v3h1c1.1 0 2 .9 2 2s-.9 2-2 2h-1v6c0 .55-.45 1-1 1s-1-.45-1-1v-2.18c-.31.11-.65.18-1 .18v2c0 .55-.45 1-1 1s-1-.45-1-1v-2H8v2c0 .55-.45 1-1 1s-1-.45-1-1v-2c-.35 0-.69-.07-1-.18V16c0 .55-.45 1-1 1s-1-.45-1-1v-4H2v-1c0-1.66 1.34-3 3-3h2V1h1zm5 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1z'; break; case 'buddicons-bbpress-logo': - path = 'M8.5 12.6c.3-1.3 0-2.3-1.1-2.3-.8 0-1.6.6-1.8 1.5l-.3 1.7c-.3 1 .3 1.5 1 1.5 1.2 0 1.9-1.1 2.2-2.4zm-4-6.4C3.7 7.3 3.3 8.6 3.3 10c0 1 .2 1.9.6 2.8l1-4.6c.3-1.7.4-2-.4-2zm9.3 6.4c.3-1.3 0-2.3-1.1-2.3-.8 0-1.6.6-1.8 1.5l-.4 1.7c-.2 1.1.4 1.6 1.1 1.6 1.1-.1 1.9-1.2 2.2-2.5zM10 3.3c-2 0-3.9.9-5.1 2.3.6-.1 1.4-.2 1.8-.3.2 0 .2.1.2.2 0 .2-1 4.8-1 4.8.5-.3 1.2-.7 1.8-.7.9 0 1.5.4 1.9.9l.5-2.4c.4-1.6.4-1.9-.4-1.9-.4 0-.4-.5 0-.6.6-.1 1.8-.2 2.3-.3.2 0 .2.1.2.2l-1 4.8c.5-.4 1.2-.7 1.9-.7 1.7 0 2.5 1.3 2.1 3-.3 1.7-2 3-3.8 3-1.3 0-2.1-.7-2.3-1.4-.7.8-1.7 1.3-2.8 1.4 1.1.7 2.4 1.1 3.7 1.1 3.7 0 6.7-3 6.7-6.7s-3-6.7-6.7-6.7zM10 2c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 15.5c-2.1 0-4-.8-5.3-2.2-.3-.4-.7-.8-1-1.2-.7-1.2-1.2-2.6-1.2-4.1 0-4.1 3.4-7.5 7.5-7.5s7.5 3.4 7.5 7.5-3.4 7.5-7.5 7.5z'; + path = + 'M8.5 12.6c.3-1.3 0-2.3-1.1-2.3-.8 0-1.6.6-1.8 1.5l-.3 1.7c-.3 1 .3 1.5 1 1.5 1.2 0 1.9-1.1 2.2-2.4zm-4-6.4C3.7 7.3 3.3 8.6 3.3 10c0 1 .2 1.9.6 2.8l1-4.6c.3-1.7.4-2-.4-2zm9.3 6.4c.3-1.3 0-2.3-1.1-2.3-.8 0-1.6.6-1.8 1.5l-.4 1.7c-.2 1.1.4 1.6 1.1 1.6 1.1-.1 1.9-1.2 2.2-2.5zM10 3.3c-2 0-3.9.9-5.1 2.3.6-.1 1.4-.2 1.8-.3.2 0 .2.1.2.2 0 .2-1 4.8-1 4.8.5-.3 1.2-.7 1.8-.7.9 0 1.5.4 1.9.9l.5-2.4c.4-1.6.4-1.9-.4-1.9-.4 0-.4-.5 0-.6.6-.1 1.8-.2 2.3-.3.2 0 .2.1.2.2l-1 4.8c.5-.4 1.2-.7 1.9-.7 1.7 0 2.5 1.3 2.1 3-.3 1.7-2 3-3.8 3-1.3 0-2.1-.7-2.3-1.4-.7.8-1.7 1.3-2.8 1.4 1.1.7 2.4 1.1 3.7 1.1 3.7 0 6.7-3 6.7-6.7s-3-6.7-6.7-6.7zM10 2c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 15.5c-2.1 0-4-.8-5.3-2.2-.3-.4-.7-.8-1-1.2-.7-1.2-1.2-2.6-1.2-4.1 0-4.1 3.4-7.5 7.5-7.5s7.5 3.4 7.5 7.5-3.4 7.5-7.5 7.5z'; break; case 'buddicons-buddypress-logo': - path = 'M10 0c5.52 0 10 4.48 10 10s-4.48 10-10 10S0 15.52 0 10 4.48 0 10 0zm0 .5C4.75.5.5 4.75.5 10s4.25 9.5 9.5 9.5 9.5-4.25 9.5-9.5S15.25.5 10 .5zm0 1c4.7 0 8.5 3.8 8.5 8.5s-3.8 8.5-8.5 8.5-8.5-3.8-8.5-8.5S5.3 1.5 10 1.5zm1.8 1.71c-.57 0-1.1.17-1.55.45 1.56.37 2.73 1.77 2.73 3.45 0 .69-.21 1.33-.55 1.87 1.31-.29 2.29-1.45 2.29-2.85 0-1.61-1.31-2.92-2.92-2.92zm-2.38 1c-1.61 0-2.92 1.31-2.92 2.93 0 1.61 1.31 2.92 2.92 2.92 1.62 0 2.93-1.31 2.93-2.92 0-1.62-1.31-2.93-2.93-2.93zm4.25 5.01l-.51.59c2.34.69 2.45 3.61 2.45 3.61h1.28c0-4.71-3.22-4.2-3.22-4.2zm-2.1.8l-2.12 2.09-2.12-2.09C3.12 10.24 3.89 15 3.89 15h11.08c.47-4.98-3.4-4.98-3.4-4.98z'; + path = + 'M10 0c5.52 0 10 4.48 10 10s-4.48 10-10 10S0 15.52 0 10 4.48 0 10 0zm0 .5C4.75.5.5 4.75.5 10s4.25 9.5 9.5 9.5 9.5-4.25 9.5-9.5S15.25.5 10 .5zm0 1c4.7 0 8.5 3.8 8.5 8.5s-3.8 8.5-8.5 8.5-8.5-3.8-8.5-8.5S5.3 1.5 10 1.5zm1.8 1.71c-.57 0-1.1.17-1.55.45 1.56.37 2.73 1.77 2.73 3.45 0 .69-.21 1.33-.55 1.87 1.31-.29 2.29-1.45 2.29-2.85 0-1.61-1.31-2.92-2.92-2.92zm-2.38 1c-1.61 0-2.92 1.31-2.92 2.93 0 1.61 1.31 2.92 2.92 2.92 1.62 0 2.93-1.31 2.93-2.92 0-1.62-1.31-2.93-2.93-2.93zm4.25 5.01l-.51.59c2.34.69 2.45 3.61 2.45 3.61h1.28c0-4.71-3.22-4.2-3.22-4.2zm-2.1.8l-2.12 2.09-2.12-2.09C3.12 10.24 3.89 15 3.89 15h11.08c.47-4.98-3.4-4.98-3.4-4.98z'; break; case 'buddicons-community': - path = 'M9 3c0-.67-.47-1.43-1-2-.5.5-1 1.38-1 2 0 .48.45 1 1 1s1-.47 1-1zm4 0c0-.67-.47-1.43-1-2-.5.5-1 1.38-1 2 0 .48.45 1 1 1s1-.47 1-1zM9 9V5.5c0-.55-.45-1-1-1-.57 0-1 .49-1 1V9c0 .55.45 1 1 1 .57 0 1-.49 1-1zm4 0V5.5c0-.55-.45-1-1-1-.57 0-1 .49-1 1V9c0 .55.45 1 1 1 .57 0 1-.49 1-1zm4 1c0-1.48-1.41-2.77-3.5-3.46V9c0 .83-.67 1.5-1.5 1.5s-1.5-.67-1.5-1.5V6.01c-.17 0-.33-.01-.5-.01s-.33.01-.5.01V9c0 .83-.67 1.5-1.5 1.5S6.5 9.83 6.5 9V6.54C4.41 7.23 3 8.52 3 10c0 1.41.95 2.65 3.21 3.37 1.11.35 2.39 1.12 3.79 1.12s2.69-.78 3.79-1.13C16.04 12.65 17 11.41 17 10zm-7 5.43c1.43 0 2.74-.79 3.88-1.11 1.9-.53 2.49-1.34 3.12-2.32v3c0 2.21-3.13 4-7 4s-7-1.79-7-4v-3c.64.99 1.32 1.8 3.15 2.33 1.13.33 2.44 1.1 3.85 1.1z'; + path = + 'M9 3c0-.67-.47-1.43-1-2-.5.5-1 1.38-1 2 0 .48.45 1 1 1s1-.47 1-1zm4 0c0-.67-.47-1.43-1-2-.5.5-1 1.38-1 2 0 .48.45 1 1 1s1-.47 1-1zM9 9V5.5c0-.55-.45-1-1-1-.57 0-1 .49-1 1V9c0 .55.45 1 1 1 .57 0 1-.49 1-1zm4 0V5.5c0-.55-.45-1-1-1-.57 0-1 .49-1 1V9c0 .55.45 1 1 1 .57 0 1-.49 1-1zm4 1c0-1.48-1.41-2.77-3.5-3.46V9c0 .83-.67 1.5-1.5 1.5s-1.5-.67-1.5-1.5V6.01c-.17 0-.33-.01-.5-.01s-.33.01-.5.01V9c0 .83-.67 1.5-1.5 1.5S6.5 9.83 6.5 9V6.54C4.41 7.23 3 8.52 3 10c0 1.41.95 2.65 3.21 3.37 1.11.35 2.39 1.12 3.79 1.12s2.69-.78 3.79-1.13C16.04 12.65 17 11.41 17 10zm-7 5.43c1.43 0 2.74-.79 3.88-1.11 1.9-.53 2.49-1.34 3.12-2.32v3c0 2.21-3.13 4-7 4s-7-1.79-7-4v-3c.64.99 1.32 1.8 3.15 2.33 1.13.33 2.44 1.1 3.85 1.1z'; break; case 'buddicons-forums': - path = 'M13.5 7h-7C5.67 7 5 6.33 5 5.5S5.67 4 6.5 4h1.59C8.04 3.84 8 3.68 8 3.5 8 2.67 8.67 2 9.5 2h1c.83 0 1.5.67 1.5 1.5 0 .18-.04.34-.09.5h1.59c.83 0 1.5.67 1.5 1.5S14.33 7 13.5 7zM4 8h12c.55 0 1 .45 1 1s-.45 1-1 1H4c-.55 0-1-.45-1-1s.45-1 1-1zm1 3h10c.55 0 1 .45 1 1s-.45 1-1 1H5c-.55 0-1-.45-1-1s.45-1 1-1zm2 3h6c.55 0 1 .45 1 1s-.45 1-1 1h-1.09c.05.16.09.32.09.5 0 .83-.67 1.5-1.5 1.5h-1c-.83 0-1.5-.67-1.5-1.5 0-.18.04-.34.09-.5H7c-.55 0-1-.45-1-1s.45-1 1-1z'; + path = + 'M13.5 7h-7C5.67 7 5 6.33 5 5.5S5.67 4 6.5 4h1.59C8.04 3.84 8 3.68 8 3.5 8 2.67 8.67 2 9.5 2h1c.83 0 1.5.67 1.5 1.5 0 .18-.04.34-.09.5h1.59c.83 0 1.5.67 1.5 1.5S14.33 7 13.5 7zM4 8h12c.55 0 1 .45 1 1s-.45 1-1 1H4c-.55 0-1-.45-1-1s.45-1 1-1zm1 3h10c.55 0 1 .45 1 1s-.45 1-1 1H5c-.55 0-1-.45-1-1s.45-1 1-1zm2 3h6c.55 0 1 .45 1 1s-.45 1-1 1h-1.09c.05.16.09.32.09.5 0 .83-.67 1.5-1.5 1.5h-1c-.83 0-1.5-.67-1.5-1.5 0-.18.04-.34.09-.5H7c-.55 0-1-.45-1-1s.45-1 1-1z'; break; case 'buddicons-friends': - path = 'M8.75 5.77C8.75 4.39 7 2 7 2S5.25 4.39 5.25 5.77 5.9 7.5 7 7.5s1.75-.35 1.75-1.73zm6 0C14.75 4.39 13 2 13 2s-1.75 2.39-1.75 3.77S11.9 7.5 13 7.5s1.75-.35 1.75-1.73zM9 17V9c0-.55-.45-1-1-1H6c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1h2c.55 0 1-.45 1-1zm6 0V9c0-.55-.45-1-1-1h-2c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1h2c.55 0 1-.45 1-1zm-9-6l2-1v2l-2 1v-2zm6 0l2-1v2l-2 1v-2zm-6 3l2-1v2l-2 1v-2zm6 0l2-1v2l-2 1v-2z'; + path = + 'M8.75 5.77C8.75 4.39 7 2 7 2S5.25 4.39 5.25 5.77 5.9 7.5 7 7.5s1.75-.35 1.75-1.73zm6 0C14.75 4.39 13 2 13 2s-1.75 2.39-1.75 3.77S11.9 7.5 13 7.5s1.75-.35 1.75-1.73zM9 17V9c0-.55-.45-1-1-1H6c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1h2c.55 0 1-.45 1-1zm6 0V9c0-.55-.45-1-1-1h-2c-.55 0-1 .45-1 1v8c0 .55.45 1 1 1h2c.55 0 1-.45 1-1zm-9-6l2-1v2l-2 1v-2zm6 0l2-1v2l-2 1v-2zm-6 3l2-1v2l-2 1v-2zm6 0l2-1v2l-2 1v-2z'; break; case 'buddicons-groups': - path = 'M15.45 6.25c1.83.94 1.98 3.18.7 4.98-.8 1.12-2.33 1.88-3.46 1.78L10.05 18H9l-2.65-4.99c-1.13.16-2.73-.63-3.55-1.79-1.28-1.8-1.13-4.04.71-4.97.48-.24.96-.33 1.43-.31-.01.4.01.8.07 1.21.26 1.69 1.41 3.53 2.86 4.37-.19.55-.49.99-.88 1.25L9 16.58v-5.66C7.64 10.55 6.26 8.76 6 7c-.4-2.65 1-5 3.5-5s3.9 2.35 3.5 5c-.26 1.76-1.64 3.55-3 3.92v5.77l2.07-3.84c-.44-.23-.77-.71-.99-1.3 1.48-.83 2.65-2.69 2.91-4.4.06-.41.08-.82.07-1.22.46-.01.92.08 1.39.32z'; + path = + 'M15.45 6.25c1.83.94 1.98 3.18.7 4.98-.8 1.12-2.33 1.88-3.46 1.78L10.05 18H9l-2.65-4.99c-1.13.16-2.73-.63-3.55-1.79-1.28-1.8-1.13-4.04.71-4.97.48-.24.96-.33 1.43-.31-.01.4.01.8.07 1.21.26 1.69 1.41 3.53 2.86 4.37-.19.55-.49.99-.88 1.25L9 16.58v-5.66C7.64 10.55 6.26 8.76 6 7c-.4-2.65 1-5 3.5-5s3.9 2.35 3.5 5c-.26 1.76-1.64 3.55-3 3.92v5.77l2.07-3.84c-.44-.23-.77-.71-.99-1.3 1.48-.83 2.65-2.69 2.91-4.4.06-.41.08-.82.07-1.22.46-.01.92.08 1.39.32z'; break; case 'buddicons-pm': - path = 'M10 2c3 0 8 5 8 5v11H2V7s5-5 8-5zm7 14.72l-3.73-2.92L17 11l-.43-.37-2.26 1.3.24-4.31-8.77-.52-.46 4.54-1.99-.95L3 11l3.73 2.8-3.44 2.85.4.43L10 13l6.53 4.15z'; + path = + 'M10 2c3 0 8 5 8 5v11H2V7s5-5 8-5zm7 14.72l-3.73-2.92L17 11l-.43-.37-2.26 1.3.24-4.31-8.77-.52-.46 4.54-1.99-.95L3 11l3.73 2.8-3.44 2.85.4.43L10 13l6.53 4.15z'; break; case 'buddicons-replies': - path = 'M17.54 10.29c1.17 1.17 1.17 3.08 0 4.25-1.18 1.17-3.08 1.17-4.25 0l-.34-.52c0 3.66-2 4.38-2.95 4.98-.82-.6-2.95-1.28-2.95-4.98l-.34.52c-1.17 1.17-3.07 1.17-4.25 0-1.17-1.17-1.17-3.08 0-4.25 0 0 1.02-.67 2.1-1.3C3.71 7.84 3.2 6.42 3.2 4.88c0-.34.03-.67.08-1C3.53 5.66 4.47 7.22 5.8 8.3c.67-.35 1.85-.83 2.37-.92H8c-1.1 0-2-.9-2-2s.9-2 2-2v-.5c0-.28.22-.5.5-.5s.5.22.5.5v.5h2v-.5c0-.28.22-.5.5-.5s.5.22.5.5v.5c1.1 0 2 .9 2 2s-.9 2-2 2h-.17c.51.09 1.78.61 2.38.92 1.33-1.08 2.27-2.64 2.52-4.42.05.33.08.66.08 1 0 1.54-.51 2.96-1.36 4.11 1.08.63 2.09 1.3 2.09 1.3zM8.5 6.38c.5 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm3-2c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm-2.3 5.73c-.12.11-.19.26-.19.43.02.25.23.46.49.46h1c.26 0 .47-.21.49-.46 0-.15-.07-.29-.19-.43-.08-.06-.18-.11-.3-.11h-1c-.12 0-.22.05-.3.11zM12 12.5c0-.12-.06-.28-.19-.38-.09-.07-.19-.12-.31-.12h-3c-.12 0-.22.05-.31.12-.11.1-.19.25-.19.38 0 .28.22.5.5.5h3c.28 0 .5-.22.5-.5zM8.5 15h3c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-3c-.28 0-.5.22-.5.5s.22.5.5.5zm1 2h1c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-1c-.28 0-.5.22-.5.5s.22.5.5.5z'; + path = + 'M17.54 10.29c1.17 1.17 1.17 3.08 0 4.25-1.18 1.17-3.08 1.17-4.25 0l-.34-.52c0 3.66-2 4.38-2.95 4.98-.82-.6-2.95-1.28-2.95-4.98l-.34.52c-1.17 1.17-3.07 1.17-4.25 0-1.17-1.17-1.17-3.08 0-4.25 0 0 1.02-.67 2.1-1.3C3.71 7.84 3.2 6.42 3.2 4.88c0-.34.03-.67.08-1C3.53 5.66 4.47 7.22 5.8 8.3c.67-.35 1.85-.83 2.37-.92H8c-1.1 0-2-.9-2-2s.9-2 2-2v-.5c0-.28.22-.5.5-.5s.5.22.5.5v.5h2v-.5c0-.28.22-.5.5-.5s.5.22.5.5v.5c1.1 0 2 .9 2 2s-.9 2-2 2h-.17c.51.09 1.78.61 2.38.92 1.33-1.08 2.27-2.64 2.52-4.42.05.33.08.66.08 1 0 1.54-.51 2.96-1.36 4.11 1.08.63 2.09 1.3 2.09 1.3zM8.5 6.38c.5 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm3-2c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm-2.3 5.73c-.12.11-.19.26-.19.43.02.25.23.46.49.46h1c.26 0 .47-.21.49-.46 0-.15-.07-.29-.19-.43-.08-.06-.18-.11-.3-.11h-1c-.12 0-.22.05-.3.11zM12 12.5c0-.12-.06-.28-.19-.38-.09-.07-.19-.12-.31-.12h-3c-.12 0-.22.05-.31.12-.11.1-.19.25-.19.38 0 .28.22.5.5.5h3c.28 0 .5-.22.5-.5zM8.5 15h3c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-3c-.28 0-.5.22-.5.5s.22.5.5.5zm1 2h1c.28 0 .5-.22.5-.5s-.22-.5-.5-.5h-1c-.28 0-.5.22-.5.5s.22.5.5.5z'; break; case 'buddicons-topics': - path = 'M10.44 1.66c-.59-.58-1.54-.58-2.12 0L2.66 7.32c-.58.58-.58 1.53 0 2.12.6.6 1.56.56 2.12 0l5.66-5.66c.58-.58.59-1.53 0-2.12zm2.83 2.83c-.59-.59-1.54-.59-2.12 0l-5.66 5.66c-.59.58-.59 1.53 0 2.12.6.6 1.56.55 2.12 0l5.66-5.66c.58-.58.58-1.53 0-2.12zm1.06 6.72l4.18 4.18c.59.58.59 1.53 0 2.12s-1.54.59-2.12 0l-4.18-4.18-1.77 1.77c-.59.58-1.54.58-2.12 0-.59-.59-.59-1.54 0-2.13l5.66-5.65c.58-.59 1.53-.59 2.12 0 .58.58.58 1.53 0 2.12zM5 15c0-1.59-1.66-4-1.66-4S2 13.78 2 15s.6 2 1.34 2h.32C4.4 17 5 16.59 5 15z'; + path = + 'M10.44 1.66c-.59-.58-1.54-.58-2.12 0L2.66 7.32c-.58.58-.58 1.53 0 2.12.6.6 1.56.56 2.12 0l5.66-5.66c.58-.58.59-1.53 0-2.12zm2.83 2.83c-.59-.59-1.54-.59-2.12 0l-5.66 5.66c-.59.58-.59 1.53 0 2.12.6.6 1.56.55 2.12 0l5.66-5.66c.58-.58.58-1.53 0-2.12zm1.06 6.72l4.18 4.18c.59.58.59 1.53 0 2.12s-1.54.59-2.12 0l-4.18-4.18-1.77 1.77c-.59.58-1.54.58-2.12 0-.59-.59-.59-1.54 0-2.13l5.66-5.65c.58-.59 1.53-.59 2.12 0 .58.58.58 1.53 0 2.12zM5 15c0-1.59-1.66-4-1.66-4S2 13.78 2 15s.6 2 1.34 2h.32C4.4 17 5 16.59 5 15z'; break; case 'buddicons-tracking': - path = 'M10.98 6.78L15.5 15c-1 2-3.5 3-5.5 3s-4.5-1-5.5-3L9 6.82c-.75-1.23-2.28-1.98-4.29-2.03l2.46-2.92c1.68 1.19 2.46 2.32 2.97 3.31.56-.87 1.2-1.68 2.7-2.12l1.83 2.86c-1.42-.34-2.64.08-3.69.86zM8.17 10.4l-.93 1.69c.49.11 1 .16 1.54.16 1.35 0 2.58-.36 3.55-.95l-1.01-1.82c-.87.53-1.96.86-3.15.92zm.86 5.38c1.99 0 3.73-.74 4.74-1.86l-.98-1.76c-1 1.12-2.74 1.87-4.74 1.87-.62 0-1.21-.08-1.76-.21l-.63 1.15c.94.5 2.1.81 3.37.81z'; + path = + 'M10.98 6.78L15.5 15c-1 2-3.5 3-5.5 3s-4.5-1-5.5-3L9 6.82c-.75-1.23-2.28-1.98-4.29-2.03l2.46-2.92c1.68 1.19 2.46 2.32 2.97 3.31.56-.87 1.2-1.68 2.7-2.12l1.83 2.86c-1.42-.34-2.64.08-3.69.86zM8.17 10.4l-.93 1.69c.49.11 1 .16 1.54.16 1.35 0 2.58-.36 3.55-.95l-1.01-1.82c-.87.53-1.96.86-3.15.92zm.86 5.38c1.99 0 3.73-.74 4.74-1.86l-.98-1.76c-1 1.12-2.74 1.87-4.74 1.87-.62 0-1.21-.08-1.76-.21l-.63 1.15c.94.5 2.1.81 3.37.81z'; break; case 'building': - path = 'M3 20h14V0H3v20zM7 3H5V1h2v2zm4 0H9V1h2v2zm4 0h-2V1h2v2zM7 6H5V4h2v2zm4 0H9V4h2v2zm4 0h-2V4h2v2zM7 9H5V7h2v2zm4 0H9V7h2v2zm4 0h-2V7h2v2zm-8 3H5v-2h2v2zm4 0H9v-2h2v2zm4 0h-2v-2h2v2zm-4 7H5v-6h6v6zm4-4h-2v-2h2v2zm0 3h-2v-2h2v2z'; + path = + 'M3 20h14V0H3v20zM7 3H5V1h2v2zm4 0H9V1h2v2zm4 0h-2V1h2v2zM7 6H5V4h2v2zm4 0H9V4h2v2zm4 0h-2V4h2v2zM7 9H5V7h2v2zm4 0H9V7h2v2zm4 0h-2V7h2v2zm-8 3H5v-2h2v2zm4 0H9v-2h2v2zm4 0h-2v-2h2v2zm-4 7H5v-6h6v6zm4-4h-2v-2h2v2zm0 3h-2v-2h2v2z'; break; case 'businessman': - path = 'M7.3 6l-.03-.19c-.04-.37-.05-.73-.03-1.08.02-.36.1-.71.25-1.04.14-.32.31-.61.52-.86s.49-.46.83-.6c.34-.15.72-.23 1.13-.23.69 0 1.26.2 1.71.59s.76.87.91 1.44.18 1.16.09 1.78l-.03.19c-.01.09-.05.25-.11.48-.05.24-.12.47-.2.69-.08.21-.19.45-.34.72-.14.27-.3.49-.47.69-.18.19-.4.34-.67.48-.27.13-.55.19-.86.19s-.59-.06-.87-.19c-.26-.13-.49-.29-.67-.5-.18-.2-.34-.42-.49-.66-.15-.25-.26-.49-.34-.73-.09-.25-.16-.47-.21-.67-.06-.21-.1-.37-.12-.5zm9.2 6.24c.41.7.5 1.41.5 2.14v2.49c0 .03-.12.08-.29.13-.18.04-.42.13-.97.27-.55.12-1.1.24-1.65.34s-1.19.19-1.95.27c-.75.08-1.46.12-2.13.12-.68 0-1.39-.04-2.14-.12-.75-.07-1.4-.17-1.98-.27-.58-.11-1.08-.23-1.56-.34-.49-.11-.8-.21-1.06-.29L3 16.87v-2.49c0-.75.07-1.46.46-2.15s.81-1.25 1.5-1.68C5.66 10.12 7.19 10 8 10l1.67 1.67L9 13v3l1.02 1.08L11 16v-3l-.68-1.33L11.97 10c.77 0 2.2.07 2.9.52.71.45 1.21 1.02 1.63 1.72z'; + path = + 'M7.3 6l-.03-.19c-.04-.37-.05-.73-.03-1.08.02-.36.1-.71.25-1.04.14-.32.31-.61.52-.86s.49-.46.83-.6c.34-.15.72-.23 1.13-.23.69 0 1.26.2 1.71.59s.76.87.91 1.44.18 1.16.09 1.78l-.03.19c-.01.09-.05.25-.11.48-.05.24-.12.47-.2.69-.08.21-.19.45-.34.72-.14.27-.3.49-.47.69-.18.19-.4.34-.67.48-.27.13-.55.19-.86.19s-.59-.06-.87-.19c-.26-.13-.49-.29-.67-.5-.18-.2-.34-.42-.49-.66-.15-.25-.26-.49-.34-.73-.09-.25-.16-.47-.21-.67-.06-.21-.1-.37-.12-.5zm9.2 6.24c.41.7.5 1.41.5 2.14v2.49c0 .03-.12.08-.29.13-.18.04-.42.13-.97.27-.55.12-1.1.24-1.65.34s-1.19.19-1.95.27c-.75.08-1.46.12-2.13.12-.68 0-1.39-.04-2.14-.12-.75-.07-1.4-.17-1.98-.27-.58-.11-1.08-.23-1.56-.34-.49-.11-.8-.21-1.06-.29L3 16.87v-2.49c0-.75.07-1.46.46-2.15s.81-1.25 1.5-1.68C5.66 10.12 7.19 10 8 10l1.67 1.67L9 13v3l1.02 1.08L11 16v-3l-.68-1.33L11.97 10c.77 0 2.2.07 2.9.52.71.45 1.21 1.02 1.63 1.72z'; break; case 'button': - path = 'M17 5H3c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm1 7c0 .6-.4 1-1 1H3c-.6 0-1-.4-1-1V7c0-.6.4-1 1-1h14c.6 0 1 .4 1 1v5z'; + path = + 'M17 5H3c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm1 7c0 .6-.4 1-1 1H3c-.6 0-1-.4-1-1V7c0-.6.4-1 1-1h14c.6 0 1 .4 1 1v5z'; break; case 'calendar-alt': - path = 'M15 4h3v15H2V4h3V3c0-.41.15-.76.44-1.06.29-.29.65-.44 1.06-.44s.77.15 1.06.44c.29.3.44.65.44 1.06v1h4V3c0-.41.15-.76.44-1.06.29-.29.65-.44 1.06-.44s.77.15 1.06.44c.29.3.44.65.44 1.06v1zM6 3v2.5c0 .14.05.26.15.36.09.09.21.14.35.14s.26-.05.35-.14c.1-.1.15-.22.15-.36V3c0-.14-.05-.26-.15-.35-.09-.1-.21-.15-.35-.15s-.26.05-.35.15c-.1.09-.15.21-.15.35zm7 0v2.5c0 .14.05.26.14.36.1.09.22.14.36.14s.26-.05.36-.14c.09-.1.14-.22.14-.36V3c0-.14-.05-.26-.14-.35-.1-.1-.22-.15-.36-.15s-.26.05-.36.15c-.09.09-.14.21-.14.35zm4 15V8H3v10h14zM7 9v2H5V9h2zm2 0h2v2H9V9zm4 2V9h2v2h-2zm-6 1v2H5v-2h2zm2 0h2v2H9v-2zm4 2v-2h2v2h-2zm-6 1v2H5v-2h2zm4 2H9v-2h2v2zm4 0h-2v-2h2v2z'; + path = + 'M15 4h3v15H2V4h3V3c0-.41.15-.76.44-1.06.29-.29.65-.44 1.06-.44s.77.15 1.06.44c.29.3.44.65.44 1.06v1h4V3c0-.41.15-.76.44-1.06.29-.29.65-.44 1.06-.44s.77.15 1.06.44c.29.3.44.65.44 1.06v1zM6 3v2.5c0 .14.05.26.15.36.09.09.21.14.35.14s.26-.05.35-.14c.1-.1.15-.22.15-.36V3c0-.14-.05-.26-.15-.35-.09-.1-.21-.15-.35-.15s-.26.05-.35.15c-.1.09-.15.21-.15.35zm7 0v2.5c0 .14.05.26.14.36.1.09.22.14.36.14s.26-.05.36-.14c.09-.1.14-.22.14-.36V3c0-.14-.05-.26-.14-.35-.1-.1-.22-.15-.36-.15s-.26.05-.36.15c-.09.09-.14.21-.14.35zm4 15V8H3v10h14zM7 9v2H5V9h2zm2 0h2v2H9V9zm4 2V9h2v2h-2zm-6 1v2H5v-2h2zm2 0h2v2H9v-2zm4 2v-2h2v2h-2zm-6 1v2H5v-2h2zm4 2H9v-2h2v2zm4 0h-2v-2h2v2z'; break; case 'calendar': - path = 'M15 4h3v14H2V4h3V3c0-.83.67-1.5 1.5-1.5S8 2.17 8 3v1h4V3c0-.83.67-1.5 1.5-1.5S15 2.17 15 3v1zM6 3v2.5c0 .28.22.5.5.5s.5-.22.5-.5V3c0-.28-.22-.5-.5-.5S6 2.72 6 3zm7 0v2.5c0 .28.22.5.5.5s.5-.22.5-.5V3c0-.28-.22-.5-.5-.5s-.5.22-.5.5zm4 14V8H3v9h14zM7 16V9H5v7h2zm4 0V9H9v7h2zm4 0V9h-2v7h2z'; + path = + 'M15 4h3v14H2V4h3V3c0-.83.67-1.5 1.5-1.5S8 2.17 8 3v1h4V3c0-.83.67-1.5 1.5-1.5S15 2.17 15 3v1zM6 3v2.5c0 .28.22.5.5.5s.5-.22.5-.5V3c0-.28-.22-.5-.5-.5S6 2.72 6 3zm7 0v2.5c0 .28.22.5.5.5s.5-.22.5-.5V3c0-.28-.22-.5-.5-.5s-.5.22-.5.5zm4 14V8H3v9h14zM7 16V9H5v7h2zm4 0V9H9v7h2zm4 0V9h-2v7h2z'; break; case 'camera': - path = 'M6 5V3H3v2h3zm12 10V4H9L7 6H2v9h16zm-7-8c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3z'; + path = + 'M6 5V3H3v2h3zm12 10V4H9L7 6H2v9h16zm-7-8c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3z'; break; case 'carrot': - path = 'M2 18.43c1.51 1.36 11.64-4.67 13.14-7.21.72-1.22-.13-3.01-1.52-4.44C15.2 5.73 16.59 9 17.91 8.31c.6-.32.99-1.31.7-1.92-.52-1.08-2.25-1.08-3.42-1.21.83-.2 2.82-1.05 2.86-2.25.04-.92-1.13-1.97-2.05-1.86-1.21.14-1.65 1.88-2.06 3-.05-.71-.2-2.27-.98-2.95-1.04-.91-2.29-.05-2.32 1.05-.04 1.33 2.82 2.07 1.92 3.67C11.04 4.67 9.25 4.03 8.1 4.7c-.49.31-1.05.91-1.63 1.69.89.94 2.12 2.07 3.09 2.72.2.14.26.42.11.62-.14.21-.42.26-.62.12-.99-.67-2.2-1.78-3.1-2.71-.45.67-.91 1.43-1.34 2.23.85.86 1.93 1.83 2.79 2.41.2.14.25.42.11.62-.14.21-.42.26-.63.12-.85-.58-1.86-1.48-2.71-2.32C2.4 13.69 1.1 17.63 2 18.43z'; + path = + 'M2 18.43c1.51 1.36 11.64-4.67 13.14-7.21.72-1.22-.13-3.01-1.52-4.44C15.2 5.73 16.59 9 17.91 8.31c.6-.32.99-1.31.7-1.92-.52-1.08-2.25-1.08-3.42-1.21.83-.2 2.82-1.05 2.86-2.25.04-.92-1.13-1.97-2.05-1.86-1.21.14-1.65 1.88-2.06 3-.05-.71-.2-2.27-.98-2.95-1.04-.91-2.29-.05-2.32 1.05-.04 1.33 2.82 2.07 1.92 3.67C11.04 4.67 9.25 4.03 8.1 4.7c-.49.31-1.05.91-1.63 1.69.89.94 2.12 2.07 3.09 2.72.2.14.26.42.11.62-.14.21-.42.26-.62.12-.99-.67-2.2-1.78-3.1-2.71-.45.67-.91 1.43-1.34 2.23.85.86 1.93 1.83 2.79 2.41.2.14.25.42.11.62-.14.21-.42.26-.63.12-.85-.58-1.86-1.48-2.71-2.32C2.4 13.69 1.1 17.63 2 18.43z'; break; case 'cart': - path = 'M6 13h9c.55 0 1 .45 1 1s-.45 1-1 1H5c-.55 0-1-.45-1-1V4H2c-.55 0-1-.45-1-1s.45-1 1-1h3c.55 0 1 .45 1 1v2h13l-4 7H6v1zm-.5 3c.83 0 1.5.67 1.5 1.5S6.33 19 5.5 19 4 18.33 4 17.5 4.67 16 5.5 16zm9 0c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5-1.5-.67-1.5-1.5.67-1.5 1.5-1.5z'; + path = + 'M6 13h9c.55 0 1 .45 1 1s-.45 1-1 1H5c-.55 0-1-.45-1-1V4H2c-.55 0-1-.45-1-1s.45-1 1-1h3c.55 0 1 .45 1 1v2h13l-4 7H6v1zm-.5 3c.83 0 1.5.67 1.5 1.5S6.33 19 5.5 19 4 18.33 4 17.5 4.67 16 5.5 16zm9 0c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5-1.5-.67-1.5-1.5.67-1.5 1.5-1.5z'; break; case 'category': path = 'M5 7h13v10H2V4h7l2 2H4v9h1V7z'; break; case 'chart-area': - path = 'M18 18l.01-12.28c.59-.35.99-.99.99-1.72 0-1.1-.9-2-2-2s-2 .9-2 2c0 .8.47 1.48 1.14 1.8l-4.13 6.58c-.33-.24-.73-.38-1.16-.38-.84 0-1.55.51-1.85 1.24l-2.14-1.53c.09-.22.14-.46.14-.71 0-1.11-.89-2-2-2-1.1 0-2 .89-2 2 0 .73.4 1.36.98 1.71L1 18h17zM17 3c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM5 10c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm5.85 3c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1z'; + path = + 'M18 18l.01-12.28c.59-.35.99-.99.99-1.72 0-1.1-.9-2-2-2s-2 .9-2 2c0 .8.47 1.48 1.14 1.8l-4.13 6.58c-.33-.24-.73-.38-1.16-.38-.84 0-1.55.51-1.85 1.24l-2.14-1.53c.09-.22.14-.46.14-.71 0-1.11-.89-2-2-2-1.1 0-2 .89-2 2 0 .73.4 1.36.98 1.71L1 18h17zM17 3c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM5 10c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm5.85 3c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1z'; break; case 'chart-bar': path = 'M18 18V2h-4v16h4zm-6 0V7H8v11h4zm-6 0v-8H2v8h4z'; break; case 'chart-line': - path = 'M18 3.5c0 .62-.38 1.16-.92 1.38v13.11H1.99l4.22-6.73c-.13-.23-.21-.48-.21-.76C6 9.67 6.67 9 7.5 9S9 9.67 9 10.5c0 .13-.02.25-.05.37l1.44.63c.27-.3.67-.5 1.11-.5.18 0 .35.04.51.09l3.58-6.41c-.36-.27-.59-.7-.59-1.18 0-.83.67-1.5 1.5-1.5.19 0 .36.04.53.1l.05-.09v.11c.54.22.92.76.92 1.38zm-1.92 13.49V5.85l-3.29 5.89c.13.23.21.48.21.76 0 .83-.67 1.5-1.5 1.5s-1.5-.67-1.5-1.5l.01-.07-1.63-.72c-.25.18-.55.29-.88.29-.18 0-.35-.04-.51-.1l-3.2 5.09h12.29z'; + path = + 'M18 3.5c0 .62-.38 1.16-.92 1.38v13.11H1.99l4.22-6.73c-.13-.23-.21-.48-.21-.76C6 9.67 6.67 9 7.5 9S9 9.67 9 10.5c0 .13-.02.25-.05.37l1.44.63c.27-.3.67-.5 1.11-.5.18 0 .35.04.51.09l3.58-6.41c-.36-.27-.59-.7-.59-1.18 0-.83.67-1.5 1.5-1.5.19 0 .36.04.53.1l.05-.09v.11c.54.22.92.76.92 1.38zm-1.92 13.49V5.85l-3.29 5.89c.13.23.21.48.21.76 0 .83-.67 1.5-1.5 1.5s-1.5-.67-1.5-1.5l.01-.07-1.63-.72c-.25.18-.55.29-.88.29-.18 0-.35-.04-.51-.1l-3.2 5.09h12.29z'; break; case 'chart-pie': - path = 'M10 10V3c3.87 0 7 3.13 7 7h-7zM9 4v7h7c0 3.87-3.13 7-7 7s-7-3.13-7-7 3.13-7 7-7z'; + path = + 'M10 10V3c3.87 0 7 3.13 7 7h-7zM9 4v7h7c0 3.87-3.13 7-7 7s-7-3.13-7-7 3.13-7 7-7z'; break; case 'clipboard': - path = 'M11.9.39l1.4 1.4c1.61.19 3.5-.74 4.61.37s.18 3 .37 4.61l1.4 1.4c.39.39.39 1.02 0 1.41l-9.19 9.2c-.4.39-1.03.39-1.42 0L1.29 11c-.39-.39-.39-1.02 0-1.42l9.2-9.19c.39-.39 1.02-.39 1.41 0zm.58 2.25l-.58.58 4.95 4.95.58-.58c-.19-.6-.2-1.22-.15-1.82.02-.31.05-.62.09-.92.12-1 .18-1.63-.17-1.98s-.98-.29-1.98-.17c-.3.04-.61.07-.92.09-.6.05-1.22.04-1.82-.15zm4.02.93c.39.39.39 1.03 0 1.42s-1.03.39-1.42 0-.39-1.03 0-1.42 1.03-.39 1.42 0zm-6.72.36l-.71.7L15.44 11l.7-.71zM8.36 5.34l-.7.71 6.36 6.36.71-.7zM6.95 6.76l-.71.7 6.37 6.37.7-.71zM5.54 8.17l-.71.71 6.36 6.36.71-.71zM4.12 9.58l-.71.71 6.37 6.37.71-.71z'; + path = + 'M11.9.39l1.4 1.4c1.61.19 3.5-.74 4.61.37s.18 3 .37 4.61l1.4 1.4c.39.39.39 1.02 0 1.41l-9.19 9.2c-.4.39-1.03.39-1.42 0L1.29 11c-.39-.39-.39-1.02 0-1.42l9.2-9.19c.39-.39 1.02-.39 1.41 0zm.58 2.25l-.58.58 4.95 4.95.58-.58c-.19-.6-.2-1.22-.15-1.82.02-.31.05-.62.09-.92.12-1 .18-1.63-.17-1.98s-.98-.29-1.98-.17c-.3.04-.61.07-.92.09-.6.05-1.22.04-1.82-.15zm4.02.93c.39.39.39 1.03 0 1.42s-1.03.39-1.42 0-.39-1.03 0-1.42 1.03-.39 1.42 0zm-6.72.36l-.71.7L15.44 11l.7-.71zM8.36 5.34l-.7.71 6.36 6.36.71-.7zM6.95 6.76l-.71.7 6.37 6.37.7-.71zM5.54 8.17l-.71.71 6.36 6.36.71-.71zM4.12 9.58l-.71.71 6.37 6.37.71-.71z'; break; case 'clock': - path = 'M10 2c4.42 0 8 3.58 8 8s-3.58 8-8 8-8-3.58-8-8 3.58-8 8-8zm0 14c3.31 0 6-2.69 6-6s-2.69-6-6-6-6 2.69-6 6 2.69 6 6 6zm-.71-5.29c.07.05.14.1.23.15l-.02.02L14 13l-3.03-3.19L10 5l-.97 4.81h.01c0 .02-.01.05-.02.09S9 9.97 9 10c0 .28.1.52.29.71z'; + path = + 'M10 2c4.42 0 8 3.58 8 8s-3.58 8-8 8-8-3.58-8-8 3.58-8 8-8zm0 14c3.31 0 6-2.69 6-6s-2.69-6-6-6-6 2.69-6 6 2.69 6 6 6zm-.71-5.29c.07.05.14.1.23.15l-.02.02L14 13l-3.03-3.19L10 5l-.97 4.81h.01c0 .02-.01.05-.02.09S9 9.97 9 10c0 .28.1.52.29.71z'; break; case 'cloud-saved': - path = 'M14.8 9c.1-.3.2-.6.2-1 0-2.2-1.8-4-4-4-1.5 0-2.9.9-3.5 2.2-.3-.1-.7-.2-1-.2C5.1 6 4 7.1 4 8.5c0 .2 0 .4.1.5-1.8.3-3.1 1.7-3.1 3.5C1 14.4 2.6 16 4.5 16h10c1.9 0 3.5-1.6 3.5-3.5 0-1.8-1.4-3.3-3.2-3.5zm-6.3 5.9l-3.2-3.2 1.4-1.4 1.8 1.8 3.8-3.8 1.4 1.4-5.2 5.2z'; + path = + 'M14.8 9c.1-.3.2-.6.2-1 0-2.2-1.8-4-4-4-1.5 0-2.9.9-3.5 2.2-.3-.1-.7-.2-1-.2C5.1 6 4 7.1 4 8.5c0 .2 0 .4.1.5-1.8.3-3.1 1.7-3.1 3.5C1 14.4 2.6 16 4.5 16h10c1.9 0 3.5-1.6 3.5-3.5 0-1.8-1.4-3.3-3.2-3.5zm-6.3 5.9l-3.2-3.2 1.4-1.4 1.8 1.8 3.8-3.8 1.4 1.4-5.2 5.2z'; break; case 'cloud-upload': - path = 'M14.8 9c.1-.3.2-.6.2-1 0-2.2-1.8-4-4-4-1.5 0-2.9.9-3.5 2.2-.3-.1-.7-.2-1-.2C5.1 6 4 7.1 4 8.5c0 .2 0 .4.1.5-1.8.3-3.1 1.7-3.1 3.5C1 14.4 2.6 16 4.5 16H8v-3H5l4.5-4.5L14 13h-3v3h3.5c1.9 0 3.5-1.6 3.5-3.5 0-1.8-1.4-3.3-3.2-3.5z'; + path = + 'M14.8 9c.1-.3.2-.6.2-1 0-2.2-1.8-4-4-4-1.5 0-2.9.9-3.5 2.2-.3-.1-.7-.2-1-.2C5.1 6 4 7.1 4 8.5c0 .2 0 .4.1.5-1.8.3-3.1 1.7-3.1 3.5C1 14.4 2.6 16 4.5 16H8v-3H5l4.5-4.5L14 13h-3v3h3.5c1.9 0 3.5-1.6 3.5-3.5 0-1.8-1.4-3.3-3.2-3.5z'; break; case 'cloud': - path = 'M14.9 9c1.8.2 3.1 1.7 3.1 3.5 0 1.9-1.6 3.5-3.5 3.5h-10C2.6 16 1 14.4 1 12.5 1 10.7 2.3 9.3 4.1 9 4 8.9 4 8.7 4 8.5 4 7.1 5.1 6 6.5 6c.3 0 .7.1.9.2C8.1 4.9 9.4 4 11 4c2.2 0 4 1.8 4 4 0 .4-.1.7-.1 1z'; + path = + 'M14.9 9c1.8.2 3.1 1.7 3.1 3.5 0 1.9-1.6 3.5-3.5 3.5h-10C2.6 16 1 14.4 1 12.5 1 10.7 2.3 9.3 4.1 9 4 8.9 4 8.7 4 8.5 4 7.1 5.1 6 6.5 6c.3 0 .7.1.9.2C8.1 4.9 9.4 4 11 4c2.2 0 4 1.8 4 4 0 .4-.1.7-.1 1z'; break; case 'columns': path = 'M3 15h6V5H3v10zm8 0h6V5h-6v10z'; @@ -267,10 +323,12 @@ export default class Dashicon extends Component { path = 'M5 4l10 6-10 6V4z'; break; case 'controls-repeat': - path = 'M5 7v3l-2 1.5V5h11V3l4 3.01L14 9V7H5zm10 6v-3l2-1.5V15H6v2l-4-3.01L6 11v2h9z'; + path = + 'M5 7v3l-2 1.5V5h11V3l4 3.01L14 9V7H5zm10 6v-3l2-1.5V15H6v2l-4-3.01L6 11v2h9z'; break; case 'controls-skipback': - path = 'M11.98 7.63l6-3.6v12l-6-3.6v3.6l-8-4.8v4.8h-2v-12h2v4.8l8-4.8v3.6z'; + path = + 'M11.98 7.63l6-3.6v12l-6-3.6v3.6l-8-4.8v4.8h-2v-12h2v4.8l8-4.8v3.6z'; break; case 'controls-skipforward': path = 'M8 12.4L2 16V4l6 3.6V4l8 4.8V4h2v12h-2v-4.8L8 16v-3.6z'; @@ -279,37 +337,47 @@ export default class Dashicon extends Component { path = 'M2 7h4l5-4v14l-5-4H2V7z'; break; case 'controls-volumeon': - path = 'M2 7h4l5-4v14l-5-4H2V7zm12.69-2.46C14.82 4.59 18 5.92 18 10s-3.18 5.41-3.31 5.46c-.06.03-.13.04-.19.04-.2 0-.39-.12-.46-.31-.11-.26.02-.55.27-.65.11-.05 2.69-1.15 2.69-4.54 0-3.41-2.66-4.53-2.69-4.54-.25-.1-.38-.39-.27-.65.1-.25.39-.38.65-.27zM16 10c0 2.57-2.23 3.43-2.32 3.47-.06.02-.12.03-.18.03-.2 0-.39-.12-.47-.32-.1-.26.04-.55.29-.65.07-.02 1.68-.67 1.68-2.53s-1.61-2.51-1.68-2.53c-.25-.1-.38-.39-.29-.65.1-.25.39-.39.65-.29.09.04 2.32.9 2.32 3.47z'; + path = + 'M2 7h4l5-4v14l-5-4H2V7zm12.69-2.46C14.82 4.59 18 5.92 18 10s-3.18 5.41-3.31 5.46c-.06.03-.13.04-.19.04-.2 0-.39-.12-.46-.31-.11-.26.02-.55.27-.65.11-.05 2.69-1.15 2.69-4.54 0-3.41-2.66-4.53-2.69-4.54-.25-.1-.38-.39-.27-.65.1-.25.39-.38.65-.27zM16 10c0 2.57-2.23 3.43-2.32 3.47-.06.02-.12.03-.18.03-.2 0-.39-.12-.47-.32-.1-.26.04-.55.29-.65.07-.02 1.68-.67 1.68-2.53s-1.61-2.51-1.68-2.53c-.25-.1-.38-.39-.29-.65.1-.25.39-.39.65-.29.09.04 2.32.9 2.32 3.47z'; break; case 'cover-image': - path = 'M2.2 1h15.5c.7 0 1.3.6 1.3 1.2v11.5c0 .7-.6 1.2-1.2 1.2H2.2c-.6.1-1.2-.5-1.2-1.1V2.2C1 1.6 1.6 1 2.2 1zM17 13V3H3v10h14zm-4-4s0-5 3-5v7c0 .6-.4 1-1 1H5c-.6 0-1-.4-1-1V7c2 0 3 4 3 4s1-4 3-4 3 2 3 2zM4 17h12v2H4z'; + path = + 'M2.2 1h15.5c.7 0 1.3.6 1.3 1.2v11.5c0 .7-.6 1.2-1.2 1.2H2.2c-.6.1-1.2-.5-1.2-1.1V2.2C1 1.6 1.6 1 2.2 1zM17 13V3H3v10h14zm-4-4s0-5 3-5v7c0 .6-.4 1-1 1H5c-.6 0-1-.4-1-1V7c2 0 3 4 3 4s1-4 3-4 3 2 3 2zM4 17h12v2H4z'; break; case 'dashboard': - path = 'M3.76 16h12.48c1.1-1.37 1.76-3.11 1.76-5 0-4.42-3.58-8-8-8s-8 3.58-8 8c0 1.89.66 3.63 1.76 5zM10 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM6 6c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm8 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm-5.37 5.55L12 7v6c0 1.1-.9 2-2 2s-2-.9-2-2c0-.57.24-1.08.63-1.45zM4 10c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm12 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm-5 3c0-.55-.45-1-1-1s-1 .45-1 1 .45 1 1 1 1-.45 1-1z'; + path = + 'M3.76 16h12.48c1.1-1.37 1.76-3.11 1.76-5 0-4.42-3.58-8-8-8s-8 3.58-8 8c0 1.89.66 3.63 1.76 5zM10 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM6 6c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm8 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm-5.37 5.55L12 7v6c0 1.1-.9 2-2 2s-2-.9-2-2c0-.57.24-1.08.63-1.45zM4 10c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm12 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm-5 3c0-.55-.45-1-1-1s-1 .45-1 1 .45 1 1 1 1-.45 1-1z'; break; case 'desktop': - path = 'M3 2h14c.55 0 1 .45 1 1v10c0 .55-.45 1-1 1h-5v2h2c.55 0 1 .45 1 1v1H5v-1c0-.55.45-1 1-1h2v-2H3c-.55 0-1-.45-1-1V3c0-.55.45-1 1-1zm13 9V4H4v7h12zM5 5h9L5 9V5z'; + path = + 'M3 2h14c.55 0 1 .45 1 1v10c0 .55-.45 1-1 1h-5v2h2c.55 0 1 .45 1 1v1H5v-1c0-.55.45-1 1-1h2v-2H3c-.55 0-1-.45-1-1V3c0-.55.45-1 1-1zm13 9V4H4v7h12zM5 5h9L5 9V5z'; break; case 'dismiss': - path = 'M10 2c4.42 0 8 3.58 8 8s-3.58 8-8 8-8-3.58-8-8 3.58-8 8-8zm5 11l-3-3 3-3-2-2-3 3-3-3-2 2 3 3-3 3 2 2 3-3 3 3z'; + path = + 'M10 2c4.42 0 8 3.58 8 8s-3.58 8-8 8-8-3.58-8-8 3.58-8 8-8zm5 11l-3-3 3-3-2-2-3 3-3-3-2 2 3 3-3 3 2 2 3-3 3 3z'; break; case 'download': path = 'M14.01 4v6h2V2H4v8h2.01V4h8zm-2 2v6h3l-5 6-5-6h3V6h4z'; break; case 'edit': - path = 'M13.89 3.39l2.71 2.72c.46.46.42 1.24.03 1.64l-8.01 8.02-5.56 1.16 1.16-5.58s7.6-7.63 7.99-8.03c.39-.39 1.22-.39 1.68.07zm-2.73 2.79l-5.59 5.61 1.11 1.11 5.54-5.65zm-2.97 8.23l5.58-5.6-1.07-1.08-5.59 5.6z'; + path = + 'M13.89 3.39l2.71 2.72c.46.46.42 1.24.03 1.64l-8.01 8.02-5.56 1.16 1.16-5.58s7.6-7.63 7.99-8.03c.39-.39 1.22-.39 1.68.07zm-2.73 2.79l-5.59 5.61 1.11 1.11 5.54-5.65zm-2.97 8.23l5.58-5.6-1.07-1.08-5.59 5.6z'; break; case 'editor-aligncenter': - path = 'M14 5V3H6v2h8zm3 4V7H3v2h14zm-3 4v-2H6v2h8zm3 4v-2H3v2h14z'; + path = + 'M14 5V3H6v2h8zm3 4V7H3v2h14zm-3 4v-2H6v2h8zm3 4v-2H3v2h14z'; break; case 'editor-alignleft': - path = 'M12 5V3H3v2h9zm5 4V7H3v2h14zm-5 4v-2H3v2h9zm5 4v-2H3v2h14z'; + path = + 'M12 5V3H3v2h9zm5 4V7H3v2h14zm-5 4v-2H3v2h9zm5 4v-2H3v2h14z'; break; case 'editor-alignright': - path = 'M17 5V3H8v2h9zm0 4V7H3v2h14zm0 4v-2H8v2h9zm0 4v-2H3v2h14z'; + path = + 'M17 5V3H8v2h9zm0 4V7H3v2h14zm0 4v-2H8v2h9zm0 4v-2H3v2h14z'; break; case 'editor-bold': - path = 'M6 4v13h4.54c1.37 0 2.46-.33 3.26-1 .8-.66 1.2-1.58 1.2-2.77 0-.84-.17-1.51-.51-2.01s-.9-.85-1.67-1.03v-.09c.57-.1 1.02-.4 1.36-.9s.51-1.13.51-1.91c0-1.14-.39-1.98-1.17-2.5C12.75 4.26 11.5 4 9.78 4H6zm2.57 5.15V6.26h1.36c.73 0 1.27.11 1.61.32.34.22.51.58.51 1.07 0 .54-.16.92-.47 1.15s-.82.35-1.51.35h-1.5zm0 2.19h1.6c1.44 0 2.16.53 2.16 1.61 0 .6-.17 1.05-.51 1.34s-.86.43-1.57.43H8.57v-3.38z'; + path = + 'M6 4v13h4.54c1.37 0 2.46-.33 3.26-1 .8-.66 1.2-1.58 1.2-2.77 0-.84-.17-1.51-.51-2.01s-.9-.85-1.67-1.03v-.09c.57-.1 1.02-.4 1.36-.9s.51-1.13.51-1.91c0-1.14-.39-1.98-1.17-2.5C12.75 4.26 11.5 4 9.78 4H6zm2.57 5.15V6.26h1.36c.73 0 1.27.11 1.61.32.34.22.51.58.51 1.07 0 .54-.16.92-.47 1.15s-.82.35-1.51.35h-1.5zm0 2.19h1.6c1.44 0 2.16.53 2.16 1.61 0 .6-.17 1.05-.51 1.34s-.86.43-1.57.43H8.57v-3.38z'; break; case 'editor-break': path = 'M16 4h2v9H7v3l-5-4 5-4v3h9V4z'; @@ -318,301 +386,396 @@ export default class Dashicon extends Component { path = 'M9 6l-4 4 4 4-1 2-6-6 6-6zm2 8l4-4-4-4 1-2 6 6-6 6z'; break; case 'editor-contract': - path = 'M15.75 6.75L18 3v14l-2.25-3.75L17 12h-4v4l1.25-1.25L18 17H2l3.75-2.25L7 16v-4H3l1.25 1.25L2 17V3l2.25 3.75L3 8h4V4L5.75 5.25 2 3h16l-3.75 2.25L13 4v4h4z'; + path = + 'M15.75 6.75L18 3v14l-2.25-3.75L17 12h-4v4l1.25-1.25L18 17H2l3.75-2.25L7 16v-4H3l1.25 1.25L2 17V3l2.25 3.75L3 8h4V4L5.75 5.25 2 3h16l-3.75 2.25L13 4v4h4z'; break; case 'editor-customchar': - path = 'M10 5.4c1.27 0 2.24.36 2.91 1.08.66.71 1 1.76 1 3.13 0 1.28-.23 2.37-.69 3.27-.47.89-1.27 1.52-2.22 2.12v2h6v-2h-3.69c.92-.64 1.62-1.34 2.12-2.34.49-1.01.74-2.13.74-3.35 0-1.78-.55-3.19-1.65-4.22S11.92 3.54 10 3.54s-3.43.53-4.52 1.57c-1.1 1.04-1.65 2.44-1.65 4.2 0 1.21.24 2.31.73 3.33.48 1.01 1.19 1.71 2.1 2.36H3v2h6v-2c-.98-.64-1.8-1.28-2.24-2.17-.45-.89-.67-1.96-.67-3.22 0-1.37.33-2.41 1-3.13C7.75 5.76 8.72 5.4 10 5.4z'; + path = + 'M10 5.4c1.27 0 2.24.36 2.91 1.08.66.71 1 1.76 1 3.13 0 1.28-.23 2.37-.69 3.27-.47.89-1.27 1.52-2.22 2.12v2h6v-2h-3.69c.92-.64 1.62-1.34 2.12-2.34.49-1.01.74-2.13.74-3.35 0-1.78-.55-3.19-1.65-4.22S11.92 3.54 10 3.54s-3.43.53-4.52 1.57c-1.1 1.04-1.65 2.44-1.65 4.2 0 1.21.24 2.31.73 3.33.48 1.01 1.19 1.71 2.1 2.36H3v2h6v-2c-.98-.64-1.8-1.28-2.24-2.17-.45-.89-.67-1.96-.67-3.22 0-1.37.33-2.41 1-3.13C7.75 5.76 8.72 5.4 10 5.4z'; break; case 'editor-expand': - path = 'M7 8h6v4H7zm-5 5v4h4l-1.2-1.2L7 12l-3.8 2.2M14 17h4v-4l-1.2 1.2L13 12l2.2 3.8M14 3l1.3 1.3L13 8l3.8-2.2L18 7V3M6 3H2v4l1.2-1.2L7 8 4.7 4.3'; + path = + 'M7 8h6v4H7zm-5 5v4h4l-1.2-1.2L7 12l-3.8 2.2M14 17h4v-4l-1.2 1.2L13 12l2.2 3.8M14 3l1.3 1.3L13 8l3.8-2.2L18 7V3M6 3H2v4l1.2-1.2L7 8 4.7 4.3'; break; case 'editor-help': - path = 'M17 10c0-3.87-3.14-7-7-7-3.87 0-7 3.13-7 7s3.13 7 7 7c3.86 0 7-3.13 7-7zm-6.3 1.48H9.14v-.43c0-.38.08-.7.24-.98s.46-.57.88-.89c.41-.29.68-.53.81-.71.14-.18.2-.39.2-.62 0-.25-.09-.44-.28-.58-.19-.13-.45-.19-.79-.19-.58 0-1.25.19-2 .57l-.64-1.28c.87-.49 1.8-.74 2.77-.74.81 0 1.45.2 1.92.58.48.39.71.91.71 1.55 0 .43-.09.8-.29 1.11-.19.32-.57.67-1.11 1.06-.38.28-.61.49-.71.63-.1.15-.15.34-.15.57v.35zm-1.47 2.74c-.18-.17-.27-.42-.27-.73 0-.33.08-.58.26-.75s.43-.25.77-.25c.32 0 .57.09.75.26s.27.42.27.74c0 .3-.09.55-.27.72-.18.18-.43.27-.75.27-.33 0-.58-.09-.76-.26z'; + path = + 'M17 10c0-3.87-3.14-7-7-7-3.87 0-7 3.13-7 7s3.13 7 7 7c3.86 0 7-3.13 7-7zm-6.3 1.48H9.14v-.43c0-.38.08-.7.24-.98s.46-.57.88-.89c.41-.29.68-.53.81-.71.14-.18.2-.39.2-.62 0-.25-.09-.44-.28-.58-.19-.13-.45-.19-.79-.19-.58 0-1.25.19-2 .57l-.64-1.28c.87-.49 1.8-.74 2.77-.74.81 0 1.45.2 1.92.58.48.39.71.91.71 1.55 0 .43-.09.8-.29 1.11-.19.32-.57.67-1.11 1.06-.38.28-.61.49-.71.63-.1.15-.15.34-.15.57v.35zm-1.47 2.74c-.18-.17-.27-.42-.27-.73 0-.33.08-.58.26-.75s.43-.25.77-.25c.32 0 .57.09.75.26s.27.42.27.74c0 .3-.09.55-.27.72-.18.18-.43.27-.75.27-.33 0-.58-.09-.76-.26z'; break; case 'editor-indent': - path = 'M3 5V3h9v2H3zm10-1V3h4v1h-4zm0 3h2V5l4 3.5-4 3.5v-2h-2V7zM3 8V6h9v2H3zm2 3V9h7v2H5zm-2 3v-2h9v2H3zm10 0v-1h4v1h-4zm-4 3v-2h3v2H9z'; + path = + 'M3 5V3h9v2H3zm10-1V3h4v1h-4zm0 3h2V5l4 3.5-4 3.5v-2h-2V7zM3 8V6h9v2H3zm2 3V9h7v2H5zm-2 3v-2h9v2H3zm10 0v-1h4v1h-4zm-4 3v-2h3v2H9z'; break; case 'editor-insertmore': - path = 'M17 7V3H3v4h14zM6 11V9H3v2h3zm6 0V9H8v2h4zm5 0V9h-3v2h3zm0 6v-4H3v4h14z'; + path = + 'M17 7V3H3v4h14zM6 11V9H3v2h3zm6 0V9H8v2h4zm5 0V9h-3v2h3zm0 6v-4H3v4h14z'; break; case 'editor-italic': - path = 'M14.78 6h-2.13l-2.8 9h2.12l-.62 2H4.6l.62-2h2.14l2.8-9H8.03l.62-2h6.75z'; + path = + 'M14.78 6h-2.13l-2.8 9h2.12l-.62 2H4.6l.62-2h2.14l2.8-9H8.03l.62-2h6.75z'; break; case 'editor-justify': - path = 'M2 3h16v2H2V3zm0 4h16v2H2V7zm0 4h16v2H2v-2zm0 4h16v2H2v-2z'; + path = + 'M2 3h16v2H2V3zm0 4h16v2H2V7zm0 4h16v2H2v-2zm0 4h16v2H2v-2z'; break; case 'editor-kitchensink': - path = 'M19 2v6H1V2h18zm-1 5V3H2v4h16zM5 4v2H3V4h2zm3 0v2H6V4h2zm3 0v2H9V4h2zm3 0v2h-2V4h2zm3 0v2h-2V4h2zm2 5v9H1V9h18zm-1 8v-7H2v7h16zM5 11v2H3v-2h2zm3 0v2H6v-2h2zm3 0v2H9v-2h2zm6 0v2h-5v-2h5zm-6 3v2H3v-2h8zm3 0v2h-2v-2h2zm3 0v2h-2v-2h2z'; + path = + 'M19 2v6H1V2h18zm-1 5V3H2v4h16zM5 4v2H3V4h2zm3 0v2H6V4h2zm3 0v2H9V4h2zm3 0v2h-2V4h2zm3 0v2h-2V4h2zm2 5v9H1V9h18zm-1 8v-7H2v7h16zM5 11v2H3v-2h2zm3 0v2H6v-2h2zm3 0v2H9v-2h2zm6 0v2h-5v-2h5zm-6 3v2H3v-2h8zm3 0v2h-2v-2h2zm3 0v2h-2v-2h2z'; break; case 'editor-ltr': - path = 'M5.52 2h7.43c.55 0 1 .45 1 1s-.45 1-1 1h-1v13c0 .55-.45 1-1 1s-1-.45-1-1V5c0-.55-.45-1-1-1s-1 .45-1 1v12c0 .55-.45 1-1 1s-1-.45-1-1v-5.96h-.43C3.02 11.04 1 9.02 1 6.52S3.02 2 5.52 2zM14 14l5-4-5-4v8z'; + path = + 'M5.52 2h7.43c.55 0 1 .45 1 1s-.45 1-1 1h-1v13c0 .55-.45 1-1 1s-1-.45-1-1V5c0-.55-.45-1-1-1s-1 .45-1 1v12c0 .55-.45 1-1 1s-1-.45-1-1v-5.96h-.43C3.02 11.04 1 9.02 1 6.52S3.02 2 5.52 2zM14 14l5-4-5-4v8z'; break; case 'editor-ol-rtl': - path = 'M15.025 8.75a1.048 1.048 0 0 1 .45-.1.507.507 0 0 1 .35.11.455.455 0 0 1 .13.36.803.803 0 0 1-.06.3 1.448 1.448 0 0 1-.19.33c-.09.11-.29.32-.58.62l-.99 1v.58h2.76v-.7h-1.72v-.04l.51-.48a7.276 7.276 0 0 0 .7-.71 1.75 1.75 0 0 0 .3-.49 1.254 1.254 0 0 0 .1-.51.968.968 0 0 0-.16-.56 1.007 1.007 0 0 0-.44-.37 1.512 1.512 0 0 0-.65-.14 1.98 1.98 0 0 0-.51.06 1.9 1.9 0 0 0-.42.15 3.67 3.67 0 0 0-.48.35l.45.54a2.505 2.505 0 0 1 .45-.3zM16.695 15.29a1.29 1.29 0 0 0-.74-.3v-.02a1.203 1.203 0 0 0 .65-.37.973.973 0 0 0 .23-.65.81.81 0 0 0-.37-.71 1.72 1.72 0 0 0-1-.26 2.185 2.185 0 0 0-1.33.4l.4.6a1.79 1.79 0 0 1 .46-.23 1.18 1.18 0 0 1 .41-.07c.38 0 .58.15.58.46a.447.447 0 0 1-.22.43 1.543 1.543 0 0 1-.7.12h-.31v.66h.31a1.764 1.764 0 0 1 .75.12.433.433 0 0 1 .23.41.55.55 0 0 1-.2.47 1.084 1.084 0 0 1-.63.15 2.24 2.24 0 0 1-.57-.08 2.671 2.671 0 0 1-.52-.2v.74a2.923 2.923 0 0 0 1.18.22 1.948 1.948 0 0 0 1.22-.33 1.077 1.077 0 0 0 .43-.92.836.836 0 0 0-.26-.64zM15.005 4.17c.06-.05.16-.14.3-.28l-.02.42V7h.84V3h-.69l-1.29 1.03.4.51zM4.02 5h9v1h-9zM4.02 10h9v1h-9zM4.02 15h9v1h-9z'; + path = + 'M15.025 8.75a1.048 1.048 0 0 1 .45-.1.507.507 0 0 1 .35.11.455.455 0 0 1 .13.36.803.803 0 0 1-.06.3 1.448 1.448 0 0 1-.19.33c-.09.11-.29.32-.58.62l-.99 1v.58h2.76v-.7h-1.72v-.04l.51-.48a7.276 7.276 0 0 0 .7-.71 1.75 1.75 0 0 0 .3-.49 1.254 1.254 0 0 0 .1-.51.968.968 0 0 0-.16-.56 1.007 1.007 0 0 0-.44-.37 1.512 1.512 0 0 0-.65-.14 1.98 1.98 0 0 0-.51.06 1.9 1.9 0 0 0-.42.15 3.67 3.67 0 0 0-.48.35l.45.54a2.505 2.505 0 0 1 .45-.3zM16.695 15.29a1.29 1.29 0 0 0-.74-.3v-.02a1.203 1.203 0 0 0 .65-.37.973.973 0 0 0 .23-.65.81.81 0 0 0-.37-.71 1.72 1.72 0 0 0-1-.26 2.185 2.185 0 0 0-1.33.4l.4.6a1.79 1.79 0 0 1 .46-.23 1.18 1.18 0 0 1 .41-.07c.38 0 .58.15.58.46a.447.447 0 0 1-.22.43 1.543 1.543 0 0 1-.7.12h-.31v.66h.31a1.764 1.764 0 0 1 .75.12.433.433 0 0 1 .23.41.55.55 0 0 1-.2.47 1.084 1.084 0 0 1-.63.15 2.24 2.24 0 0 1-.57-.08 2.671 2.671 0 0 1-.52-.2v.74a2.923 2.923 0 0 0 1.18.22 1.948 1.948 0 0 0 1.22-.33 1.077 1.077 0 0 0 .43-.92.836.836 0 0 0-.26-.64zM15.005 4.17c.06-.05.16-.14.3-.28l-.02.42V7h.84V3h-.69l-1.29 1.03.4.51zM4.02 5h9v1h-9zM4.02 10h9v1h-9zM4.02 15h9v1h-9z'; break; case 'editor-ol': - path = 'M6 7V3h-.69L4.02 4.03l.4.51.46-.37c.06-.05.16-.14.3-.28l-.02.42V7H6zm2-2h9v1H8V5zm-1.23 6.95v-.7H5.05v-.04l.51-.48c.33-.31.57-.54.7-.71.14-.17.24-.33.3-.49.07-.16.1-.33.1-.51 0-.21-.05-.4-.16-.56-.1-.16-.25-.28-.44-.37s-.41-.14-.65-.14c-.19 0-.36.02-.51.06-.15.03-.29.09-.42.15-.12.07-.29.19-.48.35l.45.54c.16-.13.31-.23.45-.3.15-.07.3-.1.45-.1.14 0 .26.03.35.11s.13.2.13.36c0 .1-.02.2-.06.3s-.1.21-.19.33c-.09.11-.29.32-.58.62l-.99 1v.58h2.76zM8 10h9v1H8v-1zm-1.29 3.95c0-.3-.12-.54-.37-.71-.24-.17-.58-.26-1-.26-.52 0-.96.13-1.33.4l.4.6c.17-.11.32-.19.46-.23.14-.05.27-.07.41-.07.38 0 .58.15.58.46 0 .2-.07.35-.22.43s-.38.12-.7.12h-.31v.66h.31c.34 0 .59.04.75.12.15.08.23.22.23.41 0 .22-.07.37-.2.47-.14.1-.35.15-.63.15-.19 0-.38-.03-.57-.08s-.36-.12-.52-.2v.74c.34.15.74.22 1.18.22.53 0 .94-.11 1.22-.33.29-.22.43-.52.43-.92 0-.27-.09-.48-.26-.64s-.42-.26-.74-.3v-.02c.27-.06.49-.19.65-.37.15-.18.23-.39.23-.65zM8 15h9v1H8v-1z'; + path = + 'M6 7V3h-.69L4.02 4.03l.4.51.46-.37c.06-.05.16-.14.3-.28l-.02.42V7H6zm2-2h9v1H8V5zm-1.23 6.95v-.7H5.05v-.04l.51-.48c.33-.31.57-.54.7-.71.14-.17.24-.33.3-.49.07-.16.1-.33.1-.51 0-.21-.05-.4-.16-.56-.1-.16-.25-.28-.44-.37s-.41-.14-.65-.14c-.19 0-.36.02-.51.06-.15.03-.29.09-.42.15-.12.07-.29.19-.48.35l.45.54c.16-.13.31-.23.45-.3.15-.07.3-.1.45-.1.14 0 .26.03.35.11s.13.2.13.36c0 .1-.02.2-.06.3s-.1.21-.19.33c-.09.11-.29.32-.58.62l-.99 1v.58h2.76zM8 10h9v1H8v-1zm-1.29 3.95c0-.3-.12-.54-.37-.71-.24-.17-.58-.26-1-.26-.52 0-.96.13-1.33.4l.4.6c.17-.11.32-.19.46-.23.14-.05.27-.07.41-.07.38 0 .58.15.58.46 0 .2-.07.35-.22.43s-.38.12-.7.12h-.31v.66h.31c.34 0 .59.04.75.12.15.08.23.22.23.41 0 .22-.07.37-.2.47-.14.1-.35.15-.63.15-.19 0-.38-.03-.57-.08s-.36-.12-.52-.2v.74c.34.15.74.22 1.18.22.53 0 .94-.11 1.22-.33.29-.22.43-.52.43-.92 0-.27-.09-.48-.26-.64s-.42-.26-.74-.3v-.02c.27-.06.49-.19.65-.37.15-.18.23-.39.23-.65zM8 15h9v1H8v-1z'; break; case 'editor-outdent': - path = 'M7 4V3H3v1h4zm10 1V3H8v2h9zM7 7H5V5L1 8.5 5 12v-2h2V7zm10 1V6H8v2h9zm-2 3V9H8v2h7zm2 3v-2H8v2h9zM7 14v-1H3v1h4zm4 3v-2H8v2h3z'; + path = + 'M7 4V3H3v1h4zm10 1V3H8v2h9zM7 7H5V5L1 8.5 5 12v-2h2V7zm10 1V6H8v2h9zm-2 3V9H8v2h7zm2 3v-2H8v2h9zM7 14v-1H3v1h4zm4 3v-2H8v2h3z'; break; case 'editor-paragraph': - path = 'M15 2H7.54c-.83 0-1.59.2-2.28.6-.7.41-1.25.96-1.65 1.65C3.2 4.94 3 5.7 3 6.52s.2 1.58.61 2.27c.4.69.95 1.24 1.65 1.64.69.41 1.45.61 2.28.61h.43V17c0 .27.1.51.29.71.2.19.44.29.71.29.28 0 .51-.1.71-.29.2-.2.3-.44.3-.71V5c0-.27.09-.51.29-.71.2-.19.44-.29.71-.29s.51.1.71.29c.19.2.29.44.29.71v12c0 .27.1.51.3.71.2.19.43.29.71.29.27 0 .51-.1.71-.29.19-.2.29-.44.29-.71V4H15c.27 0 .5-.1.7-.3.2-.19.3-.43.3-.7s-.1-.51-.3-.71C15.5 2.1 15.27 2 15 2z'; + path = + 'M15 2H7.54c-.83 0-1.59.2-2.28.6-.7.41-1.25.96-1.65 1.65C3.2 4.94 3 5.7 3 6.52s.2 1.58.61 2.27c.4.69.95 1.24 1.65 1.64.69.41 1.45.61 2.28.61h.43V17c0 .27.1.51.29.71.2.19.44.29.71.29.28 0 .51-.1.71-.29.2-.2.3-.44.3-.71V5c0-.27.09-.51.29-.71.2-.19.44-.29.71-.29s.51.1.71.29c.19.2.29.44.29.71v12c0 .27.1.51.3.71.2.19.43.29.71.29.27 0 .51-.1.71-.29.19-.2.29-.44.29-.71V4H15c.27 0 .5-.1.7-.3.2-.19.3-.43.3-.7s-.1-.51-.3-.71C15.5 2.1 15.27 2 15 2z'; break; case 'editor-paste-text': - path = 'M12.38 2L15 5v1H5V5l2.64-3h4.74zM10 5c.55 0 1-.44 1-1 0-.55-.45-1-1-1s-1 .45-1 1c0 .56.45 1 1 1zm5.45-1H17c.55 0 1 .45 1 1v12c0 .56-.45 1-1 1H3c-.55 0-1-.44-1-1V5c0-.55.45-1 1-1h1.55L4 4.63V7h12V4.63zM14 11V9H6v2h3v5h2v-5h3z'; + path = + 'M12.38 2L15 5v1H5V5l2.64-3h4.74zM10 5c.55 0 1-.44 1-1 0-.55-.45-1-1-1s-1 .45-1 1c0 .56.45 1 1 1zm5.45-1H17c.55 0 1 .45 1 1v12c0 .56-.45 1-1 1H3c-.55 0-1-.44-1-1V5c0-.55.45-1 1-1h1.55L4 4.63V7h12V4.63zM14 11V9H6v2h3v5h2v-5h3z'; break; case 'editor-paste-word': - path = 'M12.38 2L15 5v1H5V5l2.64-3h4.74zM10 5c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm8 12V5c0-.55-.45-1-1-1h-1.54l.54.63V7H4V4.62L4.55 4H3c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h14c.55 0 1-.45 1-1zm-3-8l-2 7h-2l-1-5-1 5H6.92L5 9h2l1 5 1-5h2l1 5 1-5h2z'; + path = + 'M12.38 2L15 5v1H5V5l2.64-3h4.74zM10 5c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm8 12V5c0-.55-.45-1-1-1h-1.54l.54.63V7H4V4.62L4.55 4H3c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h14c.55 0 1-.45 1-1zm-3-8l-2 7h-2l-1-5-1 5H6.92L5 9h2l1 5 1-5h2l1 5 1-5h2z'; break; case 'editor-quote': - path = 'M9.49 13.22c0-.74-.2-1.38-.61-1.9-.62-.78-1.83-.88-2.53-.72-.29-1.65 1.11-3.75 2.92-4.65L7.88 4c-2.73 1.3-5.42 4.28-4.96 8.05C3.21 14.43 4.59 16 6.54 16c.85 0 1.56-.25 2.12-.75s.83-1.18.83-2.03zm8.05 0c0-.74-.2-1.38-.61-1.9-.63-.78-1.83-.88-2.53-.72-.29-1.65 1.11-3.75 2.92-4.65L15.93 4c-2.73 1.3-5.41 4.28-4.95 8.05.29 2.38 1.66 3.95 3.61 3.95.85 0 1.56-.25 2.12-.75s.83-1.18.83-2.03z'; + path = + 'M9.49 13.22c0-.74-.2-1.38-.61-1.9-.62-.78-1.83-.88-2.53-.72-.29-1.65 1.11-3.75 2.92-4.65L7.88 4c-2.73 1.3-5.42 4.28-4.96 8.05C3.21 14.43 4.59 16 6.54 16c.85 0 1.56-.25 2.12-.75s.83-1.18.83-2.03zm8.05 0c0-.74-.2-1.38-.61-1.9-.63-.78-1.83-.88-2.53-.72-.29-1.65 1.11-3.75 2.92-4.65L15.93 4c-2.73 1.3-5.41 4.28-4.95 8.05.29 2.38 1.66 3.95 3.61 3.95.85 0 1.56-.25 2.12-.75s.83-1.18.83-2.03z'; break; case 'editor-removeformatting': - path = 'M14.29 4.59l1.1 1.11c.41.4.61.94.61 1.47v2.12c0 .53-.2 1.07-.61 1.47l-6.63 6.63c-.4.41-.94.61-1.47.61s-1.07-.2-1.47-.61l-1.11-1.1-1.1-1.11c-.41-.4-.61-.94-.61-1.47v-2.12c0-.54.2-1.07.61-1.48l6.63-6.62c.4-.41.94-.61 1.47-.61s1.06.2 1.47.61zm-6.21 9.7l6.42-6.42c.39-.39.39-1.03 0-1.43L12.36 4.3c-.19-.19-.45-.29-.72-.29s-.52.1-.71.29l-6.42 6.42c-.39.4-.39 1.04 0 1.43l2.14 2.14c.38.38 1.04.38 1.43 0z'; + path = + 'M14.29 4.59l1.1 1.11c.41.4.61.94.61 1.47v2.12c0 .53-.2 1.07-.61 1.47l-6.63 6.63c-.4.41-.94.61-1.47.61s-1.07-.2-1.47-.61l-1.11-1.1-1.1-1.11c-.41-.4-.61-.94-.61-1.47v-2.12c0-.54.2-1.07.61-1.48l6.63-6.62c.4-.41.94-.61 1.47-.61s1.06.2 1.47.61zm-6.21 9.7l6.42-6.42c.39-.39.39-1.03 0-1.43L12.36 4.3c-.19-.19-.45-.29-.72-.29s-.52.1-.71.29l-6.42 6.42c-.39.4-.39 1.04 0 1.43l2.14 2.14c.38.38 1.04.38 1.43 0z'; break; case 'editor-rtl': - path = 'M5.52 2h7.43c.55 0 1 .45 1 1s-.45 1-1 1h-1v13c0 .55-.45 1-1 1s-1-.45-1-1V5c0-.55-.45-1-1-1s-1 .45-1 1v12c0 .55-.45 1-1 1s-1-.45-1-1v-5.96h-.43C3.02 11.04 1 9.02 1 6.52S3.02 2 5.52 2zM19 6l-5 4 5 4V6z'; + path = + 'M5.52 2h7.43c.55 0 1 .45 1 1s-.45 1-1 1h-1v13c0 .55-.45 1-1 1s-1-.45-1-1V5c0-.55-.45-1-1-1s-1 .45-1 1v12c0 .55-.45 1-1 1s-1-.45-1-1v-5.96h-.43C3.02 11.04 1 9.02 1 6.52S3.02 2 5.52 2zM19 6l-5 4 5 4V6z'; break; case 'editor-spellcheck': - path = 'M15.84 2.76c.25 0 .49.04.71.11.23.07.44.16.64.25l.35-.81c-.52-.26-1.08-.39-1.69-.39-.58 0-1.09.13-1.52.37-.43.25-.76.61-.99 1.08C13.11 3.83 13 4.38 13 5c0 .99.23 1.75.7 2.28s1.15.79 2.02.79c.6 0 1.13-.09 1.6-.26v-.84c-.26.08-.51.14-.74.19-.24.05-.49.08-.74.08-.59 0-1.04-.19-1.34-.57-.32-.37-.47-.93-.47-1.66 0-.7.16-1.25.48-1.65.33-.4.77-.6 1.33-.6zM6.5 8h1.04L5.3 2H4.24L2 8h1.03l.58-1.66H5.9zM8 2v6h2.17c.67 0 1.19-.15 1.57-.46.38-.3.56-.72.56-1.26 0-.4-.1-.72-.3-.95-.19-.24-.5-.39-.93-.47v-.04c.35-.06.6-.21.78-.44.18-.24.28-.53.28-.88 0-.52-.19-.9-.56-1.14-.36-.24-.96-.36-1.79-.36H8zm.98 2.48V2.82h.85c.44 0 .77.06.97.19.21.12.31.33.31.61 0 .31-.1.53-.29.66-.18.13-.48.2-.89.2h-.95zM5.64 5.5H3.9l.54-1.56c.14-.4.25-.76.32-1.1l.15.52c.07.23.13.4.17.51zm3.34-.23h.99c.44 0 .76.08.98.23.21.15.32.38.32.69 0 .34-.11.59-.32.75s-.52.24-.93.24H8.98V5.27zM4 13l5 5 9-8-1-1-8 6-4-3z'; + path = + 'M15.84 2.76c.25 0 .49.04.71.11.23.07.44.16.64.25l.35-.81c-.52-.26-1.08-.39-1.69-.39-.58 0-1.09.13-1.52.37-.43.25-.76.61-.99 1.08C13.11 3.83 13 4.38 13 5c0 .99.23 1.75.7 2.28s1.15.79 2.02.79c.6 0 1.13-.09 1.6-.26v-.84c-.26.08-.51.14-.74.19-.24.05-.49.08-.74.08-.59 0-1.04-.19-1.34-.57-.32-.37-.47-.93-.47-1.66 0-.7.16-1.25.48-1.65.33-.4.77-.6 1.33-.6zM6.5 8h1.04L5.3 2H4.24L2 8h1.03l.58-1.66H5.9zM8 2v6h2.17c.67 0 1.19-.15 1.57-.46.38-.3.56-.72.56-1.26 0-.4-.1-.72-.3-.95-.19-.24-.5-.39-.93-.47v-.04c.35-.06.6-.21.78-.44.18-.24.28-.53.28-.88 0-.52-.19-.9-.56-1.14-.36-.24-.96-.36-1.79-.36H8zm.98 2.48V2.82h.85c.44 0 .77.06.97.19.21.12.31.33.31.61 0 .31-.1.53-.29.66-.18.13-.48.2-.89.2h-.95zM5.64 5.5H3.9l.54-1.56c.14-.4.25-.76.32-1.1l.15.52c.07.23.13.4.17.51zm3.34-.23h.99c.44 0 .76.08.98.23.21.15.32.38.32.69 0 .34-.11.59-.32.75s-.52.24-.93.24H8.98V5.27zM4 13l5 5 9-8-1-1-8 6-4-3z'; break; case 'editor-strikethrough': - path = 'M15.82 12.25c.26 0 .5-.02.74-.07.23-.05.48-.12.73-.2v.84c-.46.17-.99.26-1.58.26-.88 0-1.54-.26-2.01-.79-.39-.44-.62-1.04-.68-1.79h-.94c.12.21.18.48.18.79 0 .54-.18.95-.55 1.26-.38.3-.9.45-1.56.45H8v-2.5H6.59l.93 2.5H6.49l-.59-1.67H3.62L3.04 13H2l.93-2.5H2v-1h1.31l.93-2.49H5.3l.92 2.49H8V7h1.77c1 0 1.41.17 1.77.41.37.24.55.62.55 1.13 0 .35-.09.64-.27.87l-.08.09h1.29c.05-.4.15-.77.31-1.1.23-.46.55-.82.98-1.06.43-.25.93-.37 1.51-.37.61 0 1.17.12 1.69.38l-.35.81c-.2-.1-.42-.18-.64-.25s-.46-.11-.71-.11c-.55 0-.99.2-1.31.59-.23.29-.38.66-.44 1.11H17v1h-2.95c.06.5.2.9.44 1.19.3.37.75.56 1.33.56zM4.44 8.96l-.18.54H5.3l-.22-.61c-.04-.11-.09-.28-.17-.51-.07-.24-.12-.41-.14-.51-.08.33-.18.69-.33 1.09zm4.53-1.09V9.5h1.19c.28-.02.49-.09.64-.18.19-.13.28-.35.28-.66 0-.28-.1-.48-.3-.61-.2-.12-.53-.18-.97-.18h-.84zm-3.33 2.64v-.01H3.91v.01h1.73zm5.28.01l-.03-.02H8.97v1.68h1.04c.4 0 .71-.08.92-.23.21-.16.31-.4.31-.74 0-.31-.11-.54-.32-.69z'; + path = + 'M15.82 12.25c.26 0 .5-.02.74-.07.23-.05.48-.12.73-.2v.84c-.46.17-.99.26-1.58.26-.88 0-1.54-.26-2.01-.79-.39-.44-.62-1.04-.68-1.79h-.94c.12.21.18.48.18.79 0 .54-.18.95-.55 1.26-.38.3-.9.45-1.56.45H8v-2.5H6.59l.93 2.5H6.49l-.59-1.67H3.62L3.04 13H2l.93-2.5H2v-1h1.31l.93-2.49H5.3l.92 2.49H8V7h1.77c1 0 1.41.17 1.77.41.37.24.55.62.55 1.13 0 .35-.09.64-.27.87l-.08.09h1.29c.05-.4.15-.77.31-1.1.23-.46.55-.82.98-1.06.43-.25.93-.37 1.51-.37.61 0 1.17.12 1.69.38l-.35.81c-.2-.1-.42-.18-.64-.25s-.46-.11-.71-.11c-.55 0-.99.2-1.31.59-.23.29-.38.66-.44 1.11H17v1h-2.95c.06.5.2.9.44 1.19.3.37.75.56 1.33.56zM4.44 8.96l-.18.54H5.3l-.22-.61c-.04-.11-.09-.28-.17-.51-.07-.24-.12-.41-.14-.51-.08.33-.18.69-.33 1.09zm4.53-1.09V9.5h1.19c.28-.02.49-.09.64-.18.19-.13.28-.35.28-.66 0-.28-.1-.48-.3-.61-.2-.12-.53-.18-.97-.18h-.84zm-3.33 2.64v-.01H3.91v.01h1.73zm5.28.01l-.03-.02H8.97v1.68h1.04c.4 0 .71-.08.92-.23.21-.16.31-.4.31-.74 0-.31-.11-.54-.32-.69z'; break; case 'editor-table': - path = 'M18 17V3H2v14h16zM16 7H4V5h12v2zm-7 4H4V9h5v2zm7 0h-5V9h5v2zm-7 4H4v-2h5v2zm7 0h-5v-2h5v2z'; + path = + 'M18 17V3H2v14h16zM16 7H4V5h12v2zm-7 4H4V9h5v2zm7 0h-5V9h5v2zm-7 4H4v-2h5v2zm7 0h-5v-2h5v2z'; break; case 'editor-textcolor': - path = 'M13.23 15h1.9L11 4H9L5 15h1.88l1.07-3h4.18zm-1.53-4.54H8.51L10 5.6z'; + path = + 'M13.23 15h1.9L11 4H9L5 15h1.88l1.07-3h4.18zm-1.53-4.54H8.51L10 5.6z'; break; case 'editor-ul': - path = 'M5.5 7C4.67 7 4 6.33 4 5.5 4 4.68 4.67 4 5.5 4 6.32 4 7 4.68 7 5.5 7 6.33 6.32 7 5.5 7zM8 5h9v1H8V5zm-2.5 7c-.83 0-1.5-.67-1.5-1.5C4 9.68 4.67 9 5.5 9c.82 0 1.5.68 1.5 1.5 0 .83-.68 1.5-1.5 1.5zM8 10h9v1H8v-1zm-2.5 7c-.83 0-1.5-.67-1.5-1.5 0-.82.67-1.5 1.5-1.5.82 0 1.5.68 1.5 1.5 0 .83-.68 1.5-1.5 1.5zM8 15h9v1H8v-1z'; + path = + 'M5.5 7C4.67 7 4 6.33 4 5.5 4 4.68 4.67 4 5.5 4 6.32 4 7 4.68 7 5.5 7 6.33 6.32 7 5.5 7zM8 5h9v1H8V5zm-2.5 7c-.83 0-1.5-.67-1.5-1.5C4 9.68 4.67 9 5.5 9c.82 0 1.5.68 1.5 1.5 0 .83-.68 1.5-1.5 1.5zM8 10h9v1H8v-1zm-2.5 7c-.83 0-1.5-.67-1.5-1.5 0-.82.67-1.5 1.5-1.5.82 0 1.5.68 1.5 1.5 0 .83-.68 1.5-1.5 1.5zM8 15h9v1H8v-1z'; break; case 'editor-underline': - path = 'M14 5h-2v5.71c0 1.99-1.12 2.98-2.45 2.98-1.32 0-2.55-1-2.55-2.96V5H5v5.87c0 1.91 1 4.54 4.48 4.54 3.49 0 4.52-2.58 4.52-4.5V5zm0 13v-2H5v2h9z'; + path = + 'M14 5h-2v5.71c0 1.99-1.12 2.98-2.45 2.98-1.32 0-2.55-1-2.55-2.96V5H5v5.87c0 1.91 1 4.54 4.48 4.54 3.49 0 4.52-2.58 4.52-4.5V5zm0 13v-2H5v2h9z'; break; case 'editor-unlink': - path = 'M17.74 2.26c1.68 1.69 1.68 4.41 0 6.1l-1.53 1.52c-.32.33-.69.58-1.08.77L13 10l1.69-1.64.76-.77.76-.76c.84-.84.84-2.2 0-3.04-.84-.85-2.2-.85-3.04 0l-.77.76-.76.76L10 7l-.65-2.14c.19-.38.44-.75.77-1.07l1.52-1.53c1.69-1.68 4.42-1.68 6.1 0zM2 4l8 6-6-8zm4-2l4 8-2-8H6zM2 6l8 4-8-2V6zm7.36 7.69L10 13l.74 2.35-1.38 1.39c-1.69 1.68-4.41 1.68-6.1 0-1.68-1.68-1.68-4.42 0-6.1l1.39-1.38L7 10l-.69.64-1.52 1.53c-.85.84-.85 2.2 0 3.04.84.85 2.2.85 3.04 0zM18 16l-8-6 6 8zm-4 2l-4-8 2 8h2zm4-4l-8-4 8 2v2z'; + path = + 'M17.74 2.26c1.68 1.69 1.68 4.41 0 6.1l-1.53 1.52c-.32.33-.69.58-1.08.77L13 10l1.69-1.64.76-.77.76-.76c.84-.84.84-2.2 0-3.04-.84-.85-2.2-.85-3.04 0l-.77.76-.76.76L10 7l-.65-2.14c.19-.38.44-.75.77-1.07l1.52-1.53c1.69-1.68 4.42-1.68 6.1 0zM2 4l8 6-6-8zm4-2l4 8-2-8H6zM2 6l8 4-8-2V6zm7.36 7.69L10 13l.74 2.35-1.38 1.39c-1.69 1.68-4.41 1.68-6.1 0-1.68-1.68-1.68-4.42 0-6.1l1.39-1.38L7 10l-.69.64-1.52 1.53c-.85.84-.85 2.2 0 3.04.84.85 2.2.85 3.04 0zM18 16l-8-6 6 8zm-4 2l-4-8 2 8h2zm4-4l-8-4 8 2v2z'; break; case 'editor-video': - path = 'M16 2h-3v1H7V2H4v15h3v-1h6v1h3V2zM6 3v1H5V3h1zm9 0v1h-1V3h1zm-2 1v5H7V4h6zM6 5v1H5V5h1zm9 0v1h-1V5h1zM6 7v1H5V7h1zm9 0v1h-1V7h1zM6 9v1H5V9h1zm9 0v1h-1V9h1zm-2 1v5H7v-5h6zm-7 1v1H5v-1h1zm9 0v1h-1v-1h1zm-9 2v1H5v-1h1zm9 0v1h-1v-1h1zm-9 2v1H5v-1h1zm9 0v1h-1v-1h1z'; + path = + 'M16 2h-3v1H7V2H4v15h3v-1h6v1h3V2zM6 3v1H5V3h1zm9 0v1h-1V3h1zm-2 1v5H7V4h6zM6 5v1H5V5h1zm9 0v1h-1V5h1zM6 7v1H5V7h1zm9 0v1h-1V7h1zM6 9v1H5V9h1zm9 0v1h-1V9h1zm-2 1v5H7v-5h6zm-7 1v1H5v-1h1zm9 0v1h-1v-1h1zm-9 2v1H5v-1h1zm9 0v1h-1v-1h1zm-9 2v1H5v-1h1zm9 0v1h-1v-1h1z'; break; case 'ellipsis': - path = 'M5 10c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm12-2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-7 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z'; + path = + 'M5 10c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm12-2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-7 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z'; break; case 'email-alt': - path = 'M19 14.5v-9c0-.83-.67-1.5-1.5-1.5H3.49c-.83 0-1.5.67-1.5 1.5v9c0 .83.67 1.5 1.5 1.5H17.5c.83 0 1.5-.67 1.5-1.5zm-1.31-9.11c.33.33.15.67-.03.84L13.6 9.95l3.9 4.06c.12.14.2.36.06.51-.13.16-.43.15-.56.05l-4.37-3.73-2.14 1.95-2.13-1.95-4.37 3.73c-.13.1-.43.11-.56-.05-.14-.15-.06-.37.06-.51l3.9-4.06-4.06-3.72c-.18-.17-.36-.51-.03-.84s.67-.17.95.07l6.24 5.04 6.25-5.04c.28-.24.62-.4.95-.07z'; + path = + 'M19 14.5v-9c0-.83-.67-1.5-1.5-1.5H3.49c-.83 0-1.5.67-1.5 1.5v9c0 .83.67 1.5 1.5 1.5H17.5c.83 0 1.5-.67 1.5-1.5zm-1.31-9.11c.33.33.15.67-.03.84L13.6 9.95l3.9 4.06c.12.14.2.36.06.51-.13.16-.43.15-.56.05l-4.37-3.73-2.14 1.95-2.13-1.95-4.37 3.73c-.13.1-.43.11-.56-.05-.14-.15-.06-.37.06-.51l3.9-4.06-4.06-3.72c-.18-.17-.36-.51-.03-.84s.67-.17.95.07l6.24 5.04 6.25-5.04c.28-.24.62-.4.95-.07z'; break; case 'email-alt2': - path = 'M18.01 11.18V2.51c0-1.19-.9-1.81-2-1.37L4 5.91c-1.1.44-2 1.77-2 2.97v8.66c0 1.2.9 1.81 2 1.37l12.01-4.77c1.1-.44 2-1.76 2-2.96zm-1.43-7.46l-6.04 9.33-6.65-4.6c-.1-.07-.36-.32-.17-.64.21-.36.65-.21.65-.21l6.3 2.32s4.83-6.34 5.11-6.7c.13-.17.43-.34.73-.13.29.2.16.49.07.63z'; + path = + 'M18.01 11.18V2.51c0-1.19-.9-1.81-2-1.37L4 5.91c-1.1.44-2 1.77-2 2.97v8.66c0 1.2.9 1.81 2 1.37l12.01-4.77c1.1-.44 2-1.76 2-2.96zm-1.43-7.46l-6.04 9.33-6.65-4.6c-.1-.07-.36-.32-.17-.64.21-.36.65-.21.65-.21l6.3 2.32s4.83-6.34 5.11-6.7c.13-.17.43-.34.73-.13.29.2.16.49.07.63z'; break; case 'email': - path = 'M3.87 4h13.25C18.37 4 19 4.59 19 5.79v8.42c0 1.19-.63 1.79-1.88 1.79H3.87c-1.25 0-1.88-.6-1.88-1.79V5.79c0-1.2.63-1.79 1.88-1.79zm6.62 8.6l6.74-5.53c.24-.2.43-.66.13-1.07-.29-.41-.82-.42-1.17-.17l-5.7 3.86L4.8 5.83c-.35-.25-.88-.24-1.17.17-.3.41-.11.87.13 1.07z'; + path = + 'M3.87 4h13.25C18.37 4 19 4.59 19 5.79v8.42c0 1.19-.63 1.79-1.88 1.79H3.87c-1.25 0-1.88-.6-1.88-1.79V5.79c0-1.2.63-1.79 1.88-1.79zm6.62 8.6l6.74-5.53c.24-.2.43-.66.13-1.07-.29-.41-.82-.42-1.17-.17l-5.7 3.86L4.8 5.83c-.35-.25-.88-.24-1.17.17-.3.41-.11.87.13 1.07z'; break; case 'embed-audio': - path = 'M17 4H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-7 3H7v4c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2c.4 0 .7.1 1 .3V5h4v2zm4 3.5L12.5 12l1.5 1.5V15l-3-3 3-3v1.5zm1 4.5v-1.5l1.5-1.5-1.5-1.5V9l3 3-3 3z'; + path = + 'M17 4H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-7 3H7v4c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2c.4 0 .7.1 1 .3V5h4v2zm4 3.5L12.5 12l1.5 1.5V15l-3-3 3-3v1.5zm1 4.5v-1.5l1.5-1.5-1.5-1.5V9l3 3-3 3z'; break; case 'embed-generic': - path = 'M17 4H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-3 6.5L12.5 12l1.5 1.5V15l-3-3 3-3v1.5zm1 4.5v-1.5l1.5-1.5-1.5-1.5V9l3 3-3 3z'; + path = + 'M17 4H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-3 6.5L12.5 12l1.5 1.5V15l-3-3 3-3v1.5zm1 4.5v-1.5l1.5-1.5-1.5-1.5V9l3 3-3 3z'; break; case 'embed-photo': - path = 'M17 4H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-7 8H3V6h7v6zm4-1.5L12.5 12l1.5 1.5V15l-3-3 3-3v1.5zm1 4.5v-1.5l1.5-1.5-1.5-1.5V9l3 3-3 3zm-6-4V8.5L7.2 10 6 9.2 4 11h5zM4.6 8.6c.6 0 1-.4 1-1s-.4-1-1-1-1 .4-1 1 .4 1 1 1z'; + path = + 'M17 4H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-7 8H3V6h7v6zm4-1.5L12.5 12l1.5 1.5V15l-3-3 3-3v1.5zm1 4.5v-1.5l1.5-1.5-1.5-1.5V9l3 3-3 3zm-6-4V8.5L7.2 10 6 9.2 4 11h5zM4.6 8.6c.6 0 1-.4 1-1s-.4-1-1-1-1 .4-1 1 .4 1 1 1z'; break; case 'embed-post': - path = 'M17 4H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zM8.6 9l-.4.3c-.4.4-.5 1.1-.2 1.6l-.8.8-1.1-1.1-1.3 1.3c-.2.2-1.6 1.3-1.8 1.1-.2-.2.9-1.6 1.1-1.8l1.3-1.3-1.1-1.1.8-.8c.5.3 1.2.3 1.6-.2l.3-.3c.5-.5.5-1.2.2-1.7L8 5l3 2.9-.8.8c-.5-.2-1.2-.2-1.6.3zm5.4 1.5L12.5 12l1.5 1.5V15l-3-3 3-3v1.5zm1 4.5v-1.5l1.5-1.5-1.5-1.5V9l3 3-3 3z'; + path = + 'M17 4H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zM8.6 9l-.4.3c-.4.4-.5 1.1-.2 1.6l-.8.8-1.1-1.1-1.3 1.3c-.2.2-1.6 1.3-1.8 1.1-.2-.2.9-1.6 1.1-1.8l1.3-1.3-1.1-1.1.8-.8c.5.3 1.2.3 1.6-.2l.3-.3c.5-.5.5-1.2.2-1.7L8 5l3 2.9-.8.8c-.5-.2-1.2-.2-1.6.3zm5.4 1.5L12.5 12l1.5 1.5V15l-3-3 3-3v1.5zm1 4.5v-1.5l1.5-1.5-1.5-1.5V9l3 3-3 3z'; break; case 'embed-video': - path = 'M17 4H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-7 6.5L8 9.1V11H3V6h5v1.8l2-1.3v4zm4 0L12.5 12l1.5 1.5V15l-3-3 3-3v1.5zm1 4.5v-1.5l1.5-1.5-1.5-1.5V9l3 3-3 3z'; + path = + 'M17 4H3c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-7 6.5L8 9.1V11H3V6h5v1.8l2-1.3v4zm4 0L12.5 12l1.5 1.5V15l-3-3 3-3v1.5zm1 4.5v-1.5l1.5-1.5-1.5-1.5V9l3 3-3 3z'; break; case 'excerpt-view': - path = 'M19 18V2c0-.55-.45-1-1-1H2c-.55 0-1 .45-1 1v16c0 .55.45 1 1 1h16c.55 0 1-.45 1-1zM4 3c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm13 0v6H6V3h11zM4 11c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm13 0v6H6v-6h11z'; + path = + 'M19 18V2c0-.55-.45-1-1-1H2c-.55 0-1 .45-1 1v16c0 .55.45 1 1 1h16c.55 0 1-.45 1-1zM4 3c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm13 0v6H6V3h11zM4 11c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm13 0v6H6v-6h11z'; break; case 'exit': - path = 'M13 3v2h2v10h-2v2h4V3h-4zm0 8V9H5.4l4.3-4.3-1.4-1.4L1.6 10l6.7 6.7 1.4-1.4L5.4 11H13z'; + path = + 'M13 3v2h2v10h-2v2h4V3h-4zm0 8V9H5.4l4.3-4.3-1.4-1.4L1.6 10l6.7 6.7 1.4-1.4L5.4 11H13z'; break; case 'external': - path = 'M9 3h8v8l-2-1V6.92l-5.6 5.59-1.41-1.41L14.08 5H10zm3 12v-3l2-2v7H3V6h8L9 8H5v7h7z'; + path = + 'M9 3h8v8l-2-1V6.92l-5.6 5.59-1.41-1.41L14.08 5H10zm3 12v-3l2-2v7H3V6h8L9 8H5v7h7z'; break; case 'facebook-alt': - path = 'M8.46 18h2.93v-7.3h2.45l.37-2.84h-2.82V6.04c0-.82.23-1.38 1.41-1.38h1.51V2.11c-.26-.03-1.15-.11-2.19-.11-2.18 0-3.66 1.33-3.66 3.76v2.1H6v2.84h2.46V18z'; + path = + 'M8.46 18h2.93v-7.3h2.45l.37-2.84h-2.82V6.04c0-.82.23-1.38 1.41-1.38h1.51V2.11c-.26-.03-1.15-.11-2.19-.11-2.18 0-3.66 1.33-3.66 3.76v2.1H6v2.84h2.46V18z'; break; case 'facebook': - path = 'M2.89 2h14.23c.49 0 .88.39.88.88v14.24c0 .48-.39.88-.88.88h-4.08v-6.2h2.08l.31-2.41h-2.39V7.85c0-.7.2-1.18 1.2-1.18h1.28V4.51c-.22-.03-.98-.09-1.86-.09-1.85 0-3.11 1.12-3.11 3.19v1.78H8.46v2.41h2.09V18H2.89c-.49 0-.89-.4-.89-.88V2.88c0-.49.4-.88.89-.88z'; + path = + 'M2.89 2h14.23c.49 0 .88.39.88.88v14.24c0 .48-.39.88-.88.88h-4.08v-6.2h2.08l.31-2.41h-2.39V7.85c0-.7.2-1.18 1.2-1.18h1.28V4.51c-.22-.03-.98-.09-1.86-.09-1.85 0-3.11 1.12-3.11 3.19v1.78H8.46v2.41h2.09V18H2.89c-.49 0-.89-.4-.89-.88V2.88c0-.49.4-.88.89-.88z'; break; case 'feedback': - path = 'M2 2h16c.55 0 1 .45 1 1v14c0 .55-.45 1-1 1H2c-.55 0-1-.45-1-1V3c0-.55.45-1 1-1zm15 14V7H3v9h14zM4 8v1h3V8H4zm4 0v3h8V8H8zm-4 4v1h3v-1H4zm4 0v3h8v-3H8z'; + path = + 'M2 2h16c.55 0 1 .45 1 1v14c0 .55-.45 1-1 1H2c-.55 0-1-.45-1-1V3c0-.55.45-1 1-1zm15 14V7H3v9h14zM4 8v1h3V8H4zm4 0v3h8V8H8zm-4 4v1h3v-1H4zm4 0v3h8v-3H8z'; break; case 'filter': - path = 'M3 4.5v-2s3.34-1 7-1 7 1 7 1v2l-5 7.03v6.97s-1.22-.09-2.25-.59S8 16.5 8 16.5v-4.97z'; + path = + 'M3 4.5v-2s3.34-1 7-1 7 1 7 1v2l-5 7.03v6.97s-1.22-.09-2.25-.59S8 16.5 8 16.5v-4.97z'; break; case 'flag': path = 'M5 18V3H3v15h2zm1-6V4c3-1 7 1 11 0v8c-3 1.27-8-1-11 0z'; break; case 'format-aside': - path = 'M1 1h18v12l-6 6H1V1zm3 3v1h12V4H4zm0 4v1h12V8H4zm6 5v-1H4v1h6zm2 4l5-5h-5v5z'; + path = + 'M1 1h18v12l-6 6H1V1zm3 3v1h12V4H4zm0 4v1h12V8H4zm6 5v-1H4v1h6zm2 4l5-5h-5v5z'; break; case 'format-audio': - path = 'M6.99 3.08l11.02-2c.55-.08.99.45.99 1V14.5c0 1.94-1.57 3.5-3.5 3.5S12 16.44 12 14.5c0-1.93 1.57-3.5 3.5-3.5.54 0 1.04.14 1.5.35V5.08l-9 2V16c-.24 1.7-1.74 3-3.5 3C2.57 19 1 17.44 1 15.5 1 13.57 2.57 12 4.5 12c.54 0 1.04.14 1.5.35V4.08c0-.55.44-.91.99-1z'; + path = + 'M6.99 3.08l11.02-2c.55-.08.99.45.99 1V14.5c0 1.94-1.57 3.5-3.5 3.5S12 16.44 12 14.5c0-1.93 1.57-3.5 3.5-3.5.54 0 1.04.14 1.5.35V5.08l-9 2V16c-.24 1.7-1.74 3-3.5 3C2.57 19 1 17.44 1 15.5 1 13.57 2.57 12 4.5 12c.54 0 1.04.14 1.5.35V4.08c0-.55.44-.91.99-1z'; break; case 'format-chat': - path = 'M11 6h-.82C9.07 6 8 7.2 8 8.16V10l-3 3v-3H3c-1.1 0-2-.9-2-2V3c0-1.1.9-2 2-2h6c1.1 0 2 .9 2 2v3zm0 1h6c1.1 0 2 .9 2 2v5c0 1.1-.9 2-2 2h-2v3l-3-3h-1c-1.1 0-2-.9-2-2V9c0-1.1.9-2 2-2z'; + path = + 'M11 6h-.82C9.07 6 8 7.2 8 8.16V10l-3 3v-3H3c-1.1 0-2-.9-2-2V3c0-1.1.9-2 2-2h6c1.1 0 2 .9 2 2v3zm0 1h6c1.1 0 2 .9 2 2v5c0 1.1-.9 2-2 2h-2v3l-3-3h-1c-1.1 0-2-.9-2-2V9c0-1.1.9-2 2-2z'; break; case 'format-gallery': - path = 'M16 4h1.96c.57 0 1.04.47 1.04 1.04v12.92c0 .57-.47 1.04-1.04 1.04H5.04C4.47 19 4 18.53 4 17.96V16H2.04C1.47 16 1 15.53 1 14.96V2.04C1 1.47 1.47 1 2.04 1h12.92c.57 0 1.04.47 1.04 1.04V4zM3 14h11V3H3v11zm5-8.5C8 4.67 7.33 4 6.5 4S5 4.67 5 5.5 5.67 7 6.5 7 8 6.33 8 5.5zm2 4.5s1-5 3-5v8H4V7c2 0 2 3 2 3s.33-2 2-2 2 2 2 2zm7 7V6h-1v8.96c0 .57-.47 1.04-1.04 1.04H6v1h11z'; + path = + 'M16 4h1.96c.57 0 1.04.47 1.04 1.04v12.92c0 .57-.47 1.04-1.04 1.04H5.04C4.47 19 4 18.53 4 17.96V16H2.04C1.47 16 1 15.53 1 14.96V2.04C1 1.47 1.47 1 2.04 1h12.92c.57 0 1.04.47 1.04 1.04V4zM3 14h11V3H3v11zm5-8.5C8 4.67 7.33 4 6.5 4S5 4.67 5 5.5 5.67 7 6.5 7 8 6.33 8 5.5zm2 4.5s1-5 3-5v8H4V7c2 0 2 3 2 3s.33-2 2-2 2 2 2 2zm7 7V6h-1v8.96c0 .57-.47 1.04-1.04 1.04H6v1h11z'; break; case 'format-image': - path = 'M2.25 1h15.5c.69 0 1.25.56 1.25 1.25v15.5c0 .69-.56 1.25-1.25 1.25H2.25C1.56 19 1 18.44 1 17.75V2.25C1 1.56 1.56 1 2.25 1zM17 17V3H3v14h14zM10 6c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm3 5s0-6 3-6v10c0 .55-.45 1-1 1H5c-.55 0-1-.45-1-1V8c2 0 3 4 3 4s1-3 3-3 3 2 3 2z'; + path = + 'M2.25 1h15.5c.69 0 1.25.56 1.25 1.25v15.5c0 .69-.56 1.25-1.25 1.25H2.25C1.56 19 1 18.44 1 17.75V2.25C1 1.56 1.56 1 2.25 1zM17 17V3H3v14h14zM10 6c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm3 5s0-6 3-6v10c0 .55-.45 1-1 1H5c-.55 0-1-.45-1-1V8c2 0 3 4 3 4s1-3 3-3 3 2 3 2z'; break; case 'format-quote': - path = 'M8.54 12.74c0-.87-.24-1.61-.72-2.22-.73-.92-2.14-1.03-2.96-.85-.34-1.93 1.3-4.39 3.42-5.45L6.65 1.94C3.45 3.46.31 6.96.85 11.37 1.19 14.16 2.8 16 5.08 16c1 0 1.83-.29 2.48-.88.66-.59.98-1.38.98-2.38zm9.43 0c0-.87-.24-1.61-.72-2.22-.73-.92-2.14-1.03-2.96-.85-.34-1.93 1.3-4.39 3.42-5.45l-1.63-2.28c-3.2 1.52-6.34 5.02-5.8 9.43.34 2.79 1.95 4.63 4.23 4.63 1 0 1.83-.29 2.48-.88.66-.59.98-1.38.98-2.38z'; + path = + 'M8.54 12.74c0-.87-.24-1.61-.72-2.22-.73-.92-2.14-1.03-2.96-.85-.34-1.93 1.3-4.39 3.42-5.45L6.65 1.94C3.45 3.46.31 6.96.85 11.37 1.19 14.16 2.8 16 5.08 16c1 0 1.83-.29 2.48-.88.66-.59.98-1.38.98-2.38zm9.43 0c0-.87-.24-1.61-.72-2.22-.73-.92-2.14-1.03-2.96-.85-.34-1.93 1.3-4.39 3.42-5.45l-1.63-2.28c-3.2 1.52-6.34 5.02-5.8 9.43.34 2.79 1.95 4.63 4.23 4.63 1 0 1.83-.29 2.48-.88.66-.59.98-1.38.98-2.38z'; break; case 'format-status': - path = 'M10 1c7 0 9 2.91 9 6.5S17 14 10 14s-9-2.91-9-6.5S3 1 10 1zM5.5 9C6.33 9 7 8.33 7 7.5S6.33 6 5.5 6 4 6.67 4 7.5 4.67 9 5.5 9zM10 9c.83 0 1.5-.67 1.5-1.5S10.83 6 10 6s-1.5.67-1.5 1.5S9.17 9 10 9zm4.5 0c.83 0 1.5-.67 1.5-1.5S15.33 6 14.5 6 13 6.67 13 7.5 13.67 9 14.5 9zM6 14.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5-1.5-.67-1.5-1.5.67-1.5 1.5-1.5zm-3 2c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1z'; + path = + 'M10 1c7 0 9 2.91 9 6.5S17 14 10 14s-9-2.91-9-6.5S3 1 10 1zM5.5 9C6.33 9 7 8.33 7 7.5S6.33 6 5.5 6 4 6.67 4 7.5 4.67 9 5.5 9zM10 9c.83 0 1.5-.67 1.5-1.5S10.83 6 10 6s-1.5.67-1.5 1.5S9.17 9 10 9zm4.5 0c.83 0 1.5-.67 1.5-1.5S15.33 6 14.5 6 13 6.67 13 7.5 13.67 9 14.5 9zM6 14.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5-1.5-.67-1.5-1.5.67-1.5 1.5-1.5zm-3 2c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1z'; break; case 'format-video': - path = 'M2 1h16c.55 0 1 .45 1 1v16l-18-.02V2c0-.55.45-1 1-1zm4 1L4 5h1l2-3H6zm4 0H9L7 5h1zm3 0h-1l-2 3h1zm3 0h-1l-2 3h1zm1 14V6H3v10h14zM8 7l6 4-6 4V7z'; + path = + 'M2 1h16c.55 0 1 .45 1 1v16l-18-.02V2c0-.55.45-1 1-1zm4 1L4 5h1l2-3H6zm4 0H9L7 5h1zm3 0h-1l-2 3h1zm3 0h-1l-2 3h1zm1 14V6H3v10h14zM8 7l6 4-6 4V7z'; break; case 'forms': - path = 'M2 2h7v7H2V2zm9 0v7h7V2h-7zM5.5 4.5L7 3H4zM12 8V3h5v5h-5zM4.5 5.5L3 4v3zM8 4L6.5 5.5 8 7V4zM5.5 6.5L4 8h3zM9 18v-7H2v7h7zm9 0h-7v-7h7v7zM8 12v5H3v-5h5zm6.5 1.5L16 12h-3zM12 16l1.5-1.5L12 13v3zm3.5-1.5L17 16v-3zm-1 1L13 17h3z'; + path = + 'M2 2h7v7H2V2zm9 0v7h7V2h-7zM5.5 4.5L7 3H4zM12 8V3h5v5h-5zM4.5 5.5L3 4v3zM8 4L6.5 5.5 8 7V4zM5.5 6.5L4 8h3zM9 18v-7H2v7h7zm9 0h-7v-7h7v7zM8 12v5H3v-5h5zm6.5 1.5L16 12h-3zM12 16l1.5-1.5L12 13v3zm3.5-1.5L17 16v-3zm-1 1L13 17h3z'; break; case 'googleplus': - path = 'M6.73 10h5.4c.05.29.09.57.09.95 0 3.27-2.19 5.6-5.49 5.6-3.17 0-5.73-2.57-5.73-5.73 0-3.17 2.56-5.73 5.73-5.73 1.54 0 2.84.57 3.83 1.5l-1.55 1.5c-.43-.41-1.17-.89-2.28-.89-1.96 0-3.55 1.62-3.55 3.62 0 1.99 1.59 3.61 3.55 3.61 2.26 0 3.11-1.62 3.24-2.47H6.73V10zM19 10v1.64h-1.64v1.63h-1.63v-1.63h-1.64V10h1.64V8.36h1.63V10H19z'; + path = + 'M6.73 10h5.4c.05.29.09.57.09.95 0 3.27-2.19 5.6-5.49 5.6-3.17 0-5.73-2.57-5.73-5.73 0-3.17 2.56-5.73 5.73-5.73 1.54 0 2.84.57 3.83 1.5l-1.55 1.5c-.43-.41-1.17-.89-2.28-.89-1.96 0-3.55 1.62-3.55 3.62 0 1.99 1.59 3.61 3.55 3.61 2.26 0 3.11-1.62 3.24-2.47H6.73V10zM19 10v1.64h-1.64v1.63h-1.63v-1.63h-1.64V10h1.64V8.36h1.63V10H19z'; break; case 'grid-view': - path = 'M2 1h16c.55 0 1 .45 1 1v16c0 .55-.45 1-1 1H2c-.55 0-1-.45-1-1V2c0-.55.45-1 1-1zm7.01 7.99v-6H3v6h6.01zm8 0v-6h-6v6h6zm-8 8.01v-6H3v6h6.01zm8 0v-6h-6v6h6z'; + path = + 'M2 1h16c.55 0 1 .45 1 1v16c0 .55-.45 1-1 1H2c-.55 0-1-.45-1-1V2c0-.55.45-1 1-1zm7.01 7.99v-6H3v6h6.01zm8 0v-6h-6v6h6zm-8 8.01v-6H3v6h6.01zm8 0v-6h-6v6h6z'; break; case 'groups': - path = 'M8.03 4.46c-.29 1.28.55 3.46 1.97 3.46 1.41 0 2.25-2.18 1.96-3.46-.22-.98-1.08-1.63-1.96-1.63-.89 0-1.74.65-1.97 1.63zm-4.13.9c-.25 1.08.47 2.93 1.67 2.93s1.92-1.85 1.67-2.93c-.19-.83-.92-1.39-1.67-1.39s-1.48.56-1.67 1.39zm8.86 0c-.25 1.08.47 2.93 1.66 2.93 1.2 0 1.92-1.85 1.67-2.93-.19-.83-.92-1.39-1.67-1.39-.74 0-1.47.56-1.66 1.39zm-.59 11.43l1.25-4.3C14.2 10 12.71 8.47 10 8.47c-2.72 0-4.21 1.53-3.44 4.02l1.26 4.3C8.05 17.51 9 18 10 18c.98 0 1.94-.49 2.17-1.21zm-6.1-7.63c-.49.67-.96 1.83-.42 3.59l1.12 3.79c-.34.2-.77.31-1.2.31-.85 0-1.65-.41-1.85-1.03l-1.07-3.65c-.65-2.11.61-3.4 2.92-3.4.27 0 .54.02.79.06-.1.1-.2.22-.29.33zm8.35-.39c2.31 0 3.58 1.29 2.92 3.4l-1.07 3.65c-.2.62-1 1.03-1.85 1.03-.43 0-.86-.11-1.2-.31l1.11-3.77c.55-1.78.08-2.94-.42-3.61-.08-.11-.18-.23-.28-.33.25-.04.51-.06.79-.06z'; + path = + 'M8.03 4.46c-.29 1.28.55 3.46 1.97 3.46 1.41 0 2.25-2.18 1.96-3.46-.22-.98-1.08-1.63-1.96-1.63-.89 0-1.74.65-1.97 1.63zm-4.13.9c-.25 1.08.47 2.93 1.67 2.93s1.92-1.85 1.67-2.93c-.19-.83-.92-1.39-1.67-1.39s-1.48.56-1.67 1.39zm8.86 0c-.25 1.08.47 2.93 1.66 2.93 1.2 0 1.92-1.85 1.67-2.93-.19-.83-.92-1.39-1.67-1.39-.74 0-1.47.56-1.66 1.39zm-.59 11.43l1.25-4.3C14.2 10 12.71 8.47 10 8.47c-2.72 0-4.21 1.53-3.44 4.02l1.26 4.3C8.05 17.51 9 18 10 18c.98 0 1.94-.49 2.17-1.21zm-6.1-7.63c-.49.67-.96 1.83-.42 3.59l1.12 3.79c-.34.2-.77.31-1.2.31-.85 0-1.65-.41-1.85-1.03l-1.07-3.65c-.65-2.11.61-3.4 2.92-3.4.27 0 .54.02.79.06-.1.1-.2.22-.29.33zm8.35-.39c2.31 0 3.58 1.29 2.92 3.4l-1.07 3.65c-.2.62-1 1.03-1.85 1.03-.43 0-.86-.11-1.2-.31l1.11-3.77c.55-1.78.08-2.94-.42-3.61-.08-.11-.18-.23-.28-.33.25-.04.51-.06.79-.06z'; break; case 'hammer': - path = 'M17.7 6.32l1.41 1.42-3.47 3.41-1.42-1.42.84-.82c-.32-.76-.81-1.57-1.51-2.31l-4.61 6.59-5.26 4.7c-.39.39-1.02.39-1.42 0l-1.2-1.21c-.39-.39-.39-1.02 0-1.41l10.97-9.92c-1.37-.86-3.21-1.46-5.67-1.48 2.7-.82 4.95-.93 6.58-.3 1.7.66 2.82 2.2 3.91 3.58z'; + path = + 'M17.7 6.32l1.41 1.42-3.47 3.41-1.42-1.42.84-.82c-.32-.76-.81-1.57-1.51-2.31l-4.61 6.59-5.26 4.7c-.39.39-1.02.39-1.42 0l-1.2-1.21c-.39-.39-.39-1.02 0-1.41l10.97-9.92c-1.37-.86-3.21-1.46-5.67-1.48 2.7-.82 4.95-.93 6.58-.3 1.7.66 2.82 2.2 3.91 3.58z'; break; case 'heading': path = 'M12.5 4v5.2h-5V4H5v13h2.5v-5.2h5V17H15V4'; break; case 'heart': - path = 'M10 17.12c3.33-1.4 5.74-3.79 7.04-6.21 1.28-2.41 1.46-4.81.32-6.25-1.03-1.29-2.37-1.78-3.73-1.74s-2.68.63-3.63 1.46c-.95-.83-2.27-1.42-3.63-1.46s-2.7.45-3.73 1.74c-1.14 1.44-.96 3.84.34 6.25 1.28 2.42 3.69 4.81 7.02 6.21z'; + path = + 'M10 17.12c3.33-1.4 5.74-3.79 7.04-6.21 1.28-2.41 1.46-4.81.32-6.25-1.03-1.29-2.37-1.78-3.73-1.74s-2.68.63-3.63 1.46c-.95-.83-2.27-1.42-3.63-1.46s-2.7.45-3.73 1.74c-1.14 1.44-.96 3.84.34 6.25 1.28 2.42 3.69 4.81 7.02 6.21z'; break; case 'hidden': - path = 'M17.2 3.3l.16.17c.39.39.39 1.02 0 1.41L4.55 17.7c-.39.39-1.03.39-1.41 0l-.17-.17c-.39-.39-.39-1.02 0-1.41l1.59-1.6c-1.57-1-2.76-2.3-3.56-3.93.81-1.65 2.03-2.98 3.64-3.99S8.04 5.09 10 5.09c1.2 0 2.33.21 3.4.6l2.38-2.39c.39-.39 1.03-.39 1.42 0zm-7.09 4.01c-.23.25-.34.54-.34.88 0 .31.12.58.31.81l1.8-1.79c-.13-.12-.28-.21-.45-.26-.11-.01-.28-.03-.49-.04-.33.03-.6.16-.83.4zM2.4 10.59c.69 1.23 1.71 2.25 3.05 3.05l1.28-1.28c-.51-.69-.77-1.47-.77-2.36 0-1.06.36-1.98 1.09-2.76-1.04.27-1.96.7-2.76 1.26-.8.58-1.43 1.27-1.89 2.09zm13.22-2.13l.96-.96c1.02.86 1.83 1.89 2.42 3.09-.81 1.65-2.03 2.98-3.64 3.99s-3.4 1.51-5.36 1.51c-.63 0-1.24-.07-1.83-.18l1.07-1.07c.25.02.5.05.76.05 1.63 0 3.13-.4 4.5-1.21s2.4-1.84 3.1-3.09c-.46-.82-1.09-1.51-1.89-2.09-.03-.01-.06-.03-.09-.04zm-5.58 5.58l4-4c-.01 1.1-.41 2.04-1.18 2.81-.78.78-1.72 1.18-2.82 1.19z'; + path = + 'M17.2 3.3l.16.17c.39.39.39 1.02 0 1.41L4.55 17.7c-.39.39-1.03.39-1.41 0l-.17-.17c-.39-.39-.39-1.02 0-1.41l1.59-1.6c-1.57-1-2.76-2.3-3.56-3.93.81-1.65 2.03-2.98 3.64-3.99S8.04 5.09 10 5.09c1.2 0 2.33.21 3.4.6l2.38-2.39c.39-.39 1.03-.39 1.42 0zm-7.09 4.01c-.23.25-.34.54-.34.88 0 .31.12.58.31.81l1.8-1.79c-.13-.12-.28-.21-.45-.26-.11-.01-.28-.03-.49-.04-.33.03-.6.16-.83.4zM2.4 10.59c.69 1.23 1.71 2.25 3.05 3.05l1.28-1.28c-.51-.69-.77-1.47-.77-2.36 0-1.06.36-1.98 1.09-2.76-1.04.27-1.96.7-2.76 1.26-.8.58-1.43 1.27-1.89 2.09zm13.22-2.13l.96-.96c1.02.86 1.83 1.89 2.42 3.09-.81 1.65-2.03 2.98-3.64 3.99s-3.4 1.51-5.36 1.51c-.63 0-1.24-.07-1.83-.18l1.07-1.07c.25.02.5.05.76.05 1.63 0 3.13-.4 4.5-1.21s2.4-1.84 3.1-3.09c-.46-.82-1.09-1.51-1.89-2.09-.03-.01-.06-.03-.09-.04zm-5.58 5.58l4-4c-.01 1.1-.41 2.04-1.18 2.81-.78.78-1.72 1.18-2.82 1.19z'; break; case 'html': - path = 'M4 16v-2H2v2H1v-5h1v2h2v-2h1v5H4zM7 16v-4H5.6v-1h3.7v1H8v4H7zM10 16v-5h1l1.4 3.4h.1L14 11h1v5h-1v-3.1h-.1l-1.1 2.5h-.6l-1.1-2.5H11V16h-1zM19 16h-3v-5h1v4h2v1zM9.4 4.2L7.1 6.5l2.3 2.3-.6 1.2-3.5-3.5L8.8 3l.6 1.2zm1.2 4.6l2.3-2.3-2.3-2.3.6-1.2 3.5 3.5-3.5 3.5-.6-1.2z'; + path = + 'M4 16v-2H2v2H1v-5h1v2h2v-2h1v5H4zM7 16v-4H5.6v-1h3.7v1H8v4H7zM10 16v-5h1l1.4 3.4h.1L14 11h1v5h-1v-3.1h-.1l-1.1 2.5h-.6l-1.1-2.5H11V16h-1zM19 16h-3v-5h1v4h2v1zM9.4 4.2L7.1 6.5l2.3 2.3-.6 1.2-3.5-3.5L8.8 3l.6 1.2zm1.2 4.6l2.3-2.3-2.3-2.3.6-1.2 3.5 3.5-3.5 3.5-.6-1.2z'; break; case 'id-alt': - path = 'M18 18H2V2h16v16zM8.05 7.53c.13-.07.24-.15.33-.24.09-.1.17-.21.24-.34.07-.14.13-.26.17-.37s.07-.22.1-.34L8.95 6c0-.04.01-.07.01-.09.05-.32.03-.61-.04-.9-.08-.28-.23-.52-.46-.72C8.23 4.1 7.95 4 7.6 4c-.2 0-.39.04-.56.11-.17.08-.31.18-.41.3-.11.13-.2.27-.27.44-.07.16-.11.33-.12.51s0 .36.01.55l.02.09c.01.06.03.15.06.25s.06.21.1.33.1.25.17.37c.08.12.16.23.25.33s.2.19.34.25c.13.06.28.09.43.09s.3-.03.43-.09zM16 5V4h-5v1h5zm0 2V6h-5v1h5zM7.62 8.83l-1.38-.88c-.41 0-.79.11-1.14.32-.35.22-.62.5-.81.85-.19.34-.29.7-.29 1.07v1.25l.2.05c.13.04.31.09.55.14.24.06.51.12.8.17.29.06.62.1 1 .14.37.04.73.06 1.07.06s.69-.02 1.07-.06.7-.09.98-.14c.27-.05.54-.1.82-.17.27-.06.45-.11.54-.13.09-.03.16-.05.21-.06v-1.25c0-.36-.1-.72-.31-1.07s-.49-.64-.84-.86-.72-.33-1.11-.33zM16 9V8h-3v1h3zm0 2v-1h-3v1h3zm0 3v-1H4v1h12zm0 2v-1H4v1h12z'; + path = + 'M18 18H2V2h16v16zM8.05 7.53c.13-.07.24-.15.33-.24.09-.1.17-.21.24-.34.07-.14.13-.26.17-.37s.07-.22.1-.34L8.95 6c0-.04.01-.07.01-.09.05-.32.03-.61-.04-.9-.08-.28-.23-.52-.46-.72C8.23 4.1 7.95 4 7.6 4c-.2 0-.39.04-.56.11-.17.08-.31.18-.41.3-.11.13-.2.27-.27.44-.07.16-.11.33-.12.51s0 .36.01.55l.02.09c.01.06.03.15.06.25s.06.21.1.33.1.25.17.37c.08.12.16.23.25.33s.2.19.34.25c.13.06.28.09.43.09s.3-.03.43-.09zM16 5V4h-5v1h5zm0 2V6h-5v1h5zM7.62 8.83l-1.38-.88c-.41 0-.79.11-1.14.32-.35.22-.62.5-.81.85-.19.34-.29.7-.29 1.07v1.25l.2.05c.13.04.31.09.55.14.24.06.51.12.8.17.29.06.62.1 1 .14.37.04.73.06 1.07.06s.69-.02 1.07-.06.7-.09.98-.14c.27-.05.54-.1.82-.17.27-.06.45-.11.54-.13.09-.03.16-.05.21-.06v-1.25c0-.36-.1-.72-.31-1.07s-.49-.64-.84-.86-.72-.33-1.11-.33zM16 9V8h-3v1h3zm0 2v-1h-3v1h3zm0 3v-1H4v1h12zm0 2v-1H4v1h12z'; break; case 'id': - path = 'M18 16H2V4h16v12zM7.05 8.53c.13-.07.24-.15.33-.24.09-.1.17-.21.24-.34.07-.14.13-.26.17-.37s.07-.22.1-.34L7.95 7c0-.04.01-.07.01-.09.05-.32.03-.61-.04-.9-.08-.28-.23-.52-.46-.72C7.23 5.1 6.95 5 6.6 5c-.2 0-.39.04-.56.11-.17.08-.31.18-.41.3-.11.13-.2.27-.27.44-.07.16-.11.33-.12.51s0 .36.01.55l.02.09c.01.06.03.15.06.25s.06.21.1.33.1.25.17.37c.08.12.16.23.25.33s.2.19.34.25c.13.06.28.09.43.09s.3-.03.43-.09zM17 9V5h-5v4h5zm-10.38.83l-1.38-.88c-.41 0-.79.11-1.14.32-.35.22-.62.5-.81.85-.19.34-.29.7-.29 1.07v1.25l.2.05c.13.04.31.09.55.14.24.06.51.12.8.17.29.06.62.1 1 .14.37.04.73.06 1.07.06s.69-.02 1.07-.06.7-.09.98-.14c.27-.05.54-.1.82-.17.27-.06.45-.11.54-.13.09-.03.16-.05.21-.06v-1.25c0-.36-.1-.72-.31-1.07s-.49-.64-.84-.86-.72-.33-1.11-.33zM17 11v-1h-5v1h5zm0 2v-1h-5v1h5zm0 2v-1H3v1h14z'; + path = + 'M18 16H2V4h16v12zM7.05 8.53c.13-.07.24-.15.33-.24.09-.1.17-.21.24-.34.07-.14.13-.26.17-.37s.07-.22.1-.34L7.95 7c0-.04.01-.07.01-.09.05-.32.03-.61-.04-.9-.08-.28-.23-.52-.46-.72C7.23 5.1 6.95 5 6.6 5c-.2 0-.39.04-.56.11-.17.08-.31.18-.41.3-.11.13-.2.27-.27.44-.07.16-.11.33-.12.51s0 .36.01.55l.02.09c.01.06.03.15.06.25s.06.21.1.33.1.25.17.37c.08.12.16.23.25.33s.2.19.34.25c.13.06.28.09.43.09s.3-.03.43-.09zM17 9V5h-5v4h5zm-10.38.83l-1.38-.88c-.41 0-.79.11-1.14.32-.35.22-.62.5-.81.85-.19.34-.29.7-.29 1.07v1.25l.2.05c.13.04.31.09.55.14.24.06.51.12.8.17.29.06.62.1 1 .14.37.04.73.06 1.07.06s.69-.02 1.07-.06.7-.09.98-.14c.27-.05.54-.1.82-.17.27-.06.45-.11.54-.13.09-.03.16-.05.21-.06v-1.25c0-.36-.1-.72-.31-1.07s-.49-.64-.84-.86-.72-.33-1.11-.33zM17 11v-1h-5v1h5zm0 2v-1h-5v1h5zm0 2v-1H3v1h14z'; break; case 'image-crop': - path = 'M19 12v3h-4v4h-3v-4H4V7H0V4h4V0h3v4h7l3-3 1 1-3 3v7h4zm-8-5H7v4zm-3 5h4V8z'; + path = + 'M19 12v3h-4v4h-3v-4H4V7H0V4h4V0h3v4h7l3-3 1 1-3 3v7h4zm-8-5H7v4zm-3 5h4V8z'; break; case 'image-filter': - path = 'M14 5.87c0-2.2-1.79-4-4-4s-4 1.8-4 4c0 2.21 1.79 4 4 4s4-1.79 4-4zM3.24 10.66c-1.92 1.1-2.57 3.55-1.47 5.46 1.11 1.92 3.55 2.57 5.47 1.47 1.91-1.11 2.57-3.55 1.46-5.47-1.1-1.91-3.55-2.56-5.46-1.46zm9.52 6.93c1.92 1.1 4.36.45 5.47-1.46 1.1-1.92.45-4.36-1.47-5.47-1.91-1.1-4.36-.45-5.46 1.46-1.11 1.92-.45 4.36 1.46 5.47z'; + path = + 'M14 5.87c0-2.2-1.79-4-4-4s-4 1.8-4 4c0 2.21 1.79 4 4 4s4-1.79 4-4zM3.24 10.66c-1.92 1.1-2.57 3.55-1.47 5.46 1.11 1.92 3.55 2.57 5.47 1.47 1.91-1.11 2.57-3.55 1.46-5.47-1.1-1.91-3.55-2.56-5.46-1.46zm9.52 6.93c1.92 1.1 4.36.45 5.47-1.46 1.1-1.92.45-4.36-1.47-5.47-1.91-1.1-4.36-.45-5.46 1.46-1.11 1.92-.45 4.36 1.46 5.47z'; break; case 'image-flip-horizontal': - path = 'M19 3v14h-8v3H9v-3H1V3h8V0h2v3h8zm-8.5 14V3h-1v14h1zM7 6.5L3 10l4 3.5v-7zM17 10l-4-3.5v7z'; + path = + 'M19 3v14h-8v3H9v-3H1V3h8V0h2v3h8zm-8.5 14V3h-1v14h1zM7 6.5L3 10l4 3.5v-7zM17 10l-4-3.5v7z'; break; case 'image-flip-vertical': - path = 'M20 9v2h-3v8H3v-8H0V9h3V1h14v8h3zM6.5 7h7L10 3zM17 9.5H3v1h14v-1zM13.5 13h-7l3.5 4z'; + path = + 'M20 9v2h-3v8H3v-8H0V9h3V1h14v8h3zM6.5 7h7L10 3zM17 9.5H3v1h14v-1zM13.5 13h-7l3.5 4z'; break; case 'image-rotate-left': - path = 'M7 5H5.05c0-1.74.85-2.9 2.95-2.9V0C4.85 0 2.96 2.11 2.96 5H1.18L3.8 8.39zm13-4v14h-5v5H1V10h9V1h10zm-2 2h-6v7h3v3h3V3zm-5 9H3v6h10v-6z'; + path = + 'M7 5H5.05c0-1.74.85-2.9 2.95-2.9V0C4.85 0 2.96 2.11 2.96 5H1.18L3.8 8.39zm13-4v14h-5v5H1V10h9V1h10zm-2 2h-6v7h3v3h3V3zm-5 9H3v6h10v-6z'; break; case 'image-rotate-right': - path = 'M15.95 5H14l3.2 3.39L19.82 5h-1.78c0-2.89-1.89-5-5.04-5v2.1c2.1 0 2.95 1.16 2.95 2.9zM1 1h10v9h9v10H6v-5H1V1zm2 2v10h3v-3h3V3H3zm5 9v6h10v-6H8z'; + path = + 'M15.95 5H14l3.2 3.39L19.82 5h-1.78c0-2.89-1.89-5-5.04-5v2.1c2.1 0 2.95 1.16 2.95 2.9zM1 1h10v9h9v10H6v-5H1V1zm2 2v10h3v-3h3V3H3zm5 9v6h10v-6H8z'; break; case 'image-rotate': - path = 'M10.25 1.02c5.1 0 8.75 4.04 8.75 9s-3.65 9-8.75 9c-3.2 0-6.02-1.59-7.68-3.99l2.59-1.52c1.1 1.5 2.86 2.51 4.84 2.51 3.3 0 6-2.79 6-6s-2.7-6-6-6c-1.97 0-3.72 1-4.82 2.49L7 8.02l-6 2v-7L2.89 4.6c1.69-2.17 4.36-3.58 7.36-3.58z'; + path = + 'M10.25 1.02c5.1 0 8.75 4.04 8.75 9s-3.65 9-8.75 9c-3.2 0-6.02-1.59-7.68-3.99l2.59-1.52c1.1 1.5 2.86 2.51 4.84 2.51 3.3 0 6-2.79 6-6s-2.7-6-6-6c-1.97 0-3.72 1-4.82 2.49L7 8.02l-6 2v-7L2.89 4.6c1.69-2.17 4.36-3.58 7.36-3.58z'; break; case 'images-alt': - path = 'M4 15v-3H2V2h12v3h2v3h2v10H6v-3H4zm7-12c-1.1 0-2 .9-2 2h4c0-1.1-.89-2-2-2zm-7 8V6H3v5h1zm7-3h4c0-1.1-.89-2-2-2-1.1 0-2 .9-2 2zm-5 6V9H5v5h1zm9-1c1.1 0 2-.89 2-2 0-1.1-.9-2-2-2s-2 .9-2 2c0 1.11.9 2 2 2zm2 4v-2c-5 0-5-3-10-3v5h10z'; + path = + 'M4 15v-3H2V2h12v3h2v3h2v10H6v-3H4zm7-12c-1.1 0-2 .9-2 2h4c0-1.1-.89-2-2-2zm-7 8V6H3v5h1zm7-3h4c0-1.1-.89-2-2-2-1.1 0-2 .9-2 2zm-5 6V9H5v5h1zm9-1c1.1 0 2-.89 2-2 0-1.1-.9-2-2-2s-2 .9-2 2c0 1.11.9 2 2 2zm2 4v-2c-5 0-5-3-10-3v5h10z'; break; case 'images-alt2': - path = 'M5 3h14v11h-2v2h-2v2H1V7h2V5h2V3zm13 10V4H6v9h12zm-3-4c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm1 6v-1H5V6H4v9h12zM7 6l10 6H7V6zm7 11v-1H3V8H2v9h12z'; + path = + 'M5 3h14v11h-2v2h-2v2H1V7h2V5h2V3zm13 10V4H6v9h12zm-3-4c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm1 6v-1H5V6H4v9h12zM7 6l10 6H7V6zm7 11v-1H3V8H2v9h12z'; break; case 'index-card': - path = 'M1 3.17V18h18V4H8v-.83c0-.32-.12-.6-.35-.83S7.14 2 6.82 2H2.18c-.33 0-.6.11-.83.34-.24.23-.35.51-.35.83zM10 6v2H3V6h7zm7 0v10h-5V6h5zm-7 4v2H3v-2h7zm0 4v2H3v-2h7z'; + path = + 'M1 3.17V18h18V4H8v-.83c0-.32-.12-.6-.35-.83S7.14 2 6.82 2H2.18c-.33 0-.6.11-.83.34-.24.23-.35.51-.35.83zM10 6v2H3V6h7zm7 0v10h-5V6h5zm-7 4v2H3v-2h7zm0 4v2H3v-2h7z'; break; case 'info-outline': - path = 'M9 15h2V9H9v6zm1-10c-.5 0-1 .5-1 1s.5 1 1 1 1-.5 1-1-.5-1-1-1zm0-4c-5 0-9 4-9 9s4 9 9 9 9-4 9-9-4-9-9-9zm0 16c-3.9 0-7-3.1-7-7s3.1-7 7-7 7 3.1 7 7-3.1 7-7 7z'; + path = + 'M9 15h2V9H9v6zm1-10c-.5 0-1 .5-1 1s.5 1 1 1 1-.5 1-1-.5-1-1-1zm0-4c-5 0-9 4-9 9s4 9 9 9 9-4 9-9-4-9-9-9zm0 16c-3.9 0-7-3.1-7-7s3.1-7 7-7 7 3.1 7 7-3.1 7-7 7z'; break; case 'info': - path = 'M10 2c4.42 0 8 3.58 8 8s-3.58 8-8 8-8-3.58-8-8 3.58-8 8-8zm1 4c0-.55-.45-1-1-1s-1 .45-1 1 .45 1 1 1 1-.45 1-1zm0 9V9H9v6h2z'; + path = + 'M10 2c4.42 0 8 3.58 8 8s-3.58 8-8 8-8-3.58-8-8 3.58-8 8-8zm1 4c0-.55-.45-1-1-1s-1 .45-1 1 .45 1 1 1 1-.45 1-1zm0 9V9H9v6h2z'; break; case 'insert-after': - path = 'M9 12h2v-2h2V8h-2V6H9v2H7v2h2v2zm1 4c3.9 0 7-3.1 7-7s-3.1-7-7-7-7 3.1-7 7 3.1 7 7 7zm0-12c2.8 0 5 2.2 5 5s-2.2 5-5 5-5-2.2-5-5 2.2-5 5-5zM3 19h14v-2H3v2z'; + path = + 'M9 12h2v-2h2V8h-2V6H9v2H7v2h2v2zm1 4c3.9 0 7-3.1 7-7s-3.1-7-7-7-7 3.1-7 7 3.1 7 7 7zm0-12c2.8 0 5 2.2 5 5s-2.2 5-5 5-5-2.2-5-5 2.2-5 5-5zM3 19h14v-2H3v2z'; break; case 'insert-before': - path = 'M11 8H9v2H7v2h2v2h2v-2h2v-2h-2V8zm-1-4c-3.9 0-7 3.1-7 7s3.1 7 7 7 7-3.1 7-7-3.1-7-7-7zm0 12c-2.8 0-5-2.2-5-5s2.2-5 5-5 5 2.2 5 5-2.2 5-5 5zM3 1v2h14V1H3z'; + path = + 'M11 8H9v2H7v2h2v2h2v-2h2v-2h-2V8zm-1-4c-3.9 0-7 3.1-7 7s3.1 7 7 7 7-3.1 7-7-3.1-7-7-7zm0 12c-2.8 0-5-2.2-5-5s2.2-5 5-5 5 2.2 5 5-2.2 5-5 5zM3 1v2h14V1H3z'; break; case 'insert': - path = 'M10 1c-5 0-9 4-9 9s4 9 9 9 9-4 9-9-4-9-9-9zm0 16c-3.9 0-7-3.1-7-7s3.1-7 7-7 7 3.1 7 7-3.1 7-7 7zm1-11H9v3H6v2h3v3h2v-3h3V9h-3V6z'; + path = + 'M10 1c-5 0-9 4-9 9s4 9 9 9 9-4 9-9-4-9-9-9zm0 16c-3.9 0-7-3.1-7-7s3.1-7 7-7 7 3.1 7 7-3.1 7-7 7zm1-11H9v3H6v2h3v3h2v-3h3V9h-3V6z'; break; case 'instagram': - path = 'M12.67 10A2.67 2.67 0 1 0 10 12.67 2.68 2.68 0 0 0 12.67 10zm1.43 0A4.1 4.1 0 1 1 10 5.9a4.09 4.09 0 0 1 4.1 4.1zm1.13-4.27a1 1 0 1 1-1-1 1 1 0 0 1 1 1zM10 3.44c-1.17 0-3.67-.1-4.72.32a2.67 2.67 0 0 0-1.52 1.52c-.42 1-.32 3.55-.32 4.72s-.1 3.67.32 4.72a2.74 2.74 0 0 0 1.52 1.52c1 .42 3.55.32 4.72.32s3.67.1 4.72-.32a2.83 2.83 0 0 0 1.52-1.52c.42-1.05.32-3.55.32-4.72s.1-3.67-.32-4.72a2.74 2.74 0 0 0-1.52-1.52c-1.05-.42-3.55-.32-4.72-.32zM18 10c0 1.1 0 2.2-.05 3.3a4.84 4.84 0 0 1-1.29 3.36A4.8 4.8 0 0 1 13.3 18H6.7a4.84 4.84 0 0 1-3.36-1.29 4.84 4.84 0 0 1-1.29-3.41C2 12.2 2 11.1 2 10V6.7a4.84 4.84 0 0 1 1.34-3.36A4.8 4.8 0 0 1 6.7 2.05C7.8 2 8.9 2 10 2h3.3a4.84 4.84 0 0 1 3.36 1.29A4.8 4.8 0 0 1 18 6.7V10z'; + path = + 'M12.67 10A2.67 2.67 0 1 0 10 12.67 2.68 2.68 0 0 0 12.67 10zm1.43 0A4.1 4.1 0 1 1 10 5.9a4.09 4.09 0 0 1 4.1 4.1zm1.13-4.27a1 1 0 1 1-1-1 1 1 0 0 1 1 1zM10 3.44c-1.17 0-3.67-.1-4.72.32a2.67 2.67 0 0 0-1.52 1.52c-.42 1-.32 3.55-.32 4.72s-.1 3.67.32 4.72a2.74 2.74 0 0 0 1.52 1.52c1 .42 3.55.32 4.72.32s3.67.1 4.72-.32a2.83 2.83 0 0 0 1.52-1.52c.42-1.05.32-3.55.32-4.72s.1-3.67-.32-4.72a2.74 2.74 0 0 0-1.52-1.52c-1.05-.42-3.55-.32-4.72-.32zM18 10c0 1.1 0 2.2-.05 3.3a4.84 4.84 0 0 1-1.29 3.36A4.8 4.8 0 0 1 13.3 18H6.7a4.84 4.84 0 0 1-3.36-1.29 4.84 4.84 0 0 1-1.29-3.41C2 12.2 2 11.1 2 10V6.7a4.84 4.84 0 0 1 1.34-3.36A4.8 4.8 0 0 1 6.7 2.05C7.8 2 8.9 2 10 2h3.3a4.84 4.84 0 0 1 3.36 1.29A4.8 4.8 0 0 1 18 6.7V10z'; break; case 'keyboard-hide': - path = 'M18,0 L2,0 C0.9,0 0.01,0.9 0.01,2 L0,12 C0,13.1 0.9,14 2,14 L18,14 C19.1,14 20,13.1 20,12 L20,2 C20,0.9 19.1,0 18,0 Z M18,12 L2,12 L2,2 L18,2 L18,12 Z M9,3 L11,3 L11,5 L9,5 L9,3 Z M9,6 L11,6 L11,8 L9,8 L9,6 Z M6,3 L8,3 L8,5 L6,5 L6,3 Z M6,6 L8,6 L8,8 L6,8 L6,6 Z M3,6 L5,6 L5,8 L3,8 L3,6 Z M3,3 L5,3 L5,5 L3,5 L3,3 Z M6,9 L14,9 L14,11 L6,11 L6,9 Z M12,6 L14,6 L14,8 L12,8 L12,6 Z M12,3 L14,3 L14,5 L12,5 L12,3 Z M15,6 L17,6 L17,8 L15,8 L15,6 Z M15,3 L17,3 L17,5 L15,5 L15,3 Z M10,20 L14,16 L6,16 L10,20 Z'; + path = + 'M18,0 L2,0 C0.9,0 0.01,0.9 0.01,2 L0,12 C0,13.1 0.9,14 2,14 L18,14 C19.1,14 20,13.1 20,12 L20,2 C20,0.9 19.1,0 18,0 Z M18,12 L2,12 L2,2 L18,2 L18,12 Z M9,3 L11,3 L11,5 L9,5 L9,3 Z M9,6 L11,6 L11,8 L9,8 L9,6 Z M6,3 L8,3 L8,5 L6,5 L6,3 Z M6,6 L8,6 L8,8 L6,8 L6,6 Z M3,6 L5,6 L5,8 L3,8 L3,6 Z M3,3 L5,3 L5,5 L3,5 L3,3 Z M6,9 L14,9 L14,11 L6,11 L6,9 Z M12,6 L14,6 L14,8 L12,8 L12,6 Z M12,3 L14,3 L14,5 L12,5 L12,3 Z M15,6 L17,6 L17,8 L15,8 L15,6 Z M15,3 L17,3 L17,5 L15,5 L15,3 Z M10,20 L14,16 L6,16 L10,20 Z'; break; case 'laptop': - path = 'M3 3h14c.6 0 1 .4 1 1v10c0 .6-.4 1-1 1H3c-.6 0-1-.4-1-1V4c0-.6.4-1 1-1zm13 2H4v8h12V5zm-3 1H5v4zm6 11v-1H1v1c0 .6.5 1 1.1 1h15.8c.6 0 1.1-.4 1.1-1z'; + path = + 'M3 3h14c.6 0 1 .4 1 1v10c0 .6-.4 1-1 1H3c-.6 0-1-.4-1-1V4c0-.6.4-1 1-1zm13 2H4v8h12V5zm-3 1H5v4zm6 11v-1H1v1c0 .6.5 1 1.1 1h15.8c.6 0 1.1-.4 1.1-1z'; break; case 'layout': - path = 'M2 2h5v11H2V2zm6 0h5v5H8V2zm6 0h4v16h-4V2zM8 8h5v5H8V8zm-6 6h11v4H2v-4z'; + path = + 'M2 2h5v11H2V2zm6 0h5v5H8V2zm6 0h4v16h-4V2zM8 8h5v5H8V8zm-6 6h11v4H2v-4z'; break; case 'leftright': path = 'M3 10.03L9 6v8zM11 6l6 4.03L11 14V6z'; break; case 'lightbulb': - path = 'M10 1c3.11 0 5.63 2.52 5.63 5.62 0 1.84-2.03 4.58-2.03 4.58-.33.44-.6 1.25-.6 1.8v1c0 .55-.45 1-1 1H8c-.55 0-1-.45-1-1v-1c0-.55-.27-1.36-.6-1.8 0 0-2.02-2.74-2.02-4.58C4.38 3.52 6.89 1 10 1zM7 16.87V16h6v.87c0 .62-.13 1.13-.75 1.13H12c0 .62-.4 1-1.02 1h-2c-.61 0-.98-.38-.98-1h-.25c-.62 0-.75-.51-.75-1.13z'; + path = + 'M10 1c3.11 0 5.63 2.52 5.63 5.62 0 1.84-2.03 4.58-2.03 4.58-.33.44-.6 1.25-.6 1.8v1c0 .55-.45 1-1 1H8c-.55 0-1-.45-1-1v-1c0-.55-.27-1.36-.6-1.8 0 0-2.02-2.74-2.02-4.58C4.38 3.52 6.89 1 10 1zM7 16.87V16h6v.87c0 .62-.13 1.13-.75 1.13H12c0 .62-.4 1-1.02 1h-2c-.61 0-.98-.38-.98-1h-.25c-.62 0-.75-.51-.75-1.13z'; break; case 'list-view': - path = 'M2 19h16c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1H2c-.55 0-1 .45-1 1v16c0 .55.45 1 1 1zM4 3c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm13 0v2H6V3h11zM4 7c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm13 0v2H6V7h11zM4 11c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm13 0v2H6v-2h11zM4 15c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm13 0v2H6v-2h11z'; + path = + 'M2 19h16c.55 0 1-.45 1-1V2c0-.55-.45-1-1-1H2c-.55 0-1 .45-1 1v16c0 .55.45 1 1 1zM4 3c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm13 0v2H6V3h11zM4 7c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm13 0v2H6V7h11zM4 11c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm13 0v2H6v-2h11zM4 15c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm13 0v2H6v-2h11z'; break; case 'location-alt': - path = 'M13 13.14l1.17-5.94c.79-.43 1.33-1.25 1.33-2.2 0-1.38-1.12-2.5-2.5-2.5S10.5 3.62 10.5 5c0 .95.54 1.77 1.33 2.2zm0-9.64c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5-1.5-.67-1.5-1.5.67-1.5 1.5-1.5zm1.72 4.8L18 6.97v9L13.12 18 7 15.97l-5 2v-9l5-2 4.27 1.41 1.73 7.3z'; + path = + 'M13 13.14l1.17-5.94c.79-.43 1.33-1.25 1.33-2.2 0-1.38-1.12-2.5-2.5-2.5S10.5 3.62 10.5 5c0 .95.54 1.77 1.33 2.2zm0-9.64c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5-1.5-.67-1.5-1.5.67-1.5 1.5-1.5zm1.72 4.8L18 6.97v9L13.12 18 7 15.97l-5 2v-9l5-2 4.27 1.41 1.73 7.3z'; break; case 'location': - path = 'M10 2C6.69 2 4 4.69 4 8c0 2.02 1.17 3.71 2.53 4.89.43.37 1.18.96 1.85 1.83.74.97 1.41 2.01 1.62 2.71.21-.7.88-1.74 1.62-2.71.67-.87 1.42-1.46 1.85-1.83C14.83 11.71 16 10.02 16 8c0-3.31-2.69-6-6-6zm0 2.56c1.9 0 3.44 1.54 3.44 3.44S11.9 11.44 10 11.44 6.56 9.9 6.56 8 8.1 4.56 10 4.56z'; + path = + 'M10 2C6.69 2 4 4.69 4 8c0 2.02 1.17 3.71 2.53 4.89.43.37 1.18.96 1.85 1.83.74.97 1.41 2.01 1.62 2.71.21-.7.88-1.74 1.62-2.71.67-.87 1.42-1.46 1.85-1.83C14.83 11.71 16 10.02 16 8c0-3.31-2.69-6-6-6zm0 2.56c1.9 0 3.44 1.54 3.44 3.44S11.9 11.44 10 11.44 6.56 9.9 6.56 8 8.1 4.56 10 4.56z'; break; case 'lock': - path = 'M14 9h1c.55 0 1 .45 1 1v7c0 .55-.45 1-1 1H5c-.55 0-1-.45-1-1v-7c0-.55.45-1 1-1h1V6c0-2.21 1.79-4 4-4s4 1.79 4 4v3zm-2 0V6c0-1.1-.9-2-2-2s-2 .9-2 2v3h4zm-1 7l-.36-2.15c.51-.24.86-.75.86-1.35 0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5c0 .6.35 1.11.86 1.35L9 16h2z'; + path = + 'M14 9h1c.55 0 1 .45 1 1v7c0 .55-.45 1-1 1H5c-.55 0-1-.45-1-1v-7c0-.55.45-1 1-1h1V6c0-2.21 1.79-4 4-4s4 1.79 4 4v3zm-2 0V6c0-1.1-.9-2-2-2s-2 .9-2 2v3h4zm-1 7l-.36-2.15c.51-.24.86-.75.86-1.35 0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5c0 .6.35 1.11.86 1.35L9 16h2z'; break; case 'marker': - path = 'M10 2c4.42 0 8 3.58 8 8s-3.58 8-8 8-8-3.58-8-8 3.58-8 8-8zm0 13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5z'; + path = + 'M10 2c4.42 0 8 3.58 8 8s-3.58 8-8 8-8-3.58-8-8 3.58-8 8-8zm0 13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5z'; break; case 'media-archive': - path = 'M12 2l4 4v12H4V2h8zm0 4h3l-3-3v3zM8 3.5v2l1.8-1zM11 5L9.2 6 11 7V5zM8 6.5v2l1.8-1zM11 8L9.2 9l1.8 1V8zM8 9.5v2l1.8-1zm3 1.5l-1.8 1 1.8 1v-2zm-1.5 6c.83 0 1.62-.72 1.5-1.63-.05-.38-.49-1.61-.49-1.61l-1.99-1.1s-.45 1.95-.52 2.71c-.07.77.67 1.63 1.5 1.63zm0-2.39c.42 0 .76.34.76.76 0 .43-.34.77-.76.77s-.76-.34-.76-.77c0-.42.34-.76.76-.76z'; + path = + 'M12 2l4 4v12H4V2h8zm0 4h3l-3-3v3zM8 3.5v2l1.8-1zM11 5L9.2 6 11 7V5zM8 6.5v2l1.8-1zM11 8L9.2 9l1.8 1V8zM8 9.5v2l1.8-1zm3 1.5l-1.8 1 1.8 1v-2zm-1.5 6c.83 0 1.62-.72 1.5-1.63-.05-.38-.49-1.61-.49-1.61l-1.99-1.1s-.45 1.95-.52 2.71c-.07.77.67 1.63 1.5 1.63zm0-2.39c.42 0 .76.34.76.76 0 .43-.34.77-.76.77s-.76-.34-.76-.77c0-.42.34-.76.76-.76z'; break; case 'media-audio': - path = 'M12 2l4 4v12H4V2h8zm0 4h3l-3-3v3zm1 7.26V8.09c0-.11-.04-.21-.12-.29-.07-.08-.16-.11-.27-.1 0 0-3.97.71-4.25.78C8.07 8.54 8 8.8 8 9v3.37c-.2-.09-.42-.07-.6-.07-.38 0-.7.13-.96.39-.26.27-.4.58-.4.96 0 .37.14.69.4.95.26.27.58.4.96.4.34 0 .7-.04.96-.26.26-.23.64-.65.64-1.12V10.3l3-.6V12c-.67-.2-1.17.04-1.44.31-.26.26-.39.58-.39.95 0 .38.13.69.39.96.27.26.71.39 1.08.39.38 0 .7-.13.96-.39.26-.27.4-.58.4-.96z'; + path = + 'M12 2l4 4v12H4V2h8zm0 4h3l-3-3v3zm1 7.26V8.09c0-.11-.04-.21-.12-.29-.07-.08-.16-.11-.27-.1 0 0-3.97.71-4.25.78C8.07 8.54 8 8.8 8 9v3.37c-.2-.09-.42-.07-.6-.07-.38 0-.7.13-.96.39-.26.27-.4.58-.4.96 0 .37.14.69.4.95.26.27.58.4.96.4.34 0 .7-.04.96-.26.26-.23.64-.65.64-1.12V10.3l3-.6V12c-.67-.2-1.17.04-1.44.31-.26.26-.39.58-.39.95 0 .38.13.69.39.96.27.26.71.39 1.08.39.38 0 .7-.13.96-.39.26-.27.4-.58.4-.96z'; break; case 'media-code': - path = 'M12 2l4 4v12H4V2h8zM9 13l-2-2 2-2-1-1-3 3 3 3zm3 1l3-3-3-3-1 1 2 2-2 2z'; + path = + 'M12 2l4 4v12H4V2h8zM9 13l-2-2 2-2-1-1-3 3 3 3zm3 1l3-3-3-3-1 1 2 2-2 2z'; break; case 'media-default': path = 'M12 2l4 4v12H4V2h8zm0 4h3l-3-3v3z'; break; case 'media-document': - path = 'M12 2l4 4v12H4V2h8zM5 3v1h6V3H5zm7 3h3l-3-3v3zM5 5v1h6V5H5zm10 3V7H5v1h10zM5 9v1h4V9H5zm10 3V9h-5v3h5zM5 11v1h4v-1H5zm10 3v-1H5v1h10zm-3 2v-1H5v1h7z'; + path = + 'M12 2l4 4v12H4V2h8zM5 3v1h6V3H5zm7 3h3l-3-3v3zM5 5v1h6V5H5zm10 3V7H5v1h10zM5 9v1h4V9H5zm10 3V9h-5v3h5zM5 11v1h4v-1H5zm10 3v-1H5v1h10zm-3 2v-1H5v1h7z'; break; case 'media-interactive': - path = 'M12 2l4 4v12H4V2h8zm0 4h3l-3-3v3zm2 8V8H6v6h3l-1 2h1l1-2 1 2h1l-1-2h3zm-6-3c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm5-2v2h-3V9h3zm0 3v1H7v-1h6z'; + path = + 'M12 2l4 4v12H4V2h8zm0 4h3l-3-3v3zm2 8V8H6v6h3l-1 2h1l1-2 1 2h1l-1-2h3zm-6-3c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm5-2v2h-3V9h3zm0 3v1H7v-1h6z'; break; case 'media-spreadsheet': - path = 'M12 2l4 4v12H4V2h8zm-1 4V3H5v3h6zM8 8V7H5v1h3zm3 0V7H9v1h2zm4 0V7h-3v1h3zm-7 2V9H5v1h3zm3 0V9H9v1h2zm4 0V9h-3v1h3zm-7 2v-1H5v1h3zm3 0v-1H9v1h2zm4 0v-1h-3v1h3zm-7 2v-1H5v1h3zm3 0v-1H9v1h2zm4 0v-1h-3v1h3zm-7 2v-1H5v1h3zm3 0v-1H9v1h2z'; + path = + 'M12 2l4 4v12H4V2h8zm-1 4V3H5v3h6zM8 8V7H5v1h3zm3 0V7H9v1h2zm4 0V7h-3v1h3zm-7 2V9H5v1h3zm3 0V9H9v1h2zm4 0V9h-3v1h3zm-7 2v-1H5v1h3zm3 0v-1H9v1h2zm4 0v-1h-3v1h3zm-7 2v-1H5v1h3zm3 0v-1H9v1h2zm4 0v-1h-3v1h3zm-7 2v-1H5v1h3zm3 0v-1H9v1h2z'; break; case 'media-text': - path = 'M12 2l4 4v12H4V2h8zM5 3v1h6V3H5zm7 3h3l-3-3v3zM5 5v1h6V5H5zm10 3V7H5v1h10zm0 2V9H5v1h10zm0 2v-1H5v1h10zm-4 2v-1H5v1h6z'; + path = + 'M12 2l4 4v12H4V2h8zM5 3v1h6V3H5zm7 3h3l-3-3v3zM5 5v1h6V5H5zm10 3V7H5v1h10zm0 2V9H5v1h10zm0 2v-1H5v1h10zm-4 2v-1H5v1h6z'; break; case 'media-video': - path = 'M12 2l4 4v12H4V2h8zm0 4h3l-3-3v3zm-1 8v-3c0-.27-.1-.51-.29-.71-.2-.19-.44-.29-.71-.29H7c-.27 0-.51.1-.71.29-.19.2-.29.44-.29.71v3c0 .27.1.51.29.71.2.19.44.29.71.29h3c.27 0 .51-.1.71-.29.19-.2.29-.44.29-.71zm3 1v-5l-2 2v1z'; + path = + 'M12 2l4 4v12H4V2h8zm0 4h3l-3-3v3zm-1 8v-3c0-.27-.1-.51-.29-.71-.2-.19-.44-.29-.71-.29H7c-.27 0-.51.1-.71.29-.19.2-.29.44-.29.71v3c0 .27.1.51.29.71.2.19.44.29.71.29h3c.27 0 .51-.1.71-.29.19-.2.29-.44.29-.71zm3 1v-5l-2 2v1z'; break; case 'megaphone': - path = 'M18.15 5.94c.46 1.62.38 3.22-.02 4.48-.42 1.28-1.26 2.18-2.3 2.48-.16.06-.26.06-.4.06-.06.02-.12.02-.18.02-.06.02-.14.02-.22.02h-6.8l2.22 5.5c.02.14-.06.26-.14.34-.08.1-.24.16-.34.16H6.95c-.1 0-.26-.06-.34-.16-.08-.08-.16-.2-.14-.34l-1-5.5H4.25l-.02-.02c-.5.06-1.08-.18-1.54-.62s-.88-1.08-1.06-1.88c-.24-.8-.2-1.56-.02-2.2.18-.62.58-1.08 1.06-1.3l.02-.02 9-5.4c.1-.06.18-.1.24-.16.06-.04.14-.08.24-.12.16-.08.28-.12.5-.18 1.04-.3 2.24.1 3.22.98s1.84 2.24 2.26 3.86zm-2.58 5.98h-.02c.4-.1.74-.34 1.04-.7.58-.7.86-1.76.86-3.04 0-.64-.1-1.3-.28-1.98-.34-1.36-1.02-2.5-1.78-3.24s-1.68-1.1-2.46-.88c-.82.22-1.4.96-1.7 2-.32 1.04-.28 2.36.06 3.72.38 1.36 1 2.5 1.8 3.24.78.74 1.62 1.1 2.48.88zm-2.54-7.08c.22-.04.42-.02.62.04.38.16.76.48 1.02 1s.42 1.2.42 1.78c0 .3-.04.56-.12.8-.18.48-.44.84-.86.94-.34.1-.8-.06-1.14-.4s-.64-.86-.78-1.5c-.18-.62-.12-1.24.02-1.72s.48-.84.82-.94z'; + path = + 'M18.15 5.94c.46 1.62.38 3.22-.02 4.48-.42 1.28-1.26 2.18-2.3 2.48-.16.06-.26.06-.4.06-.06.02-.12.02-.18.02-.06.02-.14.02-.22.02h-6.8l2.22 5.5c.02.14-.06.26-.14.34-.08.1-.24.16-.34.16H6.95c-.1 0-.26-.06-.34-.16-.08-.08-.16-.2-.14-.34l-1-5.5H4.25l-.02-.02c-.5.06-1.08-.18-1.54-.62s-.88-1.08-1.06-1.88c-.24-.8-.2-1.56-.02-2.2.18-.62.58-1.08 1.06-1.3l.02-.02 9-5.4c.1-.06.18-.1.24-.16.06-.04.14-.08.24-.12.16-.08.28-.12.5-.18 1.04-.3 2.24.1 3.22.98s1.84 2.24 2.26 3.86zm-2.58 5.98h-.02c.4-.1.74-.34 1.04-.7.58-.7.86-1.76.86-3.04 0-.64-.1-1.3-.28-1.98-.34-1.36-1.02-2.5-1.78-3.24s-1.68-1.1-2.46-.88c-.82.22-1.4.96-1.7 2-.32 1.04-.28 2.36.06 3.72.38 1.36 1 2.5 1.8 3.24.78.74 1.62 1.1 2.48.88zm-2.54-7.08c.22-.04.42-.02.62.04.38.16.76.48 1.02 1s.42 1.2.42 1.78c0 .3-.04.56-.12.8-.18.48-.44.84-.86.94-.34.1-.8-.06-1.14-.4s-.64-.86-.78-1.5c-.18-.62-.12-1.24.02-1.72s.48-.84.82-.94z'; break; case 'menu-alt': path = 'M3 4h14v2H3V4zm0 5h14v2H3V9zm0 5h14v2H3v-2z'; @@ -621,7 +784,8 @@ export default class Dashicon extends Component { path = 'M17 7V5H3v2h14zm0 4V9H3v2h14zm0 4v-2H3v2h14z'; break; case 'microphone': - path = 'M12 9V3c0-1.1-.89-2-2-2-1.12 0-2 .94-2 2v6c0 1.1.9 2 2 2 1.13 0 2-.94 2-2zm4 0c0 2.97-2.16 5.43-5 5.91V17h2c.56 0 1 .45 1 1s-.44 1-1 1H7c-.55 0-1-.45-1-1s.45-1 1-1h2v-2.09C6.17 14.43 4 11.97 4 9c0-.55.45-1 1-1 .56 0 1 .45 1 1 0 2.21 1.8 4 4 4 2.21 0 4-1.79 4-4 0-.55.45-1 1-1 .56 0 1 .45 1 1z'; + path = + 'M12 9V3c0-1.1-.89-2-2-2-1.12 0-2 .94-2 2v6c0 1.1.9 2 2 2 1.13 0 2-.94 2-2zm4 0c0 2.97-2.16 5.43-5 5.91V17h2c.56 0 1 .45 1 1s-.44 1-1 1H7c-.55 0-1-.45-1-1s.45-1 1-1h2v-2.09C6.17 14.43 4 11.97 4 9c0-.55.45-1 1-1 .56 0 1 .45 1 1 0 2.21 1.8 4 4 4 2.21 0 4-1.79 4-4 0-.55.45-1 1-1 .56 0 1 .45 1 1z'; break; case 'migrate': path = 'M4 6h6V4H2v12.01h8V14H4V6zm2 2h6V5l6 5-6 5v-3H6V8z'; @@ -630,43 +794,56 @@ export default class Dashicon extends Component { path = 'M4 9h12v2H4V9z'; break; case 'money': - path = 'M0 3h20v12h-.75c0-1.79-1.46-3.25-3.25-3.25-1.31 0-2.42.79-2.94 1.91-.25-.1-.52-.16-.81-.16-.98 0-1.8.63-2.11 1.5H0V3zm8.37 3.11c-.06.15-.1.31-.11.47s-.01.33.01.5l.02.08c.01.06.02.14.05.23.02.1.06.2.1.31.03.11.09.22.15.33.07.12.15.22.23.31s.18.17.31.23c.12.06.25.09.4.09.14 0 .27-.03.39-.09s.22-.14.3-.22c.09-.09.16-.2.22-.32.07-.12.12-.23.16-.33s.07-.2.09-.31c.03-.11.04-.18.05-.22s.01-.07.01-.09c.05-.29.03-.56-.04-.82s-.21-.48-.41-.66c-.21-.18-.47-.27-.79-.27-.19 0-.36.03-.52.1-.15.07-.28.16-.38.28-.09.11-.17.25-.24.4zm4.48 6.04v-1.14c0-.33-.1-.66-.29-.98s-.45-.59-.77-.79c-.32-.21-.66-.31-1.02-.31l-1.24.84-1.28-.82c-.37 0-.72.1-1.04.3-.31.2-.56.46-.74.77-.18.32-.27.65-.27.99v1.14l.18.05c.12.04.29.08.51.14.23.05.47.1.74.15.26.05.57.09.91.13.34.03.67.05.99.05.3 0 .63-.02.98-.05.34-.04.64-.08.89-.13.25-.04.5-.1.76-.16l.5-.12c.08-.02.14-.04.19-.06zm3.15.1c1.52 0 2.75 1.23 2.75 2.75s-1.23 2.75-2.75 2.75c-.73 0-1.38-.3-1.87-.77.23-.35.37-.78.37-1.23 0-.77-.39-1.46-.99-1.86.43-.96 1.37-1.64 2.49-1.64zm-5.5 3.5c0-.96.79-1.75 1.75-1.75s1.75.79 1.75 1.75-.79 1.75-1.75 1.75-1.75-.79-1.75-1.75z'; + path = + 'M0 3h20v12h-.75c0-1.79-1.46-3.25-3.25-3.25-1.31 0-2.42.79-2.94 1.91-.25-.1-.52-.16-.81-.16-.98 0-1.8.63-2.11 1.5H0V3zm8.37 3.11c-.06.15-.1.31-.11.47s-.01.33.01.5l.02.08c.01.06.02.14.05.23.02.1.06.2.1.31.03.11.09.22.15.33.07.12.15.22.23.31s.18.17.31.23c.12.06.25.09.4.09.14 0 .27-.03.39-.09s.22-.14.3-.22c.09-.09.16-.2.22-.32.07-.12.12-.23.16-.33s.07-.2.09-.31c.03-.11.04-.18.05-.22s.01-.07.01-.09c.05-.29.03-.56-.04-.82s-.21-.48-.41-.66c-.21-.18-.47-.27-.79-.27-.19 0-.36.03-.52.1-.15.07-.28.16-.38.28-.09.11-.17.25-.24.4zm4.48 6.04v-1.14c0-.33-.1-.66-.29-.98s-.45-.59-.77-.79c-.32-.21-.66-.31-1.02-.31l-1.24.84-1.28-.82c-.37 0-.72.1-1.04.3-.31.2-.56.46-.74.77-.18.32-.27.65-.27.99v1.14l.18.05c.12.04.29.08.51.14.23.05.47.1.74.15.26.05.57.09.91.13.34.03.67.05.99.05.3 0 .63-.02.98-.05.34-.04.64-.08.89-.13.25-.04.5-.1.76-.16l.5-.12c.08-.02.14-.04.19-.06zm3.15.1c1.52 0 2.75 1.23 2.75 2.75s-1.23 2.75-2.75 2.75c-.73 0-1.38-.3-1.87-.77.23-.35.37-.78.37-1.23 0-.77-.39-1.46-.99-1.86.43-.96 1.37-1.64 2.49-1.64zm-5.5 3.5c0-.96.79-1.75 1.75-1.75s1.75.79 1.75 1.75-.79 1.75-1.75 1.75-1.75-.79-1.75-1.75z'; break; case 'move': - path = 'M19 10l-4 4v-3h-4v4h3l-4 4-4-4h3v-4H5v3l-4-4 4-4v3h4V5H6l4-4 4 4h-3v4h4V6z'; + path = + 'M19 10l-4 4v-3h-4v4h3l-4 4-4-4h3v-4H5v3l-4-4 4-4v3h4V5H6l4-4 4 4h-3v4h4V6z'; break; case 'nametag': - path = 'M12 5V2c0-.55-.45-1-1-1H9c-.55 0-1 .45-1 1v3c0 .55.45 1 1 1h2c.55 0 1-.45 1-1zm-2-3c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm8 13V7c0-1.1-.9-2-2-2h-3v.33C13 6.25 12.25 7 11.33 7H8.67C7.75 7 7 6.25 7 5.33V5H4c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2zm-1-6v6H3V9h14zm-8 2c0-.55-.22-1-.5-1s-.5.45-.5 1 .22 1 .5 1 .5-.45.5-1zm3 0c0-.55-.22-1-.5-1s-.5.45-.5 1 .22 1 .5 1 .5-.45.5-1zm-5.96 1.21c.92.48 2.34.79 3.96.79s3.04-.31 3.96-.79c-.21 1-1.89 1.79-3.96 1.79s-3.75-.79-3.96-1.79z'; + path = + 'M12 5V2c0-.55-.45-1-1-1H9c-.55 0-1 .45-1 1v3c0 .55.45 1 1 1h2c.55 0 1-.45 1-1zm-2-3c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm8 13V7c0-1.1-.9-2-2-2h-3v.33C13 6.25 12.25 7 11.33 7H8.67C7.75 7 7 6.25 7 5.33V5H4c-1.1 0-2 .9-2 2v8c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2zm-1-6v6H3V9h14zm-8 2c0-.55-.22-1-.5-1s-.5.45-.5 1 .22 1 .5 1 .5-.45.5-1zm3 0c0-.55-.22-1-.5-1s-.5.45-.5 1 .22 1 .5 1 .5-.45.5-1zm-5.96 1.21c.92.48 2.34.79 3.96.79s3.04-.31 3.96-.79c-.21 1-1.89 1.79-3.96 1.79s-3.75-.79-3.96-1.79z'; break; case 'networking': - path = 'M18 13h1c.55 0 1 .45 1 1.01v2.98c0 .56-.45 1.01-1 1.01h-4c-.55 0-1-.45-1-1.01v-2.98c0-.56.45-1.01 1-1.01h1v-2h-5v2h1c.55 0 1 .45 1 1.01v2.98c0 .56-.45 1.01-1 1.01H8c-.55 0-1-.45-1-1.01v-2.98c0-.56.45-1.01 1-1.01h1v-2H4v2h1c.55 0 1 .45 1 1.01v2.98C6 17.55 5.55 18 5 18H1c-.55 0-1-.45-1-1.01v-2.98C0 13.45.45 13 1 13h1v-2c0-1.1.9-2 2-2h5V7H8c-.55 0-1-.45-1-1.01V3.01C7 2.45 7.45 2 8 2h4c.55 0 1 .45 1 1.01v2.98C13 6.55 12.55 7 12 7h-1v2h5c1.1 0 2 .9 2 2v2z'; + path = + 'M18 13h1c.55 0 1 .45 1 1.01v2.98c0 .56-.45 1.01-1 1.01h-4c-.55 0-1-.45-1-1.01v-2.98c0-.56.45-1.01 1-1.01h1v-2h-5v2h1c.55 0 1 .45 1 1.01v2.98c0 .56-.45 1.01-1 1.01H8c-.55 0-1-.45-1-1.01v-2.98c0-.56.45-1.01 1-1.01h1v-2H4v2h1c.55 0 1 .45 1 1.01v2.98C6 17.55 5.55 18 5 18H1c-.55 0-1-.45-1-1.01v-2.98C0 13.45.45 13 1 13h1v-2c0-1.1.9-2 2-2h5V7H8c-.55 0-1-.45-1-1.01V3.01C7 2.45 7.45 2 8 2h4c.55 0 1 .45 1 1.01v2.98C13 6.55 12.55 7 12 7h-1v2h5c1.1 0 2 .9 2 2v2z'; break; case 'no-alt': - path = 'M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z'; + path = + 'M14.95 6.46L11.41 10l3.54 3.54-1.41 1.41L10 11.42l-3.53 3.53-1.42-1.42L8.58 10 5.05 6.47l1.42-1.42L10 8.58l3.54-3.53z'; break; case 'no': - path = 'M12.12 10l3.53 3.53-2.12 2.12L10 12.12l-3.54 3.54-2.12-2.12L7.88 10 4.34 6.46l2.12-2.12L10 7.88l3.54-3.53 2.12 2.12z'; + path = + 'M12.12 10l3.53 3.53-2.12 2.12L10 12.12l-3.54 3.54-2.12-2.12L7.88 10 4.34 6.46l2.12-2.12L10 7.88l3.54-3.53 2.12 2.12z'; break; case 'palmtree': - path = 'M8.58 2.39c.32 0 .59.05.81.14 1.25.55 1.69 2.24 1.7 3.97.59-.82 2.15-2.29 3.41-2.29s2.94.73 3.53 3.55c-1.13-.65-2.42-.94-3.65-.94-1.26 0-2.45.32-3.29.89.4-.11.86-.16 1.33-.16 1.39 0 2.9.45 3.4 1.31.68 1.16.47 3.38-.76 4.14-.14-2.1-1.69-4.12-3.47-4.12-.44 0-.88.12-1.33.38C8 10.62 7 14.56 7 19H2c0-5.53 4.21-9.65 7.68-10.79-.56-.09-1.17-.15-1.82-.15C6.1 8.06 4.05 8.5 2 10c.76-2.96 2.78-4.1 4.69-4.1 1.25 0 2.45.5 3.2 1.29-.66-2.24-2.49-2.86-4.08-2.86-.8 0-1.55.16-2.05.35.91-1.29 3.31-2.29 4.82-2.29zM13 11.5c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5.67 1.5 1.5 1.5 1.5-.67 1.5-1.5z'; + path = + 'M8.58 2.39c.32 0 .59.05.81.14 1.25.55 1.69 2.24 1.7 3.97.59-.82 2.15-2.29 3.41-2.29s2.94.73 3.53 3.55c-1.13-.65-2.42-.94-3.65-.94-1.26 0-2.45.32-3.29.89.4-.11.86-.16 1.33-.16 1.39 0 2.9.45 3.4 1.31.68 1.16.47 3.38-.76 4.14-.14-2.1-1.69-4.12-3.47-4.12-.44 0-.88.12-1.33.38C8 10.62 7 14.56 7 19H2c0-5.53 4.21-9.65 7.68-10.79-.56-.09-1.17-.15-1.82-.15C6.1 8.06 4.05 8.5 2 10c.76-2.96 2.78-4.1 4.69-4.1 1.25 0 2.45.5 3.2 1.29-.66-2.24-2.49-2.86-4.08-2.86-.8 0-1.55.16-2.05.35.91-1.29 3.31-2.29 4.82-2.29zM13 11.5c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5.67 1.5 1.5 1.5 1.5-.67 1.5-1.5z'; break; case 'paperclip': - path = 'M17.05 2.7c1.93 1.94 1.93 5.13 0 7.07L10 16.84c-1.88 1.89-4.91 1.93-6.86.15-.06-.05-.13-.09-.19-.15-1.93-1.94-1.93-5.12 0-7.07l4.94-4.95c.91-.92 2.28-1.1 3.39-.58.3.15.59.33.83.58 1.17 1.17 1.17 3.07 0 4.24l-4.93 4.95c-.39.39-1.02.39-1.41 0s-.39-1.02 0-1.41l4.93-4.95c.39-.39.39-1.02 0-1.41-.38-.39-1.02-.39-1.4 0l-4.94 4.95c-.91.92-1.1 2.29-.57 3.4.14.3.32.59.57.84s.54.43.84.57c1.11.53 2.47.35 3.39-.57l7.05-7.07c1.16-1.17 1.16-3.08 0-4.25-.56-.55-1.28-.83-2-.86-.08.01-.16.01-.24 0-.22-.03-.43-.11-.6-.27-.39-.4-.38-1.05.02-1.45.16-.16.36-.24.56-.28.14-.02.27-.01.4.02 1.19.06 2.36.52 3.27 1.43z'; + path = + 'M17.05 2.7c1.93 1.94 1.93 5.13 0 7.07L10 16.84c-1.88 1.89-4.91 1.93-6.86.15-.06-.05-.13-.09-.19-.15-1.93-1.94-1.93-5.12 0-7.07l4.94-4.95c.91-.92 2.28-1.1 3.39-.58.3.15.59.33.83.58 1.17 1.17 1.17 3.07 0 4.24l-4.93 4.95c-.39.39-1.02.39-1.41 0s-.39-1.02 0-1.41l4.93-4.95c.39-.39.39-1.02 0-1.41-.38-.39-1.02-.39-1.4 0l-4.94 4.95c-.91.92-1.1 2.29-.57 3.4.14.3.32.59.57.84s.54.43.84.57c1.11.53 2.47.35 3.39-.57l7.05-7.07c1.16-1.17 1.16-3.08 0-4.25-.56-.55-1.28-.83-2-.86-.08.01-.16.01-.24 0-.22-.03-.43-.11-.6-.27-.39-.4-.38-1.05.02-1.45.16-.16.36-.24.56-.28.14-.02.27-.01.4.02 1.19.06 2.36.52 3.27 1.43z'; break; case 'performance': - path = 'M3.76 17.01h12.48C17.34 15.63 18 13.9 18 12c0-4.41-3.58-8-8-8s-8 3.59-8 8c0 1.9.66 3.63 1.76 5.01zM9 6c0-.55.45-1 1-1s1 .45 1 1c0 .56-.45 1-1 1s-1-.44-1-1zM4 8c0-.55.45-1 1-1s1 .45 1 1c0 .56-.45 1-1 1s-1-.44-1-1zm4.52 3.4c.84-.83 6.51-3.5 6.51-3.5s-2.66 5.68-3.49 6.51c-.84.84-2.18.84-3.02 0-.83-.83-.83-2.18 0-3.01zM3 13c0-.55.45-1 1-1s1 .45 1 1c0 .56-.45 1-1 1s-1-.44-1-1zm6 0c0-.55.45-1 1-1s1 .45 1 1c0 .56-.45 1-1 1s-1-.44-1-1zm6 0c0-.55.45-1 1-1s1 .45 1 1c0 .56-.45 1-1 1s-1-.44-1-1z'; + path = + 'M3.76 17.01h12.48C17.34 15.63 18 13.9 18 12c0-4.41-3.58-8-8-8s-8 3.59-8 8c0 1.9.66 3.63 1.76 5.01zM9 6c0-.55.45-1 1-1s1 .45 1 1c0 .56-.45 1-1 1s-1-.44-1-1zM4 8c0-.55.45-1 1-1s1 .45 1 1c0 .56-.45 1-1 1s-1-.44-1-1zm4.52 3.4c.84-.83 6.51-3.5 6.51-3.5s-2.66 5.68-3.49 6.51c-.84.84-2.18.84-3.02 0-.83-.83-.83-2.18 0-3.01zM3 13c0-.55.45-1 1-1s1 .45 1 1c0 .56-.45 1-1 1s-1-.44-1-1zm6 0c0-.55.45-1 1-1s1 .45 1 1c0 .56-.45 1-1 1s-1-.44-1-1zm6 0c0-.55.45-1 1-1s1 .45 1 1c0 .56-.45 1-1 1s-1-.44-1-1z'; break; case 'phone': - path = 'M12.06 6l-.21-.2c-.52-.54-.43-.79.08-1.3l2.72-2.75c.81-.82.96-1.21 1.73-.48l.21.2zm.53.45l4.4-4.4c.7.94 2.34 3.47 1.53 5.34-.73 1.67-1.09 1.75-2 3-1.85 2.11-4.18 4.37-6 6.07-1.26.91-1.31 1.33-3 2-1.8.71-4.4-.89-5.38-1.56l4.4-4.4 1.18 1.62c.34.46 1.2-.06 1.8-.66 1.04-1.05 3.18-3.18 4-4.07.59-.59 1.12-1.45.66-1.8zM1.57 16.5l-.21-.21c-.68-.74-.29-.9.52-1.7l2.74-2.72c.51-.49.75-.6 1.27-.11l.2.21z'; + path = + 'M12.06 6l-.21-.2c-.52-.54-.43-.79.08-1.3l2.72-2.75c.81-.82.96-1.21 1.73-.48l.21.2zm.53.45l4.4-4.4c.7.94 2.34 3.47 1.53 5.34-.73 1.67-1.09 1.75-2 3-1.85 2.11-4.18 4.37-6 6.07-1.26.91-1.31 1.33-3 2-1.8.71-4.4-.89-5.38-1.56l4.4-4.4 1.18 1.62c.34.46 1.2-.06 1.8-.66 1.04-1.05 3.18-3.18 4-4.07.59-.59 1.12-1.45.66-1.8zM1.57 16.5l-.21-.21c-.68-.74-.29-.9.52-1.7l2.74-2.72c.51-.49.75-.6 1.27-.11l.2.21z'; break; case 'playlist-audio': - path = 'M17 3V1H2v2h15zm0 4V5H2v2h15zm-7 4V9H2v2h8zm7.45-1.96l-6 1.12c-.16.02-.19.03-.29.13-.11.09-.16.22-.16.37v4.59c-.29-.13-.66-.14-.93-.14-.54 0-1 .19-1.38.57s-.56.84-.56 1.38c0 .53.18.99.56 1.37s.84.57 1.38.57c.49 0 .92-.16 1.29-.48s.59-.71.65-1.19v-4.95L17 11.27v3.48c-.29-.13-.56-.19-.83-.19-.54 0-1.11.19-1.49.57-.38.37-.57.83-.57 1.37s.19.99.57 1.37.84.57 1.38.57c.53 0 .99-.19 1.37-.57s.57-.83.57-1.37V9.6c0-.16-.05-.3-.16-.41-.11-.12-.24-.17-.39-.15zM8 15v-2H2v2h6zm-2 4v-2H2v2h4z'; + path = + 'M17 3V1H2v2h15zm0 4V5H2v2h15zm-7 4V9H2v2h8zm7.45-1.96l-6 1.12c-.16.02-.19.03-.29.13-.11.09-.16.22-.16.37v4.59c-.29-.13-.66-.14-.93-.14-.54 0-1 .19-1.38.57s-.56.84-.56 1.38c0 .53.18.99.56 1.37s.84.57 1.38.57c.49 0 .92-.16 1.29-.48s.59-.71.65-1.19v-4.95L17 11.27v3.48c-.29-.13-.56-.19-.83-.19-.54 0-1.11.19-1.49.57-.38.37-.57.83-.57 1.37s.19.99.57 1.37.84.57 1.38.57c.53 0 .99-.19 1.37-.57s.57-.83.57-1.37V9.6c0-.16-.05-.3-.16-.41-.11-.12-.24-.17-.39-.15zM8 15v-2H2v2h6zm-2 4v-2H2v2h4z'; break; case 'playlist-video': - path = 'M17 3V1H2v2h15zm0 4V5H2v2h15zM6 11V9H2v2h4zm2-2h9c.55 0 1 .45 1 1v8c0 .55-.45 1-1 1H8c-.55 0-1-.45-1-1v-8c0-.55.45-1 1-1zm3 7l3.33-2L11 12v4zm-5-1v-2H2v2h4zm0 4v-2H2v2h4z'; + path = + 'M17 3V1H2v2h15zm0 4V5H2v2h15zM6 11V9H2v2h4zm2-2h9c.55 0 1 .45 1 1v8c0 .55-.45 1-1 1H8c-.55 0-1-.45-1-1v-8c0-.55.45-1 1-1zm3 7l3.33-2L11 12v4zm-5-1v-2H2v2h4zm0 4v-2H2v2h4z'; break; case 'plus-alt': - path = 'M15.8 4.2c3.2 3.21 3.2 8.39 0 11.6-3.21 3.2-8.39 3.2-11.6 0C1 12.59 1 7.41 4.2 4.2 7.41 1 12.59 1 15.8 4.2zm-4.3 11.3v-4h4v-3h-4v-4h-3v4h-4v3h4v4h3z'; + path = + 'M15.8 4.2c3.2 3.21 3.2 8.39 0 11.6-3.21 3.2-8.39 3.2-11.6 0C1 12.59 1 7.41 4.2 4.2 7.41 1 12.59 1 15.8 4.2zm-4.3 11.3v-4h4v-3h-4v-4h-3v4h-4v3h4v4h3z'; break; case 'plus-light': path = 'M17 9v2h-6v6H9v-6H3V9h6V3h2v6h6z'; @@ -675,211 +852,275 @@ export default class Dashicon extends Component { path = 'M17 7v3h-5v5H9v-5H4V7h5V2h3v5h5z'; break; case 'portfolio': - path = 'M4 5H.78c-.37 0-.74.32-.69.84l1.56 9.99S3.5 8.47 3.86 6.7c.11-.53.61-.7.98-.7H10s-.7-2.08-.77-2.31C9.11 3.25 8.89 3 8.45 3H5.14c-.36 0-.7.23-.8.64C4.25 4.04 4 5 4 5zm4.88 0h-4s.42-1 .87-1h2.13c.48 0 1 1 1 1zM2.67 16.25c-.31.47-.76.75-1.26.75h15.73c.54 0 .92-.31 1.03-.83.44-2.19 1.68-8.44 1.68-8.44.07-.5-.3-.73-.62-.73H16V5.53c0-.16-.26-.53-.66-.53h-3.76c-.52 0-.87.58-.87.58L10 7H5.59c-.32 0-.63.19-.69.5 0 0-1.59 6.7-1.72 7.33-.07.37-.22.99-.51 1.42zM15.38 7H11s.58-1 1.13-1h2.29c.71 0 .96 1 .96 1z'; + path = + 'M4 5H.78c-.37 0-.74.32-.69.84l1.56 9.99S3.5 8.47 3.86 6.7c.11-.53.61-.7.98-.7H10s-.7-2.08-.77-2.31C9.11 3.25 8.89 3 8.45 3H5.14c-.36 0-.7.23-.8.64C4.25 4.04 4 5 4 5zm4.88 0h-4s.42-1 .87-1h2.13c.48 0 1 1 1 1zM2.67 16.25c-.31.47-.76.75-1.26.75h15.73c.54 0 .92-.31 1.03-.83.44-2.19 1.68-8.44 1.68-8.44.07-.5-.3-.73-.62-.73H16V5.53c0-.16-.26-.53-.66-.53h-3.76c-.52 0-.87.58-.87.58L10 7H5.59c-.32 0-.63.19-.69.5 0 0-1.59 6.7-1.72 7.33-.07.37-.22.99-.51 1.42zM15.38 7H11s.58-1 1.13-1h2.29c.71 0 .96 1 .96 1z'; break; case 'post-status': - path = 'M14 6c0 1.86-1.28 3.41-3 3.86V16c0 1-2 2-2 2V9.86c-1.72-.45-3-2-3-3.86 0-2.21 1.79-4 4-4s4 1.79 4 4zM8 5c0 .55.45 1 1 1s1-.45 1-1-.45-1-1-1-1 .45-1 1z'; + path = + 'M14 6c0 1.86-1.28 3.41-3 3.86V16c0 1-2 2-2 2V9.86c-1.72-.45-3-2-3-3.86 0-2.21 1.79-4 4-4s4 1.79 4 4zM8 5c0 .55.45 1 1 1s1-.45 1-1-.45-1-1-1-1 .45-1 1z'; break; case 'pressthis': - path = 'M14.76 1C16.55 1 18 2.46 18 4.25c0 1.78-1.45 3.24-3.24 3.24-.23 0-.47-.03-.7-.08L13 8.47V19H2V4h9.54c.13-2 1.52-3 3.22-3zm0 5.49C16 6.49 17 5.48 17 4.25 17 3.01 16 2 14.76 2s-2.24 1.01-2.24 2.25c0 .37.1.72.27 1.03L9.57 8.5c-.28.28-1.77 2.22-1.5 2.49.02.03.06.04.1.04.49 0 2.14-1.28 2.39-1.53l3.24-3.24c.29.14.61.23.96.23z'; + path = + 'M14.76 1C16.55 1 18 2.46 18 4.25c0 1.78-1.45 3.24-3.24 3.24-.23 0-.47-.03-.7-.08L13 8.47V19H2V4h9.54c.13-2 1.52-3 3.22-3zm0 5.49C16 6.49 17 5.48 17 4.25 17 3.01 16 2 14.76 2s-2.24 1.01-2.24 2.25c0 .37.1.72.27 1.03L9.57 8.5c-.28.28-1.77 2.22-1.5 2.49.02.03.06.04.1.04.49 0 2.14-1.28 2.39-1.53l3.24-3.24c.29.14.61.23.96.23z'; break; case 'products': - path = 'M17 8h1v11H2V8h1V6c0-2.76 2.24-5 5-5 .71 0 1.39.15 2 .42.61-.27 1.29-.42 2-.42 2.76 0 5 2.24 5 5v2zM5 6v2h2V6c0-1.13.39-2.16 1.02-3H8C6.35 3 5 4.35 5 6zm10 2V6c0-1.65-1.35-3-3-3h-.02c.63.84 1.02 1.87 1.02 3v2h2zm-5-4.22C9.39 4.33 9 5.12 9 6v2h2V6c0-.88-.39-1.67-1-2.22z'; + path = + 'M17 8h1v11H2V8h1V6c0-2.76 2.24-5 5-5 .71 0 1.39.15 2 .42.61-.27 1.29-.42 2-.42 2.76 0 5 2.24 5 5v2zM5 6v2h2V6c0-1.13.39-2.16 1.02-3H8C6.35 3 5 4.35 5 6zm10 2V6c0-1.65-1.35-3-3-3h-.02c.63.84 1.02 1.87 1.02 3v2h2zm-5-4.22C9.39 4.33 9 5.12 9 6v2h2V6c0-.88-.39-1.67-1-2.22z'; break; case 'randomize': - path = 'M18 6.01L14 9V7h-4l-5 8H2v-2h2l5-8h5V3zM2 5h3l1.15 2.17-1.12 1.8L4 7H2V5zm16 9.01L14 17v-2H9l-1.15-2.17 1.12-1.8L10 13h4v-2z'; + path = + 'M18 6.01L14 9V7h-4l-5 8H2v-2h2l5-8h5V3zM2 5h3l1.15 2.17-1.12 1.8L4 7H2V5zm16 9.01L14 17v-2H9l-1.15-2.17 1.12-1.8L10 13h4v-2z'; break; case 'redo': - path = 'M8 5h5V2l6 4-6 4V7H8c-2.2 0-4 1.8-4 4s1.8 4 4 4h5v2H8c-3.3 0-6-2.7-6-6s2.7-6 6-6z'; + path = + 'M8 5h5V2l6 4-6 4V7H8c-2.2 0-4 1.8-4 4s1.8 4 4 4h5v2H8c-3.3 0-6-2.7-6-6s2.7-6 6-6z'; break; case 'rest-api': path = 'M3 4h2v12H3z'; break; case 'rss': - path = 'M14.92 18H18C18 9.32 10.82 2.25 2 2.25v3.02c7.12 0 12.92 5.71 12.92 12.73zm-5.44 0h3.08C12.56 12.27 7.82 7.6 2 7.6v3.02c2 0 3.87.77 5.29 2.16C8.7 14.17 9.48 16.03 9.48 18zm-5.35-.02c1.17 0 2.13-.93 2.13-2.09 0-1.15-.96-2.09-2.13-2.09-1.18 0-2.13.94-2.13 2.09 0 1.16.95 2.09 2.13 2.09z'; + path = + 'M14.92 18H18C18 9.32 10.82 2.25 2 2.25v3.02c7.12 0 12.92 5.71 12.92 12.73zm-5.44 0h3.08C12.56 12.27 7.82 7.6 2 7.6v3.02c2 0 3.87.77 5.29 2.16C8.7 14.17 9.48 16.03 9.48 18zm-5.35-.02c1.17 0 2.13-.93 2.13-2.09 0-1.15-.96-2.09-2.13-2.09-1.18 0-2.13.94-2.13 2.09 0 1.16.95 2.09 2.13 2.09z'; break; case 'saved': path = 'M15.3 5.3l-6.8 6.8-2.8-2.8-1.4 1.4 4.2 4.2 8.2-8.2'; break; case 'schedule': - path = 'M2 2h16v4H2V2zm0 10V8h4v4H2zm6-2V8h4v2H8zm6 3V8h4v5h-4zm-6 5v-6h4v6H8zm-6 0v-4h4v4H2zm12 0v-3h4v3h-4z'; + path = + 'M2 2h16v4H2V2zm0 10V8h4v4H2zm6-2V8h4v2H8zm6 3V8h4v5h-4zm-6 5v-6h4v6H8zm-6 0v-4h4v4H2zm12 0v-3h4v3h-4z'; break; case 'screenoptions': - path = 'M9 9V3H3v6h6zm8 0V3h-6v6h6zm-8 8v-6H3v6h6zm8 0v-6h-6v6h6z'; + path = + 'M9 9V3H3v6h6zm8 0V3h-6v6h6zm-8 8v-6H3v6h6zm8 0v-6h-6v6h6z'; break; case 'search': - path = 'M12.14 4.18c1.87 1.87 2.11 4.75.72 6.89.12.1.22.21.36.31.2.16.47.36.81.59.34.24.56.39.66.47.42.31.73.57.94.78.32.32.6.65.84 1 .25.35.44.69.59 1.04.14.35.21.68.18 1-.02.32-.14.59-.36.81s-.49.34-.81.36c-.31.02-.65-.04-.99-.19-.35-.14-.7-.34-1.04-.59-.35-.24-.68-.52-1-.84-.21-.21-.47-.52-.77-.93-.1-.13-.25-.35-.47-.66-.22-.32-.4-.57-.56-.78-.16-.2-.29-.35-.44-.5-2.07 1.09-4.69.76-6.44-.98-2.14-2.15-2.14-5.64 0-7.78 2.15-2.15 5.63-2.15 7.78 0zm-1.41 6.36c1.36-1.37 1.36-3.58 0-4.95-1.37-1.37-3.59-1.37-4.95 0-1.37 1.37-1.37 3.58 0 4.95 1.36 1.37 3.58 1.37 4.95 0z'; + path = + 'M12.14 4.18c1.87 1.87 2.11 4.75.72 6.89.12.1.22.21.36.31.2.16.47.36.81.59.34.24.56.39.66.47.42.31.73.57.94.78.32.32.6.65.84 1 .25.35.44.69.59 1.04.14.35.21.68.18 1-.02.32-.14.59-.36.81s-.49.34-.81.36c-.31.02-.65-.04-.99-.19-.35-.14-.7-.34-1.04-.59-.35-.24-.68-.52-1-.84-.21-.21-.47-.52-.77-.93-.1-.13-.25-.35-.47-.66-.22-.32-.4-.57-.56-.78-.16-.2-.29-.35-.44-.5-2.07 1.09-4.69.76-6.44-.98-2.14-2.15-2.14-5.64 0-7.78 2.15-2.15 5.63-2.15 7.78 0zm-1.41 6.36c1.36-1.37 1.36-3.58 0-4.95-1.37-1.37-3.59-1.37-4.95 0-1.37 1.37-1.37 3.58 0 4.95 1.36 1.37 3.58 1.37 4.95 0z'; break; case 'share-alt': - path = 'M16.22 5.8c.47.69.29 1.62-.4 2.08-.69.47-1.62.29-2.08-.4-.16-.24-.35-.46-.55-.67-.21-.2-.43-.39-.67-.55s-.5-.3-.77-.41c-.27-.12-.55-.21-.84-.26-.59-.13-1.23-.13-1.82-.01-.29.06-.57.15-.84.27-.27.11-.53.25-.77.41s-.46.35-.66.55c-.21.21-.4.43-.56.67s-.3.5-.41.76c-.01.02-.01.03-.01.04-.1.24-.17.48-.23.72H1V6h2.66c.04-.07.07-.13.12-.2.27-.4.57-.77.91-1.11s.72-.65 1.11-.91c.4-.27.83-.51 1.28-.7s.93-.34 1.41-.43c.99-.21 2.03-.21 3.02 0 .48.09.96.24 1.41.43s.88.43 1.28.7c.39.26.77.57 1.11.91s.64.71.91 1.11zM12.5 10c0-1.38-1.12-2.5-2.5-2.5S7.5 8.62 7.5 10s1.12 2.5 2.5 2.5 2.5-1.12 2.5-2.5zm-8.72 4.2c-.47-.69-.29-1.62.4-2.09.69-.46 1.62-.28 2.08.41.16.24.35.46.55.67.21.2.43.39.67.55s.5.3.77.41c.27.12.55.2.84.26.59.13 1.23.12 1.82 0 .29-.06.57-.14.84-.26.27-.11.53-.25.77-.41s.46-.35.66-.55c.21-.21.4-.44.56-.67.16-.25.3-.5.41-.76.01-.02.01-.03.01-.04.1-.24.17-.48.23-.72H19v3h-2.66c-.04.06-.07.13-.12.2-.27.4-.57.77-.91 1.11s-.72.65-1.11.91c-.4.27-.83.51-1.28.7s-.93.33-1.41.43c-.99.21-2.03.21-3.02 0-.48-.1-.96-.24-1.41-.43s-.88-.43-1.28-.7c-.39-.26-.77-.57-1.11-.91s-.64-.71-.91-1.11z'; + path = + 'M16.22 5.8c.47.69.29 1.62-.4 2.08-.69.47-1.62.29-2.08-.4-.16-.24-.35-.46-.55-.67-.21-.2-.43-.39-.67-.55s-.5-.3-.77-.41c-.27-.12-.55-.21-.84-.26-.59-.13-1.23-.13-1.82-.01-.29.06-.57.15-.84.27-.27.11-.53.25-.77.41s-.46.35-.66.55c-.21.21-.4.43-.56.67s-.3.5-.41.76c-.01.02-.01.03-.01.04-.1.24-.17.48-.23.72H1V6h2.66c.04-.07.07-.13.12-.2.27-.4.57-.77.91-1.11s.72-.65 1.11-.91c.4-.27.83-.51 1.28-.7s.93-.34 1.41-.43c.99-.21 2.03-.21 3.02 0 .48.09.96.24 1.41.43s.88.43 1.28.7c.39.26.77.57 1.11.91s.64.71.91 1.11zM12.5 10c0-1.38-1.12-2.5-2.5-2.5S7.5 8.62 7.5 10s1.12 2.5 2.5 2.5 2.5-1.12 2.5-2.5zm-8.72 4.2c-.47-.69-.29-1.62.4-2.09.69-.46 1.62-.28 2.08.41.16.24.35.46.55.67.21.2.43.39.67.55s.5.3.77.41c.27.12.55.2.84.26.59.13 1.23.12 1.82 0 .29-.06.57-.14.84-.26.27-.11.53-.25.77-.41s.46-.35.66-.55c.21-.21.4-.44.56-.67.16-.25.3-.5.41-.76.01-.02.01-.03.01-.04.1-.24.17-.48.23-.72H19v3h-2.66c-.04.06-.07.13-.12.2-.27.4-.57.77-.91 1.11s-.72.65-1.11.91c-.4.27-.83.51-1.28.7s-.93.33-1.41.43c-.99.21-2.03.21-3.02 0-.48-.1-.96-.24-1.41-.43s-.88-.43-1.28-.7c-.39-.26-.77-.57-1.11-.91s-.64-.71-.91-1.11z'; break; case 'share-alt2': - path = 'M18 8l-5 4V9.01c-2.58.06-4.88.45-7 2.99.29-3.57 2.66-5.66 7-5.94V3zM4 14h11v-2l2-1.6V16H2V5h9.43c-1.83.32-3.31 1-4.41 2H4v7z'; + path = + 'M18 8l-5 4V9.01c-2.58.06-4.88.45-7 2.99.29-3.57 2.66-5.66 7-5.94V3zM4 14h11v-2l2-1.6V16H2V5h9.43c-1.83.32-3.31 1-4.41 2H4v7z'; break; case 'share': - path = 'M14.5 12c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3c0-.24.03-.46.09-.69l-4.38-2.3c-.55.61-1.33.99-2.21.99-1.66 0-3-1.34-3-3s1.34-3 3-3c.88 0 1.66.39 2.21.99l4.38-2.3c-.06-.23-.09-.45-.09-.69 0-1.66 1.34-3 3-3s3 1.34 3 3-1.34 3-3 3c-.88 0-1.66-.39-2.21-.99l-4.38 2.3c.06.23.09.45.09.69s-.03.46-.09.69l4.38 2.3c.55-.61 1.33-.99 2.21-.99z'; + path = + 'M14.5 12c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3c0-.24.03-.46.09-.69l-4.38-2.3c-.55.61-1.33.99-2.21.99-1.66 0-3-1.34-3-3s1.34-3 3-3c.88 0 1.66.39 2.21.99l4.38-2.3c-.06-.23-.09-.45-.09-.69 0-1.66 1.34-3 3-3s3 1.34 3 3-1.34 3-3 3c-.88 0-1.66-.39-2.21-.99l-4.38 2.3c.06.23.09.45.09.69s-.03.46-.09.69l4.38 2.3c.55-.61 1.33-.99 2.21-.99z'; break; case 'shield-alt': path = 'M10 2s3 2 7 2c0 11-7 14-7 14S3 15 3 4c4 0 7-2 7-2z'; break; case 'shield': - path = 'M10 2s3 2 7 2c0 11-7 14-7 14S3 15 3 4c4 0 7-2 7-2zm0 8h5s1-1 1-5c0 0-5-1-6-2v7H5c1 4 5 7 5 7v-7z'; + path = + 'M10 2s3 2 7 2c0 11-7 14-7 14S3 15 3 4c4 0 7-2 7-2zm0 8h5s1-1 1-5c0 0-5-1-6-2v7H5c1 4 5 7 5 7v-7z'; break; case 'shortcode': - path = 'M6 14H4V6h2V4H2v12h4M7.1 17h2.1l3.7-14h-2.1M14 4v2h2v8h-2v2h4V4'; + path = + 'M6 14H4V6h2V4H2v12h4M7.1 17h2.1l3.7-14h-2.1M14 4v2h2v8h-2v2h4V4'; break; case 'slides': - path = 'M5 14V6h10v8H5zm-3-1V7h2v6H2zm4-6v6h8V7H6zm10 0h2v6h-2V7zm-3 2V8H7v1h6zm0 3v-2H7v2h6z'; + path = + 'M5 14V6h10v8H5zm-3-1V7h2v6H2zm4-6v6h8V7H6zm10 0h2v6h-2V7zm-3 2V8H7v1h6zm0 3v-2H7v2h6z'; break; case 'smartphone': - path = 'M6 2h8c.55 0 1 .45 1 1v14c0 .55-.45 1-1 1H6c-.55 0-1-.45-1-1V3c0-.55.45-1 1-1zm7 12V4H7v10h6zM8 5h4l-4 5V5z'; + path = + 'M6 2h8c.55 0 1 .45 1 1v14c0 .55-.45 1-1 1H6c-.55 0-1-.45-1-1V3c0-.55.45-1 1-1zm7 12V4H7v10h6zM8 5h4l-4 5V5z'; break; case 'smiley': - path = 'M7 5.2c1.1 0 2 .89 2 2 0 .37-.11.71-.28 1C8.72 8.2 8 8 7 8s-1.72.2-1.72.2c-.17-.29-.28-.63-.28-1 0-1.11.9-2 2-2zm6 0c1.11 0 2 .89 2 2 0 .37-.11.71-.28 1 0 0-.72-.2-1.72-.2s-1.72.2-1.72.2c-.17-.29-.28-.63-.28-1 0-1.11.89-2 2-2zm-3 13.7c3.72 0 7.03-2.36 8.23-5.88l-1.32-.46C15.9 15.52 13.12 17.5 10 17.5s-5.9-1.98-6.91-4.94l-1.32.46c1.2 3.52 4.51 5.88 8.23 5.88z'; + path = + 'M7 5.2c1.1 0 2 .89 2 2 0 .37-.11.71-.28 1C8.72 8.2 8 8 7 8s-1.72.2-1.72.2c-.17-.29-.28-.63-.28-1 0-1.11.9-2 2-2zm6 0c1.11 0 2 .89 2 2 0 .37-.11.71-.28 1 0 0-.72-.2-1.72-.2s-1.72.2-1.72.2c-.17-.29-.28-.63-.28-1 0-1.11.89-2 2-2zm-3 13.7c3.72 0 7.03-2.36 8.23-5.88l-1.32-.46C15.9 15.52 13.12 17.5 10 17.5s-5.9-1.98-6.91-4.94l-1.32.46c1.2 3.52 4.51 5.88 8.23 5.88z'; break; case 'sort': path = 'M11 7H1l5 7zm-2 7h10l-5-7z'; break; case 'sos': - path = 'M18 10c0-4.42-3.58-8-8-8s-8 3.58-8 8 3.58 8 8 8 8-3.58 8-8zM7.23 3.57L8.72 7.3c-.62.29-1.13.8-1.42 1.42L3.57 7.23c.71-1.64 2.02-2.95 3.66-3.66zm9.2 3.66L12.7 8.72c-.29-.62-.8-1.13-1.42-1.42l1.49-3.73c1.64.71 2.95 2.02 3.66 3.66zM10 12c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm-6.43.77l3.73-1.49c.29.62.8 1.13 1.42 1.42l-1.49 3.73c-1.64-.71-2.95-2.02-3.66-3.66zm9.2 3.66l-1.49-3.73c.62-.29 1.13-.8 1.42-1.42l3.73 1.49c-.71 1.64-2.02 2.95-3.66 3.66z'; + path = + 'M18 10c0-4.42-3.58-8-8-8s-8 3.58-8 8 3.58 8 8 8 8-3.58 8-8zM7.23 3.57L8.72 7.3c-.62.29-1.13.8-1.42 1.42L3.57 7.23c.71-1.64 2.02-2.95 3.66-3.66zm9.2 3.66L12.7 8.72c-.29-.62-.8-1.13-1.42-1.42l1.49-3.73c1.64.71 2.95 2.02 3.66 3.66zM10 12c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm-6.43.77l3.73-1.49c.29.62.8 1.13 1.42 1.42l-1.49 3.73c-1.64-.71-2.95-2.02-3.66-3.66zm9.2 3.66l-1.49-3.73c.62-.29 1.13-.8 1.42-1.42l3.73 1.49c-.71 1.64-2.02 2.95-3.66 3.66z'; break; case 'star-empty': - path = 'M10 1L7 7l-6 .75 4.13 4.62L4 19l6-3 6 3-1.12-6.63L19 7.75 13 7zm0 2.24l2.34 4.69 4.65.58-3.18 3.56.87 5.15L10 14.88l-4.68 2.34.87-5.15-3.18-3.56 4.65-.58z'; + path = + 'M10 1L7 7l-6 .75 4.13 4.62L4 19l6-3 6 3-1.12-6.63L19 7.75 13 7zm0 2.24l2.34 4.69 4.65.58-3.18 3.56.87 5.15L10 14.88l-4.68 2.34.87-5.15-3.18-3.56 4.65-.58z'; break; case 'star-filled': - path = 'M10 1l3 6 6 .75-4.12 4.62L16 19l-6-3-6 3 1.13-6.63L1 7.75 7 7z'; + path = + 'M10 1l3 6 6 .75-4.12 4.62L16 19l-6-3-6 3 1.13-6.63L1 7.75 7 7z'; break; case 'star-half': - path = 'M10 1L7 7l-6 .75 4.13 4.62L4 19l6-3 6 3-1.12-6.63L19 7.75 13 7zm0 2.24l2.34 4.69 4.65.58-3.18 3.56.87 5.15L10 14.88V3.24z'; + path = + 'M10 1L7 7l-6 .75 4.13 4.62L4 19l6-3 6 3-1.12-6.63L19 7.75 13 7zm0 2.24l2.34 4.69 4.65.58-3.18 3.56.87 5.15L10 14.88V3.24z'; break; case 'sticky': - path = 'M5 3.61V1.04l8.99-.01-.01 2.58c-1.22.26-2.16 1.35-2.16 2.67v.5c.01 1.31.93 2.4 2.17 2.66l-.01 2.58h-3.41l-.01 2.57c0 .6-.47 4.41-1.06 4.41-.6 0-1.08-3.81-1.08-4.41v-2.56L5 12.02l.01-2.58c1.23-.25 2.15-1.35 2.15-2.66v-.5c0-1.31-.92-2.41-2.16-2.67z'; + path = + 'M5 3.61V1.04l8.99-.01-.01 2.58c-1.22.26-2.16 1.35-2.16 2.67v.5c.01 1.31.93 2.4 2.17 2.66l-.01 2.58h-3.41l-.01 2.57c0 .6-.47 4.41-1.06 4.41-.6 0-1.08-3.81-1.08-4.41v-2.56L5 12.02l.01-2.58c1.23-.25 2.15-1.35 2.15-2.66v-.5c0-1.31-.92-2.41-2.16-2.67z'; break; case 'store': - path = 'M1 10c.41.29.96.43 1.5.43.55 0 1.09-.14 1.5-.43.62-.46 1-1.17 1-2 0 .83.37 1.54 1 2 .41.29.96.43 1.5.43.55 0 1.09-.14 1.5-.43.62-.46 1-1.17 1-2 0 .83.37 1.54 1 2 .41.29.96.43 1.51.43.54 0 1.08-.14 1.49-.43.62-.46 1-1.17 1-2 0 .83.37 1.54 1 2 .41.29.96.43 1.5.43.55 0 1.09-.14 1.5-.43.63-.46 1-1.17 1-2V7l-3-7H4L0 7v1c0 .83.37 1.54 1 2zm2 8.99h5v-5h4v5h5v-7c-.37-.05-.72-.22-1-.43-.63-.45-1-.73-1-1.56 0 .83-.38 1.11-1 1.56-.41.3-.95.43-1.49.44-.55 0-1.1-.14-1.51-.44-.63-.45-1-.73-1-1.56 0 .83-.38 1.11-1 1.56-.41.3-.95.43-1.5.44-.54 0-1.09-.14-1.5-.44-.63-.45-1-.73-1-1.57 0 .84-.38 1.12-1 1.57-.29.21-.63.38-1 .44v6.99z'; + path = + 'M1 10c.41.29.96.43 1.5.43.55 0 1.09-.14 1.5-.43.62-.46 1-1.17 1-2 0 .83.37 1.54 1 2 .41.29.96.43 1.5.43.55 0 1.09-.14 1.5-.43.62-.46 1-1.17 1-2 0 .83.37 1.54 1 2 .41.29.96.43 1.51.43.54 0 1.08-.14 1.49-.43.62-.46 1-1.17 1-2 0 .83.37 1.54 1 2 .41.29.96.43 1.5.43.55 0 1.09-.14 1.5-.43.63-.46 1-1.17 1-2V7l-3-7H4L0 7v1c0 .83.37 1.54 1 2zm2 8.99h5v-5h4v5h5v-7c-.37-.05-.72-.22-1-.43-.63-.45-1-.73-1-1.56 0 .83-.38 1.11-1 1.56-.41.3-.95.43-1.49.44-.55 0-1.1-.14-1.51-.44-.63-.45-1-.73-1-1.56 0 .83-.38 1.11-1 1.56-.41.3-.95.43-1.5.44-.54 0-1.09-.14-1.5-.44-.63-.45-1-.73-1-1.57 0 .84-.38 1.12-1 1.57-.29.21-.63.38-1 .44v6.99z'; break; case 'table-col-after': - path = 'M14.08 12.864V9.216h3.648V7.424H14.08V3.776h-1.728v3.648H8.64v1.792h3.712v3.648zM0 17.92V0h20.48v17.92H0zM6.4 1.28H1.28v3.84H6.4V1.28zm0 5.12H1.28v3.84H6.4V6.4zm0 5.12H1.28v3.84H6.4v-3.84zM19.2 1.28H7.68v14.08H19.2V1.28z'; + path = + 'M14.08 12.864V9.216h3.648V7.424H14.08V3.776h-1.728v3.648H8.64v1.792h3.712v3.648zM0 17.92V0h20.48v17.92H0zM6.4 1.28H1.28v3.84H6.4V1.28zm0 5.12H1.28v3.84H6.4V6.4zm0 5.12H1.28v3.84H6.4v-3.84zM19.2 1.28H7.68v14.08H19.2V1.28z'; break; case 'table-col-before': - path = 'M6.4 3.776v3.648H2.752v1.792H6.4v3.648h1.728V9.216h3.712V7.424H8.128V3.776zM0 17.92V0h20.48v17.92H0zM12.8 1.28H1.28v14.08H12.8V1.28zm6.4 0h-5.12v3.84h5.12V1.28zm0 5.12h-5.12v3.84h5.12V6.4zm0 5.12h-5.12v3.84h5.12v-3.84z'; + path = + 'M6.4 3.776v3.648H2.752v1.792H6.4v3.648h1.728V9.216h3.712V7.424H8.128V3.776zM0 17.92V0h20.48v17.92H0zM12.8 1.28H1.28v14.08H12.8V1.28zm6.4 0h-5.12v3.84h5.12V1.28zm0 5.12h-5.12v3.84h5.12V6.4zm0 5.12h-5.12v3.84h5.12v-3.84z'; break; case 'table-col-delete': - path = 'M6.4 9.98L7.68 8.7v-.256L6.4 7.164V9.98zm6.4-1.532l1.28-1.28V9.92L12.8 8.64v-.192zm7.68 9.472V0H0v17.92h20.48zm-1.28-2.56h-5.12v-1.024l-.256.256-1.024-1.024v1.792H7.68v-1.792l-1.024 1.024-.256-.256v1.024H1.28V1.28H6.4v2.368l.704-.704.576.576V1.216h5.12V3.52l.96-.96.32.32V1.216h5.12V15.36zm-5.76-2.112l-3.136-3.136-3.264 3.264-1.536-1.536 3.264-3.264L5.632 5.44l1.536-1.536 3.136 3.136 3.2-3.2 1.536 1.536-3.2 3.2 3.136 3.136-1.536 1.536z'; + path = + 'M6.4 9.98L7.68 8.7v-.256L6.4 7.164V9.98zm6.4-1.532l1.28-1.28V9.92L12.8 8.64v-.192zm7.68 9.472V0H0v17.92h20.48zm-1.28-2.56h-5.12v-1.024l-.256.256-1.024-1.024v1.792H7.68v-1.792l-1.024 1.024-.256-.256v1.024H1.28V1.28H6.4v2.368l.704-.704.576.576V1.216h5.12V3.52l.96-.96.32.32V1.216h5.12V15.36zm-5.76-2.112l-3.136-3.136-3.264 3.264-1.536-1.536 3.264-3.264L5.632 5.44l1.536-1.536 3.136 3.136 3.2-3.2 1.536 1.536-3.2 3.2 3.136 3.136-1.536 1.536z'; break; case 'table-row-after': - path = 'M13.824 10.176h-2.88v-2.88H9.536v2.88h-2.88v1.344h2.88v2.88h1.408v-2.88h2.88zM0 17.92V0h20.48v17.92H0zM6.4 1.28H1.28v3.84H6.4V1.28zm6.4 0H7.68v3.84h5.12V1.28zm6.4 0h-5.12v3.84h5.12V1.28zm0 5.056H1.28v9.024H19.2V6.336z'; + path = + 'M13.824 10.176h-2.88v-2.88H9.536v2.88h-2.88v1.344h2.88v2.88h1.408v-2.88h2.88zM0 17.92V0h20.48v17.92H0zM6.4 1.28H1.28v3.84H6.4V1.28zm6.4 0H7.68v3.84h5.12V1.28zm6.4 0h-5.12v3.84h5.12V1.28zm0 5.056H1.28v9.024H19.2V6.336z'; break; case 'table-row-before': - path = 'M6.656 6.464h2.88v2.88h1.408v-2.88h2.88V5.12h-2.88V2.24H9.536v2.88h-2.88zM0 17.92V0h20.48v17.92H0zm7.68-2.56h5.12v-3.84H7.68v3.84zm-6.4 0H6.4v-3.84H1.28v3.84zM19.2 1.28H1.28v9.024H19.2V1.28zm0 10.24h-5.12v3.84h5.12v-3.84z'; + path = + 'M6.656 6.464h2.88v2.88h1.408v-2.88h2.88V5.12h-2.88V2.24H9.536v2.88h-2.88zM0 17.92V0h20.48v17.92H0zm7.68-2.56h5.12v-3.84H7.68v3.84zm-6.4 0H6.4v-3.84H1.28v3.84zM19.2 1.28H1.28v9.024H19.2V1.28zm0 10.24h-5.12v3.84h5.12v-3.84z'; break; case 'table-row-delete': - path = 'M17.728 11.456L14.592 8.32l3.2-3.2-1.536-1.536-3.2 3.2L9.92 3.648 8.384 5.12l3.2 3.2-3.264 3.264 1.536 1.536 3.264-3.264 3.136 3.136 1.472-1.536zM0 17.92V0h20.48v17.92H0zm19.2-6.4h-.448l-1.28-1.28H19.2V6.4h-1.792l1.28-1.28h.512V1.28H1.28v3.84h6.208l1.28 1.28H1.28v3.84h7.424l-1.28 1.28H1.28v3.84H19.2v-3.84z'; + path = + 'M17.728 11.456L14.592 8.32l3.2-3.2-1.536-1.536-3.2 3.2L9.92 3.648 8.384 5.12l3.2 3.2-3.264 3.264 1.536 1.536 3.264-3.264 3.136 3.136 1.472-1.536zM0 17.92V0h20.48v17.92H0zm19.2-6.4h-.448l-1.28-1.28H19.2V6.4h-1.792l1.28-1.28h.512V1.28H1.28v3.84h6.208l1.28 1.28H1.28v3.84h7.424l-1.28 1.28H1.28v3.84H19.2v-3.84z'; break; case 'tablet': - path = 'M4 2h12c.55 0 1 .45 1 1v14c0 .55-.45 1-1 1H4c-.55 0-1-.45-1-1V3c0-.55.45-1 1-1zm11 14V4H5v12h10zM6 5h6l-6 5V5z'; + path = + 'M4 2h12c.55 0 1 .45 1 1v14c0 .55-.45 1-1 1H4c-.55 0-1-.45-1-1V3c0-.55.45-1 1-1zm11 14V4H5v12h10zM6 5h6l-6 5V5z'; break; case 'tag': - path = 'M11 2h7v7L8 19l-7-7zm3 6c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z'; + path = + 'M11 2h7v7L8 19l-7-7zm3 6c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z'; break; case 'tagcloud': - path = 'M11 3v4H1V3h10zm8 0v4h-7V3h7zM7 8v3H1V8h6zm12 0v3H8V8h11zM9 12v2H1v-2h8zm10 0v2h-9v-2h9zM6 15v1H1v-1h5zm5 0v1H7v-1h4zm3 0v1h-2v-1h2zm5 0v1h-4v-1h4z'; + path = + 'M11 3v4H1V3h10zm8 0v4h-7V3h7zM7 8v3H1V8h6zm12 0v3H8V8h11zM9 12v2H1v-2h8zm10 0v2h-9v-2h9zM6 15v1H1v-1h5zm5 0v1H7v-1h4zm3 0v1h-2v-1h2zm5 0v1h-4v-1h4z'; break; case 'testimonial': - path = 'M4 3h12c.55 0 1.02.2 1.41.59S18 4.45 18 5v7c0 .55-.2 1.02-.59 1.41S16.55 14 16 14h-1l-5 5v-5H4c-.55 0-1.02-.2-1.41-.59S2 12.55 2 12V5c0-.55.2-1.02.59-1.41S3.45 3 4 3zm11 2H4v1h11V5zm1 3H4v1h12V8zm-3 3H4v1h9v-1z'; + path = + 'M4 3h12c.55 0 1.02.2 1.41.59S18 4.45 18 5v7c0 .55-.2 1.02-.59 1.41S16.55 14 16 14h-1l-5 5v-5H4c-.55 0-1.02-.2-1.41-.59S2 12.55 2 12V5c0-.55.2-1.02.59-1.41S3.45 3 4 3zm11 2H4v1h11V5zm1 3H4v1h12V8zm-3 3H4v1h9v-1z'; break; case 'text': - path = 'M18 3v2H2V3h16zm-6 4v2H2V7h10zm6 0v2h-4V7h4zM8 11v2H2v-2h6zm10 0v2h-8v-2h8zm-4 4v2H2v-2h12z'; + path = + 'M18 3v2H2V3h16zm-6 4v2H2V7h10zm6 0v2h-4V7h4zM8 11v2H2v-2h6zm10 0v2h-8v-2h8zm-4 4v2H2v-2h12z'; break; case 'thumbs-down': - path = 'M7.28 18c-.15.02-.26-.02-.41-.07-.56-.19-.83-.79-.66-1.35.17-.55 1-3.04 1-3.58 0-.53-.75-1-1.35-1h-3c-.6 0-1-.4-1-1s2-7 2-7c.17-.39.55-1 1-1H14v9h-2.14c-.41.41-3.3 4.71-3.58 5.27-.21.41-.6.68-1 .73zM18 12h-2V3h2v9z'; + path = + 'M7.28 18c-.15.02-.26-.02-.41-.07-.56-.19-.83-.79-.66-1.35.17-.55 1-3.04 1-3.58 0-.53-.75-1-1.35-1h-3c-.6 0-1-.4-1-1s2-7 2-7c.17-.39.55-1 1-1H14v9h-2.14c-.41.41-3.3 4.71-3.58 5.27-.21.41-.6.68-1 .73zM18 12h-2V3h2v9z'; break; case 'thumbs-up': - path = 'M12.72 2c.15-.02.26.02.41.07.56.19.83.79.66 1.35-.17.55-1 3.04-1 3.58 0 .53.75 1 1.35 1h3c.6 0 1 .4 1 1s-2 7-2 7c-.17.39-.55 1-1 1H6V8h2.14c.41-.41 3.3-4.71 3.58-5.27.21-.41.6-.68 1-.73zM2 8h2v9H2V8z'; + path = + 'M12.72 2c.15-.02.26.02.41.07.56.19.83.79.66 1.35-.17.55-1 3.04-1 3.58 0 .53.75 1 1.35 1h3c.6 0 1 .4 1 1s-2 7-2 7c-.17.39-.55 1-1 1H6V8h2.14c.41-.41 3.3-4.71 3.58-5.27.21-.41.6-.68 1-.73zM2 8h2v9H2V8z'; break; case 'tickets-alt': - path = 'M20 6.38L18.99 9.2v-.01c-.52-.19-1.03-.16-1.53.08s-.85.62-1.04 1.14-.16 1.03.07 1.53c.24.5.62.84 1.15 1.03v.01l-1.01 2.82-15.06-5.38.99-2.79c.52.19 1.03.16 1.53-.08.5-.23.84-.61 1.03-1.13s.16-1.03-.08-1.53c-.23-.49-.61-.83-1.13-1.02L4.93 1zm-4.97 5.69l1.37-3.76c.12-.31.1-.65-.04-.95s-.39-.53-.7-.65L8.14 3.98c-.64-.23-1.37.12-1.6.74L5.17 8.48c-.24.65.1 1.37.74 1.6l7.52 2.74c.14.05.28.08.43.08.52 0 1-.33 1.17-.83zM7.97 4.45l7.51 2.73c.19.07.34.21.43.39.08.18.09.38.02.57l-1.37 3.76c-.13.38-.58.59-.96.45L6.09 9.61c-.39-.14-.59-.57-.45-.96l1.37-3.76c.1-.29.39-.49.7-.49.09 0 .17.02.26.05zm6.82 12.14c.35.27.75.41 1.2.41H16v3H0v-2.96c.55 0 1.03-.2 1.41-.59.39-.38.59-.86.59-1.41s-.2-1.02-.59-1.41-.86-.59-1.41-.59V10h1.05l-.28.8 2.87 1.02c-.51.16-.89.62-.89 1.18v4c0 .69.56 1.25 1.25 1.25h8c.69 0 1.25-.56 1.25-1.25v-1.75l.83.3c.12.43.36.78.71 1.04zM3.25 17v-4c0-.41.34-.75.75-.75h.83l7.92 2.83V17c0 .41-.34.75-.75.75H4c-.41 0-.75-.34-.75-.75z'; + path = + 'M20 6.38L18.99 9.2v-.01c-.52-.19-1.03-.16-1.53.08s-.85.62-1.04 1.14-.16 1.03.07 1.53c.24.5.62.84 1.15 1.03v.01l-1.01 2.82-15.06-5.38.99-2.79c.52.19 1.03.16 1.53-.08.5-.23.84-.61 1.03-1.13s.16-1.03-.08-1.53c-.23-.49-.61-.83-1.13-1.02L4.93 1zm-4.97 5.69l1.37-3.76c.12-.31.1-.65-.04-.95s-.39-.53-.7-.65L8.14 3.98c-.64-.23-1.37.12-1.6.74L5.17 8.48c-.24.65.1 1.37.74 1.6l7.52 2.74c.14.05.28.08.43.08.52 0 1-.33 1.17-.83zM7.97 4.45l7.51 2.73c.19.07.34.21.43.39.08.18.09.38.02.57l-1.37 3.76c-.13.38-.58.59-.96.45L6.09 9.61c-.39-.14-.59-.57-.45-.96l1.37-3.76c.1-.29.39-.49.7-.49.09 0 .17.02.26.05zm6.82 12.14c.35.27.75.41 1.2.41H16v3H0v-2.96c.55 0 1.03-.2 1.41-.59.39-.38.59-.86.59-1.41s-.2-1.02-.59-1.41-.86-.59-1.41-.59V10h1.05l-.28.8 2.87 1.02c-.51.16-.89.62-.89 1.18v4c0 .69.56 1.25 1.25 1.25h8c.69 0 1.25-.56 1.25-1.25v-1.75l.83.3c.12.43.36.78.71 1.04zM3.25 17v-4c0-.41.34-.75.75-.75h.83l7.92 2.83V17c0 .41-.34.75-.75.75H4c-.41 0-.75-.34-.75-.75z'; break; case 'tickets': - path = 'M20 5.38L18.99 8.2v-.01c-1.04-.37-2.19.18-2.57 1.22-.37 1.04.17 2.19 1.22 2.56v.01l-1.01 2.82L1.57 9.42l.99-2.79c1.04.38 2.19-.17 2.56-1.21s-.17-2.18-1.21-2.55L4.93 0zm-5.45 3.37c.74-2.08-.34-4.37-2.42-5.12-2.08-.74-4.37.35-5.11 2.42-.74 2.08.34 4.38 2.42 5.12 2.07.74 4.37-.35 5.11-2.42zm-2.56-4.74c.89.32 1.57.94 1.97 1.71-.01-.01-.02-.01-.04-.02-.33-.12-.67.09-.78.4-.1.28-.03.57.05.91.04.27.09.62-.06 1.04-.1.29-.33.58-.65 1l-.74 1.01.08-4.08.4.11c.19.04.26-.24.08-.29 0 0-.57-.15-.92-.28-.34-.12-.88-.36-.88-.36-.18-.08-.3.19-.12.27 0 0 .16.08.34.16l.01 1.63L9.2 9.18l.08-4.11c.2.06.4.11.4.11.19.04.26-.23.07-.29 0 0-.56-.15-.91-.28-.07-.02-.14-.05-.22-.08.93-.7 2.19-.94 3.37-.52zM7.4 6.19c.17-.49.44-.92.78-1.27l.04 5c-.94-.95-1.3-2.39-.82-3.73zm4.04 4.75l2.1-2.63c.37-.41.57-.77.69-1.12.05-.12.08-.24.11-.35.09.57.04 1.18-.17 1.77-.45 1.25-1.51 2.1-2.73 2.33zm-.7-3.22l.02 3.22c0 .02 0 .04.01.06-.4 0-.8-.07-1.2-.21-.33-.12-.63-.28-.9-.48zm1.24 6.08l2.1.75c.24.84 1 1.45 1.91 1.45H16v3H0v-2.96c1.1 0 2-.89 2-2 0-1.1-.9-2-2-2V9h1.05l-.28.8 4.28 1.52C4.4 12.03 4 12.97 4 14c0 2.21 1.79 4 4 4s4-1.79 4-4c0-.07-.02-.13-.02-.2zm-6.53-2.33l1.48.53c-.14.04-.15.27.03.28 0 0 .18.02.37.03l.56 1.54-.78 2.36-1.31-3.9c.21-.01.41-.03.41-.03.19-.02.17-.31-.02-.3 0 0-.59.05-.96.05-.07 0-.15 0-.23-.01.13-.2.28-.38.45-.55zM4.4 14c0-.52.12-1.02.32-1.46l1.71 4.7C5.23 16.65 4.4 15.42 4.4 14zm4.19-1.41l1.72.62c.07.17.12.37.12.61 0 .31-.12.66-.28 1.16l-.35 1.2zM11.6 14c0 1.33-.72 2.49-1.79 3.11l1.1-3.18c.06-.17.1-.31.14-.46l.52.19c.02.11.03.22.03.34zm-4.62 3.45l1.08-3.14 1.11 3.03c.01.02.01.04.02.05-.37.13-.77.21-1.19.21-.35 0-.69-.06-1.02-.15z'; + path = + 'M20 5.38L18.99 8.2v-.01c-1.04-.37-2.19.18-2.57 1.22-.37 1.04.17 2.19 1.22 2.56v.01l-1.01 2.82L1.57 9.42l.99-2.79c1.04.38 2.19-.17 2.56-1.21s-.17-2.18-1.21-2.55L4.93 0zm-5.45 3.37c.74-2.08-.34-4.37-2.42-5.12-2.08-.74-4.37.35-5.11 2.42-.74 2.08.34 4.38 2.42 5.12 2.07.74 4.37-.35 5.11-2.42zm-2.56-4.74c.89.32 1.57.94 1.97 1.71-.01-.01-.02-.01-.04-.02-.33-.12-.67.09-.78.4-.1.28-.03.57.05.91.04.27.09.62-.06 1.04-.1.29-.33.58-.65 1l-.74 1.01.08-4.08.4.11c.19.04.26-.24.08-.29 0 0-.57-.15-.92-.28-.34-.12-.88-.36-.88-.36-.18-.08-.3.19-.12.27 0 0 .16.08.34.16l.01 1.63L9.2 9.18l.08-4.11c.2.06.4.11.4.11.19.04.26-.23.07-.29 0 0-.56-.15-.91-.28-.07-.02-.14-.05-.22-.08.93-.7 2.19-.94 3.37-.52zM7.4 6.19c.17-.49.44-.92.78-1.27l.04 5c-.94-.95-1.3-2.39-.82-3.73zm4.04 4.75l2.1-2.63c.37-.41.57-.77.69-1.12.05-.12.08-.24.11-.35.09.57.04 1.18-.17 1.77-.45 1.25-1.51 2.1-2.73 2.33zm-.7-3.22l.02 3.22c0 .02 0 .04.01.06-.4 0-.8-.07-1.2-.21-.33-.12-.63-.28-.9-.48zm1.24 6.08l2.1.75c.24.84 1 1.45 1.91 1.45H16v3H0v-2.96c1.1 0 2-.89 2-2 0-1.1-.9-2-2-2V9h1.05l-.28.8 4.28 1.52C4.4 12.03 4 12.97 4 14c0 2.21 1.79 4 4 4s4-1.79 4-4c0-.07-.02-.13-.02-.2zm-6.53-2.33l1.48.53c-.14.04-.15.27.03.28 0 0 .18.02.37.03l.56 1.54-.78 2.36-1.31-3.9c.21-.01.41-.03.41-.03.19-.02.17-.31-.02-.3 0 0-.59.05-.96.05-.07 0-.15 0-.23-.01.13-.2.28-.38.45-.55zM4.4 14c0-.52.12-1.02.32-1.46l1.71 4.7C5.23 16.65 4.4 15.42 4.4 14zm4.19-1.41l1.72.62c.07.17.12.37.12.61 0 .31-.12.66-.28 1.16l-.35 1.2zM11.6 14c0 1.33-.72 2.49-1.79 3.11l1.1-3.18c.06-.17.1-.31.14-.46l.52.19c.02.11.03.22.03.34zm-4.62 3.45l1.08-3.14 1.11 3.03c.01.02.01.04.02.05-.37.13-.77.21-1.19.21-.35 0-.69-.06-1.02-.15z'; break; case 'tide': - path = 'M17 7.2V3H3v7.1c2.6-.5 4.5-1.5 6.4-2.6.2-.2.4-.3.6-.5v3c-1.9 1.1-4 2.2-7 2.8V17h14V9.9c-2.6.5-4.4 1.5-6.2 2.6-.3.1-.5.3-.8.4V10c2-1.1 4-2.2 7-2.8z'; + path = + 'M17 7.2V3H3v7.1c2.6-.5 4.5-1.5 6.4-2.6.2-.2.4-.3.6-.5v3c-1.9 1.1-4 2.2-7 2.8V17h14V9.9c-2.6.5-4.4 1.5-6.2 2.6-.3.1-.5.3-.8.4V10c2-1.1 4-2.2 7-2.8z'; break; case 'translation': - path = 'M11 7H9.49c-.63 0-1.25.3-1.59.7L7 5H4.13l-2.39 7h1.69l.74-2H7v4H2c-1.1 0-2-.9-2-2V5c0-1.1.9-2 2-2h7c1.1 0 2 .9 2 2v2zM6.51 9H4.49l1-2.93zM10 8h7c1.1 0 2 .9 2 2v7c0 1.1-.9 2-2 2h-7c-1.1 0-2-.9-2-2v-7c0-1.1.9-2 2-2zm7.25 5v-1.08h-3.17V9.75h-1.16v2.17H9.75V13h1.28c.11.85.56 1.85 1.28 2.62-.87.36-1.89.62-2.31.62-.01.02.22.97.2 1.46.84 0 2.21-.5 3.28-1.15 1.09.65 2.48 1.15 3.34 1.15-.02-.49.2-1.44.2-1.46-.43 0-1.49-.27-2.38-.63.7-.77 1.14-1.77 1.25-2.61h1.36zm-3.81 1.93c-.5-.46-.85-1.13-1.01-1.93h2.09c-.17.8-.51 1.47-1 1.93l-.04.03s-.03-.02-.04-.03z'; + path = + 'M11 7H9.49c-.63 0-1.25.3-1.59.7L7 5H4.13l-2.39 7h1.69l.74-2H7v4H2c-1.1 0-2-.9-2-2V5c0-1.1.9-2 2-2h7c1.1 0 2 .9 2 2v2zM6.51 9H4.49l1-2.93zM10 8h7c1.1 0 2 .9 2 2v7c0 1.1-.9 2-2 2h-7c-1.1 0-2-.9-2-2v-7c0-1.1.9-2 2-2zm7.25 5v-1.08h-3.17V9.75h-1.16v2.17H9.75V13h1.28c.11.85.56 1.85 1.28 2.62-.87.36-1.89.62-2.31.62-.01.02.22.97.2 1.46.84 0 2.21-.5 3.28-1.15 1.09.65 2.48 1.15 3.34 1.15-.02-.49.2-1.44.2-1.46-.43 0-1.49-.27-2.38-.63.7-.77 1.14-1.77 1.25-2.61h1.36zm-3.81 1.93c-.5-.46-.85-1.13-1.01-1.93h2.09c-.17.8-.51 1.47-1 1.93l-.04.03s-.03-.02-.04-.03z'; break; case 'trash': - path = 'M12 4h3c.6 0 1 .4 1 1v1H3V5c0-.6.5-1 1-1h3c.2-1.1 1.3-2 2.5-2s2.3.9 2.5 2zM8 4h3c-.2-.6-.9-1-1.5-1S8.2 3.4 8 4zM4 7h11l-.9 10.1c0 .5-.5.9-1 .9H5.9c-.5 0-.9-.4-1-.9L4 7z'; + path = + 'M12 4h3c.6 0 1 .4 1 1v1H3V5c0-.6.5-1 1-1h3c.2-1.1 1.3-2 2.5-2s2.3.9 2.5 2zM8 4h3c-.2-.6-.9-1-1.5-1S8.2 3.4 8 4zM4 7h11l-.9 10.1c0 .5-.5.9-1 .9H5.9c-.5 0-.9-.4-1-.9L4 7z'; break; case 'twitter': - path = 'M18.94 4.46c-.49.73-1.11 1.38-1.83 1.9.01.15.01.31.01.47 0 4.85-3.69 10.44-10.43 10.44-2.07 0-4-.61-5.63-1.65.29.03.58.05.88.05 1.72 0 3.3-.59 4.55-1.57-1.6-.03-2.95-1.09-3.42-2.55.22.04.45.07.69.07.33 0 .66-.05.96-.13-1.67-.34-2.94-1.82-2.94-3.6v-.04c.5.27 1.06.44 1.66.46-.98-.66-1.63-1.78-1.63-3.06 0-.67.18-1.3.5-1.84 1.81 2.22 4.51 3.68 7.56 3.83-.06-.27-.1-.55-.1-.84 0-2.02 1.65-3.66 3.67-3.66 1.06 0 2.01.44 2.68 1.16.83-.17 1.62-.47 2.33-.89-.28.85-.86 1.57-1.62 2.02.75-.08 1.45-.28 2.11-.57z'; + path = + 'M18.94 4.46c-.49.73-1.11 1.38-1.83 1.9.01.15.01.31.01.47 0 4.85-3.69 10.44-10.43 10.44-2.07 0-4-.61-5.63-1.65.29.03.58.05.88.05 1.72 0 3.3-.59 4.55-1.57-1.6-.03-2.95-1.09-3.42-2.55.22.04.45.07.69.07.33 0 .66-.05.96-.13-1.67-.34-2.94-1.82-2.94-3.6v-.04c.5.27 1.06.44 1.66.46-.98-.66-1.63-1.78-1.63-3.06 0-.67.18-1.3.5-1.84 1.81 2.22 4.51 3.68 7.56 3.83-.06-.27-.1-.55-.1-.84 0-2.02 1.65-3.66 3.67-3.66 1.06 0 2.01.44 2.68 1.16.83-.17 1.62-.47 2.33-.89-.28.85-.86 1.57-1.62 2.02.75-.08 1.45-.28 2.11-.57z'; break; case 'undo': - path = 'M12 5H7V2L1 6l6 4V7h5c2.2 0 4 1.8 4 4s-1.8 4-4 4H7v2h5c3.3 0 6-2.7 6-6s-2.7-6-6-6z'; + path = + 'M12 5H7V2L1 6l6 4V7h5c2.2 0 4 1.8 4 4s-1.8 4-4 4H7v2h5c3.3 0 6-2.7 6-6s-2.7-6-6-6z'; break; case 'universal-access-alt': - path = 'M19 10c0-4.97-4.03-9-9-9s-9 4.03-9 9 4.03 9 9 9 9-4.03 9-9zm-9-7.4c.83 0 1.5.67 1.5 1.5s-.67 1.51-1.5 1.51c-.82 0-1.5-.68-1.5-1.51s.68-1.5 1.5-1.5zM3.4 7.36c0-.65 6.6-.76 6.6-.76s6.6.11 6.6.76-4.47 1.4-4.47 1.4 1.69 8.14 1.06 8.38c-.62.24-3.19-5.19-3.19-5.19s-2.56 5.43-3.18 5.19c-.63-.24 1.06-8.38 1.06-8.38S3.4 8.01 3.4 7.36z'; + path = + 'M19 10c0-4.97-4.03-9-9-9s-9 4.03-9 9 4.03 9 9 9 9-4.03 9-9zm-9-7.4c.83 0 1.5.67 1.5 1.5s-.67 1.51-1.5 1.51c-.82 0-1.5-.68-1.5-1.51s.68-1.5 1.5-1.5zM3.4 7.36c0-.65 6.6-.76 6.6-.76s6.6.11 6.6.76-4.47 1.4-4.47 1.4 1.69 8.14 1.06 8.38c-.62.24-3.19-5.19-3.19-5.19s-2.56 5.43-3.18 5.19c-.63-.24 1.06-8.38 1.06-8.38S3.4 8.01 3.4 7.36z'; break; case 'universal-access': - path = 'M10 2.6c.83 0 1.5.67 1.5 1.5s-.67 1.51-1.5 1.51c-.82 0-1.5-.68-1.5-1.51s.68-1.5 1.5-1.5zM3.4 7.36c0-.65 6.6-.76 6.6-.76s6.6.11 6.6.76-4.47 1.4-4.47 1.4 1.69 8.14 1.06 8.38c-.62.24-3.19-5.19-3.19-5.19s-2.56 5.43-3.18 5.19c-.63-.24 1.06-8.38 1.06-8.38S3.4 8.01 3.4 7.36z'; + path = + 'M10 2.6c.83 0 1.5.67 1.5 1.5s-.67 1.51-1.5 1.51c-.82 0-1.5-.68-1.5-1.51s.68-1.5 1.5-1.5zM3.4 7.36c0-.65 6.6-.76 6.6-.76s6.6.11 6.6.76-4.47 1.4-4.47 1.4 1.69 8.14 1.06 8.38c-.62.24-3.19-5.19-3.19-5.19s-2.56 5.43-3.18 5.19c-.63-.24 1.06-8.38 1.06-8.38S3.4 8.01 3.4 7.36z'; break; case 'unlock': - path = 'M12 9V6c0-1.1-.9-2-2-2s-2 .9-2 2H6c0-2.21 1.79-4 4-4s4 1.79 4 4v3h1c.55 0 1 .45 1 1v7c0 .55-.45 1-1 1H5c-.55 0-1-.45-1-1v-7c0-.55.45-1 1-1h7zm-1 7l-.36-2.15c.51-.24.86-.75.86-1.35 0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5c0 .6.35 1.11.86 1.35L9 16h2z'; + path = + 'M12 9V6c0-1.1-.9-2-2-2s-2 .9-2 2H6c0-2.21 1.79-4 4-4s4 1.79 4 4v3h1c.55 0 1 .45 1 1v7c0 .55-.45 1-1 1H5c-.55 0-1-.45-1-1v-7c0-.55.45-1 1-1h7zm-1 7l-.36-2.15c.51-.24.86-.75.86-1.35 0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5c0 .6.35 1.11.86 1.35L9 16h2z'; break; case 'update': - path = 'M10.2 3.28c3.53 0 6.43 2.61 6.92 6h2.08l-3.5 4-3.5-4h2.32c-.45-1.97-2.21-3.45-4.32-3.45-1.45 0-2.73.71-3.54 1.78L4.95 5.66C6.23 4.2 8.11 3.28 10.2 3.28zm-.4 13.44c-3.52 0-6.43-2.61-6.92-6H.8l3.5-4c1.17 1.33 2.33 2.67 3.5 4H5.48c.45 1.97 2.21 3.45 4.32 3.45 1.45 0 2.73-.71 3.54-1.78l1.71 1.95c-1.28 1.46-3.15 2.38-5.25 2.38z'; + path = + 'M10.2 3.28c3.53 0 6.43 2.61 6.92 6h2.08l-3.5 4-3.5-4h2.32c-.45-1.97-2.21-3.45-4.32-3.45-1.45 0-2.73.71-3.54 1.78L4.95 5.66C6.23 4.2 8.11 3.28 10.2 3.28zm-.4 13.44c-3.52 0-6.43-2.61-6.92-6H.8l3.5-4c1.17 1.33 2.33 2.67 3.5 4H5.48c.45 1.97 2.21 3.45 4.32 3.45 1.45 0 2.73-.71 3.54-1.78l1.71 1.95c-1.28 1.46-3.15 2.38-5.25 2.38z'; break; case 'upload': path = 'M8 14V8H5l5-6 5 6h-3v6H8zm-2 2v-6H4v8h12.01v-8H14v6H6z'; break; case 'vault': - path = 'M18 17V3c0-.55-.45-1-1-1H3c-.55 0-1 .45-1 1v14c0 .55.45 1 1 1h14c.55 0 1-.45 1-1zm-1 0H3V3h14v14zM4.75 4h10.5c.41 0 .75.34.75.75V6h-1v3h1v2h-1v3h1v1.25c0 .41-.34.75-.75.75H4.75c-.41 0-.75-.34-.75-.75V4.75c0-.41.34-.75.75-.75zM13 10c0-2.21-1.79-4-4-4s-4 1.79-4 4 1.79 4 4 4 4-1.79 4-4zM9 7l.77 1.15C10.49 8.46 11 9.17 11 10c0 1.1-.9 2-2 2s-2-.9-2-2c0-.83.51-1.54 1.23-1.85z'; + path = + 'M18 17V3c0-.55-.45-1-1-1H3c-.55 0-1 .45-1 1v14c0 .55.45 1 1 1h14c.55 0 1-.45 1-1zm-1 0H3V3h14v14zM4.75 4h10.5c.41 0 .75.34.75.75V6h-1v3h1v2h-1v3h1v1.25c0 .41-.34.75-.75.75H4.75c-.41 0-.75-.34-.75-.75V4.75c0-.41.34-.75.75-.75zM13 10c0-2.21-1.79-4-4-4s-4 1.79-4 4 1.79 4 4 4 4-1.79 4-4zM9 7l.77 1.15C10.49 8.46 11 9.17 11 10c0 1.1-.9 2-2 2s-2-.9-2-2c0-.83.51-1.54 1.23-1.85z'; break; case 'video-alt': - path = 'M8 5c0-.55-.45-1-1-1H2c-.55 0-1 .45-1 1 0 .57.49 1 1 1h5c.55 0 1-.45 1-1zm6 5l4-4v10l-4-4v-2zm-1 4V8c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h8c.55 0 1-.45 1-1z'; + path = + 'M8 5c0-.55-.45-1-1-1H2c-.55 0-1 .45-1 1 0 .57.49 1 1 1h5c.55 0 1-.45 1-1zm6 5l4-4v10l-4-4v-2zm-1 4V8c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h8c.55 0 1-.45 1-1z'; break; case 'video-alt2': - path = 'M12 13V7c0-1.1-.9-2-2-2H3c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h7c1.1 0 2-.9 2-2zm1-2.5l6 4.5V5l-6 4.5v1z'; + path = + 'M12 13V7c0-1.1-.9-2-2-2H3c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h7c1.1 0 2-.9 2-2zm1-2.5l6 4.5V5l-6 4.5v1z'; break; case 'video-alt3': - path = 'M19 15V5c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h13c1.1 0 2-.9 2-2zM8 14V6l6 4z'; + path = + 'M19 15V5c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h13c1.1 0 2-.9 2-2zM8 14V6l6 4z'; break; case 'visibility': - path = 'M19.7 9.4C17.7 6 14 3.9 10 3.9S2.3 6 .3 9.4L0 10l.3.6c2 3.4 5.7 5.5 9.7 5.5s7.7-2.1 9.7-5.5l.3-.6-.3-.6zM10 14.1c-3.1 0-6-1.6-7.7-4.1C3.6 8 5.7 6.6 8 6.1c-.9.6-1.5 1.7-1.5 2.9 0 1.9 1.6 3.5 3.5 3.5s3.5-1.6 3.5-3.5c0-1.2-.6-2.3-1.5-2.9 2.3.5 4.4 1.9 5.7 3.9-1.7 2.5-4.6 4.1-7.7 4.1z'; + path = + 'M19.7 9.4C17.7 6 14 3.9 10 3.9S2.3 6 .3 9.4L0 10l.3.6c2 3.4 5.7 5.5 9.7 5.5s7.7-2.1 9.7-5.5l.3-.6-.3-.6zM10 14.1c-3.1 0-6-1.6-7.7-4.1C3.6 8 5.7 6.6 8 6.1c-.9.6-1.5 1.7-1.5 2.9 0 1.9 1.6 3.5 3.5 3.5s3.5-1.6 3.5-3.5c0-1.2-.6-2.3-1.5-2.9 2.3.5 4.4 1.9 5.7 3.9-1.7 2.5-4.6 4.1-7.7 4.1z'; break; case 'warning': - path = 'M10 2c4.42 0 8 3.58 8 8s-3.58 8-8 8-8-3.58-8-8 3.58-8 8-8zm1.13 9.38l.35-6.46H8.52l.35 6.46h2.26zm-.09 3.36c.24-.23.37-.55.37-.96 0-.42-.12-.74-.36-.97s-.59-.35-1.06-.35-.82.12-1.07.35-.37.55-.37.97c0 .41.13.73.38.96.26.23.61.34 1.06.34s.8-.11 1.05-.34z'; + path = + 'M10 2c4.42 0 8 3.58 8 8s-3.58 8-8 8-8-3.58-8-8 3.58-8 8-8zm1.13 9.38l.35-6.46H8.52l.35 6.46h2.26zm-.09 3.36c.24-.23.37-.55.37-.96 0-.42-.12-.74-.36-.97s-.59-.35-1.06-.35-.82.12-1.07.35-.37.55-.37.97c0 .41.13.73.38.96.26.23.61.34 1.06.34s.8-.11 1.05-.34z'; break; case 'welcome-add-page': - path = 'M17 7V4h-2V2h-3v1H3v15h11V9h1V7h2zm-1-2v1h-2v2h-1V6h-2V5h2V3h1v2h2z'; + path = + 'M17 7V4h-2V2h-3v1H3v15h11V9h1V7h2zm-1-2v1h-2v2h-1V6h-2V5h2V3h1v2h2z'; break; case 'welcome-comments': - path = 'M5 2h10c1.1 0 2 .9 2 2v8c0 1.1-.9 2-2 2h-2l-5 5v-5H5c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2zm8.5 8.5L11 8l2.5-2.5-1-1L10 7 7.5 4.5l-1 1L9 8l-2.5 2.5 1 1L10 9l2.5 2.5z'; + path = + 'M5 2h10c1.1 0 2 .9 2 2v8c0 1.1-.9 2-2 2h-2l-5 5v-5H5c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2zm8.5 8.5L11 8l2.5-2.5-1-1L10 7 7.5 4.5l-1 1L9 8l-2.5 2.5 1 1L10 9l2.5 2.5z'; break; case 'welcome-learn-more': - path = 'M10 10L2.54 7.02 3 18H1l.48-11.41L0 6l10-4 10 4zm0-5c-.55 0-1 .22-1 .5s.45.5 1 .5 1-.22 1-.5-.45-.5-1-.5zm0 6l5.57-2.23c.71.94 1.2 2.07 1.36 3.3-.3-.04-.61-.07-.93-.07-2.55 0-4.78 1.37-6 3.41C8.78 13.37 6.55 12 4 12c-.32 0-.63.03-.93.07.16-1.23.65-2.36 1.36-3.3z'; + path = + 'M10 10L2.54 7.02 3 18H1l.48-11.41L0 6l10-4 10 4zm0-5c-.55 0-1 .22-1 .5s.45.5 1 .5 1-.22 1-.5-.45-.5-1-.5zm0 6l5.57-2.23c.71.94 1.2 2.07 1.36 3.3-.3-.04-.61-.07-.93-.07-2.55 0-4.78 1.37-6 3.41C8.78 13.37 6.55 12 4 12c-.32 0-.63.03-.93.07.16-1.23.65-2.36 1.36-3.3z'; break; case 'welcome-view-site': - path = 'M18 14V4c0-.55-.45-1-1-1H3c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h14c.55 0 1-.45 1-1zm-8-8c2.3 0 4.4 1.14 6 3-1.6 1.86-3.7 3-6 3s-4.4-1.14-6-3c1.6-1.86 3.7-3 6-3zm2 3c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm2 8h3v1H3v-1h3v-1h8v1z'; + path = + 'M18 14V4c0-.55-.45-1-1-1H3c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h14c.55 0 1-.45 1-1zm-8-8c2.3 0 4.4 1.14 6 3-1.6 1.86-3.7 3-6 3s-4.4-1.14-6-3c1.6-1.86 3.7-3 6-3zm2 3c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm2 8h3v1H3v-1h3v-1h8v1z'; break; case 'welcome-widgets-menus': - path = 'M19 16V3c0-.55-.45-1-1-1H3c-.55 0-1 .45-1 1v13c0 .55.45 1 1 1h15c.55 0 1-.45 1-1zM4 4h13v4H4V4zm1 1v2h3V5H5zm4 0v2h3V5H9zm4 0v2h3V5h-3zm-8.5 5c.28 0 .5.22.5.5s-.22.5-.5.5-.5-.22-.5-.5.22-.5.5-.5zM6 10h4v1H6v-1zm6 0h5v5h-5v-5zm-7.5 2c.28 0 .5.22.5.5s-.22.5-.5.5-.5-.22-.5-.5.22-.5.5-.5zM6 12h4v1H6v-1zm7 0v2h3v-2h-3zm-8.5 2c.28 0 .5.22.5.5s-.22.5-.5.5-.5-.22-.5-.5.22-.5.5-.5zM6 14h4v1H6v-1z'; + path = + 'M19 16V3c0-.55-.45-1-1-1H3c-.55 0-1 .45-1 1v13c0 .55.45 1 1 1h15c.55 0 1-.45 1-1zM4 4h13v4H4V4zm1 1v2h3V5H5zm4 0v2h3V5H9zm4 0v2h3V5h-3zm-8.5 5c.28 0 .5.22.5.5s-.22.5-.5.5-.5-.22-.5-.5.22-.5.5-.5zM6 10h4v1H6v-1zm6 0h5v5h-5v-5zm-7.5 2c.28 0 .5.22.5.5s-.22.5-.5.5-.5-.22-.5-.5.22-.5.5-.5zM6 12h4v1H6v-1zm7 0v2h3v-2h-3zm-8.5 2c.28 0 .5.22.5.5s-.22.5-.5.5-.5-.22-.5-.5.22-.5.5-.5zM6 14h4v1H6v-1z'; break; case 'welcome-write-blog': - path = 'M16.89 1.2l1.41 1.41c.39.39.39 1.02 0 1.41L14 8.33V18H3V3h10.67l1.8-1.8c.4-.39 1.03-.4 1.42 0zm-5.66 8.48l5.37-5.36-1.42-1.42-5.36 5.37-.71 2.12z'; + path = + 'M16.89 1.2l1.41 1.41c.39.39.39 1.02 0 1.41L14 8.33V18H3V3h10.67l1.8-1.8c.4-.39 1.03-.4 1.42 0zm-5.66 8.48l5.37-5.36-1.42-1.42-5.36 5.37-.71 2.12z'; break; case 'wordpress-alt': - path = 'M20 10c0-5.51-4.49-10-10-10C4.48 0 0 4.49 0 10c0 5.52 4.48 10 10 10 5.51 0 10-4.48 10-10zM7.78 15.37L4.37 6.22c.55-.02 1.17-.08 1.17-.08.5-.06.44-1.13-.06-1.11 0 0-1.45.11-2.37.11-.18 0-.37 0-.58-.01C4.12 2.69 6.87 1.11 10 1.11c2.33 0 4.45.87 6.05 2.34-.68-.11-1.65.39-1.65 1.58 0 .74.45 1.36.9 2.1.35.61.55 1.36.55 2.46 0 1.49-1.4 5-1.4 5l-3.03-8.37c.54-.02.82-.17.82-.17.5-.05.44-1.25-.06-1.22 0 0-1.44.12-2.38.12-.87 0-2.33-.12-2.33-.12-.5-.03-.56 1.2-.06 1.22l.92.08 1.26 3.41zM17.41 10c.24-.64.74-1.87.43-4.25.7 1.29 1.05 2.71 1.05 4.25 0 3.29-1.73 6.24-4.4 7.78.97-2.59 1.94-5.2 2.92-7.78zM6.1 18.09C3.12 16.65 1.11 13.53 1.11 10c0-1.3.23-2.48.72-3.59C3.25 10.3 4.67 14.2 6.1 18.09zm4.03-6.63l2.58 6.98c-.86.29-1.76.45-2.71.45-.79 0-1.57-.11-2.29-.33.81-2.38 1.62-4.74 2.42-7.1z'; + path = + 'M20 10c0-5.51-4.49-10-10-10C4.48 0 0 4.49 0 10c0 5.52 4.48 10 10 10 5.51 0 10-4.48 10-10zM7.78 15.37L4.37 6.22c.55-.02 1.17-.08 1.17-.08.5-.06.44-1.13-.06-1.11 0 0-1.45.11-2.37.11-.18 0-.37 0-.58-.01C4.12 2.69 6.87 1.11 10 1.11c2.33 0 4.45.87 6.05 2.34-.68-.11-1.65.39-1.65 1.58 0 .74.45 1.36.9 2.1.35.61.55 1.36.55 2.46 0 1.49-1.4 5-1.4 5l-3.03-8.37c.54-.02.82-.17.82-.17.5-.05.44-1.25-.06-1.22 0 0-1.44.12-2.38.12-.87 0-2.33-.12-2.33-.12-.5-.03-.56 1.2-.06 1.22l.92.08 1.26 3.41zM17.41 10c.24-.64.74-1.87.43-4.25.7 1.29 1.05 2.71 1.05 4.25 0 3.29-1.73 6.24-4.4 7.78.97-2.59 1.94-5.2 2.92-7.78zM6.1 18.09C3.12 16.65 1.11 13.53 1.11 10c0-1.3.23-2.48.72-3.59C3.25 10.3 4.67 14.2 6.1 18.09zm4.03-6.63l2.58 6.98c-.86.29-1.76.45-2.71.45-.79 0-1.57-.11-2.29-.33.81-2.38 1.62-4.74 2.42-7.1z'; break; case 'wordpress': - path = 'M20 10c0-5.52-4.48-10-10-10S0 4.48 0 10s4.48 10 10 10 10-4.48 10-10zM10 1.01c4.97 0 8.99 4.02 8.99 8.99s-4.02 8.99-8.99 8.99S1.01 14.97 1.01 10 5.03 1.01 10 1.01zM8.01 14.82L4.96 6.61c.49-.03 1.05-.08 1.05-.08.43-.05.38-1.01-.06-.99 0 0-1.29.1-2.13.1-.15 0-.33 0-.52-.01 1.44-2.17 3.9-3.6 6.7-3.6 2.09 0 3.99.79 5.41 2.09-.6-.08-1.45.35-1.45 1.42 0 .66.38 1.22.79 1.88.31.54.5 1.22.5 2.21 0 1.34-1.27 4.48-1.27 4.48l-2.71-7.5c.48-.03.75-.16.75-.16.43-.05.38-1.1-.05-1.08 0 0-1.3.11-2.14.11-.78 0-2.11-.11-2.11-.11-.43-.02-.48 1.06-.05 1.08l.84.08 1.12 3.04zm6.02 2.15L16.64 10s.67-1.69.39-3.81c.63 1.14.94 2.42.94 3.81 0 2.96-1.56 5.58-3.94 6.97zM2.68 6.77L6.5 17.25c-2.67-1.3-4.47-4.08-4.47-7.25 0-1.16.2-2.23.65-3.23zm7.45 4.53l2.29 6.25c-.75.27-1.57.42-2.42.42-.72 0-1.41-.11-2.06-.3z'; + path = + 'M20 10c0-5.52-4.48-10-10-10S0 4.48 0 10s4.48 10 10 10 10-4.48 10-10zM10 1.01c4.97 0 8.99 4.02 8.99 8.99s-4.02 8.99-8.99 8.99S1.01 14.97 1.01 10 5.03 1.01 10 1.01zM8.01 14.82L4.96 6.61c.49-.03 1.05-.08 1.05-.08.43-.05.38-1.01-.06-.99 0 0-1.29.1-2.13.1-.15 0-.33 0-.52-.01 1.44-2.17 3.9-3.6 6.7-3.6 2.09 0 3.99.79 5.41 2.09-.6-.08-1.45.35-1.45 1.42 0 .66.38 1.22.79 1.88.31.54.5 1.22.5 2.21 0 1.34-1.27 4.48-1.27 4.48l-2.71-7.5c.48-.03.75-.16.75-.16.43-.05.38-1.1-.05-1.08 0 0-1.3.11-2.14.11-.78 0-2.11-.11-2.11-.11-.43-.02-.48 1.06-.05 1.08l.84.08 1.12 3.04zm6.02 2.15L16.64 10s.67-1.69.39-3.81c.63 1.14.94 2.42.94 3.81 0 2.96-1.56 5.58-3.94 6.97zM2.68 6.77L6.5 17.25c-2.67-1.3-4.47-4.08-4.47-7.25 0-1.16.2-2.23.65-3.23zm7.45 4.53l2.29 6.25c-.75.27-1.57.42-2.42.42-.72 0-1.41-.11-2.06-.3z'; break; case 'yes-alt': - path = 'M10 2c-4.42 0-8 3.58-8 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm-.615 12.66h-1.34l-3.24-4.54 1.34-1.25 2.57 2.4 5.14-5.93 1.34.94-5.81 8.38z'; + path = + 'M10 2c-4.42 0-8 3.58-8 8s3.58 8 8 8 8-3.58 8-8-3.58-8-8-8zm-.615 12.66h-1.34l-3.24-4.54 1.34-1.25 2.57 2.4 5.14-5.93 1.34.94-5.81 8.38z'; break; case 'yes': - path = 'M14.83 4.89l1.34.94-5.81 8.38H9.02L5.78 9.67l1.34-1.25 2.57 2.4z'; + path = + 'M14.83 4.89l1.34.94-5.81 8.38H9.02L5.78 9.67l1.34-1.25 2.57 2.4z'; break; } @@ -887,7 +1128,9 @@ export default class Dashicon extends Component { return null; } - const iconClass = [ 'dashicon', 'dashicons-' + icon, className ].filter( Boolean ).join( ' ' ); + const iconClass = [ 'dashicon', 'dashicons-' + icon, className ] + .filter( Boolean ) + .join( ' ' ); return ( <SVG diff --git a/packages/components/src/dashicon/test/index.js b/packages/components/src/dashicon/test/index.js index 8827d516181e40..0f2cb915337e82 100644 --- a/packages/components/src/dashicon/test/index.js +++ b/packages/components/src/dashicon/test/index.js @@ -22,7 +22,9 @@ describe( 'Dashicon', () => { } ); it( 'should render an empty null element when a non dashicon is provided', () => { - const dashicon = shallow( <Dashicon icon="zomg-never-gonna-be-an-icon-icon" /> ); + const dashicon = shallow( + <Dashicon icon="zomg-never-gonna-be-an-icon-icon" /> + ); // Unrendered element. expect( dashicon.type() ).toBeNull(); } ); @@ -32,14 +34,18 @@ describe( 'Dashicon', () => { expect( dashicon.hasClass( 'dashicon' ) ).toBe( true ); expect( dashicon.hasClass( 'dashicons-wordpress' ) ).toBe( true ); expect( dashicon.type() ).toBe( SVG ); - expect( dashicon.prop( 'xmlns' ) ).toBe( 'http://www.w3.org/2000/svg' ); + expect( dashicon.prop( 'xmlns' ) ).toBe( + 'http://www.w3.org/2000/svg' + ); expect( dashicon.prop( 'width' ) ).toBe( 20 ); expect( dashicon.prop( 'height' ) ).toBe( 20 ); expect( dashicon.prop( 'viewBox' ) ).toBe( '0 0 20 20' ); } ); it( 'should render an additional class to the SVG element', () => { - const dashicon = shallow( <Dashicon icon="wordpress" className="capital-p-dangit" /> ); + const dashicon = shallow( + <Dashicon icon="wordpress" className="capital-p-dangit" /> + ); expect( dashicon.hasClass( 'capital-p-dangit' ) ).toBe( true ); } ); } ); diff --git a/packages/components/src/date-time/date.js b/packages/components/src/date-time/date.js index 7b1accff50227d..e912a2fd8f9999 100644 --- a/packages/components/src/date-time/date.js +++ b/packages/components/src/date-time/date.js @@ -25,19 +25,24 @@ class DatePicker extends Component { } /* - * Todo: We should remove this function ASAP. - * It is kept because focus is lost when we click on the previous and next month buttons. - * This focus loss closes the date picker popover. - * Ideally we should add an upstream commit on react-dates to fix this issue. - */ + * Todo: We should remove this function ASAP. + * It is kept because focus is lost when we click on the previous and next month buttons. + * This focus loss closes the date picker popover. + * Ideally we should add an upstream commit on react-dates to fix this issue. + */ keepFocusInside() { if ( ! this.nodeRef.current ) { return; } // If focus was lost. - if ( ! document.activeElement || ! this.nodeRef.current.contains( document.activeElement ) ) { + if ( + ! document.activeElement || + ! this.nodeRef.current.contains( document.activeElement ) + ) { // Retrieve the focus region div. - const focusRegion = this.nodeRef.current.querySelector( '.DayPicker_focusRegion' ); + const focusRegion = this.nodeRef.current.querySelector( + '.DayPicker_focusRegion' + ); if ( ! focusRegion ) { return; } @@ -80,10 +85,7 @@ class DatePicker extends Component { const momentDate = this.getMomentDate( currentDate ); return ( - <div - className="components-datetime__date" - ref={ this.nodeRef } - > + <div className="components-datetime__date" ref={ this.nodeRef }> <DayPickerSingleDateController date={ momentDate } daySize={ 30 } @@ -91,7 +93,9 @@ class DatePicker extends Component { hideKeyboardShortcutsPanel // This is a hack to force the calendar to update on month or year change // https://github.com/airbnb/react-dates/issues/240#issuecomment-361776665 - key={ `datepicker-controller-${ momentDate ? momentDate.format( 'MM-YYYY' ) : 'null' }` } + key={ `datepicker-controller-${ + momentDate ? momentDate.format( 'MM-YYYY' ) : 'null' + }` } noBorder numberOfMonths={ 1 } onDateChange={ this.onChangeMoment } diff --git a/packages/components/src/date-time/index.js b/packages/components/src/date-time/index.js index bb7434d2e82ce3..0decc8876c9060 100644 --- a/packages/components/src/date-time/index.js +++ b/packages/components/src/date-time/index.js @@ -26,11 +26,15 @@ export class DateTimePicker extends Component { this.state = { calendarHelpIsVisible: false }; - this.onClickDescriptionToggle = this.onClickDescriptionToggle.bind( this ); + this.onClickDescriptionToggle = this.onClickDescriptionToggle.bind( + this + ); } onClickDescriptionToggle() { - this.setState( { calendarHelpIsVisible: ! this.state.calendarHelpIsVisible } ); + this.setState( { + calendarHelpIsVisible: ! this.state.calendarHelpIsVisible, + } ); } render() { @@ -58,40 +62,98 @@ export class DateTimePicker extends Component { <div className="components-datetime__calendar-help"> <h4>{ __( 'Click to Select' ) }</h4> <ul> - <li>{ __( 'Click the right or left arrows to select other months in the past or the future.' ) }</li> - <li>{ __( 'Click the desired day to select it.' ) }</li> + <li> + { __( + 'Click the right or left arrows to select other months in the past or the future.' + ) } + </li> + <li> + { __( + 'Click the desired day to select it.' + ) } + </li> </ul> <h4>{ __( 'Navigating with a keyboard' ) }</h4> <ul> <li> - <abbr aria-label={ _x( 'Enter', 'keyboard button' ) }>↵</abbr> - { ' ' /* JSX removes whitespace, but a space is required for screen readers. */ } - <span>{ __( 'Select the date in focus.' ) }</span> + <abbr + aria-label={ _x( + 'Enter', + 'keyboard button' + ) } + > + ↵ + </abbr> + { + ' ' /* JSX removes whitespace, but a space is required for screen readers. */ + } + <span> + { __( 'Select the date in focus.' ) } + </span> </li> <li> - <abbr aria-label={ __( 'Left and Right Arrows' ) }>←/→</abbr> - { ' ' /* JSX removes whitespace, but a space is required for screen readers. */ } - { __( 'Move backward (left) or forward (right) by one day.' ) } + <abbr + aria-label={ __( + 'Left and Right Arrows' + ) } + > + ←/→ + </abbr> + { + ' ' /* JSX removes whitespace, but a space is required for screen readers. */ + } + { __( + 'Move backward (left) or forward (right) by one day.' + ) } </li> <li> - <abbr aria-label={ __( 'Up and Down Arrows' ) }>↑/↓</abbr> - { ' ' /* JSX removes whitespace, but a space is required for screen readers. */ } - { __( 'Move backward (up) or forward (down) by one week.' ) } + <abbr + aria-label={ __( + 'Up and Down Arrows' + ) } + > + ↑/↓ + </abbr> + { + ' ' /* JSX removes whitespace, but a space is required for screen readers. */ + } + { __( + 'Move backward (up) or forward (down) by one week.' + ) } </li> <li> - <abbr aria-label={ __( 'Page Up and Page Down' ) }>{ __( 'PgUp/PgDn' ) }</abbr> - { ' ' /* JSX removes whitespace, but a space is required for screen readers. */ } - { __( 'Move backward (PgUp) or forward (PgDn) by one month.' ) } + <abbr + aria-label={ __( + 'Page Up and Page Down' + ) } + > + { __( 'PgUp/PgDn' ) } + </abbr> + { + ' ' /* JSX removes whitespace, but a space is required for screen readers. */ + } + { __( + 'Move backward (PgUp) or forward (PgDn) by one month.' + ) } </li> <li> - <abbr aria-label={ __( 'Home and End' ) }>{ __( 'Home/End' ) }</abbr> - { ' ' /* JSX removes whitespace, but a space is required for screen readers. */ } - { __( 'Go to the first (home) or last (end) day of a week.' ) } + <abbr aria-label={ __( 'Home and End' ) }> + { __( 'Home/End' ) } + </abbr> + { + ' ' /* JSX removes whitespace, but a space is required for screen readers. */ + } + { __( + 'Go to the first (home) or last (end) day of a week.' + ) } </li> </ul> - <Button isSmall onClick={ this.onClickDescriptionToggle }> + <Button + isSmall + onClick={ this.onClickDescriptionToggle } + > { __( 'Close' ) } </Button> </div> diff --git a/packages/components/src/date-time/test/date.js b/packages/components/src/date-time/test/date.js index cd1b1e1e12e2b2..3646249f14b003 100644 --- a/packages/components/src/date-time/test/date.js +++ b/packages/components/src/date-time/test/date.js @@ -61,48 +61,63 @@ describe( 'DatePicker', () => { it( 'should call onChange with a formated date of the input', () => { const onChangeSpy = jest.fn(); const currentDate = '1986-10-18T11:00:00'; - const wrapper = shallow( <DatePicker currentDate={ currentDate } onChange={ onChangeSpy } /> ); + const wrapper = shallow( + <DatePicker + currentDate={ currentDate } + onChange={ onChangeSpy } + /> + ); const newDate = moment(); wrapper.instance().onChangeMoment( newDate ); - expect( onChangeSpy ).toHaveBeenCalledWith( newDate.format( TIMEZONELESS_FORMAT ) ); + expect( onChangeSpy ).toHaveBeenCalledWith( + newDate.format( TIMEZONELESS_FORMAT ) + ); } ); it( 'should call onChange with hours, minutes, seconds of the current time when currentDate is undefined', () => { let onChangeSpyArgument; - const onChangeSpy = ( arg ) => onChangeSpyArgument = arg; + const onChangeSpy = ( arg ) => ( onChangeSpyArgument = arg ); const wrapper = shallow( <DatePicker onChange={ onChangeSpy } /> ); const newDate = moment( '1986-10-18T11:00:00' ); const current = moment(); - const newDateWithCurrentTime = newDate - .clone() - .set( { - hours: current.hours(), - minutes: current.minutes(), - seconds: current.seconds(), - } ); + const newDateWithCurrentTime = newDate.clone().set( { + hours: current.hours(), + minutes: current.minutes(), + seconds: current.seconds(), + } ); wrapper.instance().onChangeMoment( newDate ); - expect( moment( onChangeSpyArgument ).isSame( newDateWithCurrentTime, 'minute' ) ).toBe( true ); + expect( + moment( onChangeSpyArgument ).isSame( + newDateWithCurrentTime, + 'minute' + ) + ).toBe( true ); } ); it( 'should call onChange with hours, minutes, seconds of the current time when currentDate is null', () => { let onChangeSpyArgument; - const onChangeSpy = ( arg ) => onChangeSpyArgument = arg; - const wrapper = shallow( <DatePicker currentDate={ null } onChange={ onChangeSpy } /> ); + const onChangeSpy = ( arg ) => ( onChangeSpyArgument = arg ); + const wrapper = shallow( + <DatePicker currentDate={ null } onChange={ onChangeSpy } /> + ); const newDate = moment( '1986-10-18T11:00:00' ); const current = moment(); - const newDateWithCurrentTime = newDate - .clone() - .set( { - hours: current.hours(), - minutes: current.minutes(), - seconds: current.seconds(), - } ); + const newDateWithCurrentTime = newDate.clone().set( { + hours: current.hours(), + minutes: current.minutes(), + seconds: current.seconds(), + } ); wrapper.instance().onChangeMoment( newDate ); - expect( moment( onChangeSpyArgument ).isSame( newDateWithCurrentTime, 'minute' ) ).toBe( true ); + expect( + moment( onChangeSpyArgument ).isSame( + newDateWithCurrentTime, + 'minute' + ) + ).toBe( true ); } ); } ); } ); diff --git a/packages/components/src/date-time/test/time.js b/packages/components/src/date-time/test/time.js index 3d61d058f330ce..52b33247e1e82e 100644 --- a/packages/components/src/date-time/test/time.js +++ b/packages/components/src/date-time/test/time.js @@ -10,29 +10,45 @@ import TimePicker from '../time'; describe( 'TimePicker', () => { it( 'matches the snapshot when the is12hour prop is true', () => { - const wrapper = shallow( <TimePicker currentTime="1986-10-18T23:00:00" is12Hour={ true } /> ); + const wrapper = shallow( + <TimePicker currentTime="1986-10-18T23:00:00" is12Hour={ true } /> + ); expect( wrapper ).toMatchSnapshot(); } ); it( 'matches the snapshot when the is12hour prop is false', () => { - const wrapper = shallow( <TimePicker currentTime="1986-10-18T23:00:00" is12Hour={ false } /> ); + const wrapper = shallow( + <TimePicker currentTime="1986-10-18T23:00:00" is12Hour={ false } /> + ); expect( wrapper ).toMatchSnapshot(); } ); it( 'matches the snapshot when the is12hour prop is specified', () => { - const wrapper = shallow( <TimePicker currentTime="1986-10-18T23:00:00" is12Hour /> ); + const wrapper = shallow( + <TimePicker currentTime="1986-10-18T23:00:00" is12Hour /> + ); expect( wrapper ).toMatchSnapshot(); } ); it( 'matches the snapshot when the is12hour prop is undefined', () => { - const wrapper = shallow( <TimePicker currentTime="1986-10-18T23:00:00" /> ); + const wrapper = shallow( + <TimePicker currentTime="1986-10-18T23:00:00" /> + ); expect( wrapper ).toMatchSnapshot(); } ); it( 'should call onChange with an updated day', () => { const onChangeSpy = jest.fn(); - const picker = shallow( <TimePicker currentTime="1986-10-18T11:00:00" onChange={ onChangeSpy } is12Hour /> ); - const input = picker.find( '.components-datetime__time-field-day-input' ).at( 0 ); + const picker = shallow( + <TimePicker + currentTime="1986-10-18T11:00:00" + onChange={ onChangeSpy } + is12Hour + /> + ); + const input = picker + .find( '.components-datetime__time-field-day-input' ) + .at( 0 ); input.simulate( 'change', { target: { value: '10' } } ); input.simulate( 'blur' ); expect( onChangeSpy ).toHaveBeenCalledWith( '1986-10-10T11:00:00' ); @@ -40,8 +56,16 @@ describe( 'TimePicker', () => { it( 'should call onChange with an updated month', () => { const onChangeSpy = jest.fn(); - const picker = shallow( <TimePicker currentTime="1986-10-18T11:00:00" onChange={ onChangeSpy } is12Hour /> ); - const input = picker.find( '.components-datetime__time-field-month-select' ).at( 0 ); + const picker = shallow( + <TimePicker + currentTime="1986-10-18T11:00:00" + onChange={ onChangeSpy } + is12Hour + /> + ); + const input = picker + .find( '.components-datetime__time-field-month-select' ) + .at( 0 ); input.simulate( 'change', { target: { value: '12' } } ); input.simulate( 'blur' ); expect( onChangeSpy ).toHaveBeenCalledWith( '1986-12-18T11:00:00' ); @@ -49,8 +73,16 @@ describe( 'TimePicker', () => { it( 'should call onChange with an updated year', () => { const onChangeSpy = jest.fn(); - const picker = shallow( <TimePicker currentTime="1986-10-18T11:00:00" onChange={ onChangeSpy } is12Hour /> ); - const input = picker.find( '.components-datetime__time-field-year-input' ).at( 0 ); + const picker = shallow( + <TimePicker + currentTime="1986-10-18T11:00:00" + onChange={ onChangeSpy } + is12Hour + /> + ); + const input = picker + .find( '.components-datetime__time-field-year-input' ) + .at( 0 ); input.simulate( 'change', { target: { value: '2018' } } ); input.simulate( 'blur' ); expect( onChangeSpy ).toHaveBeenCalledWith( '2018-10-18T11:00:00' ); @@ -58,8 +90,16 @@ describe( 'TimePicker', () => { it( 'should call onChange with an updated hour (12-hour clock)', () => { const onChangeSpy = jest.fn(); - const picker = shallow( <TimePicker currentTime="1986-10-18T11:00:00" onChange={ onChangeSpy } is12Hour /> ); - const input = picker.find( '.components-datetime__time-field-hours-input' ).at( 0 ); + const picker = shallow( + <TimePicker + currentTime="1986-10-18T11:00:00" + onChange={ onChangeSpy } + is12Hour + /> + ); + const input = picker + .find( '.components-datetime__time-field-hours-input' ) + .at( 0 ); input.simulate( 'change', { target: { value: '10' } } ); input.simulate( 'blur' ); expect( onChangeSpy ).toHaveBeenCalledWith( '1986-10-18T10:00:00' ); @@ -67,8 +107,16 @@ describe( 'TimePicker', () => { it( 'should not call onChange with an updated hour (12-hour clock) if the hour is out of bounds', () => { const onChangeSpy = jest.fn(); - const picker = shallow( <TimePicker currentTime="1986-10-18T11:00:00" onChange={ onChangeSpy } is12Hour /> ); - const input = picker.find( '.components-datetime__time-field-hours-input' ).at( 0 ); + const picker = shallow( + <TimePicker + currentTime="1986-10-18T11:00:00" + onChange={ onChangeSpy } + is12Hour + /> + ); + const input = picker + .find( '.components-datetime__time-field-hours-input' ) + .at( 0 ); input.simulate( 'change', { target: { value: '22' } } ); input.simulate( 'blur' ); expect( onChangeSpy ).not.toHaveBeenCalled(); @@ -76,8 +124,16 @@ describe( 'TimePicker', () => { it( 'should call onChange with an updated hour (24-hour clock)', () => { const onChangeSpy = jest.fn(); - const picker = shallow( <TimePicker currentTime="1986-10-18T11:00:00" onChange={ onChangeSpy } is12Hour={ false } /> ); - const input = picker.find( '.components-datetime__time-field-hours-input' ).at( 0 ); + const picker = shallow( + <TimePicker + currentTime="1986-10-18T11:00:00" + onChange={ onChangeSpy } + is12Hour={ false } + /> + ); + const input = picker + .find( '.components-datetime__time-field-hours-input' ) + .at( 0 ); input.simulate( 'change', { target: { value: '22' } } ); input.simulate( 'blur' ); expect( onChangeSpy ).toHaveBeenCalledWith( '1986-10-18T22:00:00' ); @@ -85,8 +141,16 @@ describe( 'TimePicker', () => { it( 'should call onChange with an updated minute', () => { const onChangeSpy = jest.fn(); - const picker = shallow( <TimePicker currentTime="1986-10-18T11:00:00" onChange={ onChangeSpy } is12Hour /> ); - const input = picker.find( '.components-datetime__time-field-minutes-input' ).at( 0 ); + const picker = shallow( + <TimePicker + currentTime="1986-10-18T11:00:00" + onChange={ onChangeSpy } + is12Hour + /> + ); + const input = picker + .find( '.components-datetime__time-field-minutes-input' ) + .at( 0 ); input.simulate( 'change', { target: { value: '10' } } ); input.simulate( 'blur' ); expect( onChangeSpy ).toHaveBeenCalledWith( '1986-10-18T11:10:00' ); @@ -94,8 +158,16 @@ describe( 'TimePicker', () => { it( 'should not call onChange with an updated minute if out of bounds', () => { const onChangeSpy = jest.fn(); - const picker = shallow( <TimePicker currentTime="1986-10-18T11:00:00" onChange={ onChangeSpy } is12Hour /> ); - const input = picker.find( '.components-datetime__time-field-minutes-input' ).at( 0 ); + const picker = shallow( + <TimePicker + currentTime="1986-10-18T11:00:00" + onChange={ onChangeSpy } + is12Hour + /> + ); + const input = picker + .find( '.components-datetime__time-field-minutes-input' ) + .at( 0 ); input.simulate( 'change', { target: { value: '99' } } ); input.simulate( 'blur' ); expect( onChangeSpy ).not.toHaveBeenCalled(); @@ -103,7 +175,13 @@ describe( 'TimePicker', () => { it( 'should switch to PM correctly', () => { const onChangeSpy = jest.fn(); - const button = shallow( <TimePicker currentTime="1986-10-18T11:00:00" onChange={ onChangeSpy } is12Hour /> ); + const button = shallow( + <TimePicker + currentTime="1986-10-18T11:00:00" + onChange={ onChangeSpy } + is12Hour + /> + ); const pmButton = button.find( '.components-datetime__time-pm-button' ); pmButton.simulate( 'click' ); expect( onChangeSpy ).toHaveBeenCalledWith( '1986-10-18T23:00:00' ); @@ -111,7 +189,13 @@ describe( 'TimePicker', () => { it( 'should switch to AM correctly', () => { const onChangeSpy = jest.fn(); - const button = shallow( <TimePicker currentTime="1986-10-18T23:00:00" onChange={ onChangeSpy } is12Hour /> ); + const button = shallow( + <TimePicker + currentTime="1986-10-18T23:00:00" + onChange={ onChangeSpy } + is12Hour + /> + ); const pmButton = button.find( '.components-datetime__time-am-button' ); pmButton.simulate( 'click' ); expect( onChangeSpy ).toHaveBeenCalledWith( '1986-10-18T11:00:00' ); diff --git a/packages/components/src/date-time/time.js b/packages/components/src/date-time/time.js index 6c7915feff7c48..25955a3335af28 100644 --- a/packages/components/src/date-time/time.js +++ b/packages/components/src/date-time/time.js @@ -107,9 +107,13 @@ class TimePicker extends Component { return; } - const newDate = is12Hour ? - date.clone().hours( am === 'AM' ? value % 12 : ( ( ( value % 12 ) + 12 ) % 24 ) ) : - date.clone().hours( value ); + const newDate = is12Hour + ? date + .clone() + .hours( + am === 'AM' ? value % 12 : ( ( value % 12 ) + 12 ) % 24 + ) + : date.clone().hours( value ); this.changeDate( newDate ); } @@ -165,7 +169,9 @@ class TimePicker extends Component { } let newDate; if ( value === 'PM' ) { - newDate = date.clone().hours( ( ( parseInt( hours, 10 ) % 12 ) + 12 ) % 24 ); + newDate = date + .clone() + .hours( ( ( parseInt( hours, 10 ) % 12 ) + 12 ) % 24 ); } else { newDate = date.clone().hours( parseInt( hours, 10 ) % 12 ); } @@ -192,13 +198,16 @@ class TimePicker extends Component { onChangeMinutes( event ) { const minutes = event.target.value; this.setState( { - minutes: ( minutes === '' ) ? '' : ( '0' + minutes ).slice( -2 ), + minutes: minutes === '' ? '' : ( '0' + minutes ).slice( -2 ), } ); } renderMonth( month ) { return ( - <div key="render-month" className="components-datetime__time-field components-datetime__time-field-month"> + <div + key="render-month" + className="components-datetime__time-field components-datetime__time-field-month" + > <select aria-label={ __( 'Month' ) } className="components-datetime__time-field-month-select" @@ -225,7 +234,10 @@ class TimePicker extends Component { renderDay( day ) { return ( - <div key="render-day" className="components-datetime__time-field components-datetime__time-field-day"> + <div + key="render-day" + className="components-datetime__time-field components-datetime__time-field-day" + > <input aria-label={ __( 'Day' ) } className="components-datetime__time-field-day-input" @@ -252,7 +264,9 @@ class TimePicker extends Component { return ( <div className={ classnames( 'components-datetime__time' ) }> <fieldset> - <legend className="components-datetime__time-legend invisible">{ __( 'Date' ) }</legend> + <legend className="components-datetime__time-legend invisible"> + { __( 'Date' ) } + </legend> <div className="components-datetime__time-wrapper"> { this.renderDayMonthFormat( is12Hour ) } <div className="components-datetime__time-field components-datetime__time-field-year"> @@ -270,7 +284,9 @@ class TimePicker extends Component { </fieldset> <fieldset> - <legend className="components-datetime__time-legend invisible">{ __( 'Time' ) }</legend> + <legend className="components-datetime__time-legend invisible"> + { __( 'Time' ) } + </legend> <div className="components-datetime__time-wrapper"> <div className="components-datetime__time-field components-datetime__time-field-time"> <input @@ -286,7 +302,10 @@ class TimePicker extends Component { /> <span className="components-datetime__time-separator" - aria-hidden="true">:</span> + aria-hidden="true" + > + : + </span> <input aria-label={ __( 'Minutes' ) } className="components-datetime__time-field-minutes-input" diff --git a/packages/components/src/dimension-control/index.js b/packages/components/src/dimension-control/index.js index c69b80f5c49e02..0ed09d86014f52 100644 --- a/packages/components/src/dimension-control/index.js +++ b/packages/components/src/dimension-control/index.js @@ -10,15 +10,10 @@ import { isFunction } from 'lodash'; /** * Internal dependencies */ -import { - Icon, - SelectControl, -} from '../'; +import { Icon, SelectControl } from '../'; import { __ } from '@wordpress/i18n'; -import { - Fragment, -} from '@wordpress/element'; +import { Fragment } from '@wordpress/element'; /** * Internal dependencies @@ -26,7 +21,14 @@ import { import sizesTable, { findSizeBySlug } from './sizes'; export function DimensionControl( props ) { - const { label, value, sizes = sizesTable, icon, onChange, className = '' } = props; + const { + label, + value, + sizes = sizesTable, + icon, + onChange, + className = '', + } = props; const onChangeSpacingSize = ( val ) => { const theSize = findSizeBySlug( sizes, val ); @@ -44,26 +46,27 @@ export function DimensionControl( props ) { value: slug, } ) ); - return [ { - label: __( 'Default' ), - value: '', - } ].concat( options ); + return [ + { + label: __( 'Default' ), + value: '', + }, + ].concat( options ); }; const selectLabel = ( <Fragment> - { icon && ( - <Icon - icon={ icon } - /> - ) } + { icon && <Icon icon={ icon } /> } { label } </Fragment> ); return ( <SelectControl - className={ classnames( className, 'block-editor-dimension-control' ) } + className={ classnames( + className, + 'block-editor-dimension-control' + ) } label={ selectLabel } hideLabelFromVision={ false } value={ value } diff --git a/packages/components/src/dimension-control/sizes.js b/packages/components/src/dimension-control/sizes.js index 41fa0717028725..f1d768894dda21 100644 --- a/packages/components/src/dimension-control/sizes.js +++ b/packages/components/src/dimension-control/sizes.js @@ -20,7 +20,8 @@ import { __ } from '@wordpress/i18n'; * @param {string} slug a string representation of the size (eg: `medium`) * @return {Object} the matching size definition */ -export const findSizeBySlug = ( sizes, slug ) => sizes.find( ( size ) => slug === size.slug ); +export const findSizeBySlug = ( sizes, slug ) => + sizes.find( ( size ) => slug === size.slug ); export default [ { @@ -38,7 +39,8 @@ export default [ { name: __( 'Large' ), slug: 'large', - }, { + }, + { name: __( 'Extra Large' ), slug: 'xlarge', }, diff --git a/packages/components/src/dimension-control/test/index.test.js b/packages/components/src/dimension-control/test/index.test.js index 0b053a493ee123..f1a6d99393b989 100644 --- a/packages/components/src/dimension-control/test/index.test.js +++ b/packages/components/src/dimension-control/test/index.test.js @@ -90,17 +90,23 @@ describe( 'DimensionControl', () => { /> ); - wrapper.find( 'select' ).at( 0 ).simulate( 'change', { - target: { - value: 'small', - }, - } ); + wrapper + .find( 'select' ) + .at( 0 ) + .simulate( 'change', { + target: { + value: 'small', + }, + } ); - wrapper.find( 'select' ).at( 0 ).simulate( 'change', { - target: { - value: 'medium', - }, - } ); + wrapper + .find( 'select' ) + .at( 0 ) + .simulate( 'change', { + target: { + value: 'medium', + }, + } ); expect( onChangeHandler ).toHaveBeenCalledTimes( 2 ); expect( onChangeHandler.mock.calls[ 0 ][ 0 ] ).toEqual( 'small' ); @@ -116,11 +122,14 @@ describe( 'DimensionControl', () => { /> ); - wrapper.find( 'select' ).at( 0 ).simulate( 'change', { - target: { - value: '', // this happens when you select the "default" <option /> - }, - } ); + wrapper + .find( 'select' ) + .at( 0 ) + .simulate( 'change', { + target: { + value: '', // this happens when you select the "default" <option /> + }, + } ); expect( onChangeHandler ).toHaveBeenNthCalledWith( 1, undefined ); } ); diff --git a/packages/components/src/disabled/index.js b/packages/components/src/disabled/index.js index 73c9ecc9349d81..3c93cb359f47bb 100644 --- a/packages/components/src/disabled/index.js +++ b/packages/components/src/disabled/index.js @@ -65,7 +65,9 @@ class Disabled extends Component { disable() { focus.focusable.find( this.node ).forEach( ( focusable ) => { - if ( includes( DISABLED_ELIGIBLE_NODE_NAMES, focusable.nodeName ) ) { + if ( + includes( DISABLED_ELIGIBLE_NODE_NAMES, focusable.nodeName ) + ) { focusable.setAttribute( 'disabled', '' ); } diff --git a/packages/components/src/disabled/test/index.js b/packages/components/src/disabled/test/index.js index 54120a61a61bc9..0ca20e7798f239 100644 --- a/packages/components/src/disabled/test/index.js +++ b/packages/components/src/disabled/test/index.js @@ -25,13 +25,15 @@ jest.mock( '@wordpress/dom', () => { // In JSDOM, all elements have zero'd widths and height. // This is a metric for focusable's `isVisible`, so find // and apply an arbitrary non-zero width. - Array.from( context.querySelectorAll( '*' ) ).forEach( ( element ) => { - Object.defineProperties( element, { - offsetWidth: { - get: () => 1, - }, - } ); - } ); + Array.from( context.querySelectorAll( '*' ) ).forEach( + ( element ) => { + Object.defineProperties( element, { + offsetWidth: { + get: () => 1, + }, + } ); + } + ); return focus.focusable.find( ...arguments ); }, @@ -56,13 +58,28 @@ describe( 'Disabled', () => { window.MutationObserver = MutationObserver; } ); - const Form = () => <form><input /><div contentEditable tabIndex="0" /></form>; + const Form = () => ( + <form> + <input /> + <div contentEditable tabIndex="0" /> + </form> + ); it( 'will disable all fields', () => { - const wrapper = TestUtils.renderIntoDocument( <Disabled><Form /></Disabled> ); - - const input = TestUtils.findRenderedDOMComponentWithTag( wrapper, 'input' ); - const div = TestUtils.scryRenderedDOMComponentsWithTag( wrapper, 'div' )[ 1 ]; + const wrapper = TestUtils.renderIntoDocument( + <Disabled> + <Form /> + </Disabled> + ); + + const input = TestUtils.findRenderedDOMComponentWithTag( + wrapper, + 'input' + ); + const div = TestUtils.scryRenderedDOMComponentsWithTag( + wrapper, + 'div' + )[ 1 ]; expect( input.hasAttribute( 'disabled' ) ).toBe( true ); expect( div.getAttribute( 'contenteditable' ) ).toBe( 'false' ); @@ -83,16 +100,23 @@ describe( 'Disabled', () => { } render() { - return this.state.isDisabled ? - <Disabled><Form /></Disabled> : - <Form />; + return this.state.isDisabled ? ( + <Disabled> + <Form /> + </Disabled> + ) : ( + <Form /> + ); } } const wrapper = TestUtils.renderIntoDocument( <MaybeDisable /> ); wrapper.setState( { isDisabled: false } ); - const input = TestUtils.findRenderedDOMComponentWithTag( wrapper, 'input' ); + const input = TestUtils.findRenderedDOMComponentWithTag( + wrapper, + 'input' + ); const div = TestUtils.findRenderedDOMComponentWithTag( wrapper, 'div' ); expect( input.hasAttribute( 'disabled' ) ).toBe( false ); @@ -115,7 +139,9 @@ describe( 'Disabled', () => { return ( <p> <Disabled.Consumer> - { ( isDisabled ) => isDisabled ? 'Disabled' : 'Not disabled' } + { ( isDisabled ) => + isDisabled ? 'Disabled' : 'Not disabled' + } </Disabled.Consumer> </p> ); @@ -123,14 +149,24 @@ describe( 'Disabled', () => { } test( "lets components know that they're disabled via context", () => { - const wrapper = TestUtils.renderIntoDocument( <Disabled><DisabledStatus /></Disabled> ); - const wrapperElement = TestUtils.findRenderedDOMComponentWithTag( wrapper, 'p' ); + const wrapper = TestUtils.renderIntoDocument( + <Disabled> + <DisabledStatus /> + </Disabled> + ); + const wrapperElement = TestUtils.findRenderedDOMComponentWithTag( + wrapper, + 'p' + ); expect( wrapperElement.textContent ).toBe( 'Disabled' ); } ); test( "lets components know that they're not disabled via context", () => { const wrapper = TestUtils.renderIntoDocument( <DisabledStatus /> ); - const wrapperElement = TestUtils.findRenderedDOMComponentWithTag( wrapper, 'p' ); + const wrapperElement = TestUtils.findRenderedDOMComponentWithTag( + wrapper, + 'p' + ); expect( wrapperElement.textContent ).toBe( 'Not disabled' ); } ); } ); diff --git a/packages/components/src/draggable/index.js b/packages/components/src/draggable/index.js index cd7d8c310e9530..d979bf75aa4778 100644 --- a/packages/components/src/draggable/index.js +++ b/packages/components/src/draggable/index.js @@ -47,10 +47,18 @@ class Draggable extends Component { * @param {Object} event The non-custom DragEvent. */ onDragOver( event ) { - this.cloneWrapper.style.top = - `${ parseInt( this.cloneWrapper.style.top, 10 ) + event.clientY - this.cursorTop }px`; - this.cloneWrapper.style.left = - `${ parseInt( this.cloneWrapper.style.left, 10 ) + event.clientX - this.cursorLeft }px`; + this.cloneWrapper.style.top = `${ parseInt( + this.cloneWrapper.style.top, + 10 + ) + + event.clientY - + this.cursorTop }px`; + this.cloneWrapper.style.left = `${ parseInt( + this.cloneWrapper.style.left, + 10 + ) + + event.clientX - + this.cursorLeft }px`; // Update cursor coordinates. this.cursorLeft = event.clientX; @@ -100,7 +108,8 @@ class Draggable extends Component { clone.id = `clone-${ elementId }`; this.cloneWrapper = document.createElement( 'div' ); this.cloneWrapper.classList.add( cloneWrapperClass ); - this.cloneWrapper.style.width = `${ elementRect.width + ( clonePadding * 2 ) }px`; + this.cloneWrapper.style.width = `${ elementRect.width + + clonePadding * 2 }px`; if ( elementRect.height > cloneHeightTransformationBreakpoint ) { // Scale down clone if original element is larger than 700px. @@ -111,12 +120,16 @@ class Draggable extends Component { this.cloneWrapper.style.left = `${ event.clientX }px`; } else { // Position clone right over the original element (20px padding). - this.cloneWrapper.style.top = `${ elementTopOffset - clonePadding }px`; - this.cloneWrapper.style.left = `${ elementLeftOffset - clonePadding }px`; + this.cloneWrapper.style.top = `${ elementTopOffset - + clonePadding }px`; + this.cloneWrapper.style.left = `${ elementLeftOffset - + clonePadding }px`; } // Hack: Remove iFrames as it's causing the embeds drag clone to freeze - Array.from( clone.querySelectorAll( 'iframe' ) ).forEach( ( child ) => child.parentNode.removeChild( child ) ); + Array.from( clone.querySelectorAll( 'iframe' ) ).forEach( ( child ) => + child.parentNode.removeChild( child ) + ); this.cloneWrapper.appendChild( clone ); elementWrapper.appendChild( this.cloneWrapper ); diff --git a/packages/components/src/draggable/stories/index.js b/packages/components/src/draggable/stories/index.js index e7b60b4c27a6fb..816870dbc63a69 100644 --- a/packages/components/src/draggable/stories/index.js +++ b/packages/components/src/draggable/stories/index.js @@ -35,7 +35,10 @@ const DraggalbeExample = () => { return ( <div> <p>Is Dragging? { isDragging ? 'Yes' : 'No' }</p> - <div id="draggable-example-box" style={ { display: 'inline-flex' } }> + <div + id="draggable-example-box" + style={ { display: 'inline-flex' } } + > <Draggable elementId="draggable-example-box"> { ( { onDraggableStart, onDraggableEnd } ) => { const handleOnDragStart = ( event ) => { diff --git a/packages/components/src/drop-zone/index.js b/packages/components/src/drop-zone/index.js index ab4da28ff5ba34..716b609cdbe63b 100644 --- a/packages/components/src/drop-zone/index.js +++ b/packages/components/src/drop-zone/index.js @@ -70,11 +70,14 @@ function DropZoneComponent( { onDrop, } ) { const element = useRef(); - const { - isDraggingOverDocument, - isDraggingOverElement, - type, - } = useDropZone( { element, onFilesDrop, onHTMLDrop, onDrop } ); + const { isDraggingOverDocument, isDraggingOverElement, type } = useDropZone( + { + element, + onFilesDrop, + onHTMLDrop, + onDrop, + } + ); let children; @@ -94,11 +97,11 @@ function DropZoneComponent( { } const classes = classnames( 'components-drop-zone', className, { - 'is-active': ( isDraggingOverDocument || isDraggingOverElement ) && ( - ( type === 'file' && onFilesDrop ) || - ( type === 'html' && onHTMLDrop ) || - ( type === 'default' && onDrop ) - ), + 'is-active': + ( isDraggingOverDocument || isDraggingOverElement ) && + ( ( type === 'file' && onFilesDrop ) || + ( type === 'html' && onHTMLDrop ) || + ( type === 'default' && onDrop ) ), 'is-dragging-over-document': isDraggingOverDocument, 'is-dragging-over-element': isDraggingOverElement, [ `is-dragging-${ type }` ]: !! type, diff --git a/packages/components/src/drop-zone/provider.js b/packages/components/src/drop-zone/provider.js index 45c0a70749c384..cd71dc000e4ff4 100644 --- a/packages/components/src/drop-zone/provider.js +++ b/packages/components/src/drop-zone/provider.js @@ -34,9 +34,11 @@ const getDragEventType = ( { dataTransfer } ) => { }; const isTypeSupportedByDropZone = ( type, dropZone ) => { - return ( type === 'file' && dropZone.onFilesDrop ) || + return ( + ( type === 'file' && dropZone.onFilesDrop ) || ( type === 'html' && dropZone.onHTMLDrop ) || - ( type === 'default' && dropZone.onDrop ); + ( type === 'default' && dropZone.onDrop ) + ); }; const isWithinElementBounds = ( element, x, y ) => { @@ -46,7 +48,9 @@ const isWithinElementBounds = ( element, x, y ) => { return false; } - return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom; + return ( + x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom + ); }; class DropZoneProvider extends Component { @@ -61,7 +65,10 @@ class DropZoneProvider extends Component { this.removeDropZone = this.removeDropZone.bind( this ); // Utility methods this.resetDragState = this.resetDragState.bind( this ); - this.toggleDraggingOverDocument = throttle( this.toggleDraggingOverDocument.bind( this ), 200 ); + this.toggleDraggingOverDocument = throttle( + this.toggleDraggingOverDocument.bind( this ), + 200 + ); this.dropZones = []; this.dropZoneCallbacks = { @@ -108,12 +115,14 @@ class DropZoneProvider extends Component { position: null, } ); - this.dropZones.forEach( ( dropZone ) => dropZone.setState( { - isDraggingOverDocument: false, - isDraggingOverElement: false, - position: null, - type: null, - } ) ); + this.dropZones.forEach( ( dropZone ) => + dropZone.setState( { + isDraggingOverDocument: false, + isDraggingOverElement: false, + position: null, + type: null, + } ) + ); } toggleDraggingOverDocument( event, dragEventType ) { @@ -123,18 +132,36 @@ class DropZoneProvider extends Component { // as the `detail` property. // // See: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events - const detail = window.CustomEvent && event instanceof window.CustomEvent ? event.detail : event; + const detail = + window.CustomEvent && event instanceof window.CustomEvent + ? event.detail + : event; // Index of hovered dropzone. - const hoveredDropZones = filter( this.dropZones, ( dropZone ) => - isTypeSupportedByDropZone( dragEventType, dropZone ) && - isWithinElementBounds( dropZone.element.current, detail.clientX, detail.clientY ) + const hoveredDropZones = filter( + this.dropZones, + ( dropZone ) => + isTypeSupportedByDropZone( dragEventType, dropZone ) && + isWithinElementBounds( + dropZone.element.current, + detail.clientX, + detail.clientY + ) ); // Find the leaf dropzone not containing another dropzone - const hoveredDropZone = find( hoveredDropZones, ( zone ) => ( - ! some( hoveredDropZones, ( subZone ) => subZone !== zone && zone.element.current.parentElement.contains( subZone.element.current ) ) - ) ); + const hoveredDropZone = find( + hoveredDropZones, + ( zone ) => + ! some( + hoveredDropZones, + ( subZone ) => + subZone !== zone && + zone.element.current.parentElement.contains( + subZone.element.current + ) + ) + ); const hoveredDropZoneIndex = this.dropZones.indexOf( hoveredDropZone ); @@ -169,7 +196,10 @@ class DropZoneProvider extends Component { const index = this.dropZones.indexOf( dropZone ); const isDraggingOverDropZone = index === hoveredDropZoneIndex; dropZone.setState( { - isDraggingOverDocument: isTypeSupportedByDropZone( dragEventType, dropZone ), + isDraggingOverDocument: isTypeSupportedByDropZone( + dragEventType, + dropZone + ), isDraggingOverElement: isDraggingOverDropZone, position: isDraggingOverDropZone ? position : null, type: isDraggingOverDropZone ? dragEventType : null, @@ -204,10 +234,16 @@ class DropZoneProvider extends Component { if ( dropZone ) { switch ( dragEventType ) { case 'file': - dropZone.onFilesDrop( [ ...event.dataTransfer.files ], position ); + dropZone.onFilesDrop( + [ ...event.dataTransfer.files ], + position + ); break; case 'html': - dropZone.onHTMLDrop( event.dataTransfer.getData( 'text/html' ), position ); + dropZone.onHTMLDrop( + event.dataTransfer.getData( 'text/html' ), + position + ); break; case 'default': dropZone.onDrop( event, position ); @@ -220,7 +256,10 @@ class DropZoneProvider extends Component { render() { return ( - <div onDrop={ this.onDrop } className="components-drop-zone__provider"> + <div + onDrop={ this.onDrop } + className="components-drop-zone__provider" + > <Provider value={ this.dropZoneCallbacks }> { this.props.children } </Provider> diff --git a/packages/components/src/dropdown-menu/index.js b/packages/components/src/dropdown-menu/index.js index 8c3b67cc028ca7..1d5f334a585e16 100644 --- a/packages/components/src/dropdown-menu/index.js +++ b/packages/components/src/dropdown-menu/index.js @@ -24,7 +24,10 @@ function mergeProps( defaultProps = {}, props = {} ) { }; if ( props.className && defaultProps.className ) { - mergedProps.className = classnames( props.className, defaultProps.className ); + mergedProps.className = classnames( + props.className, + defaultProps.className + ); } return mergedProps; @@ -70,10 +73,13 @@ function DropdownMenu( { controlSets = [ controlSets ]; } } - const mergedPopoverProps = mergeProps( { - className: 'components-dropdown-menu__popover', - position, - }, popoverProps ); + const mergedPopoverProps = mergeProps( + { + className: 'components-dropdown-menu__popover', + position, + }, + popoverProps + ); return ( <Dropdown @@ -87,11 +93,17 @@ function DropdownMenu( { onToggle(); } }; - const mergedToggleProps = mergeProps( { - className: classnames( 'components-dropdown-menu__toggle', { - 'is-opened': isOpen, - } ), - }, toggleProps ); + const mergedToggleProps = mergeProps( + { + className: classnames( + 'components-dropdown-menu__toggle', + { + 'is-opened': isOpen, + } + ), + }, + toggleProps + ); return ( <Button @@ -114,30 +126,31 @@ function DropdownMenu( { label={ label } showTooltip > - { ( ! icon || hasArrowIndicator ) && <span className="components-dropdown-menu__indicator" /> } + { ( ! icon || hasArrowIndicator ) && ( + <span className="components-dropdown-menu__indicator" /> + ) } </Button> ); } } renderContent={ ( props ) => { - const mergedMenuProps = mergeProps( { - 'aria-label': menuLabel || label, - className: 'components-dropdown-menu__menu', - }, menuProps ); + const mergedMenuProps = mergeProps( + { + 'aria-label': menuLabel || label, + className: 'components-dropdown-menu__menu', + }, + menuProps + ); return ( - <NavigableMenu - { ...mergedMenuProps } - role="menu" - > - { - isFunction( children ) ? - children( props ) : - null - } - { flatMap( controlSets, ( controlSet, indexOfSet ) => ( + <NavigableMenu { ...mergedMenuProps } role="menu"> + { isFunction( children ) ? children( props ) : null } + { flatMap( controlSets, ( controlSet, indexOfSet ) => controlSet.map( ( control, indexOfControl ) => ( <Button - key={ [ indexOfSet, indexOfControl ].join() } + key={ [ + indexOfSet, + indexOfControl, + ].join() } onClick={ ( event ) => { event.stopPropagation(); props.onClose(); @@ -148,19 +161,31 @@ function DropdownMenu( { className={ classnames( 'components-dropdown-menu__menu-item', { - 'has-separator': indexOfSet > 0 && indexOfControl === 0, + 'has-separator': + indexOfSet > 0 && + indexOfControl === 0, 'is-active': control.isActive, - }, + } ) } icon={ control.icon } - aria-checked={ ( control.role === 'menuitemcheckbox' || control.role === 'menuitemradio' ) ? control.isActive : undefined } - role={ ( control.role === 'menuitemcheckbox' || control.role === 'menuitemradio' ) ? control.role : 'menuitem' } + aria-checked={ + control.role === 'menuitemcheckbox' || + control.role === 'menuitemradio' + ? control.isActive + : undefined + } + role={ + control.role === 'menuitemcheckbox' || + control.role === 'menuitemradio' + ? control.role + : 'menuitem' + } disabled={ control.isDisabled } > { control.title } </Button> ) ) - ) ) } + ) } </NavigableMenu> ); } } diff --git a/packages/components/src/dropdown-menu/index.native.js b/packages/components/src/dropdown-menu/index.native.js index 65306eee3081f0..6243847023227e 100644 --- a/packages/components/src/dropdown-menu/index.native.js +++ b/packages/components/src/dropdown-menu/index.native.js @@ -1,4 +1,3 @@ - function DropdownMenu() { return null; } diff --git a/packages/components/src/dropdown-menu/test/index.js b/packages/components/src/dropdown-menu/test/index.js index 6680a501ca9c8f..6486af19dcb1ee 100644 --- a/packages/components/src/dropdown-menu/test/index.js +++ b/packages/components/src/dropdown-menu/test/index.js @@ -58,7 +58,9 @@ describe( 'DropdownMenu', () => { it( 'should open menu on arrow down (controls)', () => { const wrapper = mount( <DropdownMenu controls={ controls } /> ); - const button = wrapper.find( Button ).filter( '.components-dropdown-menu__toggle' ); + const button = wrapper + .find( Button ) + .filter( '.components-dropdown-menu__toggle' ); button.simulate( 'keydown', { stopPropagation: () => {}, @@ -67,12 +69,18 @@ describe( 'DropdownMenu', () => { } ); expect( wrapper.find( NavigableMenu ) ).toHaveLength( 1 ); - expect( wrapper.find( Button ).filter( '.components-dropdown-menu__menu-item' ) ).toHaveLength( controls.length ); + expect( + wrapper + .find( Button ) + .filter( '.components-dropdown-menu__menu-item' ) + ).toHaveLength( controls.length ); } ); it( 'should open menu on arrow down (children)', () => { const wrapper = mount( <DropdownMenu children={ children } /> ); - const button = wrapper.find( Button ).filter( '.components-dropdown-menu__toggle' ); + const button = wrapper + .find( Button ) + .filter( '.components-dropdown-menu__toggle' ); button.simulate( 'keydown', { stopPropagation: () => {}, @@ -82,7 +90,10 @@ describe( 'DropdownMenu', () => { expect( wrapper.find( NavigableMenu ) ).toHaveLength( 1 ); - wrapper.find( MenuItem ).props().onClick(); + wrapper + .find( MenuItem ) + .props() + .onClick(); wrapper.update(); expect( wrapper.find( NavigableMenu ) ).toHaveLength( 0 ); diff --git a/packages/components/src/dropdown/index.js b/packages/components/src/dropdown/index.js index 02de89eda69143..ebb9efb6273e7f 100644 --- a/packages/components/src/dropdown/index.js +++ b/packages/components/src/dropdown/index.js @@ -90,7 +90,10 @@ class Dropdown extends Component { const args = { isOpen, onToggle: this.toggle, onClose: this.close }; return ( - <div className={ classnames( 'components-dropdown', className ) } ref={ this.containerRef }> + <div + className={ classnames( 'components-dropdown', className ) } + ref={ this.containerRef } + > { renderToggle( args ) } { isOpen && ( <Popover diff --git a/packages/components/src/dropdown/index.native.js b/packages/components/src/dropdown/index.native.js index de7c7b7839613a..232a16b6106bcc 100644 --- a/packages/components/src/dropdown/index.native.js +++ b/packages/components/src/dropdown/index.native.js @@ -43,10 +43,7 @@ class Dropdown extends Component { render() { const { isOpen } = this.state; - const { - renderContent, - renderToggle, - } = this.props; + const { renderContent, renderToggle } = this.props; const args = { isOpen, onToggle: this.toggle, onClose: this.close }; diff --git a/packages/components/src/dropdown/test/index.js b/packages/components/src/dropdown/test/index.js index 3de873a76787b8..44e57b56532dbf 100644 --- a/packages/components/src/dropdown/test/index.js +++ b/packages/components/src/dropdown/test/index.js @@ -11,34 +11,39 @@ import Dropdown from '../'; describe( 'Dropdown', () => { const expectPopoverVisible = ( wrapper, visible ) => { expect( - TestUtils.scryRenderedDOMComponentsWithClass( wrapper, 'components-popover' ) ) - .toHaveLength( visible ? 1 : 0 ); + TestUtils.scryRenderedDOMComponentsWithClass( + wrapper, + 'components-popover' + ) + ).toHaveLength( visible ? 1 : 0 ); }; - const buttonElement = ( wrapper ) => TestUtils.findRenderedDOMComponentWithTag( - wrapper, - 'button' - ); - const openCloseElement = ( wrapper, className ) => TestUtils - .findRenderedDOMComponentWithClass( wrapper, className ); + const buttonElement = ( wrapper ) => + TestUtils.findRenderedDOMComponentWithTag( wrapper, 'button' ); + const openCloseElement = ( wrapper, className ) => + TestUtils.findRenderedDOMComponentWithClass( wrapper, className ); it( 'should toggle the dropdown properly', () => { const expectButtonExpanded = ( wrapper, expanded ) => { expect( - TestUtils.scryRenderedDOMComponentsWithTag( wrapper, 'button' ) ) - .toHaveLength( 1 ); + TestUtils.scryRenderedDOMComponentsWithTag( wrapper, 'button' ) + ).toHaveLength( 1 ); expect( buttonElement( wrapper ).getAttribute( 'aria-expanded' ) ).toBe( expanded.toString() ); }; - const wrapper = TestUtils.renderIntoDocument( <Dropdown - className="container" - contentClassName="content" - renderToggle={ ( { isOpen, onToggle } ) => ( - <button aria-expanded={ isOpen } onClick={ onToggle }>Toggleee</button> - ) } - renderContent={ () => <span>test</span> } - /> ); + const wrapper = TestUtils.renderIntoDocument( + <Dropdown + className="container" + contentClassName="content" + renderToggle={ ( { isOpen, onToggle } ) => ( + <button aria-expanded={ isOpen } onClick={ onToggle }> + Toggleee + </button> + ) } + renderContent={ () => <span>test</span> } + /> + ); expectButtonExpanded( wrapper, false ); expectPopoverVisible( wrapper, false ); @@ -50,15 +55,26 @@ describe( 'Dropdown', () => { } ); it( 'should close the dropdown when calling onClose', () => { - const wrapper = TestUtils.renderIntoDocument( <Dropdown - className="container" - contentClassName="content" - renderToggle={ ( { isOpen, onToggle, onClose } ) => [ - <button key="open" className="open" aria-expanded={ isOpen } onClick={ onToggle }>Toggleee</button>, - <button key="close" className="close" onClick={ onClose } >closee</button>, - ] } - renderContent={ () => null } - /> ); + const wrapper = TestUtils.renderIntoDocument( + <Dropdown + className="container" + contentClassName="content" + renderToggle={ ( { isOpen, onToggle, onClose } ) => [ + <button + key="open" + className="open" + aria-expanded={ isOpen } + onClick={ onToggle } + > + Toggleee + </button>, + <button key="close" className="close" onClick={ onClose }> + closee + </button>, + ] } + renderContent={ () => null } + /> + ); expectPopoverVisible( wrapper, false ); diff --git a/packages/components/src/external-link/index.js b/packages/components/src/external-link/index.js index cc9750a4b779c9..23e3f3a537b435 100644 --- a/packages/components/src/external-link/index.js +++ b/packages/components/src/external-link/index.js @@ -16,25 +16,33 @@ import { forwardRef } from '@wordpress/element'; import Dashicon from '../dashicon'; import VisuallyHidden from '../visually-hidden'; -export function ExternalLink( { href, children, className, rel = '', ...additionalProps }, ref ) { - rel = uniq( compact( [ - ...rel.split( ' ' ), - 'external', - 'noreferrer', - 'noopener', - ] ) ).join( ' ' ); +export function ExternalLink( + { href, children, className, rel = '', ...additionalProps }, + ref +) { + rel = uniq( + compact( [ ...rel.split( ' ' ), 'external', 'noreferrer', 'noopener' ] ) + ).join( ' ' ); const classes = classnames( 'components-external-link', className ); return ( - // eslint-disable-next-line react/jsx-no-target-blank - <a { ...additionalProps } className={ classes } href={ href } target="_blank" rel={ rel } ref={ ref }> + <a + { ...additionalProps } + className={ classes } + href={ href } + // eslint-disable-next-line react/jsx-no-target-blank + target="_blank" + rel={ rel } + ref={ ref } + > { children } <VisuallyHidden as="span"> - { - /* translators: accessibility text */ - __( '(opens in a new tab)' ) - } + { /* translators: accessibility text */ + __( '(opens in a new tab)' ) } </VisuallyHidden> - <Dashicon icon="external" className="components-external-link__icon" /> + <Dashicon + icon="external" + className="components-external-link__icon" + /> </a> ); } diff --git a/packages/components/src/focal-point-picker/index.js b/packages/components/src/focal-point-picker/index.js index b0154404226b60..e692a9693d25a2 100644 --- a/packages/components/src/focal-point-picker/index.js +++ b/packages/components/src/focal-point-picker/index.js @@ -31,8 +31,12 @@ export class FocalPointPicker extends Component { }; this.containerRef = createRef(); this.imageRef = createRef(); - this.horizontalPositionChanged = this.horizontalPositionChanged.bind( this ); - this.verticalPositionChanged = this.verticalPositionChanged.bind( this ); + this.horizontalPositionChanged = this.horizontalPositionChanged.bind( + this + ); + this.verticalPositionChanged = this.verticalPositionChanged.bind( + this + ); this.onLoad = this.onLoad.bind( this ); } componentDidUpdate( prevProps ) { @@ -91,19 +95,21 @@ export class FocalPointPicker extends Component { }; const left = Math.max( bounds.left, - Math.min( - cursorPosition.left, bounds.right - ) + Math.min( cursorPosition.left, bounds.right ) ); const top = Math.max( bounds.top, - Math.min( - cursorPosition.top, bounds.bottom - ) + Math.min( cursorPosition.top, bounds.bottom ) ); const percentages = { - x: ( ( left - bounds.left ) / ( pickerDimensions.width - ( bounds.left * 2 ) ) ).toFixed( 2 ), - y: ( ( top - bounds.top ) / ( pickerDimensions.height - ( bounds.top * 2 ) ) ).toFixed( 2 ), + x: ( + ( left - bounds.left ) / + ( pickerDimensions.width - bounds.left * 2 ) + ).toFixed( 2 ), + y: ( + ( top - bounds.top ) / + ( pickerDimensions.height - bounds.top * 2 ) + ).toFixed( 2 ), }; this.setState( { percentages }, function() { onChange( { @@ -126,7 +132,9 @@ export class FocalPointPicker extends Component { const { onChange } = this.props; const { percentages } = this.state; const cleanValue = Math.max( Math.min( parseInt( value ), 100 ), 0 ); - percentages[ axis ] = ( cleanValue ? cleanValue / 100 : 0 ).toFixed( 2 ); + percentages[ axis ] = ( cleanValue ? cleanValue / 100 : 0 ).toFixed( + 2 + ); this.setState( { percentages }, function() { onChange( { x: this.state.percentages.x, @@ -139,7 +147,9 @@ export class FocalPointPicker extends Component { return { width: this.containerRef.current.clientWidth, height: this.containerRef.current.clientHeight, - top: this.containerRef.current.getBoundingClientRect().top + document.body.scrollTop, + top: + this.containerRef.current.getBoundingClientRect().top + + document.body.scrollTop, left: this.containerRef.current.getBoundingClientRect().left, }; } @@ -160,8 +170,12 @@ export class FocalPointPicker extends Component { const { bounds, isDragging, percentages } = this.state; const pickerDimensions = this.pickerDimensions(); const iconCoordinates = { - left: ( value.x * ( pickerDimensions.width - ( bounds.left * 2 ) ) ) + bounds.left, - top: ( value.y * ( pickerDimensions.height - ( bounds.top * 2 ) ) ) + bounds.top, + left: + value.x * ( pickerDimensions.width - bounds.left * 2 ) + + bounds.left, + top: + value.y * ( pickerDimensions.height - bounds.top * 2 ) + + bounds.top, }; const iconContainerStyle = { left: `${ iconCoordinates.left }px`, @@ -175,13 +189,24 @@ export class FocalPointPicker extends Component { const horizontalPositionId = `inspector-focal-point-picker-control-horizontal-position-${ instanceId }`; const verticalPositionId = `inspector-focal-point-picker-control-vertical-position-${ instanceId }`; return ( - <BaseControl label={ label } id={ id } help={ help } className={ className }> + <BaseControl + label={ label } + id={ id } + help={ help } + className={ className } + > <div className="components-focal-point-picker-wrapper"> <div className="components-focal-point-picker" - onMouseDown={ () => this.setState( { isDragging: true } ) } - onDragStart={ () => this.setState( { isDragging: true } ) } - onMouseUp={ () => this.setState( { isDragging: false } ) } + onMouseDown={ () => + this.setState( { isDragging: true } ) + } + onDragStart={ () => + this.setState( { isDragging: true } ) + } + onMouseUp={ () => + this.setState( { isDragging: false } ) + } onDrop={ () => this.setState( { isDragging: false } ) } onMouseMove={ this.onMouseMove } ref={ this.containerRef } @@ -195,16 +220,32 @@ export class FocalPointPicker extends Component { src={ url } draggable="false" /> - <div className={ iconContainerClasses } style={ iconContainerStyle }> - <SVG className="components-focal-point-picker__icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"> - <Path className="components-focal-point-picker__icon-outline" d="M15 1C7.3 1 1 7.3 1 15s6.3 14 14 14 14-6.3 14-14S22.7 1 15 1zm0 22c-4.4 0-8-3.6-8-8s3.6-8 8-8 8 3.6 8 8-3.6 8-8 8z" /> - <Path className="components-focal-point-picker__icon-fill" d="M15 3C8.4 3 3 8.4 3 15s5.4 12 12 12 12-5.4 12-12S21.6 3 15 3zm0 22C9.5 25 5 20.5 5 15S9.5 5 15 5s10 4.5 10 10-4.5 10-10 10z" /> + <div + className={ iconContainerClasses } + style={ iconContainerStyle } + > + <SVG + className="components-focal-point-picker__icon" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 30 30" + > + <Path + className="components-focal-point-picker__icon-outline" + d="M15 1C7.3 1 1 7.3 1 15s6.3 14 14 14 14-6.3 14-14S22.7 1 15 1zm0 22c-4.4 0-8-3.6-8-8s3.6-8 8-8 8 3.6 8 8-3.6 8-8 8z" + /> + <Path + className="components-focal-point-picker__icon-fill" + d="M15 3C8.4 3 3 8.4 3 15s5.4 12 12 12 12-5.4 12-12S21.6 3 15 3zm0 22C9.5 25 5 20.5 5 15S9.5 5 15 5s10 4.5 10 10-4.5 10-10 10z" + /> </SVG> </div> </div> </div> <div className="components-focal-point-picker_position-display-container"> - <BaseControl label={ __( 'Horizontal Pos.' ) } id={ horizontalPositionId }> + <BaseControl + label={ __( 'Horizontal Pos.' ) } + id={ horizontalPositionId } + > <input className="components-text-control__input" id={ horizontalPositionId } @@ -216,7 +257,10 @@ export class FocalPointPicker extends Component { /> <span>%</span> </BaseControl> - <BaseControl label={ __( 'Vertical Pos.' ) } id={ verticalPositionId }> + <BaseControl + label={ __( 'Vertical Pos.' ) } + id={ verticalPositionId } + > <input className="components-text-control__input" id={ verticalPositionId } @@ -243,4 +287,6 @@ FocalPointPicker.defaultProps = { onChange: () => {}, }; -export default compose( [ withInstanceId, withFocusOutside ] )( FocalPointPicker ); +export default compose( [ withInstanceId, withFocusOutside ] )( + FocalPointPicker +); diff --git a/packages/components/src/font-size-picker/index.js b/packages/components/src/font-size-picker/index.js index ed5691d8336b3b..01c11c9af0ce85 100644 --- a/packages/components/src/font-size-picker/index.js +++ b/packages/components/src/font-size-picker/index.js @@ -1,4 +1,3 @@ - /** * WordPress dependencies */ @@ -18,7 +17,9 @@ const CUSTOM_FONT_SIZE = 'custom'; function getSelectValueFromFontSize( fontSizes, value ) { if ( value ) { - const fontSizeValue = fontSizes.find( ( font ) => font.size === Number( value ) ); + const fontSizeValue = fontSizes.find( + ( font ) => font.size === Number( value ) + ); return fontSizeValue ? fontSizeValue.slug : CUSTOM_FONT_SIZE; } return DEFAULT_FONT_SIZE; @@ -28,15 +29,15 @@ function getSelectOptions( optionsArray, disableCustomFontSizes ) { optionsArray = [ { slug: DEFAULT_FONT_SIZE, name: __( 'Default' ) }, ...optionsArray, - ...disableCustomFontSizes ? [] : [ { slug: CUSTOM_FONT_SIZE, name: __( 'Custom' ) } ], + ...( disableCustomFontSizes + ? [] + : [ { slug: CUSTOM_FONT_SIZE, name: __( 'Custom' ) } ] ), ]; - return optionsArray.map( ( option ) => ( - { - key: option.slug, - name: option.name, - style: { fontSize: option.size }, - } - ) ); + return optionsArray.map( ( option ) => ( { + key: option.slug, + name: option.name, + style: { fontSize: option.size }, + } ) ); } export default function FontSizePicker( { @@ -48,7 +49,9 @@ export default function FontSizePicker( { withSlider = false, } ) { const instanceId = useInstanceId( FontSizePicker ); - const [ currentSelectValue, setCurrentSelectValue ] = useState( getSelectValueFromFontSize( fontSizes, value ) ); + const [ currentSelectValue, setCurrentSelectValue ] = useState( + getSelectValueFromFontSize( fontSizes, value ) + ); if ( disableCustomFontSizes && ! fontSizes.length ) { return null; @@ -105,15 +108,18 @@ export default function FontSizePicker( { label={ __( 'Preset Size' ) } options={ options } value={ - options.find( ( option ) => option.key === currentSelectValue ) || - options[ 0 ] + options.find( + ( option ) => option.key === currentSelectValue + ) || options[ 0 ] } onChange={ onSelectChangeValue } /> ) } - { ( ! withSlider && ! disableCustomFontSizes ) && + { ! withSlider && ! disableCustomFontSizes && ( <div className="components-range-control__number-container"> - <label htmlFor={ rangeControlNumberId }>{ __( 'Custom' ) }</label> + <label htmlFor={ rangeControlNumberId }> + { __( 'Custom' ) } + </label> <input id={ rangeControlNumberId } className="components-range-control__number" @@ -123,7 +129,7 @@ export default function FontSizePicker( { value={ value || '' } /> </div> - } + ) } <Button className="components-color-palette__clear" disabled={ value === undefined } @@ -134,7 +140,7 @@ export default function FontSizePicker( { { __( 'Reset' ) } </Button> </div> - { withSlider && + { withSlider && ( <RangeControl className="components-font-size-picker__custom-input" label={ __( 'Custom Size' ) } @@ -146,7 +152,7 @@ export default function FontSizePicker( { beforeIcon="editor-textcolor" afterIcon="editor-textcolor" /> - } + ) } </fieldset> ); } diff --git a/packages/components/src/font-size-picker/stories/index.js b/packages/components/src/font-size-picker/stories/index.js index 21baaca669930e..b90fc69a0b1520 100644 --- a/packages/components/src/font-size-picker/stories/index.js +++ b/packages/components/src/font-size-picker/stories/index.js @@ -13,7 +13,10 @@ import { useState } from '@wordpress/element'; */ import FontSizePicker from '../'; -export default { title: 'Components/FontSizePicker', component: FontSizePicker }; +export default { + title: 'Components/FontSizePicker', + component: FontSizePicker, +}; const FontSizePickerWithState = ( { ...props } ) => { const [ fontSize, setFontSize ] = useState( 16 ); @@ -45,11 +48,7 @@ export const _default = () => { size: 26, }, ] ); - return ( - <FontSizePickerWithState - fontSizes={ fontSizes } - /> - ); + return <FontSizePickerWithState fontSizes={ fontSizes } />; }; export const withSlider = () => { diff --git a/packages/components/src/form-file-upload/index.js b/packages/components/src/form-file-upload/index.js index 28ce218c3ac6a9..d998bcbbf60938 100644 --- a/packages/components/src/form-file-upload/index.js +++ b/packages/components/src/form-file-upload/index.js @@ -33,15 +33,13 @@ class FormFileUpload extends Component { ...props } = this.props; - const ui = render ? - render( { openFileDialog: this.openFileDialog } ) : ( - <Button - onClick={ this.openFileDialog } - { ...props } - > - { children } - </Button> - ); + const ui = render ? ( + render( { openFileDialog: this.openFileDialog } ) + ) : ( + <Button onClick={ this.openFileDialog } { ...props }> + { children } + </Button> + ); return ( <div className="components-form-file-upload"> { ui } diff --git a/packages/components/src/form-toggle/index.js b/packages/components/src/form-toggle/index.js index 63650f194c3612..3dfeaf375cf197 100644 --- a/packages/components/src/form-toggle/index.js +++ b/packages/components/src/form-toggle/index.js @@ -10,11 +10,9 @@ import { noop } from 'lodash'; import { Path, SVG } from '@wordpress/primitives'; function FormToggle( { className, checked, id, onChange = noop, ...props } ) { - const wrapperClasses = classnames( - 'components-form-toggle', - className, - { 'is-checked': checked }, - ); + const wrapperClasses = classnames( 'components-form-toggle', className, { + 'is-checked': checked, + } ); return ( <span className={ wrapperClasses }> @@ -28,10 +26,30 @@ function FormToggle( { className, checked, id, onChange = noop, ...props } ) { /> <span className="components-form-toggle__track"></span> <span className="components-form-toggle__thumb"></span> - { checked ? - <SVG className="components-form-toggle__on" width="2" height="6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 6"><Path d="M0 0h2v6H0z" /></SVG> : - <SVG className="components-form-toggle__off" width="6" height="6" aria-hidden="true" role="img" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 6 6"><Path d="M3 1.5c.8 0 1.5.7 1.5 1.5S3.8 4.5 3 4.5 1.5 3.8 1.5 3 2.2 1.5 3 1.5M3 0C1.3 0 0 1.3 0 3s1.3 3 3 3 3-1.3 3-3-1.3-3-3-3z" /></SVG> - } + { checked ? ( + <SVG + className="components-form-toggle__on" + width="2" + height="6" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 2 6" + > + <Path d="M0 0h2v6H0z" /> + </SVG> + ) : ( + <SVG + className="components-form-toggle__off" + width="6" + height="6" + aria-hidden="true" + role="img" + focusable="false" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 6 6" + > + <Path d="M3 1.5c.8 0 1.5.7 1.5 1.5S3.8 4.5 3 4.5 1.5 3.8 1.5 3 2.2 1.5 3 1.5M3 0C1.3 0 0 1.3 0 3s1.3 3 3 3 3-1.3 3-3-1.3-3-3-3z" /> + </SVG> + ) } </span> ); } diff --git a/packages/components/src/form-toggle/test/index.js b/packages/components/src/form-toggle/test/index.js index 14cae967bfb78a..8d64abb164746d 100644 --- a/packages/components/src/form-toggle/test/index.js +++ b/packages/components/src/form-toggle/test/index.js @@ -13,7 +13,9 @@ describe( 'FormToggle', () => { describe( 'basic rendering', () => { it( 'should render a span element with an unchecked checkbox', () => { const formToggle = shallow( <FormToggle /> ); - expect( formToggle.hasClass( 'components-form-toggle' ) ).toBe( true ); + expect( formToggle.hasClass( 'components-form-toggle' ) ).toBe( + true + ); expect( formToggle.hasClass( 'is-checked' ) ).toBe( false ); expect( formToggle.type() ).toBe( 'span' ); } ); @@ -21,7 +23,11 @@ describe( 'FormToggle', () => { it( 'should render a checked checkbox and change the accessibility text to On when providing checked prop', () => { const formToggle = shallow( <FormToggle checked /> ); expect( formToggle.hasClass( 'is-checked' ) ).toBe( true ); - expect( formToggle.find( '.components-form-toggle__input' ).prop( 'checked' ) ).toBe( true ); + expect( + formToggle + .find( '.components-form-toggle__input' ) + .prop( 'checked' ) + ).toBe( true ); } ); it( 'should render with an additional className', () => { @@ -34,19 +40,35 @@ describe( 'FormToggle', () => { // `withInstanceId`. In this case, it's fine to use literal IDs. // eslint-disable-next-line no-restricted-syntax const formToggle = shallow( <FormToggle id="test" /> ); - expect( formToggle.find( '.components-form-toggle__input' ).prop( 'id' ) ).toBe( 'test' ); + expect( + formToggle.find( '.components-form-toggle__input' ).prop( 'id' ) + ).toBe( 'test' ); } ); it( 'should render a checkbox with a noop onChange', () => { const formToggle = shallow( <FormToggle /> ); - const checkBox = formToggle.prop( 'children' ).find( ( child ) => 'input' === child.type && 'checkbox' === child.props.type ); + const checkBox = formToggle + .prop( 'children' ) + .find( + ( child ) => + 'input' === child.type && + 'checkbox' === child.props.type + ); expect( checkBox.props.onChange ).toBe( noop ); } ); it( 'should render a checkbox with a user-provided onChange', () => { const testFunction = ( event ) => event; - const formToggle = shallow( <FormToggle onChange={ testFunction } /> ); - const checkBox = formToggle.prop( 'children' ).find( ( child ) => 'input' === child.type && 'checkbox' === child.props.type ); + const formToggle = shallow( + <FormToggle onChange={ testFunction } /> + ); + const checkBox = formToggle + .prop( 'children' ) + .find( + ( child ) => + 'input' === child.type && + 'checkbox' === child.props.type + ); expect( checkBox.props.onChange ).toBe( testFunction ); } ); } ); diff --git a/packages/components/src/form-token-field/index.js b/packages/components/src/form-token-field/index.js index bdb8871f0a5aa3..b2c81977a50fbc 100644 --- a/packages/components/src/form-token-field/index.js +++ b/packages/components/src/form-token-field/index.js @@ -1,7 +1,17 @@ /** * External dependencies */ -import { last, take, clone, uniq, map, difference, each, identity, some } from 'lodash'; +import { + last, + take, + clone, + uniq, + map, + difference, + each, + identity, + some, +} from 'lodash'; import classnames from 'classnames'; /** @@ -10,7 +20,17 @@ import classnames from 'classnames'; import { __, _n, sprintf } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { withInstanceId } from '@wordpress/compose'; -import { BACKSPACE, ENTER, UP, DOWN, LEFT, RIGHT, SPACE, DELETE, ESCAPE } from '@wordpress/keycodes'; +import { + BACKSPACE, + ENTER, + UP, + DOWN, + LEFT, + RIGHT, + SPACE, + DELETE, + ESCAPE, +} from '@wordpress/keycodes'; import isShallowEqual from '@wordpress/is-shallow-equal'; /** @@ -59,7 +79,10 @@ class FormTokenField extends Component { } const { suggestions, value } = this.props; - const suggestionsDidUpdate = ! isShallowEqual( suggestions, prevProps.suggestions ); + const suggestionsDidUpdate = ! isShallowEqual( + suggestions, + prevProps.suggestions + ); if ( suggestionsDidUpdate || value !== prevProps.value ) { this.updateSuggestions( suggestionsDidUpdate ); } @@ -115,7 +138,9 @@ class FormTokenField extends Component { switch ( event.keyCode ) { case BACKSPACE: - preventDefault = this.handleDeleteKey( this.deleteTokenBeforeInput ); + preventDefault = this.handleDeleteKey( + this.deleteTokenBeforeInput + ); break; case ENTER: preventDefault = this.addCurrentToken(); @@ -133,7 +158,9 @@ class FormTokenField extends Component { preventDefault = this.handleDownArrowKey(); break; case DELETE: - preventDefault = this.handleDeleteKey( this.deleteTokenAfterInput ); + preventDefault = this.handleDeleteKey( + this.deleteTokenAfterInput + ); break; case SPACE: if ( this.props.tokenizeOnSpace ) { @@ -206,7 +233,10 @@ class FormTokenField extends Component { this.addNewTokens( items.slice( 0, -1 ) ); } - this.setState( { incompleteTokenValue: tokenValue }, this.updateSuggestions ); + this.setState( + { incompleteTokenValue: tokenValue }, + this.updateSuggestions + ); this.props.onInputChange( tokenValue ); } @@ -243,15 +273,16 @@ class FormTokenField extends Component { handleUpArrowKey() { this.setState( ( state, props ) => ( { - selectedSuggestionIndex: ( - ( state.selectedSuggestionIndex === 0 ? this.getMatchingSuggestions( - state.incompleteTokenValue, - props.suggestions, - props.value, - props.maxSuggestions, - props.saveTransform - ).length : state.selectedSuggestionIndex ) - 1 - ), + selectedSuggestionIndex: + ( state.selectedSuggestionIndex === 0 + ? this.getMatchingSuggestions( + state.incompleteTokenValue, + props.suggestions, + props.value, + props.maxSuggestions, + props.saveTransform + ).length + : state.selectedSuggestionIndex ) - 1, selectedSuggestionScroll: true, } ) ); @@ -260,15 +291,15 @@ class FormTokenField extends Component { handleDownArrowKey() { this.setState( ( state, props ) => ( { - selectedSuggestionIndex: ( - ( state.selectedSuggestionIndex + 1 ) % this.getMatchingSuggestions( + selectedSuggestionIndex: + ( state.selectedSuggestionIndex + 1 ) % + this.getMatchingSuggestions( state.incompleteTokenValue, props.suggestions, props.value, props.maxSuggestions, props.saveTransform - ).length - ), + ).length, selectedSuggestionScroll: true, } ) ); @@ -301,7 +332,10 @@ class FormTokenField extends Component { moveInputBeforePreviousToken() { this.setState( ( state, props ) => ( { - inputOffsetFromEnd: Math.min( state.inputOffsetFromEnd + 1, props.value.length ), + inputOffsetFromEnd: Math.min( + state.inputOffsetFromEnd + 1, + props.value.length + ), } ) ); } @@ -399,7 +433,7 @@ class FormTokenField extends Component { suggestions = this.props.suggestions, value = this.props.value, maxSuggestions = this.props.maxSuggestions, - saveTransform = this.props.saveTransform, + saveTransform = this.props.saveTransform ) { let match = saveTransform( searchValue ); const startsWithMatch = []; @@ -429,7 +463,9 @@ class FormTokenField extends Component { getSelectedSuggestion() { if ( this.state.selectedSuggestionIndex !== -1 ) { - return this.getMatchingSuggestions()[ this.state.selectedSuggestionIndex ]; + return this.getMatchingSuggestions()[ + this.state.selectedSuggestionIndex + ]; } } @@ -448,14 +484,19 @@ class FormTokenField extends Component { } inputHasValidValue() { - return this.props.saveTransform( this.state.incompleteTokenValue ).length > 0; + return ( + this.props.saveTransform( this.state.incompleteTokenValue ).length > + 0 + ); } updateSuggestions( resetSelectedSuggestion = true ) { const { incompleteTokenValue } = this.state; const inputHasMinimumChars = incompleteTokenValue.trim().length > 1; - const matchingSuggestions = this.getMatchingSuggestions( incompleteTokenValue ); + const matchingSuggestions = this.getMatchingSuggestions( + incompleteTokenValue + ); const hasMatchingSuggestions = matchingSuggestions.length > 0; const newState = { @@ -471,13 +512,16 @@ class FormTokenField extends Component { if ( inputHasMinimumChars ) { const { debouncedSpeak } = this.props; - const message = hasMatchingSuggestions ? - sprintf( _n( - '%d result found, use up and down arrow keys to navigate.', - '%d results found, use up and down arrow keys to navigate.', - matchingSuggestions.length - ), matchingSuggestions.length ) : - __( 'No results.' ); + const message = hasMatchingSuggestions + ? sprintf( + _n( + '%d result found, use up and down arrow keys to navigate.', + '%d results found, use up and down arrow keys to navigate.', + matchingSuggestions.length + ), + matchingSuggestions.length + ) + : __( 'No results.' ); debouncedSpeak( message, 'assertive' ); } @@ -516,7 +560,13 @@ class FormTokenField extends Component { } renderInput() { - const { autoCapitalize, autoComplete, maxLength, value, instanceId } = this.props; + const { + autoCapitalize, + autoComplete, + maxLength, + value, + instanceId, + } = this.props; let props = { instanceId, @@ -535,9 +585,7 @@ class FormTokenField extends Component { props = { ...props, onChange: this.onInputChange }; } - return ( - <TokenInput { ...props } /> - ); + return <TokenInput { ...props } />; } render() { @@ -548,10 +596,14 @@ class FormTokenField extends Component { className, } = this.props; const { isExpanded } = this.state; - const classes = classnames( className, 'components-form-token-field__input-container', { - 'is-active': this.state.isActive, - 'is-disabled': disabled, - } ); + const classes = classnames( + className, + 'components-form-token-field__input-container', + { + 'is-active': this.state.isActive, + 'is-disabled': disabled, + } + ); let tokenFieldProps = { className: 'components-form-token-field', @@ -579,7 +631,8 @@ class FormTokenField extends Component { > { label } </label> - <div ref={ this.bindTokensAndInput } + <div + ref={ this.bindTokensAndInput } className={ classes } tabIndex="-1" onMouseDown={ this.onContainerTouched } @@ -589,21 +642,29 @@ class FormTokenField extends Component { { isExpanded && ( <SuggestionsList instanceId={ instanceId } - match={ this.props.saveTransform( this.state.incompleteTokenValue ) } + match={ this.props.saveTransform( + this.state.incompleteTokenValue + ) } displayTransform={ this.props.displayTransform } suggestions={ matchingSuggestions } selectedIndex={ this.state.selectedSuggestionIndex } - scrollIntoView={ this.state.selectedSuggestionScroll } + scrollIntoView={ + this.state.selectedSuggestionScroll + } onHover={ this.onSuggestionHovered } onSelect={ this.onSuggestionSelected } /> ) } </div> - <p id={ `components-form-token-suggestions-howto-${ instanceId }` } className="components-form-token-field__help"> - { this.props.tokenizeOnSpace ? - __( 'Separate with commas, spaces, or the Enter key.' ) : - __( 'Separate with commas or the Enter key.' ) - } + <p + id={ `components-form-token-suggestions-howto-${ instanceId }` } + className="components-form-token-field__help" + > + { this.props.tokenizeOnSpace + ? __( + 'Separate with commas, spaces, or the Enter key.' + ) + : __( 'Separate with commas or the Enter key.' ) } </p> </div> ); diff --git a/packages/components/src/form-token-field/suggestions-list.js b/packages/components/src/form-token-field/suggestions-list.js index 2d7e64f84be7d0..a44e263ff79b1a 100644 --- a/packages/components/src/form-token-field/suggestions-list.js +++ b/packages/components/src/form-token-field/suggestions-list.js @@ -23,9 +23,13 @@ class SuggestionsList extends Component { // when already expanded if ( this.props.selectedIndex > -1 && this.props.scrollIntoView ) { this.scrollingIntoView = true; - scrollIntoView( this.list.children[ this.props.selectedIndex ], this.list, { - onlyScrollIfNeeded: true, - } ); + scrollIntoView( + this.list.children[ this.props.selectedIndex ], + this.list, + { + onlyScrollIfNeeded: true, + } + ); this.props.setTimeout( () => { this.scrollingIntoView = false; @@ -57,7 +61,9 @@ class SuggestionsList extends Component { } computeSuggestionMatch( suggestion ) { - const match = this.props.displayTransform( this.props.match || '' ).toLocaleLowerCase(); + const match = this.props + .displayTransform( this.props.match || '' ) + .toLocaleLowerCase(); if ( match.length === 0 ) { return null; } @@ -67,8 +73,13 @@ class SuggestionsList extends Component { return { suggestionBeforeMatch: suggestion.substring( 0, indexOfMatch ), - suggestionMatch: suggestion.substring( indexOfMatch, indexOfMatch + match.length ), - suggestionAfterMatch: suggestion.substring( indexOfMatch + match.length ), + suggestionMatch: suggestion.substring( + indexOfMatch, + indexOfMatch + match.length + ), + suggestionAfterMatch: suggestion.substring( + indexOfMatch + match.length + ), }; } @@ -84,42 +95,46 @@ class SuggestionsList extends Component { id={ `components-form-token-suggestions-${ this.props.instanceId }` } role="listbox" > - { - map( this.props.suggestions, ( suggestion, index ) => { - const match = this.computeSuggestionMatch( suggestion ); - const classeName = classnames( 'components-form-token-field__suggestion', { + { map( this.props.suggestions, ( suggestion, index ) => { + const match = this.computeSuggestionMatch( suggestion ); + const classeName = classnames( + 'components-form-token-field__suggestion', + { 'is-selected': index === this.props.selectedIndex, - } ); + } + ); - /* eslint-disable jsx-a11y/click-events-have-key-events */ - return ( - <li - id={ `components-form-token-suggestions-${ this.props.instanceId }-${ index }` } - role="option" - className={ classeName } - key={ suggestion } - onMouseDown={ this.handleMouseDown } - onClick={ this.handleClick( suggestion ) } - onMouseEnter={ this.handleHover( suggestion ) } - aria-selected={ index === this.props.selectedIndex } - > - { match ? - ( - <span aria-label={ this.props.displayTransform( suggestion ) }> - { match.suggestionBeforeMatch } - <strong className="components-form-token-field__suggestion-match"> - { match.suggestionMatch } - </strong> - { match.suggestionAfterMatch } - </span> - ) : - this.props.displayTransform( suggestion ) - } - </li> - ); - /* eslint-enable jsx-a11y/click-events-have-key-events */ - } ) - } + /* eslint-disable jsx-a11y/click-events-have-key-events */ + return ( + <li + id={ `components-form-token-suggestions-${ this.props.instanceId }-${ index }` } + role="option" + className={ classeName } + key={ suggestion } + onMouseDown={ this.handleMouseDown } + onClick={ this.handleClick( suggestion ) } + onMouseEnter={ this.handleHover( suggestion ) } + aria-selected={ index === this.props.selectedIndex } + > + { match ? ( + <span + aria-label={ this.props.displayTransform( + suggestion + ) } + > + { match.suggestionBeforeMatch } + <strong className="components-form-token-field__suggestion-match"> + { match.suggestionMatch } + </strong> + { match.suggestionAfterMatch } + </span> + ) : ( + this.props.displayTransform( suggestion ) + ) } + </li> + ); + /* eslint-enable jsx-a11y/click-events-have-key-events */ + } ) } </ul> ); } diff --git a/packages/components/src/form-token-field/test/index.js b/packages/components/src/form-token-field/test/index.js index 3bfcf6e5baf0d7..064fab1ca061fe 100644 --- a/packages/components/src/form-token-field/test/index.js +++ b/packages/components/src/form-token-field/test/index.js @@ -35,40 +35,35 @@ describe( 'FormTokenField', function() { let wrapper, wrapperElement, textInputElement, textInputComponent; function setText( text ) { - TestUtils.Simulate.change( - textInputElement(), - { - target: { - value: text, - }, - } - ); + TestUtils.Simulate.change( textInputElement(), { + target: { + value: text, + }, + } ); } function sendKeyDown( keyCode, shiftKey ) { - TestUtils.Simulate.keyDown( - wrapperElement(), - { - keyCode, - shiftKey: ! ! shiftKey, - } - ); + TestUtils.Simulate.keyDown( wrapperElement(), { + keyCode, + shiftKey: !! shiftKey, + } ); } function sendKeyPress( charCode ) { - TestUtils.Simulate.keyPress( - wrapperElement(), - { charCode } - ); + TestUtils.Simulate.keyPress( wrapperElement(), { charCode } ); } function getTokensHTML() { - const textNodes = wrapperElement().querySelectorAll( '.components-form-token-field__token-text span[aria-hidden]' ); + const textNodes = wrapperElement().querySelectorAll( + '.components-form-token-field__token-text span[aria-hidden]' + ); return map( textNodes, ( node ) => node.innerHTML ); } function getSuggestionsText( selector ) { - const suggestionNodes = wrapperElement().querySelectorAll( selector || '.components-form-token-field__suggestion' ); + const suggestionNodes = wrapperElement().querySelectorAll( + selector || '.components-form-token-field__suggestion' + ); return map( suggestionNodes, getSuggestionNodeText ); } @@ -93,25 +88,24 @@ describe( 'FormTokenField', function() { } function getSelectedSuggestion() { - const selectedSuggestions = getSuggestionsText( '.components-form-token-field__suggestion.is-selected' ); + const selectedSuggestions = getSuggestionsText( + '.components-form-token-field__suggestion.is-selected' + ); return selectedSuggestions[ 0 ] || null; } beforeEach( function() { - wrapper = TestUtils.renderIntoDocument( - <TokenFieldWrapper />, - ); + wrapper = TestUtils.renderIntoDocument( <TokenFieldWrapper /> ); /* eslint-disable react/no-find-dom-node */ wrapperElement = () => ReactDOM.findDOMNode( wrapper ); - textInputElement = () => TestUtils.findRenderedDOMComponentWithClass( - wrapper, - 'components-form-token-field__input' - ); - textInputComponent = () => TestUtils.findRenderedComponentWithType( - wrapper, - TokenInput - ); + textInputElement = () => + TestUtils.findRenderedDOMComponentWithClass( + wrapper, + 'components-form-token-field__input' + ); + textInputComponent = () => + TestUtils.findRenderedComponentWithType( wrapper, TokenInput ); /* eslint-enable react/no-find-dom-node */ TestUtils.Simulate.focus( textInputElement() ); } ); @@ -129,7 +123,9 @@ describe( 'FormTokenField', function() { tokens: fixtures.specialTokens.textEscaped, isExpanded: true, } ); - expect( getTokensHTML() ).toEqual( fixtures.specialTokens.htmlEscaped ); + expect( getTokensHTML() ).toEqual( + fixtures.specialTokens.htmlEscaped + ); } ); it( 'should display tokens with special characters properly', function() { @@ -143,7 +139,9 @@ describe( 'FormTokenField', function() { tokens: fixtures.specialTokens.textUnescaped, isExpanded: true, } ); - expect( getTokensHTML() ).toEqual( fixtures.specialTokens.htmlUnescaped ); + expect( getTokensHTML() ).toEqual( + fixtures.specialTokens.htmlUnescaped + ); } ); } ); @@ -154,7 +152,9 @@ describe( 'FormTokenField', function() { } ); expect( getSuggestionsText() ).toEqual( [] ); setText( 'th' ); - expect( getSuggestionsText() ).toEqual( fixtures.matchingSuggestions.th ); + expect( getSuggestionsText() ).toEqual( + fixtures.matchingSuggestions.th + ); } ); it( 'should remove already added tags from suggestions', function() { @@ -169,7 +169,9 @@ describe( 'FormTokenField', function() { isExpanded: true, } ); setText( 'so' ); - expect( getSuggestionsText() ).toEqual( fixtures.matchingSuggestions.so ); + expect( getSuggestionsText() ).toEqual( + fixtures.matchingSuggestions.so + ); } ); it( 'should match against the unescaped values of suggestions with special characters', function() { @@ -178,7 +180,9 @@ describe( 'FormTokenField', function() { isExpanded: true, } ); setText( '& S' ); - expect( getSuggestionsText() ).toEqual( fixtures.specialSuggestions.matchAmpersandUnescaped ); + expect( getSuggestionsText() ).toEqual( + fixtures.specialSuggestions.matchAmpersandUnescaped + ); } ); it( 'should match against the unescaped values of suggestions with special characters (including spaces)', function() { @@ -187,7 +191,9 @@ describe( 'FormTokenField', function() { isExpanded: true, } ); setText( 's &' ); - expect( getSuggestionsText() ).toEqual( fixtures.specialSuggestions.matchAmpersandSequence ); + expect( getSuggestionsText() ).toEqual( + fixtures.specialSuggestions.matchAmpersandSequence + ); } ); it( 'should not match against the escaped values of suggestions with special characters', function() { @@ -196,7 +202,9 @@ describe( 'FormTokenField', function() { tokenSuggestions: fixtures.specialSuggestions.textUnescaped, isExpanded: true, } ); - expect( getSuggestionsText() ).toEqual( fixtures.specialSuggestions.matchAmpersandEscaped ); + expect( getSuggestionsText() ).toEqual( + fixtures.specialSuggestions.matchAmpersandEscaped + ); } ); it( 'should match suggestions even with trailing spaces', function() { @@ -204,7 +212,9 @@ describe( 'FormTokenField', function() { isExpanded: true, } ); setText( ' at ' ); - expect( getSuggestionsText() ).toEqual( fixtures.matchingSuggestions.at ); + expect( getSuggestionsText() ).toEqual( + fixtures.matchingSuggestions.at + ); } ); it( 'should manage the selected suggestion based on both keyboard and mouse events', function() { @@ -212,16 +222,22 @@ describe( 'FormTokenField', function() { isExpanded: true, } ); setText( 'th' ); - expect( getSuggestionsText() ).toEqual( fixtures.matchingSuggestions.th ); + expect( getSuggestionsText() ).toEqual( + fixtures.matchingSuggestions.th + ); expect( getSelectedSuggestion() ).toBe( null ); sendKeyDown( keyCodes.downArrow ); // 'the' expect( getSelectedSuggestion() ).toEqual( [ 'th', 'e' ] ); sendKeyDown( keyCodes.downArrow ); // 'that' expect( getSelectedSuggestion() ).toEqual( [ 'th', 'at' ] ); - const hoverSuggestion = wrapperElement() - .querySelectorAll( '.components-form-token-field__suggestion' )[ 3 ]; // 'with' - expect( getSuggestionNodeText( hoverSuggestion ) ).toEqual( [ 'wi', 'th' ] ); + const hoverSuggestion = wrapperElement().querySelectorAll( + '.components-form-token-field__suggestion' + )[ 3 ]; // 'with' + expect( getSuggestionNodeText( hoverSuggestion ) ).toEqual( [ + 'wi', + 'th', + ] ); // before sending a hover event, we need to wait for // SuggestionList#_scrollingIntoView to become false @@ -250,7 +266,9 @@ describe( 'FormTokenField', function() { wrapper.setState( { tokenSuggestions: fixtures.specialSuggestions.default, } ); - expect( getSuggestionsText() ).toEqual( fixtures.matchingSuggestions.so ); + expect( getSuggestionsText() ).toEqual( + fixtures.matchingSuggestions.so + ); wrapper.setState( { tokenSuggestions: [], @@ -311,7 +329,9 @@ describe( 'FormTokenField', function() { describe( 'removing tokens', function() { it( 'should remove tokens when X icon clicked', function() { - const forClickNode = wrapperElement().querySelector( '.components-form-token-field__remove-token' ).firstChild; + const forClickNode = wrapperElement().querySelector( + '.components-form-token-field__remove-token' + ).firstChild; TestUtils.Simulate.click( forClickNode ); expect( wrapper.state.tokens ).toEqual( [ 'bar' ] ); } ); diff --git a/packages/components/src/form-token-field/test/lib/fixtures.js b/packages/components/src/form-token-field/test/lib/fixtures.js index 6811275377788d..12a61bfd62aed2 100644 --- a/packages/components/src/form-token-field/test/lib/fixtures.js +++ b/packages/components/src/form-token-field/test/lib/fixtures.js @@ -1,18 +1,68 @@ export default { specialTokens: { textEscaped: [ 'a b', 'i &lt;3 tags', '1&amp;2&amp;3&amp;4' ], - htmlEscaped: [ 'a&nbsp;&nbsp;&nbsp;b', 'i&nbsp;&lt;3&nbsp;tags', '1&amp;2&amp;3&amp;4' ], + htmlEscaped: [ + 'a&nbsp;&nbsp;&nbsp;b', + 'i&nbsp;&lt;3&nbsp;tags', + '1&amp;2&amp;3&amp;4', + ], textUnescaped: [ 'a b', 'i <3 tags', '1&2&3&4' ], - htmlUnescaped: [ 'a&nbsp;&nbsp;&nbsp;b', 'i&nbsp;&lt;3&nbsp;tags', '1&amp;2&amp;3&amp;4' ], + htmlUnescaped: [ + 'a&nbsp;&nbsp;&nbsp;b', + 'i&nbsp;&lt;3&nbsp;tags', + '1&amp;2&amp;3&amp;4', + ], }, specialSuggestions: { default: [ - 'the', 'of', 'and', 'to', 'a', 'in', 'for', 'is', 'on', 'that', 'by', 'this', 'with', 'i', 'you', 'it', - 'not', 'or', 'be', 'are', 'from', 'at', 'as', 'your', 'all', 'have', 'new', 'more', 'an', 'was', 'we', - 'associate', 'snake', 'pipes', 'sound', + 'the', + 'of', + 'and', + 'to', + 'a', + 'in', + 'for', + 'is', + 'on', + 'that', + 'by', + 'this', + 'with', + 'i', + 'you', + 'it', + 'not', + 'or', + 'be', + 'are', + 'from', + 'at', + 'as', + 'your', + 'all', + 'have', + 'new', + 'more', + 'an', + 'was', + 'we', + 'associate', + 'snake', + 'pipes', + 'sound', + ], + textEscaped: [ + '&lt;3', + 'Stuff &amp; Things', + 'Tags &amp; Stuff', + 'Tags &amp; Stuff 2', + ], + textUnescaped: [ + '<3', + 'Stuff & Things', + 'Tags & Stuff', + 'Tags & Stuff 2', ], - textEscaped: [ '&lt;3', 'Stuff &amp; Things', 'Tags &amp; Stuff', 'Tags &amp; Stuff 2' ], - textUnescaped: [ '<3', 'Stuff & Things', 'Tags & Stuff', 'Tags & Stuff 2' ], matchAmpersandUnescaped: [ [ 'Tags ', '& S', 'tuff' ], [ 'Tags ', '& S', 'tuff 2' ], @@ -34,10 +84,6 @@ export default { [ 'so', 'und' ], [ 'as', 'so', 'ciate' ], ], - at: [ - [ 'at' ], - [ 'th', 'at' ], - [ 'associ', 'at', 'e' ], - ], + at: [ [ 'at' ], [ 'th', 'at' ], [ 'associ', 'at', 'e' ] ], }, }; diff --git a/packages/components/src/form-token-field/test/lib/token-field-wrapper.js b/packages/components/src/form-token-field/test/lib/token-field-wrapper.js index 12dc95581b4336..8262b86f1a6bb2 100644 --- a/packages/components/src/form-token-field/test/lib/token-field-wrapper.js +++ b/packages/components/src/form-token-field/test/lib/token-field-wrapper.js @@ -14,7 +14,9 @@ import { Component } from '@wordpress/element'; import fixtures from './fixtures'; import TokenField from '../../'; -const { specialSuggestions: { default: suggestions } } = fixtures; +const { + specialSuggestions: { default: suggestions }, +} = fixtures; function unescapeAndFormatSpaces( str ) { const nbsp = String.fromCharCode( 160 ); @@ -35,7 +37,9 @@ class TokenFieldWrapper extends Component { render() { return ( <TokenField - suggestions={ this.state.isExpanded ? this.state.tokenSuggestions : null } + suggestions={ + this.state.isExpanded ? this.state.tokenSuggestions : null + } value={ this.state.tokens } displayTransform={ unescapeAndFormatSpaces } onChange={ this.onTokensChange } diff --git a/packages/components/src/form-token-field/token-input.js b/packages/components/src/form-token-field/token-input.js index 3f2fd4e2dec5e4..a92dfa3fe37571 100644 --- a/packages/components/src/form-token-field/token-input.js +++ b/packages/components/src/form-token-field/token-input.js @@ -29,7 +29,13 @@ class TokenInput extends Component { } render() { - const { value, isExpanded, instanceId, selectedSuggestionIndex, ...props } = this.props; + const { + value, + isExpanded, + instanceId, + selectedSuggestionIndex, + ...props + } = this.props; const size = value.length + 1; return ( @@ -45,8 +51,16 @@ class TokenInput extends Component { role="combobox" aria-expanded={ isExpanded } aria-autocomplete="list" - aria-owns={ isExpanded ? `components-form-token-suggestions-${ instanceId }` : undefined } - aria-activedescendant={ selectedSuggestionIndex !== -1 ? `components-form-token-suggestions-${ instanceId }-${ selectedSuggestionIndex }` : undefined } + aria-owns={ + isExpanded + ? `components-form-token-suggestions-${ instanceId }` + : undefined + } + aria-activedescendant={ + selectedSuggestionIndex !== -1 + ? `components-form-token-suggestions-${ instanceId }-${ selectedSuggestionIndex }` + : undefined + } aria-describedby={ `components-form-token-suggestions-howto-${ instanceId }` } /> ); diff --git a/packages/components/src/form-token-field/token.js b/packages/components/src/form-token-field/token.js index 211cf2fdcc1434..276490e675f7c5 100644 --- a/packages/components/src/form-token-field/token.js +++ b/packages/components/src/form-token-field/token.js @@ -61,7 +61,9 @@ export default function Token( { className="components-form-token-field__token-text" id={ `components-form-token-field__token-text-${ instanceId }` } > - <VisuallyHidden as="span">{ termPositionAndCount }</VisuallyHidden> + <VisuallyHidden as="span"> + { termPositionAndCount } + </VisuallyHidden> <span aria-hidden="true">{ transformedValue }</span> </span> diff --git a/packages/components/src/gradient-picker/index.js b/packages/components/src/gradient-picker/index.js index 875db188bbaf0e..2f44ebe6d4b07f 100644 --- a/packages/components/src/gradient-picker/index.js +++ b/packages/components/src/gradient-picker/index.js @@ -23,53 +23,52 @@ export default function GradientPicker( { clearable = true, disableCustomGradients = false, } ) { - const clearGradient = useCallback( - () => onChange( undefined ), - [ onChange ] - ); - const gradientOptions = useMemo( - () => { - return map( gradients, ( { gradient, name } ) => ( - <CircularOptionPicker.Option - key={ gradient } - value={ gradient } - isSelected={ value === gradient } - tooltipText={ name || - // translators: %s: gradient code e.g: "linear-gradient(90deg, rgba(98,16,153,1) 0%, rgba(172,110,22,1) 100%);". - sprintf( __( 'Gradient code: %s' ), gradient ) - } - style={ { color: 'rgba( 0,0,0,0 )', background: gradient } } - onClick={ - value === gradient ? - clearGradient : - () => ( onChange( gradient ) ) - } - aria-label={ name ? - // translators: %s: The name of the gradient e.g: "Angular red to blue". - sprintf( __( 'Gradient: %s' ), name ) : - // translators: %s: gradient code e.g: "linear-gradient(90deg, rgba(98,16,153,1) 0%, rgba(172,110,22,1) 100%);". - sprintf( __( 'Gradient code: %s' ), gradient ) - } - /> - ) ); - }, - [ gradients, value, onChange, clearGradient ] - ); + const clearGradient = useCallback( () => onChange( undefined ), [ + onChange, + ] ); + const gradientOptions = useMemo( () => { + return map( gradients, ( { gradient, name } ) => ( + <CircularOptionPicker.Option + key={ gradient } + value={ gradient } + isSelected={ value === gradient } + tooltipText={ + name || + // translators: %s: gradient code e.g: "linear-gradient(90deg, rgba(98,16,153,1) 0%, rgba(172,110,22,1) 100%);". + sprintf( __( 'Gradient code: %s' ), gradient ) + } + style={ { color: 'rgba( 0,0,0,0 )', background: gradient } } + onClick={ + value === gradient + ? clearGradient + : () => onChange( gradient ) + } + aria-label={ + name + ? // translators: %s: The name of the gradient e.g: "Angular red to blue". + sprintf( __( 'Gradient: %s' ), name ) + : // translators: %s: gradient code e.g: "linear-gradient(90deg, rgba(98,16,153,1) 0%, rgba(172,110,22,1) 100%);". + sprintf( __( 'Gradient code: %s' ), gradient ) + } + /> + ) ); + }, [ gradients, value, onChange, clearGradient ] ); return ( <CircularOptionPicker className={ className } options={ gradientOptions } - actions={ clearable && ( - <CircularOptionPicker.ButtonAction onClick={ clearGradient }> - { __( 'Clear' ) } - </CircularOptionPicker.ButtonAction> - ) } + actions={ + clearable && ( + <CircularOptionPicker.ButtonAction + onClick={ clearGradient } + > + { __( 'Clear' ) } + </CircularOptionPicker.ButtonAction> + ) + } > { ! disableCustomGradients && ( - <CustomGradientPicker - value={ value } - onChange={ onChange } - /> + <CustomGradientPicker value={ value } onChange={ onChange } /> ) } </CircularOptionPicker> ); diff --git a/packages/components/src/guide/icons.js b/packages/components/src/guide/icons.js index f6813ed214e430..7c589cc2af926a 100644 --- a/packages/components/src/guide/icons.js +++ b/packages/components/src/guide/icons.js @@ -19,6 +19,11 @@ export const ForwardButtonIcon = () => ( export const PageControlIcon = ( { isSelected } ) => ( <SVG width="12" height="12" fill="none" xmlns="http://www.w3.org/2000/svg"> - <Circle cx="6" cy="6" r="6" fill={ isSelected ? '#419ECD' : '#E1E3E6' } /> + <Circle + cx="6" + cy="6" + r="6" + fill={ isSelected ? '#419ECD' : '#E1E3E6' } + /> </SVG> ); diff --git a/packages/components/src/guide/index.js b/packages/components/src/guide/index.js index 650f9b82d311a4..857df38df0ffde 100644 --- a/packages/components/src/guide/index.js +++ b/packages/components/src/guide/index.js @@ -19,7 +19,13 @@ import PageControl from './page-control'; import { BackButtonIcon, ForwardButtonIcon } from './icons'; import FinishButton from './finish-button'; -export default function Guide( { children, className, contentLabel, finishButtonText, onFinish } ) { +export default function Guide( { + children, + className, + contentLabel, + finishButtonText, + onFinish, +} ) { const [ currentPage, setCurrentPage ] = useState( 0 ); const numberOfPages = Children.count( children ); @@ -48,14 +54,15 @@ export default function Guide( { children, className, contentLabel, finishButton contentLabel={ contentLabel } onRequestClose={ onFinish } > - - <KeyboardShortcuts key={ currentPage } shortcuts={ { - left: goBack, - right: goForward, - } } /> + <KeyboardShortcuts + key={ currentPage } + shortcuts={ { + left: goBack, + right: goForward, + } } + /> <div className="components-guide__container"> - { children[ currentPage ] } { ! canGoForward && ( @@ -100,9 +107,7 @@ export default function Guide( { children, className, contentLabel, finishButton </FinishButton> ) } </div> - </div> - </Modal> ); } diff --git a/packages/components/src/guide/page-control.js b/packages/components/src/guide/page-control.js index ce2461f41bbbba..837a0b31e7d5ad 100644 --- a/packages/components/src/guide/page-control.js +++ b/packages/components/src/guide/page-control.js @@ -14,9 +14,16 @@ import { __, sprintf } from '@wordpress/i18n'; import Button from '../button'; import { PageControlIcon } from './icons'; -export default function PageControl( { currentPage, numberOfPages, setCurrentPage } ) { +export default function PageControl( { + currentPage, + numberOfPages, + setCurrentPage, +} ) { return ( - <ul className="components-guide__page-control" aria-label={ __( 'Guide controls' ) }> + <ul + className="components-guide__page-control" + aria-label={ __( 'Guide controls' ) } + > { times( numberOfPages, ( page ) => ( <li key={ page } @@ -25,9 +32,17 @@ export default function PageControl( { currentPage, numberOfPages, setCurrentPag > <Button key={ page } - icon={ <PageControlIcon isSelected={ page === currentPage } /> } + icon={ + <PageControlIcon + isSelected={ page === currentPage } + /> + } /* translators: %1$d: current page number %2$d: total number of pages */ - aria-label={ sprintf( __( 'Page %1$d of %2$d' ), page + 1, numberOfPages ) } + aria-label={ sprintf( + __( 'Page %1$d of %2$d' ), + page + 1, + numberOfPages + ) } onClick={ () => setCurrentPage( page ) } /> </li> diff --git a/packages/components/src/guide/test/index.js b/packages/components/src/guide/test/index.js index bb1fad16aa3a7a..7739987e502beb 100644 --- a/packages/components/src/guide/test/index.js +++ b/packages/components/src/guide/test/index.js @@ -35,9 +35,15 @@ describe( 'Guide', () => { </Guide> ); expect( wrapper.find( PageControl ).prop( 'currentPage' ) ).toBe( 0 ); - expect( wrapper.find( '.components-guide__back-button' ) ).toHaveLength( 0 ); - expect( wrapper.find( '.components-guide__forward-button' ) ).toHaveLength( 1 ); - expect( wrapper.find( '.components-guide__finish-button' ) ).toHaveLength( 0 ); + expect( wrapper.find( '.components-guide__back-button' ) ).toHaveLength( + 0 + ); + expect( + wrapper.find( '.components-guide__forward-button' ) + ).toHaveLength( 1 ); + expect( + wrapper.find( '.components-guide__finish-button' ) + ).toHaveLength( 0 ); } ); it( 'shows back button and shows finish button on the last page', () => { @@ -49,9 +55,15 @@ describe( 'Guide', () => { ); wrapper.find( '.components-guide__forward-button' ).simulate( 'click' ); expect( wrapper.find( PageControl ).prop( 'currentPage' ) ).toBe( 1 ); - expect( wrapper.find( '.components-guide__back-button' ) ).toHaveLength( 1 ); - expect( wrapper.find( '.components-guide__forward-button' ) ).toHaveLength( 0 ); - expect( wrapper.find( '.components-guide__finish-button' ) ).toHaveLength( 1 ); + expect( wrapper.find( '.components-guide__back-button' ) ).toHaveLength( + 1 + ); + expect( + wrapper.find( '.components-guide__forward-button' ) + ).toHaveLength( 0 ); + expect( + wrapper.find( '.components-guide__finish-button' ) + ).toHaveLength( 1 ); } ); it( 'calls onFinish when the finish button is clicked', () => { diff --git a/packages/components/src/guide/test/page-control.js b/packages/components/src/guide/test/page-control.js index c75fd24e41e5fb..6c4ebce377b825 100644 --- a/packages/components/src/guide/test/page-control.js +++ b/packages/components/src/guide/test/page-control.js @@ -15,12 +15,16 @@ import PageControl from '../page-control'; describe( 'PageControl', () => { it( 'renders an empty list when there are no pages', () => { - const wrapper = shallow( <PageControl currentPage={ 0 } numberOfPages={ 0 } /> ); + const wrapper = shallow( + <PageControl currentPage={ 0 } numberOfPages={ 0 } /> + ); expect( wrapper.find( Button ) ).toHaveLength( 0 ); } ); it( 'renders a button for each page', () => { - const wrapper = shallow( <PageControl currentPage={ 0 } numberOfPages={ 5 } /> ); + const wrapper = shallow( + <PageControl currentPage={ 0 } numberOfPages={ 5 } /> + ); expect( wrapper.find( Button ) ).toHaveLength( 5 ); } ); @@ -33,7 +37,10 @@ describe( 'PageControl', () => { setCurrentPage={ setCurrentPage } /> ); - wrapper.find( Button ).at( 1 ).simulate( 'click' ); + wrapper + .find( Button ) + .at( 1 ) + .simulate( 'click' ); expect( setCurrentPage ).toHaveBeenCalledWith( 1 ); } ); } ); diff --git a/packages/components/src/higher-order/navigate-regions/index.js b/packages/components/src/higher-order/navigate-regions/index.js index 91f2584510b40b..06af54a1baa811 100644 --- a/packages/components/src/higher-order/navigate-regions/index.js +++ b/packages/components/src/higher-order/navigate-regions/index.js @@ -7,7 +7,10 @@ import classnames from 'classnames'; * WordPress dependencies */ import { useCallback, useState, useRef } from '@wordpress/element'; -import { createHigherOrderComponent, useKeyboardShortcut } from '@wordpress/compose'; +import { + createHigherOrderComponent, + useKeyboardShortcut, +} from '@wordpress/compose'; import { rawShortcut } from '@wordpress/keycodes'; const defaultShortcuts = { @@ -15,50 +18,54 @@ const defaultShortcuts = { next: [ 'ctrl+`', rawShortcut.access( 'n' ) ], }; -export default createHigherOrderComponent( - ( WrappedComponent ) => { - return ( { shortcuts = defaultShortcuts, ...props } ) => { - const container = useRef(); - const [ isFocusingRegions, setIsFocusingRegions ] = useState( false ); - const className = classnames( 'components-navigate-regions', { - 'is-focusing-regions': isFocusingRegions, - } ); +export default createHigherOrderComponent( ( WrappedComponent ) => { + return ( { shortcuts = defaultShortcuts, ...props } ) => { + const container = useRef(); + const [ isFocusingRegions, setIsFocusingRegions ] = useState( false ); + const className = classnames( 'components-navigate-regions', { + 'is-focusing-regions': isFocusingRegions, + } ); - function focusRegion( offset ) { - const regions = Array.from( container.current.querySelectorAll( '[role="region"]' ) ); - if ( ! regions.length ) { - return; - } - let nextRegion = regions[ 0 ]; - const selectedIndex = regions.indexOf( document.activeElement ); - if ( selectedIndex !== -1 ) { - let nextIndex = selectedIndex + offset; - nextIndex = nextIndex === -1 ? regions.length - 1 : nextIndex; - nextIndex = nextIndex === regions.length ? 0 : nextIndex; - nextRegion = regions[ nextIndex ]; - } - - nextRegion.focus(); - setIsFocusingRegions( true ); + function focusRegion( offset ) { + const regions = Array.from( + container.current.querySelectorAll( '[role="region"]' ) + ); + if ( ! regions.length ) { + return; + } + let nextRegion = regions[ 0 ]; + const selectedIndex = regions.indexOf( document.activeElement ); + if ( selectedIndex !== -1 ) { + let nextIndex = selectedIndex + offset; + nextIndex = nextIndex === -1 ? regions.length - 1 : nextIndex; + nextIndex = nextIndex === regions.length ? 0 : nextIndex; + nextRegion = regions[ nextIndex ]; } - const focusPrevious = useCallback( () => focusRegion( -1 ), [ container ] ); - const focusNext = useCallback( () => focusRegion( 1 ), [ container ] ); - useKeyboardShortcut( shortcuts.previous, focusPrevious, { bindGlobal: true } ); - useKeyboardShortcut( shortcuts.next, focusNext, { bindGlobal: true } ); + nextRegion.focus(); + setIsFocusingRegions( true ); + } + const focusPrevious = useCallback( () => focusRegion( -1 ), [ + container, + ] ); + const focusNext = useCallback( () => focusRegion( 1 ), [ container ] ); - // Disable reason: Clicking the editor should dismiss the regions focus style - /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ - return ( - <div - ref={ container } - className={ className } - onClick={ () => setIsFocusingRegions( false ) } - > - <WrappedComponent { ...props } /> - </div> - ); - /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ - }; - }, 'navigateRegions' -); + useKeyboardShortcut( shortcuts.previous, focusPrevious, { + bindGlobal: true, + } ); + useKeyboardShortcut( shortcuts.next, focusNext, { bindGlobal: true } ); + + // Disable reason: Clicking the editor should dismiss the regions focus style + /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ + return ( + <div + ref={ container } + className={ className } + onClick={ () => setIsFocusingRegions( false ) } + > + <WrappedComponent { ...props } /> + </div> + ); + /* eslint-enable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ + }; +}, 'navigateRegions' ); diff --git a/packages/components/src/higher-order/with-constrained-tabbing/index.js b/packages/components/src/higher-order/with-constrained-tabbing/index.js index 75745cc9d02429..d3e372d8655de1 100644 --- a/packages/components/src/higher-order/with-constrained-tabbing/index.js +++ b/packages/components/src/higher-order/with-constrained-tabbing/index.js @@ -7,58 +7,64 @@ import { TAB } from '@wordpress/keycodes'; import { focus } from '@wordpress/dom'; const withConstrainedTabbing = createHigherOrderComponent( - ( WrappedComponent ) => class extends Component { - constructor() { - super( ...arguments ); + ( WrappedComponent ) => + class extends Component { + constructor() { + super( ...arguments ); - this.focusContainRef = createRef(); - this.handleTabBehaviour = this.handleTabBehaviour.bind( this ); - } - - handleTabBehaviour( event ) { - if ( event.keyCode !== TAB ) { - return; + this.focusContainRef = createRef(); + this.handleTabBehaviour = this.handleTabBehaviour.bind( this ); } - const tabbables = focus.tabbable.find( this.focusContainRef.current ); - if ( ! tabbables.length ) { - return; - } - const firstTabbable = tabbables[ 0 ]; - const lastTabbable = tabbables[ tabbables.length - 1 ]; + handleTabBehaviour( event ) { + if ( event.keyCode !== TAB ) { + return; + } - if ( event.shiftKey && event.target === firstTabbable ) { - event.preventDefault(); - lastTabbable.focus(); - } else if ( ! event.shiftKey && event.target === lastTabbable ) { - event.preventDefault(); - firstTabbable.focus(); - /* - * When pressing Tab and none of the tabbables has focus, the keydown - * event happens on the wrapper div: move focus on the first tabbable. - */ - } else if ( ! tabbables.includes( event.target ) ) { - event.preventDefault(); - firstTabbable.focus(); + const tabbables = focus.tabbable.find( + this.focusContainRef.current + ); + if ( ! tabbables.length ) { + return; + } + const firstTabbable = tabbables[ 0 ]; + const lastTabbable = tabbables[ tabbables.length - 1 ]; + + if ( event.shiftKey && event.target === firstTabbable ) { + event.preventDefault(); + lastTabbable.focus(); + } else if ( + ! event.shiftKey && + event.target === lastTabbable + ) { + event.preventDefault(); + firstTabbable.focus(); + /* + * When pressing Tab and none of the tabbables has focus, the keydown + * event happens on the wrapper div: move focus on the first tabbable. + */ + } else if ( ! tabbables.includes( event.target ) ) { + event.preventDefault(); + firstTabbable.focus(); + } } - } - render() { - // Disable reason: this component is non-interactive, but must capture - // events from the wrapped component to determine when the Tab key is used. - /* eslint-disable jsx-a11y/no-static-element-interactions */ - return ( - <div - onKeyDown={ this.handleTabBehaviour } - ref={ this.focusContainRef } - tabIndex="-1" - > - <WrappedComponent { ...this.props } /> - </div> - ); - /* eslint-enable jsx-a11y/no-static-element-interactions */ - } - }, + render() { + // Disable reason: this component is non-interactive, but must capture + // events from the wrapped component to determine when the Tab key is used. + /* eslint-disable jsx-a11y/no-static-element-interactions */ + return ( + <div + onKeyDown={ this.handleTabBehaviour } + ref={ this.focusContainRef } + tabIndex="-1" + > + <WrappedComponent { ...this.props } /> + </div> + ); + /* eslint-enable jsx-a11y/no-static-element-interactions */ + } + }, 'withConstrainedTabbing' ); diff --git a/packages/components/src/higher-order/with-fallback-styles/index.js b/packages/components/src/higher-order/with-fallback-styles/index.js index 0771f567bd474f..c5ce92c758141f 100644 --- a/packages/components/src/higher-order/with-fallback-styles/index.js +++ b/packages/components/src/higher-order/with-fallback-styles/index.js @@ -9,8 +9,8 @@ import { every, isEqual } from 'lodash'; import { Component } from '@wordpress/element'; import { createHigherOrderComponent } from '@wordpress/compose'; -export default ( mapNodeToProps ) => createHigherOrderComponent( - ( WrappedComponent ) => { +export default ( mapNodeToProps ) => + createHigherOrderComponent( ( WrappedComponent ) => { return class extends Component { constructor() { super( ...arguments ); @@ -41,7 +41,10 @@ export default ( mapNodeToProps ) => createHigherOrderComponent( grabFallbackStyles() { const { grabStylesCompleted, fallbackStyles } = this.state; if ( this.nodeRef && ! grabStylesCompleted ) { - const newFallbackStyles = mapNodeToProps( this.nodeRef, this.props ); + const newFallbackStyles = mapNodeToProps( + this.nodeRef, + this.props + ); if ( ! isEqual( newFallbackStyles, fallbackStyles ) ) { this.setState( { fallbackStyles: newFallbackStyles, @@ -52,10 +55,17 @@ export default ( mapNodeToProps ) => createHigherOrderComponent( } render() { - const wrappedComponent = <WrappedComponent { ...this.props } { ...this.state.fallbackStyles } />; - return this.props.node ? wrappedComponent : <div ref={ this.bindRef }> { wrappedComponent } </div>; + const wrappedComponent = ( + <WrappedComponent + { ...this.props } + { ...this.state.fallbackStyles } + /> + ); + return this.props.node ? ( + wrappedComponent + ) : ( + <div ref={ this.bindRef }> { wrappedComponent } </div> + ); } }; - }, - 'withFallbackStyles' -); + }, 'withFallbackStyles' ); diff --git a/packages/components/src/higher-order/with-filters/test/index.js b/packages/components/src/higher-order/with-filters/test/index.js index 68aecd9685e80b..3b45c480a0b41b 100644 --- a/packages/components/src/higher-order/with-filters/test/index.js +++ b/packages/components/src/higher-order/with-filters/test/index.js @@ -41,8 +41,10 @@ describe( 'withFilters', () => { afterEach( () => { if ( wrapper ) { - // eslint-disable-next-line react/no-find-dom-node - ReactDOM.unmountComponentAtNode( ReactDOM.findDOMNode( wrapper ).parentNode ); + ReactDOM.unmountComponentAtNode( + // eslint-disable-next-line react/no-find-dom-node + ReactDOM.findDOMNode( wrapper ).parentNode + ); } if ( shallowWrapper ) { shallowWrapper.unmount(); @@ -69,7 +71,9 @@ describe( 'withFilters', () => { shallowWrapper = shallow( <EnhancedComponent /> ); - expect( shallowWrapper.html() ).toBe( '<div>Overridden component</div>' ); + expect( shallowWrapper.html() ).toBe( + '<div>Overridden component</div>' + ); } ); it( 'should display two components composed by the filter', () => { @@ -88,7 +92,9 @@ describe( 'withFilters', () => { shallowWrapper = shallow( <EnhancedComponent /> ); - expect( shallowWrapper.html() ).toBe( '<div><div>My component</div><div>Composed component</div></div>' ); + expect( shallowWrapper.html() ).toBe( + '<div><div>My component</div><div>Composed component</div></div>' + ); } ); it( 'should not re-render component when new filter added before component was mounted', () => { @@ -104,11 +110,13 @@ describe( 'withFilters', () => { <blockquote> <FilteredComponent /> </blockquote> - ), + ) ); const EnhancedComponent = withFilters( hookName )( SpiedComponent ); - wrapper = TestUtils.renderIntoDocument( getTestComponent( EnhancedComponent ) ); + wrapper = TestUtils.renderIntoDocument( + getTestComponent( EnhancedComponent ) + ); jest.runAllTimers(); @@ -127,7 +135,9 @@ describe( 'withFilters', () => { }; const EnhancedComponent = withFilters( hookName )( SpiedComponent ); - wrapper = TestUtils.renderIntoDocument( getTestComponent( EnhancedComponent ) ); + wrapper = TestUtils.renderIntoDocument( + getTestComponent( EnhancedComponent ) + ); spy.mockClear(); addFilter( @@ -137,7 +147,7 @@ describe( 'withFilters', () => { <blockquote> <FilteredComponent /> </blockquote> - ), + ) ); jest.runAllTimers(); @@ -155,7 +165,9 @@ describe( 'withFilters', () => { return <div>Spied component</div>; }; const EnhancedComponent = withFilters( hookName )( SpiedComponent ); - wrapper = TestUtils.renderIntoDocument( getTestComponent( EnhancedComponent ) ); + wrapper = TestUtils.renderIntoDocument( + getTestComponent( EnhancedComponent ) + ); spy.mockClear(); @@ -166,7 +178,7 @@ describe( 'withFilters', () => { <blockquote> <FilteredComponent /> </blockquote> - ), + ) ); addFilter( hookName, @@ -175,7 +187,7 @@ describe( 'withFilters', () => { <section> <FilteredComponent /> </section> - ), + ) ); jest.runAllTimers(); @@ -193,7 +205,9 @@ describe( 'withFilters', () => { return <div>Spied component</div>; }; const EnhancedComponent = withFilters( hookName )( SpiedComponent ); - wrapper = TestUtils.renderIntoDocument( getTestComponent( EnhancedComponent ) ); + wrapper = TestUtils.renderIntoDocument( + getTestComponent( EnhancedComponent ) + ); spy.mockClear(); addFilter( @@ -203,21 +217,15 @@ describe( 'withFilters', () => { <div> <FilteredComponent /> </div> - ), + ) ); jest.runAllTimers(); - removeFilter( - hookName, - 'test/enhanced-component-spy', - ); + removeFilter( hookName, 'test/enhanced-component-spy' ); jest.runAllTimers(); expect( spy ).toHaveBeenCalledTimes( 2 ); - assertExpectedHtml( - wrapper, - '<div>Spied component</div>' - ); + assertExpectedHtml( wrapper, '<div>Spied component</div>' ); } ); it( 'should re-render both components once each when one filter added', () => { @@ -233,7 +241,9 @@ describe( 'withFilters', () => { <EnhancedComponent /> </section> ); - wrapper = TestUtils.renderIntoDocument( getTestComponent( CombinedComponents ) ); + wrapper = TestUtils.renderIntoDocument( + getTestComponent( CombinedComponents ) + ); spy.mockClear(); addFilter( @@ -243,7 +253,7 @@ describe( 'withFilters', () => { <blockquote> <FilteredComponent /> </blockquote> - ), + ) ); jest.runAllTimers(); diff --git a/packages/components/src/higher-order/with-focus-outside/index.js b/packages/components/src/higher-order/with-focus-outside/index.js index 5f178b4a34df28..ca966eb048af37 100644 --- a/packages/components/src/higher-order/with-focus-outside/index.js +++ b/packages/components/src/higher-order/with-focus-outside/index.js @@ -15,10 +15,7 @@ import { createHigherOrderComponent } from '@wordpress/compose'; * * @type {string[]} */ -const INPUT_BUTTON_TYPES = [ - 'button', - 'submit', -]; +const INPUT_BUTTON_TYPES = [ 'button', 'submit' ]; /** * Returns true if the given element is a button element subject to focus @@ -43,104 +40,103 @@ function isFocusNormalizedButton( element ) { return false; } -export default createHigherOrderComponent( - ( WrappedComponent ) => { - return class extends Component { - constructor() { - super( ...arguments ); - - this.bindNode = this.bindNode.bind( this ); - this.cancelBlurCheck = this.cancelBlurCheck.bind( this ); - this.queueBlurCheck = this.queueBlurCheck.bind( this ); - this.normalizeButtonFocus = this.normalizeButtonFocus.bind( this ); - } - - componentWillUnmount() { +export default createHigherOrderComponent( ( WrappedComponent ) => { + return class extends Component { + constructor() { + super( ...arguments ); + + this.bindNode = this.bindNode.bind( this ); + this.cancelBlurCheck = this.cancelBlurCheck.bind( this ); + this.queueBlurCheck = this.queueBlurCheck.bind( this ); + this.normalizeButtonFocus = this.normalizeButtonFocus.bind( this ); + } + + componentWillUnmount() { + this.cancelBlurCheck(); + } + + bindNode( node ) { + if ( node ) { + this.node = node; + } else { + delete this.node; this.cancelBlurCheck(); } + } - bindNode( node ) { - if ( node ) { - this.node = node; - } else { - delete this.node; - this.cancelBlurCheck(); - } - } + queueBlurCheck( event ) { + // React does not allow using an event reference asynchronously + // due to recycling behavior, except when explicitly persisted. + event.persist(); - queueBlurCheck( event ) { - // React does not allow using an event reference asynchronously - // due to recycling behavior, except when explicitly persisted. - event.persist(); + // Skip blur check if clicking button. See `normalizeButtonFocus`. + if ( this.preventBlurCheck ) { + return; + } - // Skip blur check if clicking button. See `normalizeButtonFocus`. - if ( this.preventBlurCheck ) { + this.blurCheckTimeout = setTimeout( () => { + // If document is not focused then focus should remain + // inside the wrapped component and therefore we cancel + // this blur event thereby leaving focus in place. + // https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus. + if ( ! document.hasFocus() ) { + event.preventDefault(); return; } - - this.blurCheckTimeout = setTimeout( () => { - // If document is not focused then focus should remain - // inside the wrapped component and therefore we cancel - // this blur event thereby leaving focus in place. - // https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus. - if ( ! document.hasFocus() ) { - event.preventDefault(); - return; - } - if ( 'function' === typeof this.node.handleFocusOutside ) { - this.node.handleFocusOutside( event ); - } - }, 0 ); - } - - cancelBlurCheck() { - clearTimeout( this.blurCheckTimeout ); - } - - /** - * Handles a mousedown or mouseup event to respectively assign and - * unassign a flag for preventing blur check on button elements. Some - * browsers, namely Firefox and Safari, do not emit a focus event on - * button elements when clicked, while others do. The logic here - * intends to normalize this as treating click on buttons as focus. - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus - * - * @param {MouseEvent} event Event for mousedown or mouseup. - */ - normalizeButtonFocus( event ) { - const { type, target } = event; - - const isInteractionEnd = includes( [ 'mouseup', 'touchend' ], type ); - - if ( isInteractionEnd ) { - this.preventBlurCheck = false; - } else if ( isFocusNormalizedButton( target ) ) { - this.preventBlurCheck = true; + if ( 'function' === typeof this.node.handleFocusOutside ) { + this.node.handleFocusOutside( event ); } + }, 0 ); + } + + cancelBlurCheck() { + clearTimeout( this.blurCheckTimeout ); + } + + /** + * Handles a mousedown or mouseup event to respectively assign and + * unassign a flag for preventing blur check on button elements. Some + * browsers, namely Firefox and Safari, do not emit a focus event on + * button elements when clicked, while others do. The logic here + * intends to normalize this as treating click on buttons as focus. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus + * + * @param {MouseEvent} event Event for mousedown or mouseup. + */ + normalizeButtonFocus( event ) { + const { type, target } = event; + + const isInteractionEnd = includes( + [ 'mouseup', 'touchend' ], + type + ); + + if ( isInteractionEnd ) { + this.preventBlurCheck = false; + } else if ( isFocusNormalizedButton( target ) ) { + this.preventBlurCheck = true; } - - render() { - // Disable reason: See `normalizeButtonFocus` for browser-specific - // focus event normalization. - - /* eslint-disable jsx-a11y/no-static-element-interactions */ - return ( - <div - onFocus={ this.cancelBlurCheck } - onMouseDown={ this.normalizeButtonFocus } - onMouseUp={ this.normalizeButtonFocus } - onTouchStart={ this.normalizeButtonFocus } - onTouchEnd={ this.normalizeButtonFocus } - onBlur={ this.queueBlurCheck } - > - <WrappedComponent - ref={ this.bindNode } - { ...this.props } /> - </div> - ); - /* eslint-enable jsx-a11y/no-static-element-interactions */ - } - }; - }, 'withFocusOutside' -); + } + + render() { + // Disable reason: See `normalizeButtonFocus` for browser-specific + // focus event normalization. + + /* eslint-disable jsx-a11y/no-static-element-interactions */ + return ( + <div + onFocus={ this.cancelBlurCheck } + onMouseDown={ this.normalizeButtonFocus } + onMouseUp={ this.normalizeButtonFocus } + onTouchStart={ this.normalizeButtonFocus } + onTouchEnd={ this.normalizeButtonFocus } + onBlur={ this.queueBlurCheck } + > + <WrappedComponent ref={ this.bindNode } { ...this.props } /> + </div> + ); + /* eslint-enable jsx-a11y/no-static-element-interactions */ + } + }; +}, 'withFocusOutside' ); diff --git a/packages/components/src/higher-order/with-focus-outside/index.native.js b/packages/components/src/higher-order/with-focus-outside/index.native.js index 3efc036cdd46eb..98249c09fbdd22 100644 --- a/packages/components/src/higher-order/with-focus-outside/index.native.js +++ b/packages/components/src/higher-order/with-focus-outside/index.native.js @@ -16,10 +16,7 @@ import { createHigherOrderComponent } from '@wordpress/compose'; * * @type {string[]} */ -const INPUT_BUTTON_TYPES = [ - 'button', - 'submit', -]; +const INPUT_BUTTON_TYPES = [ 'button', 'submit' ]; /** * Returns true if the given element is a button element subject to focus @@ -44,94 +41,93 @@ function isFocusNormalizedButton( element ) { return false; } -export default createHigherOrderComponent( - ( WrappedComponent ) => { - return class extends Component { - constructor() { - super( ...arguments ); - - this.bindNode = this.bindNode.bind( this ); - this.cancelBlurCheck = this.cancelBlurCheck.bind( this ); - this.queueBlurCheck = this.queueBlurCheck.bind( this ); - this.normalizeButtonFocus = this.normalizeButtonFocus.bind( this ); - } - - componentWillUnmount() { +export default createHigherOrderComponent( ( WrappedComponent ) => { + return class extends Component { + constructor() { + super( ...arguments ); + + this.bindNode = this.bindNode.bind( this ); + this.cancelBlurCheck = this.cancelBlurCheck.bind( this ); + this.queueBlurCheck = this.queueBlurCheck.bind( this ); + this.normalizeButtonFocus = this.normalizeButtonFocus.bind( this ); + } + + componentWillUnmount() { + this.cancelBlurCheck(); + } + + bindNode( node ) { + if ( node ) { + this.node = node; + } else { + delete this.node; this.cancelBlurCheck(); } + } - bindNode( node ) { - if ( node ) { - this.node = node; - } else { - delete this.node; - this.cancelBlurCheck(); - } - } - - queueBlurCheck( event ) { - // React does not allow using an event reference asynchronously - // due to recycling behavior, except when explicitly persisted. - event.persist(); + queueBlurCheck( event ) { + // React does not allow using an event reference asynchronously + // due to recycling behavior, except when explicitly persisted. + event.persist(); - // Skip blur check if clicking button. See `normalizeButtonFocus`. - if ( this.preventBlurCheck ) { - return; - } - - this.blurCheckTimeout = setTimeout( () => { - if ( 'function' === typeof this.node.handleFocusOutside ) { - this.node.handleFocusOutside( event ); - } - }, 0 ); - } - - cancelBlurCheck() { - clearTimeout( this.blurCheckTimeout ); + // Skip blur check if clicking button. See `normalizeButtonFocus`. + if ( this.preventBlurCheck ) { + return; } - /** - * Handles a mousedown or mouseup event to respectively assign and - * unassign a flag for preventing blur check on button elements. Some - * browsers, namely Firefox and Safari, do not emit a focus event on - * button elements when clicked, while others do. The logic here - * intends to normalize this as treating click on buttons as focus. - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus - * - * @param {MouseEvent} event Event for mousedown or mouseup. - */ - normalizeButtonFocus( event ) { - const { type, target } = event; - - const isInteractionEnd = includes( [ 'mouseup', 'touchend' ], type ); - - if ( isInteractionEnd ) { - this.preventBlurCheck = false; - } else if ( isFocusNormalizedButton( target ) ) { - this.preventBlurCheck = true; + this.blurCheckTimeout = setTimeout( () => { + if ( 'function' === typeof this.node.handleFocusOutside ) { + this.node.handleFocusOutside( event ); } + }, 0 ); + } + + cancelBlurCheck() { + clearTimeout( this.blurCheckTimeout ); + } + + /** + * Handles a mousedown or mouseup event to respectively assign and + * unassign a flag for preventing blur check on button elements. Some + * browsers, namely Firefox and Safari, do not emit a focus event on + * button elements when clicked, while others do. The logic here + * intends to normalize this as treating click on buttons as focus. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus + * + * @param {MouseEvent} event Event for mousedown or mouseup. + */ + normalizeButtonFocus( event ) { + const { type, target } = event; + + const isInteractionEnd = includes( + [ 'mouseup', 'touchend' ], + type + ); + + if ( isInteractionEnd ) { + this.preventBlurCheck = false; + } else if ( isFocusNormalizedButton( target ) ) { + this.preventBlurCheck = true; } - - render() { - // Disable reason: See `normalizeButtonFocus` for browser-specific - // focus event normalization. - - return ( - <View - onFocus={ this.cancelBlurCheck } - onMouseDown={ this.normalizeButtonFocus } - onMouseUp={ this.normalizeButtonFocus } - onTouchStart={ this.normalizeButtonFocus } - onTouchEnd={ this.normalizeButtonFocus } - onBlur={ this.queueBlurCheck } - > - <WrappedComponent - ref={ this.bindNode } - { ...this.props } /> - </View> - ); - } - }; - }, 'withFocusOutside' -); + } + + render() { + // Disable reason: See `normalizeButtonFocus` for browser-specific + // focus event normalization. + + return ( + <View + onFocus={ this.cancelBlurCheck } + onMouseDown={ this.normalizeButtonFocus } + onMouseUp={ this.normalizeButtonFocus } + onTouchStart={ this.normalizeButtonFocus } + onTouchEnd={ this.normalizeButtonFocus } + onBlur={ this.queueBlurCheck } + > + <WrappedComponent ref={ this.bindNode } { ...this.props } /> + </View> + ); + } + }; +}, 'withFocusOutside' ); diff --git a/packages/components/src/higher-order/with-focus-outside/test/index.js b/packages/components/src/higher-order/with-focus-outside/test/index.js index ac47e3a57290a2..2ca341d25e2158 100644 --- a/packages/components/src/higher-order/with-focus-outside/test/index.js +++ b/packages/components/src/higher-order/with-focus-outside/test/index.js @@ -48,7 +48,10 @@ describe( 'withFocusOutside', () => { }; const simulateEvent = ( event, index = 0 ) => { - const element = TestUtils.scryRenderedDOMComponentsWithTag( wrapper, 'input' ); + const element = TestUtils.scryRenderedDOMComponentsWithTag( + wrapper, + 'input' + ); TestUtils.Simulate[ event ]( element[ index ] ); }; @@ -119,8 +122,10 @@ describe( 'withFocusOutside', () => { simulateEvent( 'focus' ); simulateEvent( 'input' ); - // eslint-disable-next-line react/no-find-dom-node - ReactDOM.unmountComponentAtNode( ReactDOM.findDOMNode( wrapper ).parentNode ); + ReactDOM.unmountComponentAtNode( + // eslint-disable-next-line react/no-find-dom-node + ReactDOM.findDOMNode( wrapper ).parentNode + ); jest.runAllTimers(); diff --git a/packages/components/src/higher-order/with-focus-return/index.js b/packages/components/src/higher-order/with-focus-return/index.js index 6a411ad909af9f..6527fca5536127 100644 --- a/packages/components/src/higher-order/with-focus-return/index.js +++ b/packages/components/src/higher-order/with-focus-return/index.js @@ -23,10 +23,7 @@ import Provider, { Consumer } from './context'; * @return {boolean} Whether object is component-like. */ function isComponentLike( object ) { - return ( - object instanceof Component || - typeof object === 'function' - ); + return object instanceof Component || typeof object === 'function'; } /** @@ -57,9 +54,9 @@ function withFocusReturn( options ) { constructor() { super( ...arguments ); - this.ownFocusedElements = new Set; + this.ownFocusedElements = new Set(); this.activeElementOnMount = document.activeElement; - this.setIsFocusedFalse = () => this.isFocused = false; + this.setIsFocusedFalse = () => ( this.isFocused = false ); this.setIsFocusedTrue = ( event ) => { this.ownFocusedElements.add( event.target ); this.isFocused = true; @@ -117,7 +114,9 @@ function withFocusReturn( options ) { return ( props ) => ( <Consumer> - { ( context ) => <FocusReturn childProps={ props } focus={ context } /> } + { ( context ) => ( + <FocusReturn childProps={ props } focus={ context } /> + ) } </Consumer> ); }; diff --git a/packages/components/src/higher-order/with-focus-return/test/index.js b/packages/components/src/higher-order/with-focus-return/test/index.js index f3e2b326b6daca..cd110d4fa7cd8e 100644 --- a/packages/components/src/higher-order/with-focus-return/test/index.js +++ b/packages/components/src/higher-order/with-focus-return/test/index.js @@ -17,7 +17,9 @@ import withFocusReturn, { Provider } from '../'; class Test extends Component { render() { return ( - <div className="test"><textarea /></div> + <div className="test"> + <textarea /> + </div> ); } } @@ -48,18 +50,24 @@ describe( 'withFocusReturn()', () => { const wrappedElementShallow = wrappedElement.children[ 0 ]; expect( wrappedElementShallow.props.className ).toBe( 'test' ); expect( wrappedElementShallow.type ).toBe( 'div' ); - expect( wrappedElementShallow.children[ 0 ].type ).toBe( 'textarea' ); + expect( wrappedElementShallow.children[ 0 ].type ).toBe( + 'textarea' + ); } ); it( 'should pass own props through to the wrapped element', () => { - const renderedComposite = renderer.create( <Composite test="test" /> ); + const renderedComposite = renderer.create( + <Composite test="test" /> + ); const wrappedElement = renderedComposite.root.findByType( Test ); // Ensure that the wrapped Test element has the appropriate props. expect( wrappedElement.props.test ).toBe( 'test' ); } ); it( 'should not pass any withFocusReturn context props through to the wrapped element', () => { - const renderedComposite = renderer.create( <Composite test="test" /> ); + const renderedComposite = renderer.create( + <Composite test="test" /> + ); const wrappedElement = renderedComposite.root.findByType( Test ); // Ensure that the wrapped Test element has the appropriate props. expect( wrappedElement.props.focusHistory ).toBeUndefined(); @@ -68,7 +76,9 @@ describe( 'withFocusReturn()', () => { it( 'should not switch focus back to the bound focus element', () => { const mountedComposite = renderer.create( <Composite /> ); - expect( getInstance( mountedComposite ).activeElementOnMount ).toBe( activeElement ); + expect( getInstance( mountedComposite ).activeElementOnMount ).toBe( + activeElement + ); // Change activeElement. switchFocusTo.focus(); @@ -81,7 +91,10 @@ describe( 'withFocusReturn()', () => { it( 'should switch focus back when unmounted while having focus', () => { const wrapper = mount( <Composite /> ); - wrapper.find( 'textarea' ).at( 0 ).simulate( 'focus' ); + wrapper + .find( 'textarea' ) + .at( 0 ) + .simulate( 'focus' ); // Should return to the activeElement saved with this component. wrapper.unmount(); @@ -96,7 +109,9 @@ describe( 'withFocusReturn()', () => { ( props ) => ( <Provider> <input name="first" /> - { props.renderSecondInput && <input name="second" /> } + { props.renderSecondInput && ( + <input name="second" /> + ) } { props.renderComposite && <Composite /> } </Provider> ), @@ -112,14 +127,19 @@ describe( 'withFocusReturn()', () => { } focus( 'input[name="first"]' ); - jest.spyOn( wrapper.find( 'input[name="first"]' ).getDOMNode(), 'focus' ); + jest.spyOn( + wrapper.find( 'input[name="first"]' ).getDOMNode(), + 'focus' + ); focus( 'input[name="second"]' ); wrapper.setProps( { renderComposite: true } ); focus( 'textarea' ); wrapper.setProps( { renderSecondInput: false } ); wrapper.setProps( { renderComposite: false } ); - expect( wrapper.find( 'input[name="first"]' ).getDOMNode().focus ).toHaveBeenCalled(); + expect( + wrapper.find( 'input[name="first"]' ).getDOMNode().focus + ).toHaveBeenCalled(); } ); } ); } ); diff --git a/packages/components/src/higher-order/with-notices/index.js b/packages/components/src/higher-order/with-notices/index.js index 9097728b2921a8..4806d6d8d6254c 100644 --- a/packages/components/src/higher-order/with-notices/index.js +++ b/packages/components/src/higher-order/with-notices/index.js @@ -71,7 +71,9 @@ export default createHigherOrderComponent( ( OriginalComponent ) => { */ removeNotice( id ) { this.setState( ( state ) => ( { - noticeList: state.noticeList.filter( ( notice ) => notice.id !== id ), + noticeList: state.noticeList.filter( + ( notice ) => notice.id !== id + ), } ) ); } @@ -90,11 +92,13 @@ export default createHigherOrderComponent( ( OriginalComponent ) => { noticeList={ this.state.noticeList } noticeOperations={ this.noticeOperations } noticeUI={ - this.state.noticeList.length > 0 && <NoticeList - className="components-with-notices-ui" - notices={ this.state.noticeList } - onRemove={ this.removeNotice } - /> + this.state.noticeList.length > 0 && ( + <NoticeList + className="components-with-notices-ui" + notices={ this.state.noticeList } + onRemove={ this.removeNotice } + /> + ) } { ...this.props } /> diff --git a/packages/components/src/higher-order/with-spoken-messages/index.js b/packages/components/src/higher-order/with-spoken-messages/index.js index 406d10bf0a245a..6649fc0337cb0c 100644 --- a/packages/components/src/higher-order/with-spoken-messages/index.js +++ b/packages/components/src/higher-order/with-spoken-messages/index.js @@ -35,7 +35,8 @@ export default createHigherOrderComponent( ( WrappedComponent ) => { render() { return ( - <WrappedComponent { ...this.props } + <WrappedComponent + { ...this.props } speak={ this.speak } debouncedSpeak={ this.debouncedSpeak } /> diff --git a/packages/components/src/higher-order/with-spoken-messages/test/index.js b/packages/components/src/higher-order/with-spoken-messages/test/index.js index b13164b27c0f7e..f6d90b2a418cd2 100644 --- a/packages/components/src/higher-order/with-spoken-messages/test/index.js +++ b/packages/components/src/higher-order/with-spoken-messages/test/index.js @@ -13,11 +13,13 @@ describe( 'withSpokenMessages', () => { it( 'should generate speak and debouncedSpeak props', () => { const testSpeak = jest.fn(); const testDebouncedSpeak = jest.fn(); - const DumpComponent = withSpokenMessages( ( { speak, debouncedSpeak } ) => { - testSpeak( isFunction( speak ) ); - testDebouncedSpeak( isFunction( debouncedSpeak ) ); - return <div />; - } ); + const DumpComponent = withSpokenMessages( + ( { speak, debouncedSpeak } ) => { + testSpeak( isFunction( speak ) ); + testDebouncedSpeak( isFunction( debouncedSpeak ) ); + return <div />; + } + ); render( <DumpComponent /> ); // Unrendered element. diff --git a/packages/components/src/icon/index.js b/packages/components/src/icon/index.js index f98318d798c06f..71cff3b5abcf14 100644 --- a/packages/components/src/icon/index.js +++ b/packages/components/src/icon/index.js @@ -1,7 +1,12 @@ /** * WordPress dependencies */ -import { cloneElement, createElement, Component, isValidElement } from '@wordpress/element'; +import { + cloneElement, + createElement, + Component, + isValidElement, +} from '@wordpress/element'; import { SVG } from '@wordpress/primitives'; /** @@ -14,7 +19,13 @@ function Icon( { icon = null, size, ...additionalProps } ) { const dashiconSize = size || 20; if ( 'string' === typeof icon ) { - return <Dashicon icon={ icon } size={ dashiconSize } { ...additionalProps } />; + return ( + <Dashicon + icon={ icon } + size={ dashiconSize } + { ...additionalProps } + /> + ); } if ( icon && Dashicon === icon.type ) { @@ -28,7 +39,10 @@ function Icon( { icon = null, size, ...additionalProps } ) { const iconSize = size || 24; if ( 'function' === typeof icon ) { if ( icon.prototype instanceof Component ) { - return createElement( icon, { size: iconSize, ...additionalProps } ); + return createElement( icon, { + size: iconSize, + ...additionalProps, + } ); } return icon( { size: iconSize, ...additionalProps } ); diff --git a/packages/components/src/icon/stories/index.js b/packages/components/src/icon/stories/index.js index b531efac47d11d..d5ccf2e0db3517 100644 --- a/packages/components/src/icon/stories/index.js +++ b/packages/components/src/icon/stories/index.js @@ -15,7 +15,9 @@ import Icon from '../'; export default { title: 'Components/Icon', component: Icon }; -const IconSizeLabel = ( { size } ) => <div style={ { fontSize: 12 } }>{ size }px</div>; +const IconSizeLabel = ( { size } ) => ( + <div style={ { fontSize: 12 } }>{ size }px</div> +); export const _default = () => { const icon = text( 'Icon', 'screenoptions' ); @@ -35,7 +37,10 @@ export const sizes = () => { return ( <> { iconSizes.map( ( size ) => ( - <div key={ size } style={ { padding: 20, display: 'inline-block' } }> + <div + key={ size } + style={ { padding: 20, display: 'inline-block' } } + > <Icon icon="screenoptions" size={ size } /> <IconSizeLabel size={ size } /> </div> diff --git a/packages/components/src/icon/test/index.js b/packages/components/src/icon/test/index.js index a645568c1e2eee..744ecebfcee9f2 100644 --- a/packages/components/src/icon/test/index.js +++ b/packages/components/src/icon/test/index.js @@ -17,7 +17,11 @@ import { Path, SVG } from '../../'; describe( 'Icon', () => { const className = 'example-class'; - const svg = <SVG><Path d="M5 4v3h5.5v12h3V7H19V4z" /></SVG>; + const svg = ( + <SVG> + <Path d="M5 4v3h5.5v12h3V7H19V4z" /> + </SVG> + ); const style = { fill: 'red' }; it( 'renders nothing when icon omitted', () => { @@ -29,7 +33,9 @@ describe( 'Icon', () => { it( 'renders a dashicon by slug', () => { const wrapper = shallow( <Icon icon="format-image" /> ); - expect( wrapper.find( 'Dashicon' ).prop( 'icon' ) ).toBe( 'format-image' ); + expect( wrapper.find( 'Dashicon' ).prop( 'icon' ) ).toBe( + 'format-image' + ); } ); it( 'renders a dashicon by slug and with a default size of 20', () => { @@ -39,7 +45,9 @@ describe( 'Icon', () => { } ); it( 'renders a dashicon by element and with a default size of 20', () => { - const wrapper = shallow( <Icon icon={ <Dashicon icon="format-image" /> } /> ); + const wrapper = shallow( + <Icon icon={ <Dashicon icon="format-image" /> } /> + ); expect( wrapper.find( 'Dashicon' ).prop( 'size' ) ).toBe( 20 ); } ); @@ -70,7 +78,16 @@ describe( 'Icon', () => { } ); it( 'renders an svg element and passes the size as its width and height', () => { - const wrapper = shallow( <Icon icon={ <SVG width={ 64 } height={ 64 }><Path d="M5 4v3h5.5v12h3V7H19V4z" /></SVG> } size={ 32 } /> ); + const wrapper = shallow( + <Icon + icon={ + <SVG width={ 64 } height={ 64 }> + <Path d="M5 4v3h5.5v12h3V7H19V4z" /> + </SVG> + } + size={ 32 } + /> + ); expect( wrapper.prop( 'width' ) ).toBe( 64 ); expect( wrapper.prop( 'height' ) ).toBe( 64 ); @@ -89,16 +106,14 @@ describe( 'Icon', () => { return <span />; } } - const wrapper = shallow( - <Icon icon={ MyComponent } /> - ); + const wrapper = shallow( <Icon icon={ MyComponent } /> ); expect( wrapper.name() ).toBe( 'MyComponent' ); } ); describe( 'props passing', () => { class MyComponent extends Component { - render( ) { + render() { return <span className={ this.props.className } />; } } @@ -124,7 +139,13 @@ describe( 'Icon', () => { } ); it( 'should pass through all other props', () => { - const wrapper = shallow( <Icon { ...props } style={ style } className={ className } /> ); + const wrapper = shallow( + <Icon + { ...props } + style={ style } + className={ className } + /> + ); expect( wrapper.prop( 'style' ) ).toBe( style ); expect( wrapper.prop( 'className' ) ).toBe( className ); diff --git a/packages/components/src/index.js b/packages/components/src/index.js index 6f4ee09bb05835..a086197861bcdd 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -1,5 +1,14 @@ // Primitives -export { SVG, Path, Circle, Polygon, Rect, G, HorizontalRule, BlockQuotation } from '@wordpress/primitives'; +export { + SVG, + Path, + Circle, + Polygon, + Rect, + G, + HorizontalRule, + BlockQuotation, +} from '@wordpress/primitives'; // Components export { default as Animate } from './animate'; @@ -25,7 +34,10 @@ export { DateTimePicker, DatePicker, TimePicker } from './date-time'; export { default as __experimentalDimensionControl } from './dimension-control'; export { default as Disabled } from './disabled'; export { default as Draggable } from './draggable'; -export { default as DropZone, useDropZone as __unstableUseDropZone } from './drop-zone'; +export { + default as DropZone, + useDropZone as __unstableUseDropZone, +} from './drop-zone'; export { default as DropZoneProvider } from './drop-zone/provider'; export { default as Dropdown } from './dropdown'; export { default as DropdownMenu } from './dropdown-menu'; @@ -91,12 +103,8 @@ export { // Higher-Order Components export { default as navigateRegions } from './higher-order/navigate-regions'; -export { - default as withConstrainedTabbing, -} from './higher-order/with-constrained-tabbing'; -export { - default as withFallbackStyles, -} from './higher-order/with-fallback-styles'; +export { default as withConstrainedTabbing } from './higher-order/with-constrained-tabbing'; +export { default as withFallbackStyles } from './higher-order/with-fallback-styles'; export { default as withFilters } from './higher-order/with-filters'; export { default as withFocusOutside } from './higher-order/with-focus-outside'; export { @@ -104,7 +112,5 @@ export { Provider as FocusReturnProvider, } from './higher-order/with-focus-return'; export { default as withNotices } from './higher-order/with-notices'; -export { - default as withSpokenMessages, -} from './higher-order/with-spoken-messages'; +export { default as withSpokenMessages } from './higher-order/with-spoken-messages'; export * from './text'; diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js index a8cfaff721aa24..624033f1cbead8 100644 --- a/packages/components/src/index.native.js +++ b/packages/components/src/index.native.js @@ -1,4 +1,13 @@ -export { SVG, Path, Circle, Polygon, Rect, G, HorizontalRule, BlockQuotation } from '@wordpress/primitives'; +export { + SVG, + Path, + Circle, + Polygon, + Rect, + G, + HorizontalRule, + BlockQuotation, +} from '@wordpress/primitives'; export { default as ColorIndicator } from './color-indicator'; export { default as ColorPalette } from './color-palette'; export { default as Dashicon } from './dashicon'; @@ -10,7 +19,12 @@ export { default as __experimentalToolbarItem } from './toolbar-item'; export { default as Icon } from './icon'; export { default as IconButton } from './button/deprecated'; export { default as Spinner } from './spinner'; -export { createSlotFill, Slot, Fill, Provider as SlotFillProvider } from './slot-fill'; +export { + createSlotFill, + Slot, + Fill, + Provider as SlotFillProvider, +} from './slot-fill'; export { default as BaseControl } from './base-control'; export { default as TextareaControl } from './textarea-control'; export { default as PanelBody } from './panel/body'; diff --git a/packages/components/src/isolated-event-container/index.js b/packages/components/src/isolated-event-container/index.js index 74fc49276575ff..83a5ad4725d614 100644 --- a/packages/components/src/isolated-event-container/index.js +++ b/packages/components/src/isolated-event-container/index.js @@ -12,11 +12,7 @@ export default forwardRef( ( { children, ...props }, ref ) => { // - onMouseDown is disabled as this can cause interactions with other DOM elements /* eslint-disable jsx-a11y/no-static-element-interactions */ return ( - <div - { ...props } - ref={ ref } - onMouseDown={ stopPropagation } - > + <div { ...props } ref={ ref } onMouseDown={ stopPropagation }> { children } </div> ); diff --git a/packages/components/src/isolated-event-container/test/index.js b/packages/components/src/isolated-event-container/test/index.js index 7d34c53961365f..a629ce9bfdd887 100644 --- a/packages/components/src/isolated-event-container/test/index.js +++ b/packages/components/src/isolated-event-container/test/index.js @@ -10,7 +10,9 @@ import IsolatedEventContainer from '../'; describe( 'IsolatedEventContainer', () => { it( 'should pass props to container', () => { - const isolated = shallow( <IsolatedEventContainer className="test" onClick="click" /> ); + const isolated = shallow( + <IsolatedEventContainer className="test" onClick="click" /> + ); expect( isolated.hasClass( 'test' ) ).toBe( true ); expect( isolated.prop( 'onClick' ) ).toBe( 'click' ); diff --git a/packages/components/src/keyboard-shortcuts/index.js b/packages/components/src/keyboard-shortcuts/index.js index 3cdc88e7c5d5a2..07d3801cf54d07 100644 --- a/packages/components/src/keyboard-shortcuts/index.js +++ b/packages/components/src/keyboard-shortcuts/index.js @@ -9,7 +9,13 @@ import { map } from 'lodash'; import { useRef, Children } from '@wordpress/element'; import { useKeyboardShortcut } from '@wordpress/compose'; -function KeyboardShortcut( { target, callback, shortcut, bindGlobal, eventName } ) { +function KeyboardShortcut( { + target, + callback, + shortcut, + bindGlobal, + eventName, +} ) { useKeyboardShortcut( shortcut, callback, { bindGlobal, target, diff --git a/packages/components/src/keyboard-shortcuts/test/index.js b/packages/components/src/keyboard-shortcuts/test/index.js index caaa459780b0aa..774ac4a1ba4b21 100644 --- a/packages/components/src/keyboard-shortcuts/test/index.js +++ b/packages/components/src/keyboard-shortcuts/test/index.js @@ -30,7 +30,8 @@ describe( 'KeyboardShortcuts', () => { <KeyboardShortcuts shortcuts={ { d: spy, - } } /> + } } + /> ); keyPress( 68, document ); @@ -49,7 +50,8 @@ describe( 'KeyboardShortcuts', () => { bindGlobal shortcuts={ { d: spy, - } } /> + } } + /> <textarea></textarea> </div>, { attachTo: attachNode } @@ -71,7 +73,8 @@ describe( 'KeyboardShortcuts', () => { eventName="keyup" shortcuts={ { d: spy, - } } /> + } } + /> <textarea></textarea> </div>, { attachTo: attachNode } diff --git a/packages/components/src/menu-group/index.js b/packages/components/src/menu-group/index.js index b6bc145440c0c0..5353033e899324 100644 --- a/packages/components/src/menu-group/index.js +++ b/packages/components/src/menu-group/index.js @@ -9,11 +9,7 @@ import classnames from 'classnames'; import { Children } from '@wordpress/element'; import { useInstanceId } from '@wordpress/compose'; -export function MenuGroup( { - children, - className = '', - label, -} ) { +export function MenuGroup( { children, className = '', label } ) { const instanceId = useInstanceId( MenuGroup ); if ( ! Children.count( children ) ) { @@ -21,14 +17,11 @@ export function MenuGroup( { } const labelId = `components-menu-group-label-${ instanceId }`; - const classNames = classnames( - className, - 'components-menu-group' - ); + const classNames = classnames( className, 'components-menu-group' ); return ( <div className={ classNames }> - { label && + { label && ( <div className="components-menu-group__label" id={ labelId } @@ -36,7 +29,7 @@ export function MenuGroup( { > { label } </div> - } + ) } <div role="group" aria-labelledby={ label ? labelId : null }> { children } </div> diff --git a/packages/components/src/menu-group/test/index.js b/packages/components/src/menu-group/test/index.js index 7f5a476614b22d..63551df4f982ec 100644 --- a/packages/components/src/menu-group/test/index.js +++ b/packages/components/src/menu-group/test/index.js @@ -17,10 +17,7 @@ describe( 'MenuGroup', () => { test( 'should match snapshot', () => { const wrapper = shallow( - <MenuGroup - label="My group" - instanceId="1" - > + <MenuGroup label="My group" instanceId="1"> <p>My item</p> </MenuGroup> ); diff --git a/packages/components/src/menu-item/index.js b/packages/components/src/menu-item/index.js index 8b70eba81356d0..44f8d13cf62a6c 100644 --- a/packages/components/src/menu-item/index.js +++ b/packages/components/src/menu-item/index.js @@ -36,10 +36,7 @@ export function MenuItem( { children = ( <span className="components-menu-item__info-wrapper"> { children } - <span - className="components-menu-item__info"> - { info } - </span> + <span className="components-menu-item__info">{ info }</span> </span> ); } @@ -56,13 +53,20 @@ export function MenuItem( { <Button icon={ icon } // Make sure aria-checked matches spec https://www.w3.org/TR/wai-aria-1.1/#aria-checked - aria-checked={ ( role === 'menuitemcheckbox' || role === 'menuitemradio' ) ? isSelected : undefined } + aria-checked={ + role === 'menuitemcheckbox' || role === 'menuitemradio' + ? isSelected + : undefined + } role={ role } className={ className } { ...props } > { children } - <Shortcut className="components-menu-item__shortcut" shortcut={ shortcut } /> + <Shortcut + className="components-menu-item__shortcut" + shortcut={ shortcut } + /> </Button> ); } diff --git a/packages/components/src/menu-item/test/index.js b/packages/components/src/menu-item/test/index.js index 63ac41b337dff9..3146a95c2c163d 100644 --- a/packages/components/src/menu-item/test/index.js +++ b/packages/components/src/menu-item/test/index.js @@ -11,11 +11,7 @@ import { MenuItem } from '../'; describe( 'MenuItem', () => { it( 'should match snapshot when only label provided', () => { - const wrapper = shallow( - <MenuItem> - My item - </MenuItem> - ); + const wrapper = shallow( <MenuItem>My item</MenuItem> ); expect( wrapper ).toMatchSnapshot(); } ); @@ -54,9 +50,7 @@ describe( 'MenuItem', () => { it( 'should match snapshot when info is provided', () => { const wrapper = shallow( - <MenuItem info="Extended description of My Item"> - My item - </MenuItem> + <MenuItem info="Extended description of My Item">My item</MenuItem> ); expect( wrapper ).toMatchSnapshot(); @@ -64,7 +58,9 @@ describe( 'MenuItem', () => { it( 'should avoid using aria-label if only has non-string children', () => { const wrapper = shallow( - <MenuItem><div /></MenuItem> + <MenuItem> + <div /> + </MenuItem> ); expect( wrapper.prop( 'aria-label' ) ).toBeUndefined(); @@ -72,7 +68,9 @@ describe( 'MenuItem', () => { it( 'should avoid using aria-checked if only menuitem is set as aria-role', () => { const wrapper = shallow( - <MenuItem role="menuitem" isSelected={ true }><div /></MenuItem> + <MenuItem role="menuitem" isSelected={ true }> + <div /> + </MenuItem> ); expect( wrapper.prop( 'aria-checked' ) ).toBeUndefined(); @@ -80,13 +78,17 @@ describe( 'MenuItem', () => { it( 'should use aria-checked if menuitemradio or menuitemcheckbox is set as aria-role', () => { let wrapper = shallow( - <MenuItem role="menuitemradio" isSelected={ true }><div /></MenuItem> + <MenuItem role="menuitemradio" isSelected={ true }> + <div /> + </MenuItem> ); expect( wrapper.prop( 'aria-checked' ) ).toBe( true ); wrapper = shallow( - <MenuItem role="menuitemcheckbox" isSelected={ true }><div /></MenuItem> + <MenuItem role="menuitemcheckbox" isSelected={ true }> + <div /> + </MenuItem> ); expect( wrapper.prop( 'aria-checked' ) ).toBe( true ); diff --git a/packages/components/src/menu-items-choice/index.js b/packages/components/src/menu-items-choice/index.js index 7fb798486e2046..06be9158ff7b45 100644 --- a/packages/components/src/menu-items-choice/index.js +++ b/packages/components/src/menu-items-choice/index.js @@ -8,11 +8,7 @@ import { check } from '@wordpress/icons'; */ import MenuItem from '../menu-item'; -export default function MenuItemsChoice( { - choices = [], - onSelect, - value, -} ) { +export default function MenuItemsChoice( { choices = [], onSelect, value } ) { return choices.map( ( item ) => { const isSelected = value === item.value; return ( diff --git a/packages/components/src/mobile/bottom-sheet/button.native.js b/packages/components/src/mobile/bottom-sheet/button.native.js index cc9c5cdae22ab3..a9c88f18fb770b 100644 --- a/packages/components/src/mobile/bottom-sheet/button.native.js +++ b/packages/components/src/mobile/bottom-sheet/button.native.js @@ -8,21 +8,14 @@ import { TouchableOpacity, View, Text } from 'react-native'; */ import styles from './styles.scss'; -const BottomSheetButton = ( { - onPress, - disabled, - text, - color, -} ) => ( +const BottomSheetButton = ( { onPress, disabled, text, color } ) => ( <TouchableOpacity accessible={ true } onPress={ onPress } disabled={ disabled } > <View style={ { flexDirection: 'row', justifyContent: 'center' } }> - <Text style={ { ...styles.buttonText, color } }> - { text } - </Text> + <Text style={ { ...styles.buttonText, color } }>{ text }</Text> </View> </TouchableOpacity> ); diff --git a/packages/components/src/mobile/bottom-sheet/cell.native.js b/packages/components/src/mobile/bottom-sheet/cell.native.js index 9ac85380f5695f..ad4c601ab971a7 100644 --- a/packages/components/src/mobile/bottom-sheet/cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/cell.native.js @@ -1,7 +1,14 @@ /** * External dependencies */ -import { TouchableOpacity, Text, View, TextInput, I18nManager, AccessibilityInfo } from 'react-native'; +import { + TouchableOpacity, + Text, + View, + TextInput, + I18nManager, + AccessibilityInfo, +} from 'react-native'; import { isEmpty } from 'lodash'; /** @@ -26,7 +33,9 @@ class BottomSheetCell extends Component { isScreenReaderEnabled: false, }; - this.handleScreenReaderToggled = this.handleScreenReaderToggled.bind( this ); + this.handleScreenReaderToggled = this.handleScreenReaderToggled.bind( + this + ); } componentDidUpdate() { @@ -38,18 +47,20 @@ class BottomSheetCell extends Component { componentDidMount() { AccessibilityInfo.addEventListener( 'screenReaderChanged', - this.handleScreenReaderToggled, + this.handleScreenReaderToggled ); - AccessibilityInfo.isScreenReaderEnabled().then( ( isScreenReaderEnabled ) => { - this.setState( { isScreenReaderEnabled } ); - } ); + AccessibilityInfo.isScreenReaderEnabled().then( + ( isScreenReaderEnabled ) => { + this.setState( { isScreenReaderEnabled } ); + } + ); } componentWillUnmount() { AccessibilityInfo.removeEventListener( 'screenReaderChanged', - this.handleScreenReaderToggled, + this.handleScreenReaderToggled ); } @@ -100,17 +111,40 @@ class BottomSheetCell extends Component { const showValue = value !== undefined; const isValueEditable = editable && onChangeValue !== undefined; - const cellLabelStyle = getStylesFromColorScheme( styles.cellLabel, styles.cellTextDark ); - const cellLabelCenteredStyle = getStylesFromColorScheme( styles.cellLabelCentered, styles.cellTextDark ); - const cellLabelLeftAlignNoIconStyle = getStylesFromColorScheme( styles.cellLabelLeftAlignNoIcon, styles.cellTextDark ); - const defaultMissingIconAndValue = leftAlign ? cellLabelLeftAlignNoIconStyle : cellLabelCenteredStyle; - const defaultLabelStyle = showValue || icon !== undefined || customActionButton ? cellLabelStyle : defaultMissingIconAndValue; + const cellLabelStyle = getStylesFromColorScheme( + styles.cellLabel, + styles.cellTextDark + ); + const cellLabelCenteredStyle = getStylesFromColorScheme( + styles.cellLabelCentered, + styles.cellTextDark + ); + const cellLabelLeftAlignNoIconStyle = getStylesFromColorScheme( + styles.cellLabelLeftAlignNoIcon, + styles.cellTextDark + ); + const defaultMissingIconAndValue = leftAlign + ? cellLabelLeftAlignNoIconStyle + : cellLabelCenteredStyle; + const defaultLabelStyle = + showValue || icon !== undefined || customActionButton + ? cellLabelStyle + : defaultMissingIconAndValue; - const drawSeparator = ( separatorType && separatorType !== 'none' ) || separatorStyle === undefined; - const drawTopSeparator = drawSeparator && separatorType === 'topFullWidth'; + const drawSeparator = + ( separatorType && separatorType !== 'none' ) || + separatorStyle === undefined; + const drawTopSeparator = + drawSeparator && separatorType === 'topFullWidth'; - const cellContainerStyles = [ styles.cellContainer, cellContainerStyle ]; - const rowContainerStyles = [ styles.cellRowContainer, cellRowContainerStyle ]; + const cellContainerStyles = [ + styles.cellContainer, + cellContainerStyle, + ]; + const rowContainerStyles = [ + styles.cellRowContainer, + cellRowContainerStyle, + ]; const onCellPress = () => { if ( isValueEditable ) { @@ -132,9 +166,18 @@ class BottomSheetCell extends Component { const separatorStyle = () => { //eslint-disable-next-line @wordpress/no-unused-vars-before-return - const defaultSeparatorStyle = this.props.getStylesFromColorScheme( styles.separator, styles.separatorDark ); - const cellSeparatorStyle = this.props.getStylesFromColorScheme( styles.cellSeparator, styles.cellSeparatorDark ); - const leftMarginStyle = { ...cellSeparatorStyle, ...platformStyles.separatorMarginLeft }; + const defaultSeparatorStyle = this.props.getStylesFromColorScheme( + styles.separator, + styles.separatorDark + ); + const cellSeparatorStyle = this.props.getStylesFromColorScheme( + styles.cellSeparator, + styles.cellSeparatorDark + ); + const leftMarginStyle = { + ...cellSeparatorStyle, + ...platformStyles.separatorMarginLeft, + }; switch ( separatorType ) { case 'leftMargin': return leftMarginStyle; @@ -150,8 +193,15 @@ class BottomSheetCell extends Component { const getValueComponent = () => { const styleRTL = I18nManager.isRTL && styles.cellValueRTL; - const cellValueStyle = this.props.getStylesFromColorScheme( styles.cellValue, styles.cellTextDark ); - const finalStyle = { ...cellValueStyle, ...valueStyle, ...styleRTL }; + const cellValueStyle = this.props.getStylesFromColorScheme( + styles.cellValue, + styles.cellTextDark + ); + const finalStyle = { + ...cellValueStyle, + ...valueStyle, + ...styleRTL, + }; // To be able to show the `middle` ellipsizeMode on editable cells // we show the TextInput just when the user wants to edit the value, @@ -160,7 +210,7 @@ class BottomSheetCell extends Component { const shouldShowPlaceholder = isValueEditable && value === ''; return this.state.isEditingValue || shouldShowPlaceholder ? ( <TextInput - ref={ ( c ) => this._valueTextInput = c } + ref={ ( c ) => ( this._valueTextInput = c ) } numberOfLines={ 1 } style={ finalStyle } value={ value } @@ -168,7 +218,9 @@ class BottomSheetCell extends Component { placeholderTextColor={ '#87a6bc' } onChangeText={ onChangeValue } editable={ isValueEditable } - pointerEvents={ this.state.isEditingValue ? 'auto' : 'none' } + pointerEvents={ + this.state.isEditingValue ? 'auto' : 'none' + } onFocus={ startEditing } onBlur={ finishEditing } keyboardType={ this.typeToKeyboardType( type, step ) } @@ -189,67 +241,92 @@ class BottomSheetCell extends Component { if ( accessibilityLabel || ! showValue ) { return accessibilityLabel || label; } - return isEmpty( value ) ? - sprintf( - /* translators: accessibility text. Empty state of a inline textinput cell. %s: The cell's title */ - _x( '%s. Empty', 'inline textinput cell' ), - label - ) : - // Separating by ',' is necessary to make a pause on urls (non-capitalized text) - sprintf( - /* translators: accessibility text. Inline textinput title and value.%1: Cell title, %2: cell value. */ - _x( '%1$s, %2$s', 'inline textinput cell' ), - label, - value - ); + return isEmpty( value ) + ? sprintf( + /* translators: accessibility text. Empty state of a inline textinput cell. %s: The cell's title */ + _x( '%s. Empty', 'inline textinput cell' ), + label + ) + : // Separating by ',' is necessary to make a pause on urls (non-capitalized text) + sprintf( + /* translators: accessibility text. Inline textinput title and value.%1: Cell title, %2: cell value. */ + _x( '%1$s, %2$s', 'inline textinput cell' ), + label, + value + ); }; - const iconStyle = getStylesFromColorScheme( styles.icon, styles.iconDark ); - const resetButtonStyle = getStylesFromColorScheme( styles.resetButton, styles.resetButtonDark ); - const containerPointerEvents = this.state.isScreenReaderEnabled && accessible ? 'none' : 'auto'; + const iconStyle = getStylesFromColorScheme( + styles.icon, + styles.iconDark + ); + const resetButtonStyle = getStylesFromColorScheme( + styles.resetButton, + styles.resetButtonDark + ); + const containerPointerEvents = + this.state.isScreenReaderEnabled && accessible ? 'none' : 'auto'; const { title, handler } = customActionButton || {}; return ( <TouchableOpacity - accessible={ accessible !== undefined ? accessible : ! this.state.isEditingValue } + accessible={ + accessible !== undefined + ? accessible + : ! this.state.isEditingValue + } accessibilityLabel={ getAccessibilityLabel() } accessibilityRole={ accessibilityRole || 'button' } - accessibilityHint={ isValueEditable ? - /* translators: accessibility text */ - __( 'Double tap to edit this value' ) : - accessibilityHint + accessibilityHint={ + isValueEditable + ? /* translators: accessibility text */ + __( 'Double tap to edit this value' ) + : accessibilityHint } disabled={ disabled } onPress={ onCellPress } style={ [ styles.clipToBounds, style ] } > - { drawTopSeparator && ( - <View style={ separatorStyle() } /> - ) } - <View style={ cellContainerStyles } pointerEvents={ containerPointerEvents }> + { drawTopSeparator && <View style={ separatorStyle() } /> } + <View + style={ cellContainerStyles } + pointerEvents={ containerPointerEvents } + > <View style={ rowContainerStyles }> <View style={ styles.cellRowContainer }> { icon && ( <View style={ styles.cellRowContainer }> - <Dashicon icon={ icon } size={ 24 } color={ iconStyle.color } /> - <View style={ platformStyles.labelIconSeparator } /> + <Dashicon + icon={ icon } + size={ 24 } + color={ iconStyle.color } + /> + <View + style={ + platformStyles.labelIconSeparator + } + /> </View> ) } <Text style={ [ defaultLabelStyle, labelStyle ] }> { label } </Text> </View> - { customActionButton && <TouchableOpacity onPress={ handler } accessibilityRole={ 'button' }> - <Text style={ resetButtonStyle }>{ title } - </Text> - </TouchableOpacity> } + { customActionButton && ( + <TouchableOpacity + onPress={ handler } + accessibilityRole={ 'button' } + > + <Text style={ resetButtonStyle }> + { title } + </Text> + </TouchableOpacity> + ) } </View> { showValue && getValueComponent() } { children } </View> - { ! drawTopSeparator && ( - <View style={ separatorStyle() } /> - ) } + { ! drawTopSeparator && <View style={ separatorStyle() } /> } </TouchableOpacity> ); } diff --git a/packages/components/src/mobile/bottom-sheet/index.native.js b/packages/components/src/mobile/bottom-sheet/index.native.js index 449bc5dc05059e..b60aaeb58ba2b9 100644 --- a/packages/components/src/mobile/bottom-sheet/index.native.js +++ b/packages/components/src/mobile/bottom-sheet/index.native.js @@ -30,11 +30,16 @@ class BottomSheet extends Component { safeAreaBottomInset: 0, }; - SafeArea.getSafeAreaInsetsForRootView().then( this.onSafeAreaInsetsUpdate ); + SafeArea.getSafeAreaInsetsForRootView().then( + this.onSafeAreaInsetsUpdate + ); } componentDidMount() { - this.safeAreaEventSubscription = SafeArea.addEventListener( 'safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsUpdate ); + this.safeAreaEventSubscription = SafeArea.addEventListener( + 'safeAreaInsetsForRootViewDidChange', + this.onSafeAreaInsetsUpdate + ); } componentWillUnmount() { @@ -43,7 +48,10 @@ class BottomSheet extends Component { } this.safeAreaEventSubscription.remove(); this.safeAreaEventSubscription = null; - SafeArea.removeEventListener( 'safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsUpdate ); + SafeArea.removeEventListener( + 'safeAreaInsetsForRootViewDidChange', + this.onSafeAreaInsetsUpdate + ); } onSafeAreaInsetsUpdate( result ) { @@ -82,23 +90,20 @@ class BottomSheet extends Component { const getHeader = () => ( <View> <View style={ styles.head }> - <View style={ { flex: 1 } }> - { leftButton } - </View> + <View style={ { flex: 1 } }>{ leftButton }</View> <View style={ styles.titleContainer }> - <Text style={ styles.title }> - { title } - </Text> - </View> - <View style={ { flex: 1 } }> - { rightButton } + <Text style={ styles.title }>{ title }</Text> </View> + <View style={ { flex: 1 } }>{ rightButton }</View> </View> <View style={ styles.separator } /> </View> ); - const backgroundStyle = getStylesFromColorScheme( styles.background, styles.backgroundDark ); + const backgroundStyle = getStylesFromColorScheme( + styles.background, + styles.backgroundDark + ); return ( <Modal @@ -112,34 +117,50 @@ class BottomSheet extends Component { onBackdropPress={ this.props.onClose } onBackButtonPress={ this.props.onClose } onSwipe={ this.props.onClose } - onDismiss={ Platform.OS === 'ios' ? this.props.onDismiss : undefined } - onModalHide={ Platform.OS === 'android' ? this.props.onDismiss : undefined } + onDismiss={ + Platform.OS === 'ios' ? this.props.onDismiss : undefined + } + onModalHide={ + Platform.OS === 'android' ? this.props.onDismiss : undefined + } swipeDirection="down" - onMoveShouldSetResponder={ panResponder.panHandlers.onMoveShouldSetResponder } - onMoveShouldSetResponderCapture={ panResponder.panHandlers.onMoveShouldSetResponderCapture } + onMoveShouldSetResponder={ + panResponder.panHandlers.onMoveShouldSetResponder + } + onMoveShouldSetResponderCapture={ + panResponder.panHandlers.onMoveShouldSetResponderCapture + } onAccessibilityEscape={ this.props.onClose } > <KeyboardAvoidingView behavior={ Platform.OS === 'ios' && 'padding' } - style={ { ...backgroundStyle, borderColor: 'rgba(0, 0, 0, 0.1)', ...style } } + style={ { + ...backgroundStyle, + borderColor: 'rgba(0, 0, 0, 0.1)', + ...style, + } } keyboardVerticalOffset={ -this.state.safeAreaBottomInset } > <View style={ styles.dragIndicator } /> - { hideHeader && ( <View style={ styles.emptyHeaderSpace } /> ) } + { hideHeader && <View style={ styles.emptyHeaderSpace } /> } { ! hideHeader && getHeader() } <View style={ [ styles.content, contentStyle ] }> { this.props.children } </View> - <View style={ { height: this.state.safeAreaBottomInset } } /> + <View + style={ { height: this.state.safeAreaBottomInset } } + /> </KeyboardAvoidingView> </Modal> - ); } } function getWidth() { - return Math.min( Dimensions.get( 'window' ).width, styles.background.maxWidth ); + return Math.min( + Dimensions.get( 'window' ).width, + styles.background.maxWidth + ); } const ThemedBottomSheet = withPreferredColorScheme( BottomSheet ); diff --git a/packages/components/src/mobile/bottom-sheet/keyboard-avoiding-view.native.js b/packages/components/src/mobile/bottom-sheet/keyboard-avoiding-view.native.js index 85214b40d51c06..644c449fbd4b59 100644 --- a/packages/components/src/mobile/bottom-sheet/keyboard-avoiding-view.native.js +++ b/packages/components/src/mobile/bottom-sheet/keyboard-avoiding-view.native.js @@ -1,4 +1,3 @@ - /** * External dependencies */ @@ -36,7 +35,8 @@ class KeyboardAvoidingView extends React.Component { } const windowHeight = Dimensions.get( 'window' ).height; - const keyboardY = keyboardFrame.screenY - this.props.keyboardVerticalOffset; + const keyboardY = + keyboardFrame.screenY - this.props.keyboardVerticalOffset; const final = Math.max( windowHeight - keyboardY, 0 ); return final; @@ -73,7 +73,10 @@ class KeyboardAvoidingView extends React.Component { componentDidMount() { if ( Platform.OS === 'ios' ) { this._subscriptions = [ - Keyboard.addListener( 'keyboardWillChangeFrame', this._onKeyboardChange ), + Keyboard.addListener( + 'keyboardWillChangeFrame', + this._onKeyboardChange + ), ]; } } @@ -96,14 +99,13 @@ class KeyboardAvoidingView extends React.Component { let finalStyle = style; if ( Platform.OS === 'ios' ) { const bottomHeight = enabled ? this.state.bottom : 0; - finalStyle = StyleSheet.compose( style, { paddingBottom: bottomHeight } ); + finalStyle = StyleSheet.compose( style, { + paddingBottom: bottomHeight, + } ); } return ( - <View - style={ finalStyle } - { ...props } - > + <View style={ finalStyle } { ...props }> { children } </View> ); diff --git a/packages/components/src/mobile/bottom-sheet/picker-cell.native.js b/packages/components/src/mobile/bottom-sheet/picker-cell.native.js index 135b03f5df40d4..6722739b2f2346 100644 --- a/packages/components/src/mobile/bottom-sheet/picker-cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/picker-cell.native.js @@ -36,11 +36,12 @@ export default function BottomSheetPickerCell( props ) { onPress={ onCellPress } editable={ false } value={ label } - { ...cellProps } > + { ...cellProps } + > <Picker leftAlign hideCancelButton={ hideCancelButton } - ref={ ( instance ) => picker = instance } + ref={ ( instance ) => ( picker = instance ) } options={ options } onChange={ onChange } /> diff --git a/packages/components/src/mobile/bottom-sheet/range-cell.native.js b/packages/components/src/mobile/bottom-sheet/range-cell.native.js index b2daebf041e041..717ab84232a097 100644 --- a/packages/components/src/mobile/bottom-sheet/range-cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/range-cell.native.js @@ -1,7 +1,15 @@ /** * External dependencies */ -import { Platform, AccessibilityInfo, findNodeHandle, TextInput, View, PixelRatio, AppState } from 'react-native'; +import { + Platform, + AccessibilityInfo, + findNodeHandle, + TextInput, + View, + PixelRatio, + AppState, +} from 'react-native'; import Slider from '@react-native-community/slider'; /** @@ -28,10 +36,18 @@ class BottomSheetRangeCell extends Component { this.onCellPress = this.onCellPress.bind( this ); this.handleChangePixelRatio = this.handleChangePixelRatio.bind( this ); - const initialValue = this.validateInput( props.value || props.defaultValue || props.minimumValue ); + const initialValue = this.validateInput( + props.value || props.defaultValue || props.minimumValue + ); const fontScale = this.getFontScale(); - this.state = { accessible: true, sliderValue: initialValue, initialValue, hasFocus: false, fontScale }; + this.state = { + accessible: true, + sliderValue: initialValue, + initialValue, + hasFocus: false, + fontScale, + }; } componentDidMount() { @@ -79,7 +95,13 @@ class BottomSheetRangeCell extends Component { if ( typeof text === 'number' ) { return Math.min( Math.max( text, minimumValue ), maximumValue ); } - return Math.min( Math.max( text.replace( /[^0-9]/g, '' ).replace( /^0+(?=\d)/, '' ), minimumValue ), maximumValue ); + return Math.min( + Math.max( + text.replace( /[^0-9]/g, '' ).replace( /^0+(?=\d)/, '' ), + minimumValue + ), + maximumValue + ); } handleValueSave( text ) { @@ -124,8 +146,12 @@ class BottomSheetRangeCell extends Component { disabled, step = 1, preferredColorScheme, - minimumTrackTintColor = preferredColorScheme === 'light' ? '#00669b' : '#5198d9', - maximumTrackTintColor = Platform.OS === 'ios' ? '#e9eff3' : '#909090', + minimumTrackTintColor = preferredColorScheme === 'light' + ? '#00669b' + : '#5198d9', + maximumTrackTintColor = Platform.OS === 'ios' + ? '#e9eff3' + : '#909090', thumbTintColor = Platform.OS === 'android' && '#00669b', getStylesFromColorScheme, ...cellProps @@ -133,14 +159,20 @@ class BottomSheetRangeCell extends Component { const { hasFocus, sliderValue, accessible, fontScale } = this.state; - const accessibilityLabel = - sprintf( + const accessibilityLabel = sprintf( /* translators: accessibility text. Inform about current value. %1$s: Control label %2$s: Current value. */ - _x( '%1$s. Current value is %2$s', 'Slider for picking a number inside a range' ), - cellProps.label, value + _x( + '%1$s. Current value is %2$s', + 'Slider for picking a number inside a range' + ), + cellProps.label, + value ); - const defaultSliderStyle = getStylesFromColorScheme( styles.sliderTextInput, styles.sliderDarkTextInput ); + const defaultSliderStyle = getStylesFromColorScheme( + styles.sliderTextInput, + styles.sliderDarkTextInput + ); return ( <Cell diff --git a/packages/components/src/mobile/bottom-sheet/stepper-cell/index.native.js b/packages/components/src/mobile/bottom-sheet/stepper-cell/index.native.js index 542d0a2e866fd7..e53332ea269b9c 100644 --- a/packages/components/src/mobile/bottom-sheet/stepper-cell/index.native.js +++ b/packages/components/src/mobile/bottom-sheet/stepper-cell/index.native.js @@ -25,9 +25,13 @@ class BottomSheetStepperCell extends Component { this.announceValue = this.announceValue.bind( this ); this.onDecrementValue = this.onDecrementValue.bind( this ); - this.onDecrementValuePressIn = this.onDecrementValuePressIn.bind( this ); + this.onDecrementValuePressIn = this.onDecrementValuePressIn.bind( + this + ); this.onIncrementValue = this.onIncrementValue.bind( this ); - this.onIncrementValuePressIn = this.onIncrementValuePressIn.bind( this ); + this.onIncrementValuePressIn = this.onIncrementValuePressIn.bind( + this + ); this.onPressOut = this.onPressOut.bind( this ); this.startPressInterval = this.startPressInterval.bind( this ); } @@ -93,23 +97,34 @@ class BottomSheetStepperCell extends Component { announceValue( value ) { const { label } = this.props; - if ( Platform.OS === 'ios' ) { // On Android it triggers the accessibilityLabel with the value change + if ( Platform.OS === 'ios' ) { + // On Android it triggers the accessibilityLabel with the value change clearTimeout( this.timeoutAnnounceValue ); this.timeoutAnnounceValue = setTimeout( () => { - AccessibilityInfo.announceForAccessibility( `${ value } ${ label }` ); + AccessibilityInfo.announceForAccessibility( + `${ value } ${ label }` + ); }, 300 ); } } render() { - const { label, icon, minValue, maxValue, value, separatorType } = this.props; + const { + label, + icon, + minValue, + maxValue, + value, + separatorType, + } = this.props; const isMinValue = value === minValue; const isMaxValue = value === maxValue; const accessibilityLabel = sprintf( /* translators: accessibility text. Inform about current value. %1$s: Control label %2$s: Current value. */ __( '%1$s. Current value is %2$s' ), - label, value + label, + value ); return ( @@ -130,7 +145,8 @@ class BottomSheetStepperCell extends Component { this.onDecrementValue(); break; } - } }> + } } + > <Cell accessibilityRole="none" accessible={ false } diff --git a/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.android.js b/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.android.js index 632cb8b7a967f7..8215550b4f4a55 100644 --- a/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.android.js +++ b/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.android.js @@ -23,27 +23,51 @@ function Stepper( { onPressOut, value, } ) { - const valueStyle = getStylesFromColorScheme( styles.value, styles.valueTextDark ); - const buttonIconStyle = getStylesFromColorScheme( styles.buttonNoBg, styles.buttonNoBgTextDark ); + const valueStyle = getStylesFromColorScheme( + styles.value, + styles.valueTextDark + ); + const buttonIconStyle = getStylesFromColorScheme( + styles.buttonNoBg, + styles.buttonNoBgTextDark + ); return ( - <View style={ styles.container } accesibility={ false } importantForAccessibility="no-hide-descendants"> + <View + style={ styles.container } + accesibility={ false } + importantForAccessibility="no-hide-descendants" + > <TouchableOpacity disabled={ isMinValue } onPressIn={ onPressInDecrement } onPressOut={ onPressOut } - style={ [ styles.buttonNoBg, isMinValue ? { opacity: 0.4 } : null ] } + style={ [ + styles.buttonNoBg, + isMinValue ? { opacity: 0.4 } : null, + ] } > - <Dashicon icon="arrow-down-alt2" size={ 18 } color={ buttonIconStyle.color } /> + <Dashicon + icon="arrow-down-alt2" + size={ 18 } + color={ buttonIconStyle.color } + /> </TouchableOpacity> <Text style={ valueStyle }>{ value }</Text> <TouchableOpacity disabled={ isMaxValue } onPressIn={ onPressInIncrement } onPressOut={ onPressOut } - style={ [ styles.buttonNoBg, isMaxValue ? { opacity: 0.4 } : null ] } + style={ [ + styles.buttonNoBg, + isMaxValue ? { opacity: 0.4 } : null, + ] } > - <Dashicon icon="arrow-up-alt2" size={ 18 } color={ buttonIconStyle.color } /> + <Dashicon + icon="arrow-up-alt2" + size={ 18 } + color={ buttonIconStyle.color } + /> </TouchableOpacity> </View> ); diff --git a/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.ios.js b/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.ios.js index cc8c6da1e6e3f0..db9df17d11dacd 100644 --- a/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.ios.js +++ b/packages/components/src/mobile/bottom-sheet/stepper-cell/stepper.ios.js @@ -23,8 +23,14 @@ function Stepper( { onPressOut, value, } ) { - const valueStyle = getStylesFromColorScheme( styles.value, styles.valueTextDark ); - const buttonStyle = getStylesFromColorScheme( styles.button, styles.buttonDark ); + const valueStyle = getStylesFromColorScheme( + styles.value, + styles.valueTextDark + ); + const buttonStyle = getStylesFromColorScheme( + styles.button, + styles.buttonDark + ); return ( <View style={ styles.container }> @@ -35,7 +41,11 @@ function Stepper( { onPressOut={ onPressOut } style={ [ buttonStyle, isMinValue ? { opacity: 0.4 } : null ] } > - <Dashicon icon="minus" size={ 24 } color={ buttonStyle.color } /> + <Dashicon + icon="minus" + size={ 24 } + color={ buttonStyle.color } + /> </TouchableOpacity> <TouchableOpacity disabled={ isMaxValue } @@ -43,7 +53,12 @@ function Stepper( { onPressOut={ onPressOut } style={ [ buttonStyle, isMaxValue ? { opacity: 0.4 } : null ] } > - <Dashicon icon="plus" size={ 24 } color={ buttonStyle.color } style={ styles.plus } /> + <Dashicon + icon="plus" + size={ 24 } + color={ buttonStyle.color } + style={ styles.plus } + /> </TouchableOpacity> </View> ); diff --git a/packages/components/src/mobile/bottom-sheet/switch-cell.native.js b/packages/components/src/mobile/bottom-sheet/switch-cell.native.js index 011f5798499008..82001188e35999 100644 --- a/packages/components/src/mobile/bottom-sheet/switch-cell.native.js +++ b/packages/components/src/mobile/bottom-sheet/switch-cell.native.js @@ -1,4 +1,3 @@ - /** * External dependencies */ @@ -13,27 +12,23 @@ import { __, _x, sprintf } from '@wordpress/i18n'; import Cell from './cell'; export default function BottomSheetSwitchCell( props ) { - const { - value, - onValueChange, - ...cellProps - } = props; + const { value, onValueChange, ...cellProps } = props; const onPress = () => { onValueChange( ! value ); }; - const accessibilityLabel = value ? - sprintf( - /* translators: accessibility text. Switch setting ON state. %s: Switch title. */ - _x( '%s. On', 'switch control' ), - cellProps.label - ) : - sprintf( - /* translators: accessibility text. Switch setting OFF state. %s: Switch title. */ - _x( '%s. Off', 'switch control' ), - cellProps.label - ); + const accessibilityLabel = value + ? sprintf( + /* translators: accessibility text. Switch setting ON state. %s: Switch title. */ + _x( '%s. On', 'switch control' ), + cellProps.label + ) + : sprintf( + /* translators: accessibility text. Switch setting OFF state. %s: Switch title. */ + _x( '%s. Off', 'switch control' ), + cellProps.label + ); return ( <Cell @@ -48,10 +43,7 @@ export default function BottomSheetSwitchCell( props ) { editable={ false } value={ '' } > - <Switch - value={ value } - onValueChange={ onValueChange } - /> + <Switch value={ value } onValueChange={ onValueChange } /> </Cell> ); } diff --git a/packages/components/src/mobile/html-text-input/container.android.js b/packages/components/src/mobile/html-text-input/container.android.js index ef62fc0178a3d6..68d69783f3b9fb 100644 --- a/packages/components/src/mobile/html-text-input/container.android.js +++ b/packages/components/src/mobile/html-text-input/container.android.js @@ -10,10 +10,11 @@ import KeyboardAvoidingView from '../keyboard-avoiding-view'; import styles from './style.android.scss'; const HTMLInputContainer = ( { children, parentHeight } ) => ( - <KeyboardAvoidingView style={ styles.keyboardAvoidingView } parentHeight={ parentHeight }> - <ScrollView style={ styles.scrollView } > - { children } - </ScrollView> + <KeyboardAvoidingView + style={ styles.keyboardAvoidingView } + parentHeight={ parentHeight } + > + <ScrollView style={ styles.scrollView }>{ children }</ScrollView> </KeyboardAvoidingView> ); diff --git a/packages/components/src/mobile/html-text-input/index.native.js b/packages/components/src/mobile/html-text-input/index.native.js index f71efdf1490ef2..d9131770f1eaa6 100644 --- a/packages/components/src/mobile/html-text-input/index.native.js +++ b/packages/components/src/mobile/html-text-input/index.native.js @@ -11,7 +11,11 @@ import { __ } from '@wordpress/i18n'; import { parse } from '@wordpress/blocks'; import { withDispatch, withSelect } from '@wordpress/data'; import { addAction, removeAction } from '@wordpress/hooks'; -import { withInstanceId, compose, withPreferredColorScheme } from '@wordpress/compose'; +import { + withInstanceId, + compose, + withPreferredColorScheme, +} from '@wordpress/compose'; /** * Internal dependencies @@ -25,7 +29,11 @@ export class HTMLTextInput extends Component { this.edit = this.edit.bind( this ); this.stopEditing = this.stopEditing.bind( this ); - addAction( 'native-editor.persist-html', 'core/editor', this.stopEditing ); + addAction( + 'native-editor.persist-html', + 'core/editor', + this.stopEditing + ); this.state = {}; } @@ -61,8 +69,14 @@ export class HTMLTextInput extends Component { render() { const { getStylesFromColorScheme } = this.props; - const htmlStyle = getStylesFromColorScheme( styles.htmlView, styles.htmlViewDark ); - const placeholderStyle = getStylesFromColorScheme( styles.placeholder, styles.placeholderDark ); + const htmlStyle = getStylesFromColorScheme( + styles.htmlView, + styles.htmlViewDark + ); + const placeholderStyle = getStylesFromColorScheme( + styles.placeholder, + styles.placeholderDark + ); return ( <HTMLInputContainer parentHeight={ this.props.parentHeight }> <TextInput @@ -96,10 +110,9 @@ export class HTMLTextInput extends Component { export default compose( [ withSelect( ( select ) => { - const { - getEditedPostAttribute, - getEditedPostContent, - } = select( 'core/editor' ); + const { getEditedPostAttribute, getEditedPostContent } = select( + 'core/editor' + ); return { title: getEditedPostAttribute( 'title' ), diff --git a/packages/components/src/mobile/html-text-input/test/index.native.js b/packages/components/src/mobile/html-text-input/test/index.native.js index 2481e533a4fad8..02f14029e2aa94 100644 --- a/packages/components/src/mobile/html-text-input/test/index.native.js +++ b/packages/components/src/mobile/html-text-input/test/index.native.js @@ -15,9 +15,12 @@ import { HTMLTextInput } from '..'; // Utility to find a TextInput in a ShallowWrapper const findTextInputInWrapper = ( wrapper, matchingProps ) => { - return wrapper.dive().findWhere( ( node ) => { - return node.name() === 'TextInput' && node.is( matchingProps ); - } ).first(); + return wrapper + .dive() + .findWhere( ( node ) => { + return node.name() === 'TextInput' && node.is( matchingProps ); + } ) + .first(); }; // Finds the Content TextInput in our HTMLInputView @@ -40,7 +43,9 @@ const getStylesFromColorScheme = () => { describe( 'HTMLTextInput', () => { it( 'HTMLTextInput renders', () => { const wrapper = shallow( - <HTMLTextInput getStylesFromColorScheme={ getStylesFromColorScheme } /> + <HTMLTextInput + getStylesFromColorScheme={ getStylesFromColorScheme } + /> ); expect( wrapper ).toBeTruthy(); } ); @@ -120,4 +125,3 @@ describe( 'HTMLTextInput', () => { expect( editTitle ).toHaveBeenCalledWith( 'text' ); } ); } ); - diff --git a/packages/components/src/mobile/keyboard-avoiding-view/index.ios.js b/packages/components/src/mobile/keyboard-avoiding-view/index.ios.js index 55fac41b263e99..4c6ee21a8e5e5e 100644 --- a/packages/components/src/mobile/keyboard-avoiding-view/index.ios.js +++ b/packages/components/src/mobile/keyboard-avoiding-view/index.ios.js @@ -1,14 +1,21 @@ /** * External dependencies */ -import { KeyboardAvoidingView as IOSKeyboardAvoidingView, Dimensions } from 'react-native'; +import { + KeyboardAvoidingView as IOSKeyboardAvoidingView, + Dimensions, +} from 'react-native'; export const KeyboardAvoidingView = ( { parentHeight, ...otherProps } ) => { const { height: fullHeight } = Dimensions.get( 'window' ); const keyboardVerticalOffset = fullHeight - parentHeight; return ( - <IOSKeyboardAvoidingView { ...otherProps } behavior="padding" keyboardVerticalOffset={ keyboardVerticalOffset } /> + <IOSKeyboardAvoidingView + { ...otherProps } + behavior="padding" + keyboardVerticalOffset={ keyboardVerticalOffset } + /> ); }; diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js index ef9c2eb279a9c0..e8f5e5c6474519 100644 --- a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js +++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js @@ -30,12 +30,18 @@ export const KeyboardAwareFlatList = ( { } } onKeyboardDidHide={ () => { setTimeout( () => { - if ( ! this.keyboardWillShowIndicator && + if ( + ! this.keyboardWillShowIndicator && this.latestContentOffsetY !== undefined && - ! shouldPreventAutomaticScroll() ) { + ! shouldPreventAutomaticScroll() + ) { // Reset the content position if keyboard is still closed if ( this.scrollViewRef ) { - this.scrollViewRef.props.scrollToPosition( 0, this.latestContentOffsetY, true ); + this.scrollViewRef.props.scrollToPosition( + 0, + this.latestContentOffsetY, + true + ); } } }, 50 ); @@ -46,20 +52,28 @@ export const KeyboardAwareFlatList = ( { scrollEnabled={ containerStyle ? false : true } onScroll={ ( event ) => { this.latestContentOffsetY = event.nativeEvent.contentOffset.y; - } } > + } } + > <FlatList { ...listProps } horizontal={ containerStyle ? true : false } - contentContainerStyle={ containerStyle ? containerStyle : undefined } + contentContainerStyle={ + containerStyle ? containerStyle : undefined + } /> </KeyboardAwareScrollView> ); -KeyboardAwareFlatList.handleCaretVerticalPositionChange = ( scrollView, targetId, caretY, previousCaretY ) => { - if ( previousCaretY ) { //if this is not the first tap +KeyboardAwareFlatList.handleCaretVerticalPositionChange = ( + scrollView, + targetId, + caretY, + previousCaretY +) => { + if ( previousCaretY ) { + //if this is not the first tap scrollView.props.refreshScrollForField( targetId ); } }; export default KeyboardAwareFlatList; - diff --git a/packages/components/src/mobile/modal-header-bar/button.native.js b/packages/components/src/mobile/modal-header-bar/button.native.js index 75dab67644c1ae..1ee4330109d82d 100644 --- a/packages/components/src/mobile/modal-header-bar/button.native.js +++ b/packages/components/src/mobile/modal-header-bar/button.native.js @@ -18,17 +18,34 @@ import styles from './button.scss'; const ICON_SIZE = 24; -const Button = withPreferredColorScheme( ( { icon, onPress, title, isPrimary, getStylesFromColorScheme } ) => { - const titleStyle = getStylesFromColorScheme( styles.title, styles.titleDark ); - return ( - <TouchableOpacity onPress={ onPress }> - { icon ? - <Icon icon={ icon } size={ ICON_SIZE } style={ styles.icon } /> : - <Text style={ [ titleStyle, isPrimary && styles.titlePrimary ] }>{ title }</Text> - } - </TouchableOpacity> - ); -} ); +const Button = withPreferredColorScheme( + ( { icon, onPress, title, isPrimary, getStylesFromColorScheme } ) => { + const titleStyle = getStylesFromColorScheme( + styles.title, + styles.titleDark + ); + return ( + <TouchableOpacity onPress={ onPress }> + { icon ? ( + <Icon + icon={ icon } + size={ ICON_SIZE } + style={ styles.icon } + /> + ) : ( + <Text + style={ [ + titleStyle, + isPrimary && styles.titlePrimary, + ] } + > + { title } + </Text> + ) } + </TouchableOpacity> + ); + } +); Button.displayName = 'ModalHeaderBar.Button'; @@ -44,9 +61,7 @@ const CloseButton = ( { onPress } ) => { icon: closeIcon, }, } ); - return ( - <Button onPress={ onPress } { ...props } /> - ); + return <Button onPress={ onPress } { ...props } />; }; CloseButton.displayName = 'ModalHeaderBar.CloseButton'; diff --git a/packages/components/src/mobile/modal-header-bar/close-icon.js b/packages/components/src/mobile/modal-header-bar/close-icon.js index 0f176a4da253ce..81a5c83fac537f 100644 --- a/packages/components/src/mobile/modal-header-bar/close-icon.js +++ b/packages/components/src/mobile/modal-header-bar/close-icon.js @@ -4,5 +4,8 @@ import { Path, SVG } from '@wordpress/primitives'; export default ( - <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" /><Path d="M0 0h24v24H0z" fill="none" /></SVG> + <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <Path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" /> + <Path d="M0 0h24v24H0z" fill="none" /> + </SVG> ); diff --git a/packages/components/src/mobile/modal-header-bar/index.native.js b/packages/components/src/mobile/modal-header-bar/index.native.js index 0d9ce12fe0f71f..474ad021cc8738 100644 --- a/packages/components/src/mobile/modal-header-bar/index.native.js +++ b/packages/components/src/mobile/modal-header-bar/index.native.js @@ -23,29 +23,33 @@ const ModalHeaderBar = withPreferredColorScheme( ( props ) => { getStylesFromColorScheme, } = props; - const separatorStyle = getStylesFromColorScheme( styles.separator, styles.separatorDark ); - const titleStyle = getStylesFromColorScheme( styles.title, styles.titleDark ); - const subtitleStyle = getStylesFromColorScheme( styles.subtitle, styles.subtitleDark ); + const separatorStyle = getStylesFromColorScheme( + styles.separator, + styles.separatorDark + ); + const titleStyle = getStylesFromColorScheme( + styles.title, + styles.titleDark + ); + const subtitleStyle = getStylesFromColorScheme( + styles.subtitle, + styles.subtitleDark + ); return ( <View> <View style={ [ styles.bar, subtitle && styles.subtitleBar ] }> - <View style={ styles.leftContainer }> - { leftButton } - </View> - <View style={ styles.titleContainer } accessibilityRole="header"> - <Text style={ titleStyle }> - { title } - </Text> - { subtitle && - <Text style={ subtitleStyle }> - { subtitle } - </Text> - } - </View> - <View style={ styles.rightContainer }> - { rightButton } + <View style={ styles.leftContainer }>{ leftButton }</View> + <View + style={ styles.titleContainer } + accessibilityRole="header" + > + <Text style={ titleStyle }>{ title }</Text> + { subtitle && ( + <Text style={ subtitleStyle }>{ subtitle }</Text> + ) } </View> + <View style={ styles.rightContainer }>{ rightButton }</View> </View> <View style={ separatorStyle } /> </View> diff --git a/packages/components/src/mobile/picker/index.android.js b/packages/components/src/mobile/picker/index.android.js index 372378e0a2b3ed..9b0b79b5eaa81a 100644 --- a/packages/components/src/mobile/picker/index.android.js +++ b/packages/components/src/mobile/picker/index.android.js @@ -48,7 +48,7 @@ export default class Picker extends Component { hideHeader > <View> - { this.props.options.map( ( option, index ) => + { this.props.options.map( ( option, index ) => ( <BottomSheet.Cell icon={ option.icon } key={ index } @@ -57,12 +57,14 @@ export default class Picker extends Component { separatorType={ 'none' } onPress={ () => this.onCellPress( option.value ) } /> + ) ) } + { ! this.props.hideCancelButton && ( + <BottomSheet.Cell + label={ __( 'Cancel' ) } + onPress={ this.onClose } + separatorType={ 'none' } + /> ) } - { ! this.props.hideCancelButton && <BottomSheet.Cell - label={ __( 'Cancel' ) } - onPress={ this.onClose } - separatorType={ 'none' } - /> } </View> </BottomSheet> ); diff --git a/packages/components/src/mobile/picker/index.ios.js b/packages/components/src/mobile/picker/index.ios.js index 9de17d1ff7060a..fff27f50e15838 100644 --- a/packages/components/src/mobile/picker/index.ios.js +++ b/packages/components/src/mobile/picker/index.ios.js @@ -27,7 +27,7 @@ class Picker extends Component { } const selected = options[ buttonIndex - 1 ]; onChange( selected.value ); - }, + } ); } diff --git a/packages/components/src/mobile/stepper-control/index.native.js b/packages/components/src/mobile/stepper-control/index.native.js index f4de5f76edc484..ba9b8de2036708 100644 --- a/packages/components/src/mobile/stepper-control/index.native.js +++ b/packages/components/src/mobile/stepper-control/index.native.js @@ -13,16 +13,18 @@ function StepperControl( { step, value, } ) { - return <StepperCell - icon={ icon } - label={ label } - maxValue={ maxValue } - minValue={ minValue } - onChangeValue={ onChangeValue } - separatorType={ separatorType } - step={ step } - value={ value } - />; + return ( + <StepperCell + icon={ icon } + label={ label } + maxValue={ maxValue } + minValue={ minValue } + onChangeValue={ onChangeValue } + separatorType={ separatorType } + step={ step } + value={ value } + /> + ); } export default StepperControl; diff --git a/packages/components/src/modal/aria-helper.js b/packages/components/src/modal/aria-helper.js index 8b714b147d55e4..277293b337f466 100644 --- a/packages/components/src/modal/aria-helper.js +++ b/packages/components/src/modal/aria-helper.js @@ -32,9 +32,7 @@ export function hideApp( unhiddenElement ) { } const elements = document.body.children; forEach( elements, ( element ) => { - if ( - element === unhiddenElement - ) { + if ( element === unhiddenElement ) { return; } if ( elementShouldBeHidden( element ) ) { diff --git a/packages/components/src/modal/frame.js b/packages/components/src/modal/frame.js index 286171a06e0ca2..955085b43428a3 100644 --- a/packages/components/src/modal/frame.js +++ b/packages/components/src/modal/frame.js @@ -107,10 +107,7 @@ class ModalFrame extends Component { const { overlayClassName, contentLabel, - aria: { - describedby, - labelledby, - }, + aria: { describedby, labelledby }, children, className, role, @@ -119,7 +116,10 @@ class ModalFrame extends Component { return ( <IsolatedEventContainer - className={ classnames( 'components-modal__screen-overlay', overlayClassName ) } + className={ classnames( + 'components-modal__screen-overlay', + overlayClassName + ) } onKeyDown={ this.handleKeyDown } > <div diff --git a/packages/components/src/modal/header.js b/packages/components/src/modal/header.js index 7226dd5061a1dc..8c59444e4203a8 100644 --- a/packages/components/src/modal/header.js +++ b/packages/components/src/modal/header.js @@ -8,33 +8,39 @@ import { __ } from '@wordpress/i18n'; */ import Button from '../button'; -const ModalHeader = ( { icon, title, onClose, closeLabel, headingId, isDismissible } ) => { +const ModalHeader = ( { + icon, + title, + onClose, + closeLabel, + headingId, + isDismissible, +} ) => { const label = closeLabel ? closeLabel : __( 'Close dialog' ); return ( - <div - className="components-modal__header" - > + <div className="components-modal__header"> <div className="components-modal__header-heading-container"> - { icon && - <span className="components-modal__icon-container" aria-hidden> + { icon && ( + <span + className="components-modal__icon-container" + aria-hidden + > { icon } </span> - } - { title && - <h1 id={ headingId } - className="components-modal__header-heading"> + ) } + { title && ( + <h1 + id={ headingId } + className="components-modal__header-heading" + > { title } </h1> - } + ) } </div> - { isDismissible && - <Button - onClick={ onClose } - icon="no-alt" - label={ label } - /> - } + { isDismissible && ( + <Button onClick={ onClose } icon="no-alt" label={ label } /> + ) } </div> ); }; diff --git a/packages/components/src/modal/index.js b/packages/components/src/modal/index.js index a4ccfff0259e8a..ce0a74b9833bdb 100644 --- a/packages/components/src/modal/index.js +++ b/packages/components/src/modal/index.js @@ -116,11 +116,13 @@ class Modal extends Component { ...otherProps } = this.props; - const headingId = aria.labelledby || `components-modal-header-${ instanceId }`; + const headingId = + aria.labelledby || `components-modal-header-${ instanceId }`; if ( isDismissable ) { deprecated( 'isDismissable prop of the Modal component', { - alternative: 'isDismissible prop (renamed) of the Modal component', + alternative: + 'isDismissible prop (renamed) of the Modal component', } ); } // Disable reason: this stops mouse events from triggering tooltips and @@ -134,7 +136,11 @@ class Modal extends Component { } } { ...otherProps } > - <div className={ 'components-modal__content' } tabIndex="0" role="document"> + <div + className={ 'components-modal__content' } + tabIndex="0" + role="document" + > <ModalHeader closeLabel={ closeButtonLabel } headingId={ headingId } diff --git a/packages/components/src/navigable-container/container.js b/packages/components/src/navigable-container/container.js index e101c6d21eddf9..595570122556e7 100644 --- a/packages/components/src/navigable-container/container.js +++ b/packages/components/src/navigable-container/container.js @@ -81,7 +81,12 @@ class NavigableContainer extends Component { } const { getFocusableContext } = this; - const { cycle = true, eventToOffset, onNavigate = noop, stopNavigationEvents } = this.props; + const { + cycle = true, + eventToOffset, + onNavigate = noop, + stopNavigationEvents, + } = this.props; const offset = eventToOffset( event ); @@ -107,7 +112,9 @@ class NavigableContainer extends Component { } const { index, focusables } = context; - const nextIndex = cycle ? cycleValue( index, focusables.length, offset ) : index + offset; + const nextIndex = cycle + ? cycleValue( index, focusables.length, offset ) + : index + offset; if ( nextIndex >= 0 && nextIndex < focusables.length ) { focusables[ nextIndex ].focus(); onNavigate( nextIndex, focusables[ nextIndex ] ); @@ -117,7 +124,8 @@ class NavigableContainer extends Component { render() { const { children, ...props } = this.props; return ( - <div ref={ this.bindContainer } + <div + ref={ this.bindContainer } { ...omit( props, [ 'stopNavigationEvents', 'eventToOffset', diff --git a/packages/components/src/navigable-container/menu.js b/packages/components/src/navigable-container/menu.js index 16411eec386a57..52007679a70b7e 100644 --- a/packages/components/src/navigable-container/menu.js +++ b/packages/components/src/navigable-container/menu.js @@ -14,11 +14,10 @@ import { UP, DOWN, LEFT, RIGHT } from '@wordpress/keycodes'; */ import NavigableContainer from './container'; -export function NavigableMenu( { - role = 'menu', - orientation = 'vertical', - ...rest -}, ref ) { +export function NavigableMenu( + { role = 'menu', orientation = 'vertical', ...rest }, + ref +) { const eventToOffset = ( evt ) => { const { keyCode } = evt; diff --git a/packages/components/src/navigable-container/test/menu.js b/packages/components/src/navigable-container/test/menu.js index 8f19afe270cb64..eed4b235c91b4c 100644 --- a/packages/components/src/navigable-container/test/menu.js +++ b/packages/components/src/navigable-container/test/menu.js @@ -17,7 +17,9 @@ import { NavigableMenu } from '../menu'; function simulateVisible( wrapper, selector ) { const elements = wrapper.getDOMNode().querySelectorAll( selector ); each( elements, ( elem ) => { - elem.getClientRects = () => [ 'trick-jsdom-into-having-size-for-element-rect' ]; + elem.getClientRects = () => [ + 'trick-jsdom-into-having-size-for-element-rect', + ]; } ); } @@ -41,27 +43,41 @@ function fireKeyDown( container, keyCode, shiftKey ) { describe( 'NavigableMenu', () => { it( 'vertical: should navigate by up and down', () => { let currentIndex = 0; - const wrapper = mount( ( + const wrapper = mount( /* Disabled because of our rule restricting literal IDs, preferring `withInstanceId`. In this case, it's fine to use literal IDs. */ /* eslint-disable no-restricted-syntax */ - <NavigableMenu orientation="vertical" onNavigate={ ( index ) => currentIndex = index }> - <span tabIndex="-1" id="btn1">One</span> - <span tabIndex="-1" id="btn2">Two</span> + <NavigableMenu + orientation="vertical" + onNavigate={ ( index ) => ( currentIndex = index ) } + > + <span tabIndex="-1" id="btn1"> + One + </span> + <span tabIndex="-1" id="btn2"> + Two + </span> <span id="btn-deep-wrapper"> - <span id="btn-deep" tabIndex="-1">Deep</span> + <span id="btn-deep" tabIndex="-1"> + Deep + </span> + </span> + <span tabIndex="-1" id="btn3"> + Three </span> - <span tabIndex="-1" id="btn3">Three</span> </NavigableMenu> /* eslint-enable no-restricted-syntax */ - ) ); + ); simulateVisible( wrapper, '*' ); const container = wrapper.find( 'div' ); - wrapper.getDOMNode().querySelector( '#btn1' ).focus(); + wrapper + .getDOMNode() + .querySelector( '#btn1' ) + .focus(); // Navigate options function assertKeyDown( keyCode, expectedActiveIndex, expectedStop ) { @@ -85,24 +101,37 @@ describe( 'NavigableMenu', () => { it( 'vertical: should navigate by up and down, and stop at edges', () => { let currentIndex = 0; - const wrapper = mount( ( + const wrapper = mount( /* Disabled because of our rule restricting literal IDs, preferring `withInstanceId`. In this case, it's fine to use literal IDs. */ /* eslint-disable no-restricted-syntax */ - <NavigableMenu cycle={ false } orientation="vertical" onNavigate={ ( index ) => currentIndex = index }> - <span tabIndex="-1" id="btn1">One</span> - <span tabIndex="-1" id="btn2">Two</span> - <span tabIndex="-1" id="btn3">Three</span> + <NavigableMenu + cycle={ false } + orientation="vertical" + onNavigate={ ( index ) => ( currentIndex = index ) } + > + <span tabIndex="-1" id="btn1"> + One + </span> + <span tabIndex="-1" id="btn2"> + Two + </span> + <span tabIndex="-1" id="btn3"> + Three + </span> </NavigableMenu> /* eslint-enable no-restricted-syntax */ - ) ); + ); simulateVisible( wrapper, '*' ); const container = wrapper.find( 'div' ); - wrapper.getDOMNode().querySelector( '#btn1' ).focus(); + wrapper + .getDOMNode() + .querySelector( '#btn1' ) + .focus(); // Navigate options function assertKeyDown( keyCode, expectedActiveIndex, expectedStop ) { @@ -124,27 +153,41 @@ describe( 'NavigableMenu', () => { it( 'horizontal: should navigate by left and right', () => { let currentIndex = 0; - const wrapper = mount( ( + const wrapper = mount( /* Disabled because of our rule restricting literal IDs, preferring `withInstanceId`. In this case, it's fine to use literal IDs. */ /* eslint-disable no-restricted-syntax */ - <NavigableMenu orientation="horizontal" onNavigate={ ( index ) => currentIndex = index }> - <span tabIndex="-1" id="btn1">One</span> - <span tabIndex="-1" id="btn2">Two</span> + <NavigableMenu + orientation="horizontal" + onNavigate={ ( index ) => ( currentIndex = index ) } + > + <span tabIndex="-1" id="btn1"> + One + </span> + <span tabIndex="-1" id="btn2"> + Two + </span> <span id="btn-deep-wrapper"> - <span id="btn-deep" tabIndex="-1">Deep</span> + <span id="btn-deep" tabIndex="-1"> + Deep + </span> + </span> + <span tabIndex="-1" id="btn3"> + Three </span> - <span tabIndex="-1" id="btn3">Three</span> </NavigableMenu> /* eslint-enable no-restricted-syntax */ - ) ); + ); simulateVisible( wrapper, '*' ); const container = wrapper.find( 'div' ); - wrapper.getDOMNode().querySelector( '#btn1' ).focus(); + wrapper + .getDOMNode() + .querySelector( '#btn1' ) + .focus(); // Navigate options function assertKeyDown( keyCode, expectedActiveIndex, expectedStop ) { @@ -168,24 +211,37 @@ describe( 'NavigableMenu', () => { it( 'horizontal: should navigate by left and right, and stop at edges', () => { let currentIndex = 0; - const wrapper = mount( ( + const wrapper = mount( /* Disabled because of our rule restricting literal IDs, preferring `withInstanceId`. In this case, it's fine to use literal IDs. */ /* eslint-disable no-restricted-syntax */ - <NavigableMenu cycle={ false } orientation="horizontal" onNavigate={ ( index ) => currentIndex = index }> - <span tabIndex="-1" id="btn1">One</span> - <span tabIndex="-1" id="btn2">Two</span> - <span tabIndex="-1" id="btn3">Three</span> + <NavigableMenu + cycle={ false } + orientation="horizontal" + onNavigate={ ( index ) => ( currentIndex = index ) } + > + <span tabIndex="-1" id="btn1"> + One + </span> + <span tabIndex="-1" id="btn2"> + Two + </span> + <span tabIndex="-1" id="btn3"> + Three + </span> </NavigableMenu> /* eslint-enable no-restricted-syntax */ - ) ); + ); simulateVisible( wrapper, '*' ); const container = wrapper.find( 'div' ); - wrapper.getDOMNode().querySelector( '#btn1' ).focus(); + wrapper + .getDOMNode() + .querySelector( '#btn1' ) + .focus(); // Navigate options function assertKeyDown( keyCode, expectedActiveIndex, expectedStop ) { @@ -207,24 +263,30 @@ describe( 'NavigableMenu', () => { it( 'both: should navigate by up/down and left/right', () => { let currentIndex = 0; - const wrapper = mount( ( + const wrapper = mount( /* Disabled because of our rule restricting literal IDs, preferring `withInstanceId`. In this case, it's fine to use literal IDs. */ /* eslint-disable no-restricted-syntax */ - <NavigableMenu orientation="both" onNavigate={ ( index ) => currentIndex = index }> + <NavigableMenu + orientation="both" + onNavigate={ ( index ) => ( currentIndex = index ) } + > <button id="btn1">One</button> <button id="btn2">Two</button> <button id="btn3">Three</button> </NavigableMenu> /* eslint-enable no-restricted-syntax */ - ) ); + ); simulateVisible( wrapper, '*' ); const container = wrapper.find( 'div' ); - wrapper.getDOMNode().querySelector( '#btn1' ).focus(); + wrapper + .getDOMNode() + .querySelector( '#btn1' ) + .focus(); // Navigate options function assertKeyDown( keyCode, expectedActiveIndex, expectedStop ) { diff --git a/packages/components/src/navigable-container/test/tabbable.js b/packages/components/src/navigable-container/test/tabbable.js index 65bfbb387c2f3b..323cad39f4dc22 100644 --- a/packages/components/src/navigable-container/test/tabbable.js +++ b/packages/components/src/navigable-container/test/tabbable.js @@ -17,7 +17,9 @@ import { TabbableContainer } from '../tabbable'; function simulateVisible( wrapper, selector ) { const elements = wrapper.getDOMNode().querySelectorAll( selector ); each( elements, ( elem ) => { - elem.getClientRects = () => [ 'trick-jsdom-into-having-size-for-element-rect' ]; + elem.getClientRects = () => [ + 'trick-jsdom-into-having-size-for-element-rect', + ]; } ); } @@ -41,30 +43,49 @@ function fireKeyDown( container, keyCode, shiftKey ) { describe( 'TabbableContainer', () => { it( 'should navigate by keypresses', () => { let currentIndex = 0; - const wrapper = mount( ( + const wrapper = mount( /* Disabled because of our rule restricting literal IDs, preferring `withInstanceId`. In this case, it's fine to use literal IDs. */ /* eslint-disable no-restricted-syntax */ - <TabbableContainer className="wrapper" onNavigate={ ( index ) => currentIndex = index }> - <div className="section" id="section1" tabIndex="0">Section One</div> - <div className="section" id="section2" tabIndex="0">Section Two</div> + <TabbableContainer + className="wrapper" + onNavigate={ ( index ) => ( currentIndex = index ) } + > + <div className="section" id="section1" tabIndex="0"> + Section One + </div> + <div className="section" id="section2" tabIndex="0"> + Section Two + </div> <div className="deep-section-wrapper"> - <div className="section" id="section-deep" tabIndex="0">Section to <strong>not</strong> skip</div> + <div className="section" id="section-deep" tabIndex="0"> + Section to <strong>not</strong> skip + </div> + </div> + <div className="section" id="section3" tabIndex="0"> + Section Three </div> - <div className="section" id="section3" tabIndex="0">Section Three</div> </TabbableContainer> /* eslint-enable no-restricted-syntax */ - ) ); + ); simulateVisible( wrapper, '*' ); const container = wrapper.find( 'div.wrapper' ); - wrapper.getDOMNode().querySelector( '#section1' ).focus(); + wrapper + .getDOMNode() + .querySelector( '#section1' ) + .focus(); // Navigate options - function assertKeyDown( keyCode, shiftKey, expectedActiveIndex, expectedStop ) { + function assertKeyDown( + keyCode, + shiftKey, + expectedActiveIndex, + expectedStop + ) { const interaction = fireKeyDown( container, keyCode, shiftKey ); expect( currentIndex ).toBe( expectedActiveIndex ); expect( interaction.stopped ).toBe( expectedStop ); @@ -83,27 +104,45 @@ describe( 'TabbableContainer', () => { it( 'should navigate by keypresses and stop at edges', () => { let currentIndex = 0; - const wrapper = mount( ( + const wrapper = mount( /* Disabled because of our rule restricting literal IDs, preferring `withInstanceId`. In this case, it's fine to use literal IDs. */ /* eslint-disable no-restricted-syntax */ - <TabbableContainer cycle={ false } className="wrapper" onNavigate={ ( index ) => currentIndex = index }> - <div className="section" id="section1" tabIndex="0">Section One</div> - <div className="section" id="section2" tabIndex="0">Section Two</div> - <div className="section" id="section3" tabIndex="0">Section Three</div> + <TabbableContainer + cycle={ false } + className="wrapper" + onNavigate={ ( index ) => ( currentIndex = index ) } + > + <div className="section" id="section1" tabIndex="0"> + Section One + </div> + <div className="section" id="section2" tabIndex="0"> + Section Two + </div> + <div className="section" id="section3" tabIndex="0"> + Section Three + </div> </TabbableContainer> /* eslint-enable no-restricted-syntax */ - ) ); + ); simulateVisible( wrapper, '*' ); const container = wrapper.find( 'div.wrapper' ); - wrapper.getDOMNode().querySelector( '#section1' ).focus(); + wrapper + .getDOMNode() + .querySelector( '#section1' ) + .focus(); // Navigate options - function assertKeyDown( keyCode, shiftKey, expectedActiveIndex, expectedStop ) { + function assertKeyDown( + keyCode, + shiftKey, + expectedActiveIndex, + expectedStop + ) { const interaction = fireKeyDown( container, keyCode, shiftKey ); expect( currentIndex ).toBe( expectedActiveIndex ); expect( interaction.stopped ).toBe( expectedStop ); diff --git a/packages/components/src/notice/index.js b/packages/components/src/notice/index.js index 377450ec2a50e0..4177db621bf096 100644 --- a/packages/components/src/notice/index.js +++ b/packages/components/src/notice/index.js @@ -24,9 +24,14 @@ function Notice( { actions = [], __unstableHTML, } ) { - const classes = classnames( className, 'components-notice', 'is-' + status, { - 'is-dismissible': isDismissible, - } ); + const classes = classnames( + className, + 'components-notice', + 'is-' + status, + { + 'is-dismissible': isDismissible, + } + ); if ( __unstableHTML ) { children = <RawHTML>{ children }</RawHTML>; @@ -63,7 +68,6 @@ function Notice( { </Button> ); } - ) } </div> { isDismissible && ( diff --git a/packages/components/src/notice/test/index.js b/packages/components/src/notice/test/index.js index a4b46887a1ecd1..2090ed50e6887a 100644 --- a/packages/components/src/notice/test/index.js +++ b/packages/components/src/notice/test/index.js @@ -20,9 +20,7 @@ describe( 'Notice', () => { renderer.render( <Notice status="success" - actions={ [ - { label: 'View', url: 'https://example.com' }, - ] } + actions={ [ { label: 'View', url: 'https://example.com' } ] } > Example </Notice> @@ -36,7 +34,9 @@ describe( 'Notice', () => { renderer.render( <Notice isDismissible={ false } /> ); - const classes = new TokenList( renderer.getRenderOutput().props.className ); + const classes = new TokenList( + renderer.getRenderOutput().props.className + ); expect( classes.contains( 'components-notice' ) ).toBe( true ); expect( classes.contains( 'is-dismissible' ) ).toBe( false ); } ); diff --git a/packages/components/src/notice/test/list.js b/packages/components/src/notice/test/list.js index 034ff9b7dfd828..6d98031ed513d5 100644 --- a/packages/components/src/notice/test/list.js +++ b/packages/components/src/notice/test/list.js @@ -19,7 +19,9 @@ describe( 'NoticeList', () => { renderer.render( <NoticeList notices={ [] } className="is-ok" /> ); - const classes = new TokenList( renderer.getRenderOutput().props.className ); + const classes = new TokenList( + renderer.getRenderOutput().props.className + ); expect( classes.contains( 'is-ok' ) ).toBe( true ); expect( classes.contains( 'components-notice-list' ) ).toBe( true ); } ); diff --git a/packages/components/src/panel/actions.native.js b/packages/components/src/panel/actions.native.js index 02031bf53cb857..282384130787f8 100644 --- a/packages/components/src/panel/actions.native.js +++ b/packages/components/src/panel/actions.native.js @@ -6,9 +6,7 @@ import { View } from 'react-native'; /** * WordPress dependencies */ -import { - TextControl, -} from '@wordpress/components'; +import { TextControl } from '@wordpress/components'; /** * Internal dependencies diff --git a/packages/components/src/panel/body.js b/packages/components/src/panel/body.js index e1e17a8a36aa7d..4121a99b6bbe25 100644 --- a/packages/components/src/panel/body.js +++ b/packages/components/src/panel/body.js @@ -38,9 +38,18 @@ export class PanelBody extends Component { } render() { - const { title, children, opened, className, icon, forwardedRef } = this.props; + const { + title, + children, + opened, + className, + icon, + forwardedRef, + } = this.props; const isOpened = opened === undefined ? this.state.opened : opened; - const classes = classnames( 'components-panel__body', className, { 'is-opened': isOpened } ); + const classes = classnames( 'components-panel__body', className, { + 'is-opened': isOpened, + } ); return ( <div className={ classes } ref={ forwardedRef }> @@ -56,10 +65,19 @@ export class PanelBody extends Component { repaints the whole element, so this wrapping span hides that. */ } <span aria-hidden="true"> - <Icon className="components-panel__arrow" icon={ isOpened ? chevronUp : chevronDown } /> + <Icon + className="components-panel__arrow" + icon={ isOpened ? chevronUp : chevronDown } + /> </span> { title } - { icon && <Icon icon={ icon } className="components-panel__icon" size={ 20 } /> } + { icon && ( + <Icon + icon={ icon } + className="components-panel__icon" + size={ 20 } + /> + ) } </Button> </h2> ) } diff --git a/packages/components/src/panel/body.native.js b/packages/components/src/panel/body.native.js index 314650f945278e..87c5eeb60bac7f 100644 --- a/packages/components/src/panel/body.native.js +++ b/packages/components/src/panel/body.native.js @@ -13,7 +13,7 @@ import { Component } from '@wordpress/element'; import styles from './body.scss'; export class PanelBody extends Component { - constructor( ) { + constructor() { super( ...arguments ); this.state = {}; } @@ -23,7 +23,9 @@ export class PanelBody extends Component { return ( <View style={ styles.panelContainer }> - { title && <Text style={ styles.sectionHeaderText }>{ title }</Text> } + { title && ( + <Text style={ styles.sectionHeaderText }>{ title }</Text> + ) } { children } </View> ); diff --git a/packages/components/src/panel/row.js b/packages/components/src/panel/row.js index ca9e9e4854dbf8..8600b8b03306bb 100644 --- a/packages/components/src/panel/row.js +++ b/packages/components/src/panel/row.js @@ -6,11 +6,7 @@ import classnames from 'classnames'; function PanelRow( { className, children } ) { const classes = classnames( 'components-panel__row', className ); - return ( - <div className={ classes }> - { children } - </div> - ); + return <div className={ classes }>{ children }</div>; } export default PanelRow; diff --git a/packages/components/src/panel/stories/index.js b/packages/components/src/panel/stories/index.js index 470faa41d5a99b..10c9a163f45494 100644 --- a/packages/components/src/panel/stories/index.js +++ b/packages/components/src/panel/stories/index.js @@ -18,13 +18,8 @@ export const _default = () => { const rowText = text( 'Row Text', 'My Panel Inputs and Labels' ); return ( <Panel header="My Panel"> - <PanelBody - title={ bodyTitle } - opened={ opened } - > - <PanelRow> - { rowText } - </PanelRow> + <PanelBody title={ bodyTitle } opened={ opened }> + <PanelRow>{ rowText }</PanelRow> </PanelBody> </Panel> ); @@ -39,21 +34,11 @@ export const multipleBodies = () => { const row2Text = text( '2: Row Text', 'My Panel Inputs and Labels' ); return ( <Panel header="My Panel"> - <PanelBody - title={ body1Title } - opened={ body1Open } - > - <PanelRow> - { row1Text } - </PanelRow> + <PanelBody title={ body1Title } opened={ body1Open }> + <PanelRow>{ row1Text }</PanelRow> </PanelBody> - <PanelBody - title={ body2Title } - opened={ body2Open } - > - <PanelRow> - { row2Text } - </PanelRow> + <PanelBody title={ body2Title } opened={ body2Open }> + <PanelRow>{ row2Text }</PanelRow> </PanelBody> </Panel> ); @@ -66,14 +51,8 @@ export const withIcon = () => { const opened = boolean( 'Opened', true ); return ( <Panel header="My Panel"> - <PanelBody - title={ bodyTitle } - opened={ opened } - icon={ icon } - > - <PanelRow> - { rowText } - </PanelRow> + <PanelBody title={ bodyTitle } opened={ opened } icon={ icon }> + <PanelRow>{ rowText }</PanelRow> </PanelBody> </Panel> ); diff --git a/packages/components/src/panel/test/body.js b/packages/components/src/panel/test/body.js index 180df741c946b5..edd316b0ca8f34 100644 --- a/packages/components/src/panel/test/body.js +++ b/packages/components/src/panel/test/body.js @@ -12,7 +12,9 @@ describe( 'PanelBody', () => { describe( 'basic rendering', () => { it( 'should render an empty div with the matching className', () => { const panelBody = shallow( <PanelBody /> ); - expect( panelBody.hasClass( 'components-panel__body' ) ).toBe( true ); + expect( panelBody.hasClass( 'components-panel__body' ) ).toBe( + true + ); expect( panelBody.type() ).toBe( 'div' ); } ); @@ -21,20 +23,35 @@ describe( 'PanelBody', () => { const button = panelBody.find( '.components-panel__body-toggle' ); expect( panelBody.hasClass( 'is-opened' ) ).toBe( true ); expect( panelBody.state( 'opened' ) ).toBe( true ); - expect( button.prop( 'onClick' ) ).toBe( panelBody.instance().toggle ); + expect( button.prop( 'onClick' ) ).toBe( + panelBody.instance().toggle + ); expect( button.childAt( 0 ).name() ).toBe( 'span' ); - expect( button.childAt( 0 ).childAt( 0 ).name() ).toBe( 'Icon' ); + expect( + button + .childAt( 0 ) + .childAt( 0 ) + .name() + ).toBe( 'Icon' ); expect( button.childAt( 1 ).text() ).toBe( 'Some Text' ); } ); it( 'should change state and class when sidebar is closed', () => { - const panelBody = shallow( <PanelBody title="Some Text" initialOpen={ false } /> ); + const panelBody = shallow( + <PanelBody title="Some Text" initialOpen={ false } /> + ); expect( panelBody.state( 'opened' ) ).toBe( false ); expect( panelBody.hasClass( 'is-opened' ) ).toBe( false ); } ); it( 'should use the "opened" prop instead of state if provided', () => { - const panelBody = shallow( <PanelBody title="Some Text" opened={ true } initialOpen={ false } /> ); + const panelBody = shallow( + <PanelBody + title="Some Text" + opened={ true } + initialOpen={ false } + /> + ); expect( panelBody.state( 'opened' ) ).toBe( false ); expect( panelBody.hasClass( 'is-opened' ) ).toBe( true ); } ); @@ -46,7 +63,9 @@ describe( 'PanelBody', () => { } ); it( 'should pass children prop but not render when sidebar is closed', () => { - const panelBody = shallow( <PanelBody children="Some Text" initialOpen={ false } /> ); + const panelBody = shallow( + <PanelBody children="Some Text" initialOpen={ false } /> + ); expect( panelBody.instance().props.children ).toBe( 'Some Text' ); // Text should be empty even though props.children is set. expect( panelBody.text() ).toBe( '' ); diff --git a/packages/components/src/panel/test/header.js b/packages/components/src/panel/test/header.js index 1955042e5c4ff2..8bd1588065797e 100644 --- a/packages/components/src/panel/test/header.js +++ b/packages/components/src/panel/test/header.js @@ -12,9 +12,16 @@ describe( 'PanelHeader', () => { describe( 'basic rendering', () => { it( 'should render PanelHeader with empty div inside', () => { const panelHeader = shallow( <PanelHeader /> ); - expect( panelHeader.hasClass( 'components-panel__header' ) ).toBe( true ); + expect( panelHeader.hasClass( 'components-panel__header' ) ).toBe( + true + ); expect( panelHeader.type() ).toBe( 'div' ); - expect( panelHeader.find( 'div' ).shallow().children() ).toHaveLength( 0 ); + expect( + panelHeader + .find( 'div' ) + .shallow() + .children() + ).toHaveLength( 0 ); } ); it( 'should render a label matching the text provided in the prop', () => { @@ -27,12 +34,24 @@ describe( 'PanelHeader', () => { it( 'should render child elements in the panel header body when provided', () => { const panelHeader = shallow( <PanelHeader children="Some Text" /> ); expect( panelHeader.text() ).toBe( 'Some Text' ); - expect( panelHeader.find( 'div' ).shallow().children() ).toHaveLength( 1 ); + expect( + panelHeader + .find( 'div' ) + .shallow() + .children() + ).toHaveLength( 1 ); } ); it( 'should render both child elements and label when passed in', () => { - const panelHeader = shallow( <PanelHeader label="Some Label" children="Some Text" /> ); - expect( panelHeader.find( 'div' ).shallow().children() ).toHaveLength( 2 ); + const panelHeader = shallow( + <PanelHeader label="Some Label" children="Some Text" /> + ); + expect( + panelHeader + .find( 'div' ) + .shallow() + .children() + ).toHaveLength( 2 ); } ); } ); } ); diff --git a/packages/components/src/panel/test/index.js b/packages/components/src/panel/test/index.js index 18641ded21a078..e657285418ff2f 100644 --- a/packages/components/src/panel/test/index.js +++ b/packages/components/src/panel/test/index.js @@ -14,14 +14,24 @@ describe( 'Panel', () => { const panel = shallow( <Panel /> ); expect( panel.hasClass( 'components-panel' ) ).toBe( true ); expect( panel.type() ).toBe( 'div' ); - expect( panel.find( 'div' ).shallow().children() ).toHaveLength( 0 ); + expect( + panel + .find( 'div' ) + .shallow() + .children() + ).toHaveLength( 0 ); } ); it( 'should render a PanelHeader component when provided text in the header prop', () => { const panel = shallow( <Panel header="Header Label" /> ); const panelHeader = panel.find( 'PanelHeader' ); expect( panelHeader.prop( 'label' ) ).toBe( 'Header Label' ); - expect( panel.find( 'div' ).shallow().children() ).toHaveLength( 1 ); + expect( + panel + .find( 'div' ) + .shallow() + .children() + ).toHaveLength( 1 ); } ); it( 'should render an additional className', () => { @@ -32,12 +42,24 @@ describe( 'Panel', () => { it( 'should add additional child elements to be rendered in the panel', () => { const panel = shallow( <Panel children="The Panel" /> ); expect( panel.text() ).toBe( 'The Panel' ); - expect( panel.find( 'div' ).shallow().children() ).toHaveLength( 1 ); + expect( + panel + .find( 'div' ) + .shallow() + .children() + ).toHaveLength( 1 ); } ); it( 'should render both children and header when provided as props', () => { - const panel = shallow( <Panel children="The Panel" header="The Header" /> ); - expect( panel.find( 'div' ).shallow().children() ).toHaveLength( 2 ); + const panel = shallow( + <Panel children="The Panel" header="The Header" /> + ); + expect( + panel + .find( 'div' ) + .shallow() + .children() + ).toHaveLength( 2 ); } ); } ); } ); diff --git a/packages/components/src/panel/test/row.js b/packages/components/src/panel/test/row.js index c59591b00ad88f..ca049f7ae937f9 100644 --- a/packages/components/src/panel/test/row.js +++ b/packages/components/src/panel/test/row.js @@ -18,7 +18,11 @@ describe( 'PanelRow', () => { expect( wrapper.hasClass( 'custom' ) ).toBe( true ); } ); it( 'should return child components', () => { - const wrapper = shallow( <PanelRow><p>children</p></PanelRow> ); + const wrapper = shallow( + <PanelRow> + <p>children</p> + </PanelRow> + ); expect( wrapper.find( 'p' ).text() ).toBe( 'children' ); } ); } ); diff --git a/packages/components/src/placeholder/index.js b/packages/components/src/placeholder/index.js index efd3bee886f6fe..251690a0a8bb7c 100644 --- a/packages/components/src/placeholder/index.js +++ b/packages/components/src/placeholder/index.js @@ -15,33 +15,57 @@ import Icon from '../icon'; * @param {Object} props The component props. * @return {Object} The rendered placeholder. */ -function Placeholder( { icon, children, label, instructions, className, notices, preview, isColumnLayout, ...additionalProps } ) { +function Placeholder( { + icon, + children, + label, + instructions, + className, + notices, + preview, + isColumnLayout, + ...additionalProps +} ) { const [ resizeListener, { width } ] = useResizeAware(); + + // Since `useResizeAware` will report a width of `null` until after the + // first render, avoid applying any modifier classes until width is known. + let modifierClassNames; + if ( typeof width === 'number' ) { + modifierClassNames = { + 'is-large': width >= 320, + 'is-medium': width >= 160 && width < 320, + 'is-small': width < 160, + }; + } + const classes = classnames( 'components-placeholder', - ( width >= 320 ? 'is-large' : '' ), - ( width >= 160 && width < 320 ? 'is-medium' : '' ), - ( width < 160 ? 'is-small' : '' ), - className + className, + modifierClassNames ); - const fieldsetClasses = classnames( 'components-placeholder__fieldset', { 'is-column-layout': isColumnLayout } ); + const fieldsetClasses = classnames( 'components-placeholder__fieldset', { + 'is-column-layout': isColumnLayout, + } ); return ( <div { ...additionalProps } className={ classes }> { resizeListener } { notices } - { preview && + { preview && ( <div className="components-placeholder__preview"> { preview } </div> - } + ) } <div className="components-placeholder__label"> <Icon icon={ icon } /> { label } </div> - { !! instructions && <div className="components-placeholder__instructions">{ instructions }</div> } - <div className={ fieldsetClasses }> - { children } - </div> + { !! instructions && ( + <div className="components-placeholder__instructions"> + { instructions } + </div> + ) } + <div className={ fieldsetClasses }>{ children }</div> </div> ); } diff --git a/packages/components/src/placeholder/stories/index.js b/packages/components/src/placeholder/stories/index.js index b2dbcf24576bc4..171b68860a2d60 100644 --- a/packages/components/src/placeholder/stories/index.js +++ b/packages/components/src/placeholder/stories/index.js @@ -13,7 +13,10 @@ export default { title: 'Components/Placeholder', component: Placeholder }; export const _default = () => { const icon = text( 'Icon', 'smiley' ); - const instructions = text( 'Instructions', 'Here are instructions you should follow' ); + const instructions = text( + 'Instructions', + 'Here are instructions you should follow' + ); const label = text( 'Label', 'My Placeholder Label' ); const isColumnLayout = boolean( 'isColumnLayout', false ); diff --git a/packages/components/src/placeholder/test/index.js b/packages/components/src/placeholder/test/index.js index 7f8cbc82e52175..bf14ba0f00ec49 100644 --- a/packages/components/src/placeholder/test/index.js +++ b/packages/components/src/placeholder/test/index.js @@ -1,4 +1,3 @@ -jest.mock( 'react-resize-aware' ); /** * External dependencies */ @@ -10,20 +9,34 @@ import useResizeAware from 'react-resize-aware'; */ import Placeholder from '../'; +jest.mock( 'react-resize-aware' ); + describe( 'Placeholder', () => { - useResizeAware.mockReturnValue( [ <div key="1" />, { width: 320 } ] ); + beforeEach( () => { + useResizeAware.mockReturnValue( [ <div key="1" />, { width: 320 } ] ); + } ); describe( 'basic rendering', () => { it( 'should by default render label section and fieldset.', () => { const placeholder = shallow( <Placeholder /> ); - const placeholderLabel = placeholder.find( '.components-placeholder__label' ); - const placeholderInstructions = placeholder.find( '.components-placeholder__instructions' ); - const placeholderFieldset = placeholder.find( '.components-placeholder__fieldset' ); + const placeholderLabel = placeholder.find( + '.components-placeholder__label' + ); + const placeholderInstructions = placeholder.find( + '.components-placeholder__instructions' + ); + const placeholderFieldset = placeholder.find( + '.components-placeholder__fieldset' + ); - expect( placeholder.hasClass( 'components-placeholder' ) ).toBe( true ); + expect( placeholder.hasClass( 'components-placeholder' ) ).toBe( + true + ); // Test for empty label. expect( placeholderLabel.exists() ).toBe( true ); - expect( placeholderLabel.find( 'Dashicon' ).exists() ).toBe( false ); + expect( placeholderLabel.find( 'Dashicon' ).exists() ).toBe( + false + ); // Test for non existant instructions. expect( placeholderInstructions.exists() ).toBe( false ); // Test for empty fieldset. @@ -32,7 +45,9 @@ describe( 'Placeholder', () => { it( 'should render an Icon in the label section', () => { const placeholder = shallow( <Placeholder icon="wordpress" /> ); - const placeholderLabel = placeholder.find( '.components-placeholder__label' ); + const placeholderLabel = placeholder.find( + '.components-placeholder__label' + ); expect( placeholderLabel.exists() ).toBe( true ); expect( placeholderLabel.find( 'Icon' ).exists() ).toBe( true ); @@ -41,7 +56,9 @@ describe( 'Placeholder', () => { it( 'should render a label section', () => { const label = 'WordPress'; const placeholder = shallow( <Placeholder label={ label } /> ); - const placeholderLabel = placeholder.find( '.components-placeholder__label' ); + const placeholderLabel = placeholder.find( + '.components-placeholder__label' + ); const child = placeholderLabel.childAt( 1 ); expect( child.text() ).toBe( label ); @@ -49,8 +66,12 @@ describe( 'Placeholder', () => { it( 'should display an instructions element', () => { const element = <div>Instructions</div>; - const placeholder = shallow( <Placeholder instructions={ element } /> ); - const placeholderInstructions = placeholder.find( '.components-placeholder__instructions' ); + const placeholder = shallow( + <Placeholder instructions={ element } /> + ); + const placeholderInstructions = placeholder.find( + '.components-placeholder__instructions' + ); const child = placeholderInstructions.childAt( 0 ); expect( placeholderInstructions.exists() ).toBe( true ); @@ -60,7 +81,9 @@ describe( 'Placeholder', () => { it( 'should display a fieldset from the children property', () => { const element = <div>Fieldset</div>; const placeholder = shallow( <Placeholder children={ element } /> ); - const placeholderFieldset = placeholder.find( '.components-placeholder__fieldset' ); + const placeholderFieldset = placeholder.find( + '.components-placeholder__fieldset' + ); const child = placeholderFieldset.childAt( 0 ); expect( placeholderFieldset.exists() ).toBe( true ); @@ -68,7 +91,9 @@ describe( 'Placeholder', () => { } ); it( 'should add an additional className to the top container', () => { - const placeholder = shallow( <Placeholder className="wp-placeholder" /> ); + const placeholder = shallow( + <Placeholder className="wp-placeholder" /> + ); expect( placeholder.hasClass( 'wp-placeholder' ) ).toBe( true ); } ); @@ -77,4 +102,32 @@ describe( 'Placeholder', () => { expect( placeholder.prop( 'test' ) ).toBe( 'test' ); } ); } ); + + describe( 'resize aware', () => { + it( 'should not assign modifier class in first-pass `null` width from `useResizeAware`', () => { + useResizeAware.mockReturnValue( [ + <div key="1" />, + { width: 320 }, + ] ); + + const placeholder = shallow( <Placeholder /> ); + + expect( placeholder.hasClass( 'is-large' ) ).toBe( true ); + expect( placeholder.hasClass( 'is-medium' ) ).toBe( false ); + expect( placeholder.hasClass( 'is-small' ) ).toBe( false ); + } ); + + it( 'should assign modifier class', () => { + useResizeAware.mockReturnValue( [ + <div key="1" />, + { width: null }, + ] ); + + const placeholder = shallow( <Placeholder /> ); + + expect( placeholder.hasClass( 'is-large' ) ).toBe( false ); + expect( placeholder.hasClass( 'is-medium' ) ).toBe( false ); + expect( placeholder.hasClass( 'is-small' ) ).toBe( false ); + } ); + } ); } ); diff --git a/packages/components/src/popover/index.js b/packages/components/src/popover/index.js index 88944369cc3ea0..7d95d7a28e1989 100644 --- a/packages/components/src/popover/index.js +++ b/packages/components/src/popover/index.js @@ -25,7 +25,9 @@ import IsolatedEventContainer from '../isolated-event-container'; import { Slot, Fill, Consumer } from '../slot-fill'; import Animate from '../animate'; -const FocusManaged = withConstrainedTabbing( withFocusReturn( ( { children } ) => children ) ); +const FocusManaged = withConstrainedTabbing( + withFocusReturn( ( { children } ) => children ) +); /** * Name of slot in which popover should fill. @@ -148,9 +150,11 @@ function useFocusContentOnMount( focusOnMount, contentRef ) { } if ( focusOnMount === 'firstElement' ) { - // Find first tabbable node within content and shift focus, falling - // back to the popover panel itself. - const firstTabbable = focus.tabbable.find( contentRef.current )[ 0 ]; + // Find first tabbable node within content and shift focus, falling + // back to the popover panel itself. + const firstTabbable = focus.tabbable.find( + contentRef.current + )[ 0 ]; if ( firstTabbable ) { firstTabbable.focus(); @@ -162,8 +166,8 @@ function useFocusContentOnMount( focusOnMount, contentRef ) { } if ( focusOnMount === 'container' ) { - // Focus the popover panel itself so items in the popover are easily - // accessed via keyboard navigation. + // Focus the popover panel itself so items in the popover are easily + // accessed via keyboard navigation. contentRef.current.focus(); } }, 0 ); @@ -325,29 +329,70 @@ const Popover = ( { yAxis, contentHeight, contentWidth, - } = computePopoverPosition( anchor, contentRect.current, position, __unstableSticky, containerRef.current, relativeOffsetTop ); + } = computePopoverPosition( + anchor, + contentRect.current, + position, + __unstableSticky, + containerRef.current, + relativeOffsetTop + ); - if ( typeof popoverTop === 'number' && typeof popoverLeft === 'number' ) { + if ( + typeof popoverTop === 'number' && + typeof popoverLeft === 'number' + ) { if ( subpixels && __unstableAllowVerticalSubpixelPosition ) { - setStyle( containerRef.current, 'left', popoverLeft + 'px' ); + setStyle( + containerRef.current, + 'left', + popoverLeft + 'px' + ); setStyle( containerRef.current, 'top' ); - setStyle( containerRef.current, 'transform', `translateY(${ popoverTop }px)` ); - } else if ( subpixels && __unstableAllowHorizontalSubpixelPosition ) { + setStyle( + containerRef.current, + 'transform', + `translateY(${ popoverTop }px)` + ); + } else if ( + subpixels && + __unstableAllowHorizontalSubpixelPosition + ) { setStyle( containerRef.current, 'top', popoverTop + 'px' ); setStyle( containerRef.current, 'left' ); - setStyle( containerRef.current, 'transform', `translate(${ popoverLeft }px)` ); + setStyle( + containerRef.current, + 'transform', + `translate(${ popoverLeft }px)` + ); } else { setStyle( containerRef.current, 'top', popoverTop + 'px' ); - setStyle( containerRef.current, 'left', popoverLeft + 'px' ); + setStyle( + containerRef.current, + 'left', + popoverLeft + 'px' + ); setStyle( containerRef.current, 'transform' ); } } - setClass( containerRef.current, 'is-without-arrow', noArrow || ( xAxis === 'center' && yAxis === 'middle' ) ); + setClass( + containerRef.current, + 'is-without-arrow', + noArrow || ( xAxis === 'center' && yAxis === 'middle' ) + ); setAttribute( containerRef.current, 'data-x-axis', xAxis ); setAttribute( containerRef.current, 'data-y-axis', yAxis ); - setStyle( contentRef.current, 'maxHeight', typeof contentHeight === 'number' ? contentHeight + 'px' : '' ); - setStyle( contentRef.current, 'maxWidth', typeof contentWidth === 'number' ? contentWidth + 'px' : '' ); + setStyle( + contentRef.current, + 'maxHeight', + typeof contentHeight === 'number' ? contentHeight + 'px' : '' + ); + setStyle( + contentRef.current, + 'maxWidth', + typeof contentWidth === 'number' ? contentWidth + 'px' : '' + ); // Compute the animation position const yAxisMapping = { @@ -392,13 +437,14 @@ const Popover = ( { let observer; - const observeElement = ( + const observeElement = __unstableAllowVerticalSubpixelPosition || - __unstableAllowHorizontalSubpixelPosition - ); + __unstableAllowHorizontalSubpixelPosition; if ( observeElement ) { - observer = new window.MutationObserver( () => refresh( { subpixels: true } ) ); + observer = new window.MutationObserver( () => + refresh( { subpixels: true } ) + ); observer.observe( observeElement, { attributes: true } ); } @@ -407,7 +453,7 @@ const Popover = ( { window.clearInterval( intervalHandle ); window.removeEventListener( 'resize', refresh ); window.removeEventListener( 'scroll', refresh, true ); - window.addEventListener( 'click', refreshOnAnimationFrame ); + window.removeEventListener( 'click', refreshOnAnimationFrame ); window.cancelAnimationFrame( rafId ); if ( observer ) { @@ -469,7 +515,23 @@ const Popover = ( { clickEvent = new window.MouseEvent( 'click' ); } catch ( error ) { clickEvent = document.createEvent( 'MouseEvent' ); - clickEvent.initMouseEvent( 'click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null ); + clickEvent.initMouseEvent( + 'click', + true, + true, + window, + 0, + 0, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + null + ); } Object.defineProperty( clickEvent, 'target', { @@ -513,7 +575,11 @@ const Popover = ( { <span className="components-popover__header-title"> { headerTitle } </span> - <Button className="components-popover__close" icon="no-alt" onClick={ onClose } /> + <Button + className="components-popover__close" + icon="no-alt" + onClick={ onClose } + /> </div> ) } <div @@ -541,18 +607,16 @@ const Popover = ( { // In case there is no slot context in which to render, // default to an in-place rendering. if ( getSlot && getSlot( __unstableSlotName ) ) { - content = <Fill name={ __unstableSlotName }>{ content }</Fill>; + content = ( + <Fill name={ __unstableSlotName }>{ content }</Fill> + ); } if ( anchorRef || anchorRect ) { return content; } - return ( - <span ref={ anchorRefFallback }> - { content } - </span> - ); + return <span ref={ anchorRefFallback }>{ content }</span>; } } </Consumer> ); @@ -560,7 +624,8 @@ const Popover = ( { const PopoverContainer = Popover; -PopoverContainer.Slot = ( { name = SLOT_NAME } ) => - <Slot bubblesVirtually name={ name } />; +PopoverContainer.Slot = ( { name = SLOT_NAME } ) => ( + <Slot bubblesVirtually name={ name } /> +); export default PopoverContainer; diff --git a/packages/components/src/popover/stories/index.js b/packages/components/src/popover/stories/index.js index e6b4744e9bcf8f..5014b1b27f3e5c 100644 --- a/packages/components/src/popover/stories/index.js +++ b/packages/components/src/popover/stories/index.js @@ -96,5 +96,7 @@ export const positioning = () => { const content = text( 'Example: Content', 'Popover' ); const noArrow = boolean( 'noArrow', false ); - return <DragExample label={ label } content={ content } noArrow={ noArrow } />; + return ( + <DragExample label={ label } content={ content } noArrow={ noArrow } /> + ); }; diff --git a/packages/components/src/popover/test/index.js b/packages/components/src/popover/test/index.js index 1ae2b3e932fc59..8db3d9292cee3d 100644 --- a/packages/components/src/popover/test/index.js +++ b/packages/components/src/popover/test/index.js @@ -65,9 +65,14 @@ describe( 'Popover', () => { it( 'should render content', () => { let wrapper; TestUtils.act( () => { - wrapper = TestUtils.renderIntoDocument( <PopoverWrapper>Hello</PopoverWrapper> ); + wrapper = TestUtils.renderIntoDocument( + <PopoverWrapper>Hello</PopoverWrapper> + ); } ); - const content = TestUtils.findRenderedDOMComponentWithTag( wrapper, 'span' ); + const content = TestUtils.findRenderedDOMComponentWithTag( + wrapper, + 'span' + ); expect( content ).toMatchSnapshot(); } ); @@ -75,9 +80,14 @@ describe( 'Popover', () => { it( 'should pass additional props to portaled element', () => { let wrapper; TestUtils.act( () => { - wrapper = TestUtils.renderIntoDocument( <PopoverWrapper role="tooltip">Hello</PopoverWrapper> ); + wrapper = TestUtils.renderIntoDocument( + <PopoverWrapper role="tooltip">Hello</PopoverWrapper> + ); } ); - const content = TestUtils.findRenderedDOMComponentWithTag( wrapper, 'span' ); + const content = TestUtils.findRenderedDOMComponentWithTag( + wrapper, + 'span' + ); expect( content ).toMatchSnapshot(); } ); diff --git a/packages/components/src/popover/test/utils.js b/packages/components/src/popover/test/utils.js index 670d1ac2e2624b..131be264a4c1e6 100644 --- a/packages/components/src/popover/test/utils.js +++ b/packages/components/src/popover/test/utils.js @@ -23,7 +23,9 @@ describe( 'computePopoverYAxisPosition', () => { height: 300, }; - expect( computePopoverYAxisPosition( anchorRect, contentSize, 'bottom' ) ).toEqual( { + expect( + computePopoverYAxisPosition( anchorRect, contentSize, 'bottom' ) + ).toEqual( { contentHeight: null, popoverTop: 30, yAxis: 'bottom', @@ -45,7 +47,9 @@ describe( 'computePopoverYAxisPosition', () => { height: 300, }; - expect( computePopoverYAxisPosition( anchorRect, contentSize, 'top' ) ).toEqual( { + expect( + computePopoverYAxisPosition( anchorRect, contentSize, 'top' ) + ).toEqual( { contentHeight: null, popoverTop: 30, yAxis: 'bottom', @@ -67,7 +71,9 @@ describe( 'computePopoverYAxisPosition', () => { height: 500, }; - expect( computePopoverYAxisPosition( anchorRect, contentSize, 'bottom' ) ).toEqual( { + expect( + computePopoverYAxisPosition( anchorRect, contentSize, 'bottom' ) + ).toEqual( { contentHeight: 390, popoverTop: 400, yAxis: 'top', @@ -89,7 +95,9 @@ describe( 'computePopoverYAxisPosition', () => { height: 300, }; - expect( computePopoverYAxisPosition( anchorRect, contentSize, 'middle' ) ).toEqual( { + expect( + computePopoverYAxisPosition( anchorRect, contentSize, 'middle' ) + ).toEqual( { contentHeight: null, popoverTop: 410, yAxis: 'middle', @@ -113,7 +121,9 @@ describe( 'computePopoverXAxisPosition', () => { height: 300, }; - expect( computePopoverXAxisPosition( anchorRect, contentSize, 'right' ) ).toEqual( { + expect( + computePopoverXAxisPosition( anchorRect, contentSize, 'right' ) + ).toEqual( { contentWidth: null, popoverLeft: 20, xAxis: 'right', @@ -135,7 +145,9 @@ describe( 'computePopoverXAxisPosition', () => { height: 300, }; - expect( computePopoverXAxisPosition( anchorRect, contentSize, 'center' ) ).toEqual( { + expect( + computePopoverXAxisPosition( anchorRect, contentSize, 'center' ) + ).toEqual( { contentWidth: null, popoverLeft: 20, xAxis: 'right', @@ -157,7 +169,9 @@ describe( 'computePopoverXAxisPosition', () => { height: 300, }; - expect( computePopoverXAxisPosition( anchorRect, contentSize, 'right' ) ).toEqual( { + expect( + computePopoverXAxisPosition( anchorRect, contentSize, 'right' ) + ).toEqual( { contentWidth: 614, popoverLeft: 410, xAxis: 'right', @@ -181,7 +195,9 @@ describe( 'computePopoverPosition', () => { height: 300, }; - expect( computePopoverPosition( anchorRect, contentSize, 'bottom right' ) ).toEqual( { + expect( + computePopoverPosition( anchorRect, contentSize, 'bottom right' ) + ).toEqual( { contentWidth: null, popoverLeft: 20, xAxis: 'right', diff --git a/packages/components/src/popover/utils.js b/packages/components/src/popover/utils.js index 187f3c292a5642..a5a927a0b433e5 100644 --- a/packages/components/src/popover/utils.js +++ b/packages/components/src/popover/utils.js @@ -22,7 +22,14 @@ const HEIGHT_OFFSET = 10; // used by the arrow and a bit of empty space * * @return {Object} Popover xAxis position and constraints. */ -export function computePopoverXAxisPosition( anchorRect, contentSize, xAxis, corner, sticky, chosenYAxis ) { +export function computePopoverXAxisPosition( + anchorRect, + contentSize, + xAxis, + corner, + sticky, + chosenYAxis +) { const { width } = contentSize; const isRTL = document.documentElement.dir === 'rtl'; @@ -40,13 +47,14 @@ export function computePopoverXAxisPosition( anchorRect, contentSize, xAxis, cor } // x axis alignment choices - const anchorMidPoint = Math.round( anchorRect.left + ( anchorRect.width / 2 ) ); + const anchorMidPoint = Math.round( anchorRect.left + anchorRect.width / 2 ); const centerAlignment = { popoverLeft: anchorMidPoint, - contentWidth: ( - ( anchorMidPoint - ( width / 2 ) > 0 ? ( width / 2 ) : anchorMidPoint ) + - ( anchorMidPoint + ( width / 2 ) > window.innerWidth ? window.innerWidth - anchorMidPoint : ( width / 2 ) ) - ), + contentWidth: + ( anchorMidPoint - width / 2 > 0 ? width / 2 : anchorMidPoint ) + + ( anchorMidPoint + width / 2 > window.innerWidth + ? window.innerWidth - anchorMidPoint + : width / 2 ), }; let leftAlignmentX = anchorRect.left; @@ -71,7 +79,10 @@ export function computePopoverXAxisPosition( anchorRect, contentSize, xAxis, cor }; const rightAlignment = { popoverLeft: rightAlignmentX, - contentWidth: rightAlignmentX + width > window.innerWidth ? window.innerWidth - rightAlignmentX : width, + contentWidth: + rightAlignmentX + width > window.innerWidth + ? window.innerWidth - rightAlignmentX + : width, }; // Choosing the x axis @@ -83,11 +94,20 @@ export function computePopoverXAxisPosition( anchorRect, contentSize, xAxis, cor chosenXAxis = 'center'; } else if ( xAxis === 'left' && leftAlignment.contentWidth === width ) { chosenXAxis = 'left'; - } else if ( xAxis === 'right' && rightAlignment.contentWidth === width ) { + } else if ( + xAxis === 'right' && + rightAlignment.contentWidth === width + ) { chosenXAxis = 'right'; } else { - chosenXAxis = leftAlignment.contentWidth > rightAlignment.contentWidth ? 'left' : 'right'; - const chosenWidth = chosenXAxis === 'left' ? leftAlignment.contentWidth : rightAlignment.contentWidth; + chosenXAxis = + leftAlignment.contentWidth > rightAlignment.contentWidth + ? 'left' + : 'right'; + const chosenWidth = + chosenXAxis === 'left' + ? leftAlignment.contentWidth + : rightAlignment.contentWidth; contentWidth = chosenWidth !== width ? chosenWidth : null; } } @@ -124,23 +144,35 @@ export function computePopoverXAxisPosition( anchorRect, contentSize, xAxis, cor * * @return {Object} Popover xAxis position and constraints. */ -export function computePopoverYAxisPosition( anchorRect, contentSize, yAxis, corner, sticky, anchorRef, relativeOffsetTop ) { +export function computePopoverYAxisPosition( + anchorRect, + contentSize, + yAxis, + corner, + sticky, + anchorRef, + relativeOffsetTop +) { const { height } = contentSize; if ( sticky ) { - const scrollContainerEl = getScrollContainer( anchorRef ) || document.body; + const scrollContainerEl = + getScrollContainer( anchorRef ) || document.body; const scrollRect = scrollContainerEl.getBoundingClientRect(); if ( anchorRect.top - height <= scrollRect.top ) { return { yAxis, - popoverTop: Math.min( anchorRect.bottom - relativeOffsetTop, scrollRect.top + height - relativeOffsetTop ), + popoverTop: Math.min( + anchorRect.bottom - relativeOffsetTop, + scrollRect.top + height - relativeOffsetTop + ), }; } } // y axis alignment choices - let anchorMidPoint = anchorRect.top + ( anchorRect.height / 2 ); + let anchorMidPoint = anchorRect.top + anchorRect.height / 2; if ( corner === 'bottom' ) { anchorMidPoint = anchorRect.bottom; @@ -150,19 +182,26 @@ export function computePopoverYAxisPosition( anchorRect, contentSize, yAxis, cor const middleAlignment = { popoverTop: anchorMidPoint, - contentHeight: ( - ( anchorMidPoint - ( height / 2 ) > 0 ? ( height / 2 ) : anchorMidPoint ) + - ( anchorMidPoint + ( height / 2 ) > window.innerHeight ? window.innerHeight - anchorMidPoint : ( height / 2 ) ) - ), + contentHeight: + ( anchorMidPoint - height / 2 > 0 ? height / 2 : anchorMidPoint ) + + ( anchorMidPoint + height / 2 > window.innerHeight + ? window.innerHeight - anchorMidPoint + : height / 2 ), }; const topAlignment = { popoverTop: anchorRect.top, - contentHeight: anchorRect.top - HEIGHT_OFFSET - height > 0 ? height : anchorRect.top - HEIGHT_OFFSET, + contentHeight: + anchorRect.top - HEIGHT_OFFSET - height > 0 + ? height + : anchorRect.top - HEIGHT_OFFSET, }; const bottomAlignment = { popoverTop: anchorRect.bottom, - contentHeight: anchorRect.bottom + HEIGHT_OFFSET + height > window.innerHeight ? window.innerHeight - HEIGHT_OFFSET - anchorRect.bottom : height, + contentHeight: + anchorRect.bottom + HEIGHT_OFFSET + height > window.innerHeight + ? window.innerHeight - HEIGHT_OFFSET - anchorRect.bottom + : height, }; // Choosing the y axis @@ -174,11 +213,20 @@ export function computePopoverYAxisPosition( anchorRect, contentSize, yAxis, cor chosenYAxis = 'middle'; } else if ( yAxis === 'top' && topAlignment.contentHeight === height ) { chosenYAxis = 'top'; - } else if ( yAxis === 'bottom' && bottomAlignment.contentHeight === height ) { + } else if ( + yAxis === 'bottom' && + bottomAlignment.contentHeight === height + ) { chosenYAxis = 'bottom'; } else { - chosenYAxis = topAlignment.contentHeight > bottomAlignment.contentHeight ? 'top' : 'bottom'; - const chosenHeight = chosenYAxis === 'top' ? topAlignment.contentHeight : bottomAlignment.contentHeight; + chosenYAxis = + topAlignment.contentHeight > bottomAlignment.contentHeight + ? 'top' + : 'bottom'; + const chosenHeight = + chosenYAxis === 'top' + ? topAlignment.contentHeight + : bottomAlignment.contentHeight; contentHeight = chosenHeight !== height ? chosenHeight : null; } } @@ -215,11 +263,33 @@ export function computePopoverYAxisPosition( anchorRect, contentSize, yAxis, cor * * @return {Object} Popover position and constraints. */ -export function computePopoverPosition( anchorRect, contentSize, position = 'top', sticky, anchorRef, relativeOffsetTop ) { +export function computePopoverPosition( + anchorRect, + contentSize, + position = 'top', + sticky, + anchorRef, + relativeOffsetTop +) { const [ yAxis, xAxis = 'center', corner ] = position.split( ' ' ); - const yAxisPosition = computePopoverYAxisPosition( anchorRect, contentSize, yAxis, corner, sticky, anchorRef, relativeOffsetTop ); - const xAxisPosition = computePopoverXAxisPosition( anchorRect, contentSize, xAxis, corner, sticky, yAxisPosition.yAxis ); + const yAxisPosition = computePopoverYAxisPosition( + anchorRect, + contentSize, + yAxis, + corner, + sticky, + anchorRef, + relativeOffsetTop + ); + const xAxisPosition = computePopoverXAxisPosition( + anchorRect, + contentSize, + xAxis, + corner, + sticky, + yAxisPosition.yAxis + ); return { ...xAxisPosition, diff --git a/packages/components/src/query-controls/category-select.js b/packages/components/src/query-controls/category-select.js index 0ee0575addaf58..b6e11839de990a 100644 --- a/packages/components/src/query-controls/category-select.js +++ b/packages/components/src/query-controls/category-select.js @@ -4,7 +4,13 @@ import { buildTermsTree } from './terms'; import TreeSelect from '../tree-select'; -export default function CategorySelect( { label, noOptionLabel, categoriesList, selectedCategoryId, onChange } ) { +export default function CategorySelect( { + label, + noOptionLabel, + categoriesList, + selectedCategoryId, + onChange, +} ) { const termsTree = buildTermsTree( categoriesList ); return ( <TreeSelect diff --git a/packages/components/src/query-controls/index.js b/packages/components/src/query-controls/index.js index f3cbf47998325c..e2d36815993215 100644 --- a/packages/components/src/query-controls/index.js +++ b/packages/components/src/query-controls/index.js @@ -26,7 +26,7 @@ export default function QueryControls( { onOrderByChange, } ) { return [ - ( onOrderChange && onOrderByChange ) && ( + onOrderChange && onOrderByChange && ( <SelectControl key="query-controls-order-select" label={ __( 'Order by' ) } @@ -70,7 +70,8 @@ export default function QueryControls( { noOptionLabel={ __( 'All' ) } selectedCategoryId={ selectedCategoryId } onChange={ onCategoryChange } - /> ), + /> + ), onNumberOfItemsChange && ( <RangeControl key="query-controls-range-control" diff --git a/packages/components/src/query-controls/terms.js b/packages/components/src/query-controls/terms.js index ba76e68980184e..60cfdd8563d0bf 100644 --- a/packages/components/src/query-controls/terms.js +++ b/packages/components/src/query-controls/terms.js @@ -28,9 +28,10 @@ export function buildTermsTree( flatTerms ) { const children = termsByParent[ term.id ]; return { ...term, - children: children && children.length ? - fillWithChildren( children ) : - [], + children: + children && children.length + ? fillWithChildren( children ) + : [], }; } ); }; diff --git a/packages/components/src/query-controls/test/terms.js b/packages/components/src/query-controls/test/terms.js index 11cf1505d4f27e..68a1a7f10fb190 100644 --- a/packages/components/src/query-controls/test/terms.js +++ b/packages/components/src/query-controls/test/terms.js @@ -34,9 +34,11 @@ describe( 'buildTermsTree()', () => { { id: 2245, parent: 2232 }, ] ); const output = [ - { id: 2232, parent: 0, children: [ - { id: 2245, parent: 2232, children: [] }, - ] }, + { + id: 2232, + parent: 0, + children: [ { id: 2245, parent: 2232, children: [] } ], + }, ]; const termsTreem = buildTermsTree( input ); expect( termsTreem ).toEqual( output ); @@ -49,10 +51,14 @@ describe( 'buildTermsTree()', () => { { id: 2246, parent: 2232 }, ] ); const output = [ - { id: 2232, parent: 0, children: [ - { id: 2245, parent: 2232, children: [] }, - { id: 2246, parent: 2232, children: [] }, - ] }, + { + id: 2232, + parent: 0, + children: [ + { id: 2245, parent: 2232, children: [] }, + { id: 2246, parent: 2232, children: [] }, + ], + }, { id: 2249, parent: 0, children: [] }, ]; const termsTreem = buildTermsTree( input ); diff --git a/packages/components/src/radio-control/index.js b/packages/components/src/radio-control/index.js index 8565d99d04d962..fcb167b07be249 100644 --- a/packages/components/src/radio-control/index.js +++ b/packages/components/src/radio-control/index.js @@ -14,33 +14,52 @@ import { useInstanceId } from '@wordpress/compose'; */ import BaseControl from '../base-control'; -export default function RadioControl( { label, className, selected, help, onChange, options = [] } ) { +export default function RadioControl( { + label, + className, + selected, + help, + onChange, + options = [], +} ) { const instanceId = useInstanceId( RadioControl ); const id = `inspector-radio-control-${ instanceId }`; const onChangeValue = ( event ) => onChange( event.target.value ); - return ! isEmpty( options ) && ( - <BaseControl label={ label } id={ id } help={ help } className={ classnames( className, 'components-radio-control' ) }> - { options.map( ( option, index ) => - <div - key={ `${ id }-${ index }` } - className="components-radio-control__option" - > - <input - id={ `${ id }-${ index }` } - className="components-radio-control__input" - type="radio" - name={ id } - value={ option.value } - onChange={ onChangeValue } - checked={ option.value === selected } - aria-describedby={ !! help ? `${ id }__help` : undefined } - /> - <label htmlFor={ `${ id }-${ index }` }> - { option.label } - </label> - </div> - ) } - </BaseControl> + return ( + ! isEmpty( options ) && ( + <BaseControl + label={ label } + id={ id } + help={ help } + className={ classnames( + className, + 'components-radio-control' + ) } + > + { options.map( ( option, index ) => ( + <div + key={ `${ id }-${ index }` } + className="components-radio-control__option" + > + <input + id={ `${ id }-${ index }` } + className="components-radio-control__input" + type="radio" + name={ id } + value={ option.value } + onChange={ onChangeValue } + checked={ option.value === selected } + aria-describedby={ + !! help ? `${ id }__help` : undefined + } + /> + <label htmlFor={ `${ id }-${ index }` }> + { option.label } + </label> + </div> + ) ) } + </BaseControl> + ) ); } diff --git a/packages/components/src/radio-control/stories/index.js b/packages/components/src/radio-control/stories/index.js index 8dfa5b55f231b1..6db176cb5d75c8 100644 --- a/packages/components/src/radio-control/stories/index.js +++ b/packages/components/src/radio-control/stories/index.js @@ -13,7 +13,9 @@ export default { title: 'Components/RadioControl', component: RadioControl }; const RadioControlWithState = ( props ) => { const [ option, setOption ] = useState( 'public' ); - return <RadioControl { ...props } selected={ option } onChange={ setOption } />; + return ( + <RadioControl { ...props } selected={ option } onChange={ setOption } /> + ); }; const options = [ @@ -23,7 +25,9 @@ const options = [ ]; export const _default = () => { - return <RadioControlWithState label="Post visibility" options={ options } />; + return ( + <RadioControlWithState label="Post visibility" options={ options } /> + ); }; export const withHelp = () => { diff --git a/packages/components/src/range-control/index.js b/packages/components/src/range-control/index.js index eb7a6a5a2235d2..988a32a363f3c3 100644 --- a/packages/components/src/range-control/index.js +++ b/packages/components/src/range-control/index.js @@ -59,17 +59,16 @@ function RangeControl( { // The input is valid, reset the local state property used to temporaly save the value, // and call onChange with the new value as a number. resetCurrentInput(); - onChange( ( newValue === '' ) ? - undefined : - parseFloat( newValue ) - ); + onChange( newValue === '' ? undefined : parseFloat( newValue ) ); }; - const initialFallbackValue = isFinite( initialPosition ) ? - initialPosition : ''; + const initialFallbackValue = isFinite( initialPosition ) + ? initialPosition + : ''; - const initialSliderValue = isFinite( currentInputValue ) ? - currentInputValue : initialFallbackValue; + const initialSliderValue = isFinite( currentInputValue ) + ? currentInputValue + : initialFallbackValue; return ( <BaseControl @@ -88,7 +87,8 @@ function RangeControl( { aria-describedby={ !! help ? id + '__help' : undefined } min={ min } max={ max } - { ...props } /> + { ...props } + /> { afterIcon && <Dashicon icon={ afterIcon } /> } <input className="components-range-control__number" diff --git a/packages/components/src/range-control/index.native.js b/packages/components/src/range-control/index.native.js index 59c439c04d3f14..5fe4a9f733e0d6 100644 --- a/packages/components/src/range-control/index.native.js +++ b/packages/components/src/range-control/index.native.js @@ -23,7 +23,9 @@ function RangeControl( { const currentInputValue = currentInput === null ? value : currentInput; - const initialSliderValue = isFinite( currentInputValue ) ? currentInputValue : initialPosition; + const initialSliderValue = isFinite( currentInputValue ) + ? currentInputValue + : initialPosition; return ( <RangeCell diff --git a/packages/components/src/range-control/stories/index.js b/packages/components/src/range-control/stories/index.js index 219dad5d42a46b..97abc17aa11f53 100644 --- a/packages/components/src/range-control/stories/index.js +++ b/packages/components/src/range-control/stories/index.js @@ -19,23 +19,13 @@ const RangeControlWithState = ( props ) => { const initialValue = props.value === undefined ? 5 : props.value; const [ value, setValue ] = useState( initialValue ); - return ( - <RangeControl - { ...props } - value={ value } - onChange={ setValue } - /> - ); + return <RangeControl { ...props } value={ value } onChange={ setValue } />; }; export const _default = () => { const label = text( 'Label', 'How many columns should this use?' ); - return ( - <RangeControlWithState - label={ label } - /> - ); + return <RangeControlWithState label={ label } />; }; export const InitialValueZero = () => { @@ -54,14 +44,12 @@ export const InitialValueZero = () => { export const withHelp = () => { const label = text( 'Label', 'How many columns should this use?' ); - const help = text( 'Help Text', 'Please select the number of columns you would like this to contain.' ); - - return ( - <RangeControlWithState - label={ label } - help={ help } - /> + const help = text( + 'Help Text', + 'Please select the number of columns you would like this to contain.' ); + + return <RangeControlWithState label={ label } help={ help } />; }; export const withMinimumAndMaximumLimits = () => { @@ -69,46 +57,25 @@ export const withMinimumAndMaximumLimits = () => { const min = number( 'Min Value', 2 ); const max = number( 'Max Value', 10 ); - return ( - <RangeControlWithState - label={ label } - min={ min } - max={ max } - /> - ); + return <RangeControlWithState label={ label } min={ min } max={ max } />; }; export const withIconBefore = () => { const label = text( 'Label', 'How many columns should this use?' ); const icon = text( 'Icon', 'wordpress' ); - return ( - <RangeControlWithState - label={ label } - beforeIcon={ icon } - /> - ); + return <RangeControlWithState label={ label } beforeIcon={ icon } />; }; export const withIconAfter = () => { const label = text( 'Label', 'How many columns should this use?' ); const icon = text( 'Icon', 'wordpress' ); - return ( - <RangeControlWithState - label={ label } - afterIcon={ icon } - /> - ); + return <RangeControlWithState label={ label } afterIcon={ icon } />; }; export const withReset = () => { const label = text( 'Label', 'How many columns should this use?' ); - return ( - <RangeControlWithState - label={ label } - allowReset - /> - ); + return <RangeControlWithState label={ label } allowReset />; }; diff --git a/packages/components/src/range-control/test/index.js b/packages/components/src/range-control/test/index.js index e1308c06363e4c..5f333b2a40676f 100644 --- a/packages/components/src/range-control/test/index.js +++ b/packages/components/src/range-control/test/index.js @@ -21,9 +21,8 @@ describe( 'RangeControl', () => { } } - const getWrapper = ( props = {} ) => TestUtils.renderIntoDocument( - <TestWrapper { ...props } /> - ); + const getWrapper = ( props = {} ) => + TestUtils.renderIntoDocument( <TestWrapper { ...props } /> ); describe( '#render()', () => { it( 'triggers change callback with numeric value', () => { @@ -31,36 +30,32 @@ describe( 'RangeControl', () => { const onChange = jest.fn(); const wrapper = getWrapper( { onChange } ); - const rangeInputElement = () => TestUtils.findRenderedDOMComponentWithClass( - wrapper, - 'components-range-control__slider' - ); - const numberInputElement = () => TestUtils.findRenderedDOMComponentWithClass( - wrapper, - 'components-range-control__number' - ); - TestUtils.Simulate.change( - rangeInputElement(), - { - target: { - value: '5', - checkValidity() { - return true; - }, + const rangeInputElement = () => + TestUtils.findRenderedDOMComponentWithClass( + wrapper, + 'components-range-control__slider' + ); + const numberInputElement = () => + TestUtils.findRenderedDOMComponentWithClass( + wrapper, + 'components-range-control__number' + ); + TestUtils.Simulate.change( rangeInputElement(), { + target: { + value: '5', + checkValidity() { + return true; }, - } - ); - TestUtils.Simulate.change( - numberInputElement(), - { - target: { - value: '10', - checkValidity() { - return true; - }, + }, + } ); + TestUtils.Simulate.change( numberInputElement(), { + target: { + value: '10', + checkValidity() { + return true; }, - } - ); + }, + } ); expect( onChange ).toHaveBeenCalledWith( 5 ); expect( onChange ).toHaveBeenCalledWith( 10 ); @@ -68,8 +63,8 @@ describe( 'RangeControl', () => { it( 'renders with icons', () => { let wrapper, icons; - const iconElements = ( component ) => TestUtils - .scryRenderedComponentsWithType( component, Dashicon ); + const iconElements = ( component ) => + TestUtils.scryRenderedComponentsWithType( component, Dashicon ); wrapper = getWrapper(); icons = iconElements( wrapper ); expect( icons ).toHaveLength( 0 ); @@ -79,12 +74,10 @@ describe( 'RangeControl', () => { expect( icons ).toHaveLength( 1 ); expect( icons[ 0 ].props.icon ).toBe( 'format-image' ); - wrapper = getWrapper( - { - beforeIcon: 'format-image', - afterIcon: 'format-video', - } - ); + wrapper = getWrapper( { + beforeIcon: 'format-image', + afterIcon: 'format-video', + } ); icons = iconElements( wrapper ); expect( icons ).toHaveLength( 2 ); expect( icons[ 0 ].props.icon ).toBe( 'format-image' ); @@ -98,22 +91,20 @@ describe( 'RangeControl', () => { const onChange = jest.fn(); const wrapper = getWrapper( { onChange, min: 11, value: 12 } ); - const numberInputElement = () => TestUtils.findRenderedDOMComponentWithClass( - wrapper, - 'components-range-control__number' - ); - - TestUtils.Simulate.change( - numberInputElement(), - { - target: { - value: '10', - checkValidity() { - return false; - }, + const numberInputElement = () => + TestUtils.findRenderedDOMComponentWithClass( + wrapper, + 'components-range-control__number' + ); + + TestUtils.Simulate.change( numberInputElement(), { + target: { + value: '10', + checkValidity() { + return false; }, - } - ); + }, + } ); expect( onChange ).not.toHaveBeenCalled(); } ); @@ -123,22 +114,20 @@ describe( 'RangeControl', () => { const onChange = jest.fn(); const wrapper = getWrapper( { onChange, max: 20, value: 12 } ); - const numberInputElement = () => TestUtils.findRenderedDOMComponentWithClass( - wrapper, - 'components-range-control__number' - ); - - TestUtils.Simulate.change( - numberInputElement(), - { - target: { - value: '21', - checkValidity() { - return false; - }, + const numberInputElement = () => + TestUtils.findRenderedDOMComponentWithClass( + wrapper, + 'components-range-control__number' + ); + + TestUtils.Simulate.change( numberInputElement(), { + target: { + value: '21', + checkValidity() { + return false; }, - } - ); + }, + } ); expect( onChange ).not.toHaveBeenCalled(); } ); @@ -146,163 +135,160 @@ describe( 'RangeControl', () => { it( 'calls onChange after invalid inputs if the new input is valid', () => { // Mount: With shallow, cannot find input child of BaseControl const onChange = jest.fn(); - const wrapper = getWrapper( { onChange, min: 11, max: 20, value: 12 } ); - - const numberInputElement = () => TestUtils.findRenderedDOMComponentWithClass( - wrapper, - 'components-range-control__number' - ); - - TestUtils.Simulate.change( - numberInputElement(), - { - target: { - value: '10', - checkValidity() { - return false; - }, + const wrapper = getWrapper( { + onChange, + min: 11, + max: 20, + value: 12, + } ); + + const numberInputElement = () => + TestUtils.findRenderedDOMComponentWithClass( + wrapper, + 'components-range-control__number' + ); + + TestUtils.Simulate.change( numberInputElement(), { + target: { + value: '10', + checkValidity() { + return false; }, - } - ); - - TestUtils.Simulate.change( - numberInputElement(), - { - target: { - value: '21', - checkValidity() { - return false; - }, + }, + } ); + + TestUtils.Simulate.change( numberInputElement(), { + target: { + value: '21', + checkValidity() { + return false; }, - } - ); + }, + } ); expect( onChange ).not.toHaveBeenCalled(); - TestUtils.Simulate.change( - numberInputElement(), - { - target: { - value: '14', - checkValidity() { - return true; - }, + TestUtils.Simulate.change( numberInputElement(), { + target: { + value: '14', + checkValidity() { + return true; }, - } - ); + }, + } ); expect( onChange ).toHaveBeenCalledWith( 14 ); } ); it( 'validates when provided a max or min of zero', () => { const onChange = jest.fn(); - const wrapper = getWrapper( { onChange, min: -100, max: 0, value: 0 } ); - - const numberInputElement = () => TestUtils.findRenderedDOMComponentWithClass( - wrapper, - 'components-range-control__number' - ); - - TestUtils.Simulate.change( - numberInputElement(), - { - target: { - value: '1', - checkValidity() { - return false; - }, + const wrapper = getWrapper( { + onChange, + min: -100, + max: 0, + value: 0, + } ); + + const numberInputElement = () => + TestUtils.findRenderedDOMComponentWithClass( + wrapper, + 'components-range-control__number' + ); + + TestUtils.Simulate.change( numberInputElement(), { + target: { + value: '1', + checkValidity() { + return false; }, - } - ); + }, + } ); expect( onChange ).not.toHaveBeenCalled(); } ); it( 'validates when min and max are negative', () => { const onChange = jest.fn(); - const wrapper = getWrapper( { onChange, min: -100, max: -50, value: -60 } ); - - const numberInputElement = () => TestUtils.findRenderedDOMComponentWithClass( - wrapper, - 'components-range-control__number' - ); - - TestUtils.Simulate.change( - numberInputElement(), - { - target: { - value: '-101', - checkValidity() { - return false; - }, + const wrapper = getWrapper( { + onChange, + min: -100, + max: -50, + value: -60, + } ); + + const numberInputElement = () => + TestUtils.findRenderedDOMComponentWithClass( + wrapper, + 'components-range-control__number' + ); + + TestUtils.Simulate.change( numberInputElement(), { + target: { + value: '-101', + checkValidity() { + return false; }, - } - ); + }, + } ); expect( onChange ).not.toHaveBeenCalled(); - TestUtils.Simulate.change( - numberInputElement(), - { - target: { - value: '-49', - checkValidity() { - return false; - }, + TestUtils.Simulate.change( numberInputElement(), { + target: { + value: '-49', + checkValidity() { + return false; }, - } - ); + }, + } ); expect( onChange ).not.toHaveBeenCalled(); - TestUtils.Simulate.change( - numberInputElement(), - { - target: { - value: '-50', - checkValidity() { - return true; - }, + TestUtils.Simulate.change( numberInputElement(), { + target: { + value: '-50', + checkValidity() { + return true; }, - } - ); + }, + } ); expect( onChange ).toHaveBeenCalledWith( -50 ); } ); it( 'takes into account the step starting from min', () => { const onChange = jest.fn(); - const wrapper = getWrapper( { onChange, min: 0.1, step: 0.125, value: 0.1 } ); - - const numberInputElement = () => TestUtils.findRenderedDOMComponentWithClass( - wrapper, - 'components-range-control__number' - ); - - TestUtils.Simulate.change( - numberInputElement(), - { - target: { - value: '0.125', - checkValidity() { - return false; - }, + const wrapper = getWrapper( { + onChange, + min: 0.1, + step: 0.125, + value: 0.1, + } ); + + const numberInputElement = () => + TestUtils.findRenderedDOMComponentWithClass( + wrapper, + 'components-range-control__number' + ); + + TestUtils.Simulate.change( numberInputElement(), { + target: { + value: '0.125', + checkValidity() { + return false; }, - } - ); + }, + } ); expect( onChange ).not.toHaveBeenCalled(); - TestUtils.Simulate.change( - numberInputElement(), - { - target: { - value: '0.225', - checkValidity() { - return true; - }, + TestUtils.Simulate.change( numberInputElement(), { + target: { + value: '0.225', + checkValidity() { + return true; }, - } - ); + }, + } ); expect( onChange ).toHaveBeenCalledWith( 0.225 ); } ); diff --git a/packages/components/src/resizable-box/index.js b/packages/components/src/resizable-box/index.js index 1426f0b48b83e9..71db3d9cafc592 100644 --- a/packages/components/src/resizable-box/index.js +++ b/packages/components/src/resizable-box/index.js @@ -30,46 +30,46 @@ function ResizableBox( { className, showHandle = false, ...props } ) { top: classnames( handleClassName, sideHandleClassName, - 'components-resizable-box__handle-top', + 'components-resizable-box__handle-top' ), right: classnames( handleClassName, sideHandleClassName, - 'components-resizable-box__handle-right', + 'components-resizable-box__handle-right' ), bottom: classnames( handleClassName, sideHandleClassName, - 'components-resizable-box__handle-bottom', + 'components-resizable-box__handle-bottom' ), left: classnames( handleClassName, sideHandleClassName, - 'components-resizable-box__handle-left', + 'components-resizable-box__handle-left' ), topLeft: classnames( handleClassName, cornerHandleClassName, 'components-resizable-box__handle-top', - 'components-resizable-box__handle-left', + 'components-resizable-box__handle-left' ), topRight: classnames( handleClassName, cornerHandleClassName, 'components-resizable-box__handle-top', - 'components-resizable-box__handle-right', + 'components-resizable-box__handle-right' ), bottomRight: classnames( handleClassName, cornerHandleClassName, 'components-resizable-box__handle-bottom', - 'components-resizable-box__handle-right', + 'components-resizable-box__handle-right' ), bottomLeft: classnames( handleClassName, cornerHandleClassName, 'components-resizable-box__handle-bottom', - 'components-resizable-box__handle-left', + 'components-resizable-box__handle-left' ), } } handleStyles={ { diff --git a/packages/components/src/resizable-box/stories/index.js b/packages/components/src/resizable-box/stories/index.js index d0504ad6249936..da7d9133acd22b 100644 --- a/packages/components/src/resizable-box/stories/index.js +++ b/packages/components/src/resizable-box/stories/index.js @@ -16,7 +16,10 @@ import { useState } from '@wordpress/element'; export default { title: 'Components/ResizableBox', component: ResizableBox }; const Example = ( props ) => { - const [ attributes, setAttributes ] = useState( { height: 200, width: 400 } ); + const [ attributes, setAttributes ] = useState( { + height: 200, + width: 400, + } ); const { height, width } = attributes; const { children, ...restProps } = props; diff --git a/packages/components/src/responsive-wrapper/index.js b/packages/components/src/responsive-wrapper/index.js index 4c5dfca6ec363b..d5214a2f1d5dca 100644 --- a/packages/components/src/responsive-wrapper/index.js +++ b/packages/components/src/responsive-wrapper/index.js @@ -18,14 +18,17 @@ function ResponsiveWrapper( { return null; } const imageStyle = { - paddingBottom: ( naturalHeight / naturalWidth * 100 ) + '%', + paddingBottom: ( naturalHeight / naturalWidth ) * 100 + '%', }; const TagName = isInline ? 'span' : 'div'; return ( <TagName className="components-responsive-wrapper"> <TagName style={ imageStyle } /> { cloneElement( children, { - className: classnames( 'components-responsive-wrapper__content', children.props.className ), + className: classnames( + 'components-responsive-wrapper__content', + children.props.className + ), } ) } </TagName> ); diff --git a/packages/components/src/sandbox/index.js b/packages/components/src/sandbox/index.js index a607d31487cc20..fcd2508c22cf7c 100644 --- a/packages/components/src/sandbox/index.js +++ b/packages/components/src/sandbox/index.js @@ -61,7 +61,10 @@ class Sandbox extends Component { const { action, width, height } = data; const { width: oldWidth, height: oldHeight } = this.state; - if ( 'resize' === action && ( oldWidth !== width || oldHeight !== height ) ) { + if ( + 'resize' === action && + ( oldWidth !== width || oldHeight !== height ) + ) { this.setState( { width, height } ); } } @@ -163,20 +166,38 @@ class Sandbox extends Component { // Scripts go into the body rather than the head, to support embedded content such as Instagram // that expect the scripts to be part of the body. const htmlDoc = ( - <html lang={ document.documentElement.lang } className={ this.props.type }> + <html + lang={ document.documentElement.lang } + className={ this.props.type } + > <head> <title>{ this.props.title }</title> <style dangerouslySetInnerHTML={ { __html: style } } /> - { ( this.props.styles && this.props.styles.map( - ( rules, i ) => <style key={ i } dangerouslySetInnerHTML={ { __html: rules } } /> - ) ) } + { this.props.styles && + this.props.styles.map( ( rules, i ) => ( + <style + key={ i } + dangerouslySetInnerHTML={ { __html: rules } } + /> + ) ) } </head> - <body data-resizable-iframe-connected="data-resizable-iframe-connected" className={ this.props.type }> - <div dangerouslySetInnerHTML={ { __html: this.props.html } } /> - <script type="text/javascript" dangerouslySetInnerHTML={ { __html: observeAndResizeJS } } /> - { ( this.props.scripts && this.props.scripts.map( - ( src ) => <script key={ src } src={ src } /> - ) ) } + <body + data-resizable-iframe-connected="data-resizable-iframe-connected" + className={ this.props.type } + > + <div + dangerouslySetInnerHTML={ { __html: this.props.html } } + /> + <script + type="text/javascript" + dangerouslySetInnerHTML={ { + __html: observeAndResizeJS, + } } + /> + { this.props.scripts && + this.props.scripts.map( ( src ) => ( + <script key={ src } src={ src } /> + ) ) } </body> </html> ); @@ -209,7 +230,8 @@ class Sandbox extends Component { onLoad={ this.trySandbox } onFocus={ onFocus } width={ Math.ceil( this.state.width ) } - height={ Math.ceil( this.state.height ) } /> + height={ Math.ceil( this.state.height ) } + /> ); } } diff --git a/packages/components/src/scroll-lock/index.js b/packages/components/src/scroll-lock/index.js index 1d6255d8c12ef9..2808f434389a21 100644 --- a/packages/components/src/scroll-lock/index.js +++ b/packages/components/src/scroll-lock/index.js @@ -35,7 +35,8 @@ export function createScrollLockComponent( { * @param {boolean} locked Whether or not scroll should be locked. */ function setLocked( locked ) { - const scrollingElement = htmlDocument.scrollingElement || htmlDocument.body; + const scrollingElement = + htmlDocument.scrollingElement || htmlDocument.body; if ( locked ) { previousScrollTop = scrollingElement.scrollTop; diff --git a/packages/components/src/scroll-lock/stories/index.js b/packages/components/src/scroll-lock/stories/index.js index 16d3720f9b64a9..ead007d69a9287 100644 --- a/packages/components/src/scroll-lock/stories/index.js +++ b/packages/components/src/scroll-lock/stories/index.js @@ -24,7 +24,8 @@ const Example = () => { </Button> { isScrollLocked && <ScrollLock /> } <p> - Scroll locked: <strong>{ isScrollLocked ? 'Yes' : 'No' }</strong> + Scroll locked:{ ' ' } + <strong>{ isScrollLocked ? 'Yes' : 'No' }</strong> </p> </ToggleContainer> </StripedBackground> diff --git a/packages/components/src/scroll-lock/test/index.js b/packages/components/src/scroll-lock/test/index.js index 13202ec2bd7983..c5f602239a247f 100644 --- a/packages/components/src/scroll-lock/test/index.js +++ b/packages/components/src/scroll-lock/test/index.js @@ -17,13 +17,19 @@ describe( 'scroll-lock', () => { let wrapper = null; function expectLocked( locked ) { - expect( testDocument.documentElement.classList.contains( lockingClassName ) ).toBe( locked ); + expect( + testDocument.documentElement.classList.contains( lockingClassName ) + ).toBe( locked ); // Assert against `body` because `scrollingElement` does not exist on our test DOM implementation. - expect( testDocument.body.classList.contains( lockingClassName ) ).toBe( locked ); + expect( testDocument.body.classList.contains( lockingClassName ) ).toBe( + locked + ); } beforeEach( () => { - testDocument = document.implementation.createHTMLDocument( 'Test scroll-lock' ); + testDocument = document.implementation.createHTMLDocument( + 'Test scroll-lock' + ); ScrollLock = createScrollLockComponent( { htmlDocument: testDocument, className: lockingClassName, diff --git a/packages/components/src/select-control/index.js b/packages/components/src/select-control/index.js index de9b78b2c3c128..8ad65f890e7acb 100644 --- a/packages/components/src/select-control/index.js +++ b/packages/components/src/select-control/index.js @@ -27,7 +27,9 @@ export default function SelectControl( { const id = `inspector-select-control-${ instanceId }`; const onChangeValue = ( event ) => { if ( multiple ) { - const selectedOptions = [ ...event.target.options ].filter( ( { selected } ) => selected ); + const selectedOptions = [ ...event.target.options ].filter( + ( { selected } ) => selected + ); const newValues = selectedOptions.map( ( { value } ) => value ); onChange( newValues ); return; @@ -38,27 +40,35 @@ export default function SelectControl( { // Disable reason: A select with an onchange throws a warning /* eslint-disable jsx-a11y/no-onchange */ - return ! isEmpty( options ) && ( - <BaseControl label={ label } hideLabelFromVision={ hideLabelFromVision } id={ id } help={ help } className={ className }> - <select + return ( + ! isEmpty( options ) && ( + <BaseControl + label={ label } + hideLabelFromVision={ hideLabelFromVision } id={ id } - className="components-select-control__input" - onChange={ onChangeValue } - aria-describedby={ !! help ? `${ id }__help` : undefined } - multiple={ multiple } - { ...props } + help={ help } + className={ className } > - { options.map( ( option, index ) => - <option - key={ `${ option.label }-${ option.value }-${ index }` } - value={ option.value } - disabled={ option.disabled } - > - { option.label } - </option> - ) } - </select> - </BaseControl> + <select + id={ id } + className="components-select-control__input" + onChange={ onChangeValue } + aria-describedby={ !! help ? `${ id }__help` : undefined } + multiple={ multiple } + { ...props } + > + { options.map( ( option, index ) => ( + <option + key={ `${ option.label }-${ option.value }-${ index }` } + value={ option.value } + disabled={ option.disabled } + > + { option.label } + </option> + ) ) } + </select> + </BaseControl> + ) ); /* eslint-enable jsx-a11y/no-onchange */ } diff --git a/packages/components/src/shortcut/test/index.js b/packages/components/src/shortcut/test/index.js index 7e75665cd653d9..d6b5baa27dfd41 100644 --- a/packages/components/src/shortcut/test/index.js +++ b/packages/components/src/shortcut/test/index.js @@ -16,11 +16,7 @@ describe( 'Shortcut', () => { } ); it( 'renders the shortcut display text when a string is passed as the shortcut', () => { - const wrapper = shallow( - <Shortcut - shortcut="shortcut text" - /> - ); + const wrapper = shallow( <Shortcut shortcut="shortcut text" /> ); expect( wrapper.text() ).toBe( 'shortcut text' ); } ); diff --git a/packages/components/src/slot-fill/context.js b/packages/components/src/slot-fill/context.js index b4229c71ffaad5..5b6ec9527e15e6 100644 --- a/packages/components/src/slot-fill/context.js +++ b/packages/components/src/slot-fill/context.js @@ -6,7 +6,13 @@ import { sortBy, forEach, without } from 'lodash'; /** * WordPress dependencies */ -import { Component, createContext, useContext, useState, useEffect } from '@wordpress/element'; +import { + Component, + createContext, + useContext, + useState, + useEffect, +} from '@wordpress/element'; const SlotFillContext = createContext( { registerSlot: () => {}, @@ -66,10 +72,7 @@ class SlotFillProvider extends Component { } registerFill( name, instance ) { - this.fills[ name ] = [ - ...( this.fills[ name ] || [] ), - instance, - ]; + this.fills[ name ] = [ ...( this.fills[ name ] || [] ), instance ]; this.forceUpdateSlot( name ); } @@ -86,10 +89,7 @@ class SlotFillProvider extends Component { } unregisterFill( name, instance ) { - this.fills[ name ] = without( - this.fills[ name ], - instance - ); + this.fills[ name ] = without( this.fills[ name ], instance ); this.resetFillOccurrence( name ); this.forceUpdateSlot( name ); } diff --git a/packages/components/src/slot-fill/slot.js b/packages/components/src/slot-fill/slot.js index 4578656b5efaa9..7bb75172582a7f 100644 --- a/packages/components/src/slot-fill/slot.js +++ b/packages/components/src/slot-fill/slot.js @@ -1,12 +1,7 @@ /** * External dependencies */ -import { - isFunction, - isString, - map, - negate, -} from 'lodash'; +import { isFunction, isString, map, negate } from 'lodash'; /** * WordPress dependencies @@ -56,7 +51,14 @@ class SlotComponent extends Component { } render() { - const { children, name, bubblesVirtually = false, fillProps = {}, getFills, className } = this.props; + const { + children, + name, + bubblesVirtually = false, + fillProps = {}, + getFills, + className, + } = this.props; if ( bubblesVirtually ) { return <div ref={ this.bindNode } className={ className } />; @@ -64,7 +66,9 @@ class SlotComponent extends Component { const fills = map( getFills( name, this ), ( fill ) => { const fillKey = fill.occurrence; - const fillChildren = isFunction( fill.children ) ? fill.children( fillProps ) : fill.children; + const fillChildren = isFunction( fill.children ) + ? fill.children( fillProps ) + : fill.children; return Children.map( fillChildren, ( child, childIndex ) => { if ( ! child || isString( child ) ) { @@ -81,11 +85,7 @@ class SlotComponent extends Component { negate( isEmptyElement ) ); - return ( - <> - { isFunction( children ) ? children( fills ) : fills } - </> - ); + return <>{ isFunction( children ) ? children( fills ) : fills }</>; } } diff --git a/packages/components/src/slot-fill/test/slot.js b/packages/components/src/slot-fill/test/slot.js index c4a61e8e2a9a55..68fccc18926497 100644 --- a/packages/components/src/slot-fill/test/slot.js +++ b/packages/components/src/slot-fill/test/slot.js @@ -26,8 +26,14 @@ class Filler extends Component { } render() { return [ - <button key="1" type="button" onClick={ () => this.setState( { num: this.state.num + 1 } ) } />, - <Fill name={ this.props.name } key="2">{ this.props.text || this.state.num.toString() }</Fill>, + <button + key="1" + type="button" + onClick={ () => this.setState( { num: this.state.num + 1 } ) } + />, + <Fill name={ this.props.name } key="2"> + { this.props.text || this.state.num.toString() } + </Fill>, ]; } } @@ -52,9 +58,7 @@ describe( 'Slot', () => { <div> <Slot name="chicken" /> </div> - <Fill name="chicken"> - content - </Fill> + <Fill name="chicken">content</Fill> </Provider> ).toJSON(); @@ -117,11 +121,11 @@ describe( 'Slot', () => { <Provider> <div> <Slot name="chicken"> - { ( fills ) => ( ! isEmpty( fills ) && ( - <blockquote> - { fills } - </blockquote> - ) ) } + { ( fills ) => + ! isEmpty( fills ) && ( + <blockquote>{ fills }</blockquote> + ) + } </Slot> </div> <Fill name="chicken" /> @@ -136,16 +140,12 @@ describe( 'Slot', () => { <Provider> <div> <Slot name="chicken"> - { ( fills ) => ( fills && ( - <blockquote> - { fills } - </blockquote> - ) ) } + { ( fills ) => + fills && <blockquote>{ fills }</blockquote> + } </Slot> </div> - <Fill name="chicken"> - content - </Fill> + <Fill name="chicken">content</Fill> </Provider> ).toJSON(); @@ -217,7 +217,10 @@ describe( 'Slot', () => { const testRenderer = ReactTestRenderer.create( <Provider> <div data-position="first"> - <Slot name="egg" bubblesVirtually={ bubblesVirtually } /> + <Slot + name="egg" + bubblesVirtually={ bubblesVirtually } + /> </div> <div data-position="second"></div> <Fill name="egg">Content</Fill> @@ -227,10 +230,16 @@ describe( 'Slot', () => { testRenderer.update( <Provider> <div data-position="first"> - <Slot name="egg" bubblesVirtually={ bubblesVirtually } /> + <Slot + name="egg" + bubblesVirtually={ bubblesVirtually } + /> </div> <div data-position="second"> - <Slot name="egg" bubblesVirtually={ bubblesVirtually } /> + <Slot + name="egg" + bubblesVirtually={ bubblesVirtually } + /> </div> <Fill name="egg">Content</Fill> </Provider> @@ -242,7 +251,10 @@ describe( 'Slot', () => { <Provider> <div data-position="first"></div> <div data-position="second"> - <Slot name="egg" bubblesVirtually={ bubblesVirtually } /> + <Slot + name="egg" + bubblesVirtually={ bubblesVirtually } + /> </div> <Fill name="egg">Content</Fill> </Provider> @@ -250,7 +262,9 @@ describe( 'Slot', () => { expect( testRenderer.toJSON() ).toMatchSnapshot(); - expect( testRenderer.getInstance().slots ).toHaveProperty( 'egg' ); + expect( testRenderer.getInstance().slots ).toHaveProperty( + 'egg' + ); } ); } ); diff --git a/packages/components/src/snackbar/index.js b/packages/components/src/snackbar/index.js index 88140d8083891b..6b89b897277b1a 100644 --- a/packages/components/src/snackbar/index.js +++ b/packages/components/src/snackbar/index.js @@ -18,12 +18,10 @@ import { Button } from '../'; const NOTICE_TIMEOUT = 10000; -function Snackbar( { - className, - children, - actions = [], - onRemove = noop, -}, ref ) { +function Snackbar( + { className, children, actions = [], onRemove = noop }, + ref +) { useEffect( () => { const timeoutHandle = setTimeout( () => { onRemove(); @@ -35,7 +33,9 @@ function Snackbar( { const classes = classnames( className, 'components-snackbar' ); if ( actions && actions.length > 1 ) { // we need to inform developers that snackbar only accepts 1 action - warning( 'Snackbar can only have 1 action, use Notice if your message require many messages' ); + warning( + 'Snackbar can only have 1 action, use Notice if your message require many messages' + ); // return first element only while keeping it inside an array actions = [ actions[ 0 ] ]; } @@ -52,35 +52,24 @@ function Snackbar( { > <div className="components-snackbar__content"> { children } - { actions.map( - ( - { - label, - onClick, - url, - }, - index - ) => { - return ( - <Button - key={ index } - href={ url } - isTertiary - onClick={ ( event ) => { - event.stopPropagation(); - if ( onClick ) { - onClick( event ); - } - } } - className="components-snackbar__action" - - > - { label } - </Button> - ); - } - - ) } + { actions.map( ( { label, onClick, url }, index ) => { + return ( + <Button + key={ index } + href={ url } + isTertiary + onClick={ ( event ) => { + event.stopPropagation(); + if ( onClick ) { + onClick( event ); + } + } } + className="components-snackbar__action" + > + { label } + </Button> + ); + } ) } </div> </div> ); diff --git a/packages/components/src/snackbar/list.js b/packages/components/src/snackbar/list.js index f3cb5d544fcad5..1246822590fcc7 100644 --- a/packages/components/src/snackbar/list.js +++ b/packages/components/src/snackbar/list.js @@ -29,19 +29,19 @@ import Snackbar from './'; function SnackbarList( { notices, className, children, onRemove = noop } ) { const isReducedMotion = useReducedMotion(); const [ refMap ] = useState( () => new WeakMap() ); - const transitions = useTransition( - notices, - ( notice ) => notice.id, - { - from: { opacity: 0, height: 0 }, - enter: ( item ) => async ( next ) => await next( { opacity: 1, height: refMap.get( item ).offsetHeight } ), - leave: () => async ( next ) => { - await next( { opacity: 0 } ); - await next( { height: 0 } ); - }, - immediate: isReducedMotion, - } - ); + const transitions = useTransition( notices, ( notice ) => notice.id, { + from: { opacity: 0, height: 0 }, + enter: ( item ) => async ( next ) => + await next( { + opacity: 1, + height: refMap.get( item ).offsetHeight, + } ), + leave: () => async ( next ) => { + await next( { opacity: 0 } ); + await next( { height: 0 } ); + }, + immediate: isReducedMotion, + } ); className = classnames( 'components-snackbar-list', className ); const removeNotice = ( notice ) => () => onRemove( notice.id ); diff --git a/packages/components/src/spinner/index.native.js b/packages/components/src/spinner/index.native.js index 14070e423cb991..263703486a5789 100644 --- a/packages/components/src/spinner/index.native.js +++ b/packages/components/src/spinner/index.native.js @@ -8,12 +8,10 @@ import { View } from 'react-native'; */ import style from './style.scss'; -export default function Spinner( props ) { +export default function Spinner( props ) { const { progress } = props; const width = progress + '%'; - return ( - <View style={ [ style.spinner, { width } ] } /> - ); + return <View style={ [ style.spinner, { width } ] } />; } diff --git a/packages/components/src/tab-panel/index.js b/packages/components/src/tab-panel/index.js index 71f4683bcbef80..bdc40c637053b1 100644 --- a/packages/components/src/tab-panel/index.js +++ b/packages/components/src/tab-panel/index.js @@ -17,7 +17,8 @@ import { NavigableMenu } from '../navigable-container'; import Button from '../button'; const TabButton = ( { tabId, onClick, children, selected, ...rest } ) => ( - <Button role="tab" + <Button + role="tab" tabIndex={ selected ? null : -1 } aria-selected={ selected } id={ tabId } @@ -37,7 +38,8 @@ class TabPanel extends Component { this.onNavigate = this.onNavigate.bind( this ); this.state = { - selected: initialTabName || ( tabs.length > 0 ? tabs[ 0 ].name : null ), + selected: + initialTabName || ( tabs.length > 0 ? tabs[ 0 ].name : null ), }; } @@ -75,9 +77,14 @@ class TabPanel extends Component { className="components-tab-panel__tabs" > { tabs.map( ( tab ) => ( - <TabButton className={ classnames( tab.className, { [ activeClass ]: tab.name === selected } ) } + <TabButton + className={ classnames( tab.className, { + [ activeClass ]: tab.name === selected, + } ) } tabId={ instanceId + '-' + tab.name } - aria-controls={ instanceId + '-' + tab.name + '-view' } + aria-controls={ + instanceId + '-' + tab.name + '-view' + } selected={ tab.name === selected } key={ tab.name } onClick={ partial( this.handleClick, tab.name ) } @@ -87,7 +94,8 @@ class TabPanel extends Component { ) ) } </NavigableMenu> { selectedTab && ( - <div aria-labelledby={ selectedId } + <div + aria-labelledby={ selectedId } role="tabpanel" id={ selectedId + '-view' } className="components-tab-panel__tab-content" diff --git a/packages/components/src/tab-panel/stories/index.js b/packages/components/src/tab-panel/stories/index.js index dd137c21b6ce80..8647e3ddab24a9 100644 --- a/packages/components/src/tab-panel/stories/index.js +++ b/packages/components/src/tab-panel/stories/index.js @@ -12,7 +12,8 @@ export default { title: 'Components/TabPanel', component: TabPanel }; export const _default = () => { return ( - <TabPanel className="my-tab-panel" + <TabPanel + className="my-tab-panel" activeClass="active-tab" tabs={ [ { @@ -25,10 +26,9 @@ export const _default = () => { title: text( 'Tab 2 title', 'Tab 2' ), className: 'tab-two', }, - ] }> - { - ( tab ) => <p>Selected tab: { tab.title }</p> - } + ] } + > + { ( tab ) => <p>Selected tab: { tab.title }</p> } </TabPanel> ); }; diff --git a/packages/components/src/tab-panel/test/index.js b/packages/components/src/tab-panel/test/index.js index 153441cd195316..e3d511a554a27d 100644 --- a/packages/components/src/tab-panel/test/index.js +++ b/packages/components/src/tab-panel/test/index.js @@ -15,12 +15,17 @@ import { Component } from '@wordpress/element'; describe( 'TabPanel', () => { const getElementByClass = ( wrapper, className ) => { - return TestUtils.findRenderedDOMComponentWithClass( wrapper, className ); + return TestUtils.findRenderedDOMComponentWithClass( + wrapper, + className + ); }; const getElementsByClass = ( wrapper, className ) => { - return TestUtils - .scryRenderedDOMComponentsWithClass( wrapper, className ); + return TestUtils.scryRenderedDOMComponentsWithClass( + wrapper, + className + ); }; const elementClick = ( element ) => { @@ -61,7 +66,11 @@ describe( 'TabPanel', () => { }, ], children: ( tab ) => { - return <p tabIndex="0" className={ tab.name + '-view' }>{ tab.name }</p>; + return ( + <p tabIndex="0" className={ tab.name + '-view' }> + { tab.name } + </p> + ); }, }; @@ -73,12 +82,20 @@ describe( 'TabPanel', () => { const betaTab = getElementByClass( wrapper, 'beta' ); const gammaTab = getElementByClass( wrapper, 'gamma' ); - const getAlphaViews = () => getElementsByClass( wrapper, 'alpha-view' ); - const getBetaViews = () => getElementsByClass( wrapper, 'beta-view' ); - const getGammaViews = () => getElementsByClass( wrapper, 'gamma-view' ); - - const getActiveTab = () => getElementByClass( wrapper, 'active-tab' ); - const getActiveView = () => getElementByClass( wrapper, 'components-tab-panel__tab-content' ).firstChild.textContent; + const getAlphaViews = () => + getElementsByClass( wrapper, 'alpha-view' ); + const getBetaViews = () => + getElementsByClass( wrapper, 'beta-view' ); + const getGammaViews = () => + getElementsByClass( wrapper, 'gamma-view' ); + + const getActiveTab = () => + getElementByClass( wrapper, 'active-tab' ); + const getActiveView = () => + getElementByClass( + wrapper, + 'components-tab-panel__tab-content' + ).firstChild.textContent; expect( getActiveTab().innerHTML ).toBe( 'Alpha' ); expect( getAlphaViews() ).toHaveLength( 1 ); @@ -138,7 +155,11 @@ describe( 'TabPanel', () => { }, ], children: ( tab ) => { - return <p tabIndex="0" className={ tab.name + '-view' }>{ tab.name }</p>; + return ( + <p tabIndex="0" className={ tab.name + '-view' }> + { tab.name } + </p> + ); }, }; const wrapper = TestUtils.renderIntoDocument( diff --git a/packages/components/src/text-control/index.js b/packages/components/src/text-control/index.js index c2d0ccdf6711b5..a172eed483e50a 100644 --- a/packages/components/src/text-control/index.js +++ b/packages/components/src/text-control/index.js @@ -8,14 +8,30 @@ import { useInstanceId } from '@wordpress/compose'; */ import BaseControl from '../base-control'; -export default function TextControl( { label, hideLabelFromVision, value, help, className, onChange, type = 'text', ...props } ) { +export default function TextControl( { + label, + hideLabelFromVision, + value, + help, + className, + onChange, + type = 'text', + ...props +} ) { const instanceId = useInstanceId( TextControl ); const id = `inspector-text-control-${ instanceId }`; const onChangeValue = ( event ) => onChange( event.target.value ); return ( - <BaseControl label={ label } hideLabelFromVision={ hideLabelFromVision } id={ id } help={ help } className={ className }> - <input className="components-text-control__input" + <BaseControl + label={ label } + hideLabelFromVision={ hideLabelFromVision } + id={ id } + help={ help } + className={ className } + > + <input + className="components-text-control__input" type={ type } id={ id } value={ value } diff --git a/packages/components/src/text-highlight/index.js b/packages/components/src/text-highlight/index.js index 5d92f42f5382dc..f9cbc95ac90424 100644 --- a/packages/components/src/text-highlight/index.js +++ b/packages/components/src/text-highlight/index.js @@ -6,9 +6,7 @@ import { escapeRegExp } from 'lodash'; /** * WordPress dependencies */ -import { - __experimentalCreateInterpolateElement, -} from '@wordpress/element'; +import { __experimentalCreateInterpolateElement } from '@wordpress/element'; const TextHighlight = ( { text = '', highlight = '' } ) => { if ( ! highlight.trim() ) { diff --git a/packages/components/src/text-highlight/test/index.js b/packages/components/src/text-highlight/test/index.js index a93f28fc826b5d..2c5af9a06ea4c9 100644 --- a/packages/components/src/text-highlight/test/index.js +++ b/packages/components/src/text-highlight/test/index.js @@ -24,47 +24,55 @@ afterEach( () => { container = null; } ); -const defaultText = 'We call the new editor Gutenberg. The entire editing experience has been rebuilt for media rich pages and posts.'; +const defaultText = + 'We call the new editor Gutenberg. The entire editing experience has been rebuilt for media rich pages and posts.'; describe( 'Basic rendering', () => { - it.each( [ - [ 'Gutenberg' ], - [ 'media' ], - ] )( 'should highlight the singular occurance of the text "%s" in the text if it exists', ( highlight ) => { - act( () => { - render( - <TextHighlight - text={ defaultText } - highlight={ highlight } - />, container + it.each( [ [ 'Gutenberg' ], [ 'media' ] ] )( + 'should highlight the singular occurance of the text "%s" in the text if it exists', + ( highlight ) => { + act( () => { + render( + <TextHighlight + text={ defaultText } + highlight={ highlight } + />, + container + ); + } ); + + const highlightedEls = Array.from( + container.querySelectorAll( 'mark' ) ); - } ); - - const highlightedEls = Array.from( container.querySelectorAll( 'mark' ) ); - highlightedEls.forEach( ( el ) => { - expect( el.innerHTML ).toEqual( expect.stringContaining( highlight ) ); - } ); - } ); + highlightedEls.forEach( ( el ) => { + expect( el.innerHTML ).toEqual( + expect.stringContaining( highlight ) + ); + } ); + } + ); it( 'should highlight multiple occurances of the string every time it exists in the text', () => { const highlight = 'edit'; act( () => { render( - <TextHighlight - text={ defaultText } - highlight={ highlight } - />, container + <TextHighlight text={ defaultText } highlight={ highlight } />, + container ); } ); - const highlightedEls = Array.from( container.querySelectorAll( 'mark' ) ); + const highlightedEls = Array.from( + container.querySelectorAll( 'mark' ) + ); expect( highlightedEls ).toHaveLength( 2 ); highlightedEls.forEach( ( el ) => { - expect( el.innerHTML ).toEqual( expect.stringContaining( highlight ) ); + expect( el.innerHTML ).toEqual( + expect.stringContaining( highlight ) + ); } ); } ); @@ -73,14 +81,14 @@ describe( 'Basic rendering', () => { act( () => { render( - <TextHighlight - text={ defaultText } - highlight={ highlight } - />, container + <TextHighlight text={ defaultText } highlight={ highlight } />, + container ); } ); - const highlightedEls = Array.from( container.querySelectorAll( 'mark' ) ); + const highlightedEls = Array.from( + container.querySelectorAll( 'mark' ) + ); // Our component matcher is case insensitive so string.Containing will // return a false failure @@ -98,14 +106,14 @@ describe( 'Basic rendering', () => { act( () => { render( - <TextHighlight - text={ defaultText } - highlight={ highlight } - />, container + <TextHighlight text={ defaultText } highlight={ highlight } />, + container ); } ); - const highlightedEls = Array.from( container.querySelectorAll( 'mark' ) ); + const highlightedEls = Array.from( + container.querySelectorAll( 'mark' ) + ); expect( highlightedEls ).toHaveLength( 0 ); } ); diff --git a/packages/components/src/text/mixins.js b/packages/components/src/text/mixins.js index 4375f708d289de..dfa3dfecc38979 100644 --- a/packages/components/src/text/mixins.js +++ b/packages/components/src/text/mixins.js @@ -88,40 +88,40 @@ const variant = ( variantName ) => { switch ( variantName ) { case 'title.large': return css` - ${ title } - ${ titleLarge } + ${title} + ${titleLarge} `; case 'title.medium': return css` - ${ title } - ${ titleMedium } + ${title} + ${titleMedium} `; case 'title.small': return css` - ${ title } - ${ titleSmall } + ${title} + ${titleSmall} `; case 'subtitle': return css` - ${ subtitle } - ${ subtitleLarge } + ${subtitle} + ${subtitleLarge} `; case 'subtitle.small': return css` - ${ subtitle } - ${ subtitleSmall } + ${subtitle} + ${subtitleSmall} `; case 'body': return css` - ${ body } - ${ bodyLarge } + ${body} + ${bodyLarge} `; case 'body.small': return css` - ${ body } - ${ bodySmall } + ${body} + ${bodySmall} `; case 'button': @@ -144,6 +144,6 @@ const variant = ( variantName ) => { * @param {TextProps} props */ export const text = ( props ) => css` - ${ fontFamily } - ${ variant( props.variant ) } + ${fontFamily} + ${variant( props.variant )} `; diff --git a/packages/components/src/text/stories/index.js b/packages/components/src/text/stories/index.js index 01b62c944dc7ae..f1dca3b2c819b6 100644 --- a/packages/components/src/text/stories/index.js +++ b/packages/components/src/text/stories/index.js @@ -10,9 +10,15 @@ export default { export const _default = () => ( <> - <Text variant="title.large" as="h1">Title Large</Text> - <Text variant="title.medium" as="h2">Title Medium</Text> - <Text variant="title.small" as="h3">Title Small</Text> + <Text variant="title.large" as="h1"> + Title Large + </Text> + <Text variant="title.medium" as="h2"> + Title Medium + </Text> + <Text variant="title.small" as="h3"> + Title Small + </Text> <Text variant="subtitle">Subtitle</Text> <Text variant="subtitle.small">Subtitle Small</Text> <Text variant="body">Body</Text> @@ -23,12 +29,26 @@ export const _default = () => ( </> ); -export const TitleLarge = () => <Text variant="title.large" as="h1">Title Large</Text>; -export const TitleMedium = () => <Text variant="title.medium" as="h2">Title Medium</Text>; -export const TitleSmall = () => <Text variant="title.small" as="h3">Title Small</Text>; +export const TitleLarge = () => ( + <Text variant="title.large" as="h1"> + Title Large + </Text> +); +export const TitleMedium = () => ( + <Text variant="title.medium" as="h2"> + Title Medium + </Text> +); +export const TitleSmall = () => ( + <Text variant="title.small" as="h3"> + Title Small + </Text> +); export const Subtitle = () => <Text variant="subtitle">Subtitle</Text>; -export const SubtitleSmall = () => <Text variant="subtitle.small">Subtitle Small</Text>; +export const SubtitleSmall = () => ( + <Text variant="subtitle.small">Subtitle Small</Text> +); export const Body = () => <Text variant="body">Body</Text>; export const BodySmall = () => <Text variant="body.small">Body Small</Text>; diff --git a/packages/components/src/text/text.styles.js b/packages/components/src/text/text.styles.js index 6d1546c61c72d4..67545507f0d0b5 100644 --- a/packages/components/src/text/text.styles.js +++ b/packages/components/src/text/text.styles.js @@ -8,7 +8,4 @@ import styled from '@emotion/styled'; */ import { text } from './mixins'; -export const __experimentalText = styled.p( - `margin: 0;`, - text -); +export const __experimentalText = styled.p( `margin: 0;`, text ); diff --git a/packages/components/src/text/text.styles.native.js b/packages/components/src/text/text.styles.native.js index 9d738959cf176b..2304de2efa62f3 100644 --- a/packages/components/src/text/text.styles.native.js +++ b/packages/components/src/text/text.styles.native.js @@ -8,6 +8,4 @@ import styled from '@emotion/native'; */ import { text } from './mixins'; -export const __experimentalText = styled.Text( - text -); +export const __experimentalText = styled.Text( text ); diff --git a/packages/components/src/textarea-control/index.js b/packages/components/src/textarea-control/index.js index f2b5d0fa6d804c..0de73cb7a91cbf 100644 --- a/packages/components/src/textarea-control/index.js +++ b/packages/components/src/textarea-control/index.js @@ -8,13 +8,28 @@ import { useInstanceId } from '@wordpress/compose'; */ import BaseControl from '../base-control'; -export default function TextareaControl( { label, hideLabelFromVision, value, help, onChange, rows = 4, className, ...props } ) { +export default function TextareaControl( { + label, + hideLabelFromVision, + value, + help, + onChange, + rows = 4, + className, + ...props +} ) { const instanceId = useInstanceId( TextareaControl ); const id = `inspector-textarea-control-${ instanceId }`; const onChangeValue = ( event ) => onChange( event.target.value ); return ( - <BaseControl label={ label } hideLabelFromVision={ hideLabelFromVision } id={ id } help={ help } className={ className }> + <BaseControl + label={ label } + hideLabelFromVision={ hideLabelFromVision } + id={ id } + help={ help } + className={ className } + > <textarea className="components-textarea-control__input" id={ id } diff --git a/packages/components/src/textarea-control/index.native.js b/packages/components/src/textarea-control/index.native.js index 059eea86728d7b..3f0035ce4fcd57 100644 --- a/packages/components/src/textarea-control/index.native.js +++ b/packages/components/src/textarea-control/index.native.js @@ -10,7 +10,7 @@ import BaseControl from '../base-control'; function TextareaControl( { label, value, help, onChange, rows = 4 } ) { return ( - <BaseControl label={ label } help={ help } > + <BaseControl label={ label } help={ help }> <TextInput style={ { height: 80, borderColor: 'gray', borderWidth: 1 } } value={ value } diff --git a/packages/components/src/toggle-control/index.js b/packages/components/src/toggle-control/index.js index 39f4a7f372715b..785174a22811fd 100644 --- a/packages/components/src/toggle-control/index.js +++ b/packages/components/src/toggle-control/index.js @@ -43,7 +43,10 @@ class ToggleControl extends Component { <BaseControl id={ id } help={ helpLabel } - className={ classnames( 'components-toggle-control', className ) } + className={ classnames( + 'components-toggle-control', + className + ) } > <FormToggle id={ id } diff --git a/packages/components/src/toggle-control/stories/index.js b/packages/components/src/toggle-control/stories/index.js index 57af6aa329e147..e006b68bc1a38c 100644 --- a/packages/components/src/toggle-control/stories/index.js +++ b/packages/components/src/toggle-control/stories/index.js @@ -15,7 +15,11 @@ import ToggleControl from '../'; export default { title: 'Components/ToggleControl', component: ToggleControl }; -const ToggleControlWithState = ( { helpTextChecked, helpTextUnchecked, ...props } ) => { +const ToggleControlWithState = ( { + helpTextChecked, + helpTextUnchecked, + ...props +} ) => { const [ hasFixedBackground, setHasFixedBackground ] = useState( true ); return ( <ToggleControl @@ -30,17 +34,19 @@ const ToggleControlWithState = ( { helpTextChecked, helpTextUnchecked, ...props export const _default = () => { const label = text( 'Label', 'Does this have a fixed background?' ); - return ( - <ToggleControlWithState - label={ label } - /> - ); + return <ToggleControlWithState label={ label } />; }; export const withHelpText = () => { const label = text( 'Label', 'Does this have a fixed background?' ); - const helpTextChecked = text( 'Help When Checked', 'Has fixed background.' ); - const helpTextUnchecked = text( 'Help When Unchecked', 'No fixed background.' ); + const helpTextChecked = text( + 'Help When Checked', + 'Has fixed background.' + ); + const helpTextUnchecked = text( + 'Help When Unchecked', + 'No fixed background.' + ); return ( <ToggleControlWithState diff --git a/packages/components/src/toggle-control/test/index.js b/packages/components/src/toggle-control/test/index.js index 750431a58eda0e..346e57676dbc87 100644 --- a/packages/components/src/toggle-control/test/index.js +++ b/packages/components/src/toggle-control/test/index.js @@ -31,7 +31,9 @@ describe( 'ToggleControl', () => { const wrapper = renderer.create( getTestComponent( ToggleControl, { onChange } ) ); - wrapper.root.findByType( 'input' ).props.onChange( { target: { checked: true } } ); + wrapper.root + .findByType( 'input' ) + .props.onChange( { target: { checked: true } } ); expect( onChange ).toHaveBeenCalledWith( true ); } ); @@ -58,7 +60,9 @@ describe( 'ToggleControl', () => { const input = wrapper.root.findByType( 'input' ); - expect( input.props[ 'aria-describedby' ] ).toMatch( /^inspector-toggle-control-.*__help$/ ); + expect( input.props[ 'aria-describedby' ] ).toMatch( + /^inspector-toggle-control-.*__help$/ + ); } ); } ); } ); diff --git a/packages/components/src/toolbar-button/index.js b/packages/components/src/toolbar-button/index.js index 5ffa9ad2f39536..4d56476caadc8b 100644 --- a/packages/components/src/toolbar-button/index.js +++ b/packages/components/src/toolbar-button/index.js @@ -41,7 +41,10 @@ function ToolbarButton( { props.onClick( event ); } } } - className={ classnames( 'components-toolbar__control', className ) } + className={ classnames( + 'components-toolbar__control', + className + ) } isPressed={ props.isActive } disabled={ props.isDisabled } { ...extraProps } @@ -59,7 +62,9 @@ function ToolbarButton( { className={ classnames( 'components-toolbar-button', className ) } { ...props } > - { ( toolbarItemProps ) => <Button { ...toolbarItemProps }>{ children }</Button> } + { ( toolbarItemProps ) => ( + <Button { ...toolbarItemProps }>{ children }</Button> + ) } </ToolbarItem> ); } diff --git a/packages/components/src/toolbar-button/toolbar-button-container.js b/packages/components/src/toolbar-button/toolbar-button-container.js index b25ae67371adb4..dcb8ea0026256a 100644 --- a/packages/components/src/toolbar-button/toolbar-button-container.js +++ b/packages/components/src/toolbar-button/toolbar-button-container.js @@ -1,8 +1,4 @@ const ToolbarButtonContainer = ( props ) => ( - <div - className={ props.className } - > - { props.children } - </div> + <div className={ props.className }>{ props.children }</div> ); export default ToolbarButtonContainer; diff --git a/packages/components/src/toolbar-button/toolbar-button-container.native.js b/packages/components/src/toolbar-button/toolbar-button-container.native.js index 08037c8ab01ab7..2141f79430b016 100644 --- a/packages/components/src/toolbar-button/toolbar-button-container.native.js +++ b/packages/components/src/toolbar-button/toolbar-button-container.native.js @@ -3,8 +3,4 @@ */ import { View } from 'react-native'; -export default ( props ) => ( - <View> - { props.children } - </View> -); +export default ( props ) => <View>{ props.children }</View>; diff --git a/packages/components/src/toolbar-group/index.js b/packages/components/src/toolbar-group/index.js index 769df54be8af9a..50d69ccbf89eb4 100644 --- a/packages/components/src/toolbar-group/index.js +++ b/packages/components/src/toolbar-group/index.js @@ -67,7 +67,9 @@ function ToolbarGroup( { const finalClassName = classnames( // Unfortunately, there's legacy code referencing to `.components-toolbar` // So we can't get rid of it - accessibleToolbarState ? 'components-toolbar-group' : 'components-toolbar', + accessibleToolbarState + ? 'components-toolbar-group' + : 'components-toolbar', className ); @@ -96,7 +98,9 @@ function ToolbarGroup( { <ToolbarButton key={ [ indexOfSet, indexOfControl ].join() } containerClassName={ - indexOfSet > 0 && indexOfControl === 0 ? 'has-left-divider' : null + indexOfSet > 0 && indexOfControl === 0 + ? 'has-left-divider' + : null } { ...control } /> diff --git a/packages/components/src/toolbar-group/test/index.js b/packages/components/src/toolbar-group/test/index.js index 972dca89499f78..c2aa53a165f17e 100644 --- a/packages/components/src/toolbar-group/test/index.js +++ b/packages/components/src/toolbar-group/test/index.js @@ -31,7 +31,9 @@ describe( 'ToolbarGroup', () => { }, ]; const wrapper = mount( <ToolbarGroup controls={ controls } /> ); - const button = wrapper.find( '[aria-label="WordPress"]' ).hostNodes(); + const button = wrapper + .find( '[aria-label="WordPress"]' ) + .hostNodes(); expect( button.props() ).toMatchObject( { 'aria-label': 'WordPress', 'aria-pressed': false, @@ -50,7 +52,9 @@ describe( 'ToolbarGroup', () => { }, ]; const wrapper = mount( <ToolbarGroup controls={ controls } /> ); - const button = wrapper.find( '[aria-label="WordPress"]' ).hostNodes(); + const button = wrapper + .find( '[aria-label="WordPress"]' ) + .hostNodes(); expect( button.props() ).toMatchObject( { 'aria-label': 'WordPress', 'aria-pressed': true, @@ -60,13 +64,15 @@ describe( 'ToolbarGroup', () => { it( 'should render a nested list of controls with separator between', () => { const controls = [ - [ // First set + [ + // First set { icon: 'wordpress', title: 'WordPress', }, ], - [ // Second set + [ + // Second set { icon: 'wordpress', title: 'WordPress', @@ -76,7 +82,9 @@ describe( 'ToolbarGroup', () => { const wrapper = mount( <ToolbarGroup controls={ controls } /> ); const buttons = wrapper.find( 'button' ).hostNodes(); - const hasLeftDivider = wrapper.find( '.has-left-divider' ).hostNodes(); + const hasLeftDivider = wrapper + .find( '.has-left-divider' ) + .hostNodes(); expect( buttons ).toHaveLength( 2 ); expect( hasLeftDivider ).toHaveLength( 1 ); expect( hasLeftDivider.html() ).toContain( buttons.at( 1 ).html() ); @@ -93,7 +101,9 @@ describe( 'ToolbarGroup', () => { }, ]; const wrapper = mount( <ToolbarGroup controls={ controls } /> ); - const button = wrapper.find( '[aria-label="WordPress"]' ).hostNodes(); + const button = wrapper + .find( '[aria-label="WordPress"]' ) + .hostNodes(); button.simulate( 'click' ); expect( clickHandler ).toHaveBeenCalledTimes( 1 ); } ); diff --git a/packages/components/src/toolbar-group/toolbar-group-collapsed.native.js b/packages/components/src/toolbar-group/toolbar-group-collapsed.native.js index b688d842ff5966..b12c45f66e76fb 100644 --- a/packages/components/src/toolbar-group/toolbar-group-collapsed.native.js +++ b/packages/components/src/toolbar-group/toolbar-group-collapsed.native.js @@ -4,7 +4,9 @@ import DropdownMenu from '../dropdown-menu'; function ToolbarGroupCollapsed( { controls = [], ...props } ) { - return <DropdownMenu hasArrowIndicator controls={ controls } { ...props } />; + return ( + <DropdownMenu hasArrowIndicator controls={ controls } { ...props } /> + ); } export default ToolbarGroupCollapsed; diff --git a/packages/components/src/toolbar-group/toolbar-group-container.native.js b/packages/components/src/toolbar-group/toolbar-group-container.native.js index 705c6b00806c38..6eaf0dc0c3c74a 100644 --- a/packages/components/src/toolbar-group/toolbar-group-container.native.js +++ b/packages/components/src/toolbar-group/toolbar-group-container.native.js @@ -13,8 +13,17 @@ import { withPreferredColorScheme } from '@wordpress/compose'; */ import styles from './style.scss'; -const ToolbarGroupContainer = ( { getStylesFromColorScheme, passedStyle, children } ) => ( - <View style={ [ getStylesFromColorScheme( styles.container, styles.containerDark ), passedStyle ] }> +const ToolbarGroupContainer = ( { + getStylesFromColorScheme, + passedStyle, + children, +} ) => ( + <View + style={ [ + getStylesFromColorScheme( styles.container, styles.containerDark ), + passedStyle, + ] } + > { children } </View> ); diff --git a/packages/components/src/toolbar-item/index.js b/packages/components/src/toolbar-item/index.js index 484c185d0816c4..b4de3013fd8db9 100644 --- a/packages/components/src/toolbar-item/index.js +++ b/packages/components/src/toolbar-item/index.js @@ -17,15 +17,22 @@ import ToolbarContext from '../toolbar-context'; function ToolbarItem( { children, ...props }, ref ) { const accessibleToolbarState = useContext( ToolbarContext ); // https://reakit.io/docs/composition/#props-hooks - const itemProps = useToolbarItem( accessibleToolbarState, { ...props, ref } ); + const itemProps = useToolbarItem( accessibleToolbarState, { + ...props, + ref, + } ); if ( typeof children !== 'function' ) { - warning( '`ToolbarItem` is a generic headless component that accepts only function children props' ); + warning( + '`ToolbarItem` is a generic headless component that accepts only function children props' + ); return null; } if ( ! accessibleToolbarState ) { - warning( '`ToolbarItem` should be rendered within `<Toolbar __experimentalAccessibilityLabel="label">`' ); + warning( + '`ToolbarItem` should be rendered within `<Toolbar __experimentalAccessibilityLabel="label">`' + ); return null; } diff --git a/packages/components/src/toolbar-item/index.native.js b/packages/components/src/toolbar-item/index.native.js index 6fb0150cafa515..1d38d98ce9af1a 100644 --- a/packages/components/src/toolbar-item/index.native.js +++ b/packages/components/src/toolbar-item/index.native.js @@ -6,7 +6,9 @@ import warning from '@wordpress/warning'; function ToolbarItem( { children, ...props }, ref ) { if ( typeof children !== 'function' ) { - warning( '`ToolbarItem` is a generic headless component that accepts only function children props' ); + warning( + '`ToolbarItem` is a generic headless component that accepts only function children props' + ); return null; } diff --git a/packages/components/src/toolbar/index.js b/packages/components/src/toolbar/index.js index 9d37a768cadc70..74fe904adf9952 100644 --- a/packages/components/src/toolbar/index.js +++ b/packages/components/src/toolbar/index.js @@ -22,7 +22,10 @@ function Toolbar( { className, __experimentalAccessibilityLabel, ...props } ) { return ( <ToolbarContainer // `ToolbarGroup` already uses components-toolbar for compatibility reasons - className={ classnames( 'components-accessible-toolbar', className ) } + className={ classnames( + 'components-accessible-toolbar', + className + ) } accessibilityLabel={ __experimentalAccessibilityLabel } { ...props } /> diff --git a/packages/components/src/toolbar/test/index.js b/packages/components/src/toolbar/test/index.js index 6a89be423647e8..748d8a500fc600 100644 --- a/packages/components/src/toolbar/test/index.js +++ b/packages/components/src/toolbar/test/index.js @@ -48,7 +48,9 @@ describe( 'Toolbar', () => { }, ]; const wrapper = mount( <Toolbar controls={ controls } /> ); - const button = wrapper.find( '[aria-label="WordPress"]' ).hostNodes(); + const button = wrapper + .find( '[aria-label="WordPress"]' ) + .hostNodes(); expect( button.props() ).toMatchObject( { 'aria-label': 'WordPress', 'aria-pressed': false, diff --git a/packages/components/src/toolbar/toolbar-container.native.js b/packages/components/src/toolbar/toolbar-container.native.js index 75a8a45ebb92ca..68036944f250a7 100644 --- a/packages/components/src/toolbar/toolbar-container.native.js +++ b/packages/components/src/toolbar/toolbar-container.native.js @@ -3,8 +3,6 @@ */ import { View } from 'react-native'; -const ToolbarContainer = ( { children } ) => ( - <View>{ children }</View> -); +const ToolbarContainer = ( { children } ) => <View>{ children }</View>; export default ToolbarContainer; diff --git a/packages/components/src/tooltip/index.js b/packages/components/src/tooltip/index.js index 911fe0aeb05762..b6e67831fe13da 100644 --- a/packages/components/src/tooltip/index.js +++ b/packages/components/src/tooltip/index.js @@ -125,16 +125,17 @@ class Tooltip extends Component { createSetIsMouseDown( isMouseDown ) { return ( event ) => { // Preserve original child callback behavior - this.emitToChild( isMouseDown ? 'onMouseDown' : 'onMouseUp', event ); + this.emitToChild( + isMouseDown ? 'onMouseDown' : 'onMouseUp', + event + ); // On mouse down, the next `mouseup` should revert the value of the // instance property and remove its own event handler. The bind is // made on the document since the `mouseup` might not occur within // the bounds of the element. document[ - isMouseDown ? - 'addEventListener' : - 'removeEventListener' + isMouseDown ? 'addEventListener' : 'removeEventListener' ]( 'mouseup', this.cancelIsMouseDown ); this.isInMouseDown = isMouseDown; @@ -146,7 +147,9 @@ class Tooltip extends Component { if ( Children.count( children ) !== 1 ) { if ( 'development' === process.env.NODE_ENV ) { // eslint-disable-next-line no-console - console.error( 'Tooltip should be called with only a single child element.' ); + console.error( + 'Tooltip should be called with only a single child element.' + ); } return children; @@ -172,9 +175,12 @@ class Tooltip extends Component { animate={ false } > { text } - <Shortcut className="components-tooltip__shortcut" shortcut={ shortcut } /> + <Shortcut + className="components-tooltip__shortcut" + shortcut={ shortcut } + /> </Popover> - ), + ) ), } ); } diff --git a/packages/components/src/tooltip/test/index.js b/packages/components/src/tooltip/test/index.js index 7265a345d1a4ea..55b14956e7616f 100644 --- a/packages/components/src/tooltip/test/index.js +++ b/packages/components/src/tooltip/test/index.js @@ -15,7 +15,10 @@ describe( 'Tooltip', () => { it( 'should render children (abort) if multiple children passed', () => { // Mount: Enzyme shallow does not support wrapping multiple nodes const wrapper = mount( - <Tooltip><div /><div /></Tooltip> + <Tooltip> + <div /> + <div /> + </Tooltip> ); expect( wrapper.children() ).toHaveLength( 2 ); @@ -51,7 +54,12 @@ describe( 'Tooltip', () => { expect( button.childAt( 1 ).name() ).toBe( 'Popover' ); expect( popover.prop( 'focusOnMount' ) ).toBe( false ); expect( popover.prop( 'position' ) ).toBe( 'bottom right' ); - expect( popover.children().first().text() ).toBe( 'Help text' ); + expect( + popover + .children() + .first() + .text() + ).toBe( 'Help text' ); } ); it( 'should show popover on focus', () => { @@ -97,9 +105,11 @@ describe( 'Tooltip', () => { event = { type: 'mousedown' }; button.simulate( event.type, event ); - expect( originalOnMouseDown ).toHaveBeenCalledWith( expect.objectContaining( { - type: event.type, - } ) ); + expect( originalOnMouseDown ).toHaveBeenCalledWith( + expect.objectContaining( { + type: event.type, + } ) + ); event = { type: 'focus', currentTarget: {} }; button.simulate( event.type, event ); @@ -110,9 +120,11 @@ describe( 'Tooltip', () => { event = new window.MouseEvent( 'mouseup' ); document.dispatchEvent( event ); - expect( originalOnMouseUp ).toHaveBeenCalledWith( expect.objectContaining( { - type: event.type, - } ) ); + expect( originalOnMouseUp ).toHaveBeenCalledWith( + expect.objectContaining( { + type: event.type, + } ) + ); } ); it( 'should show popover on delayed mouseenter', () => { @@ -128,19 +140,32 @@ describe( 'Tooltip', () => { </Tooltip> ); - const button = TestUtils.findRenderedDOMComponentWithTag( wrapper, 'button' ); + const button = TestUtils.findRenderedDOMComponentWithTag( + wrapper, + 'button' + ); // eslint-disable-next-line react/no-find-dom-node TestUtils.Simulate.mouseEnter( ReactDOM.findDOMNode( button ) ); expect( originalMouseEnter ).toHaveBeenCalledTimes( 1 ); expect( wrapper.state.isOver ).toBe( false ); - expect( TestUtils.scryRenderedDOMComponentsWithClass( wrapper, 'components-popover' ) ).toHaveLength( 0 ); + expect( + TestUtils.scryRenderedDOMComponentsWithClass( + wrapper, + 'components-popover' + ) + ).toHaveLength( 0 ); // Force delayedSetIsOver to be called wrapper.delayedSetIsOver.flush(); expect( wrapper.state.isOver ).toBe( true ); - expect( TestUtils.scryRenderedDOMComponentsWithClass( wrapper, 'components-popover' ) ).toHaveLength( 1 ); + expect( + TestUtils.scryRenderedDOMComponentsWithClass( + wrapper, + 'components-popover' + ) + ).toHaveLength( 1 ); } ); it( 'should ignore mouseenter on disabled elements', () => { diff --git a/packages/components/src/tree-select/index.js b/packages/components/src/tree-select/index.js index a23f2744344acd..20fe2a158c69a8 100644 --- a/packages/components/src/tree-select/index.js +++ b/packages/components/src/tree-select/index.js @@ -12,7 +12,8 @@ function getSelectOptions( tree, level = 0 ) { return flatMap( tree, ( treeNode ) => [ { value: treeNode.id, - label: repeat( '\u00A0', level * 3 ) + unescapeString( treeNode.name ), + label: + repeat( '\u00A0', level * 3 ) + unescapeString( treeNode.name ), }, ...getSelectOptions( treeNode.children || [], level + 1 ), ] ); diff --git a/packages/components/src/visually-hidden/index.js b/packages/components/src/visually-hidden/index.js index b29c954c2cd9e1..17ac119ad01ad1 100644 --- a/packages/components/src/visually-hidden/index.js +++ b/packages/components/src/visually-hidden/index.js @@ -1,4 +1,3 @@ - /** * Internal dependencies */ @@ -8,10 +7,7 @@ import { renderAsRenderProps } from './utils'; * VisuallyHidden component to render text out non-visually * for use in devices such as a screen reader. */ -function VisuallyHidden( { - as = 'div', - ...props -} ) { +function VisuallyHidden( { as = 'div', ...props } ) { return renderAsRenderProps( { as, className: 'components-visually-hidden', @@ -19,4 +15,3 @@ function VisuallyHidden( { } ); } export default VisuallyHidden; - diff --git a/packages/compose/src/higher-order/if-condition/index.js b/packages/compose/src/higher-order/if-condition/index.js index 79d23b01ba4218..b0596729b7a362 100644 --- a/packages/compose/src/higher-order/if-condition/index.js +++ b/packages/compose/src/higher-order/if-condition/index.js @@ -11,15 +11,16 @@ import createHigherOrderComponent from '../../utils/create-higher-order-componen * * @return {Function} Higher-order component. */ -const ifCondition = ( predicate ) => createHigherOrderComponent( - ( WrappedComponent ) => ( props ) => { - if ( ! predicate( props ) ) { - return null; - } +const ifCondition = ( predicate ) => + createHigherOrderComponent( + ( WrappedComponent ) => ( props ) => { + if ( ! predicate( props ) ) { + return null; + } - return <WrappedComponent { ...props } />; - }, - 'ifCondition' -); + return <WrappedComponent { ...props } />; + }, + 'ifCondition' + ); export default ifCondition; diff --git a/packages/compose/src/higher-order/pure/index.js b/packages/compose/src/higher-order/pure/index.js index e721fbb7964796..957de409daa7f0 100644 --- a/packages/compose/src/higher-order/pure/index.js +++ b/packages/compose/src/higher-order/pure/index.js @@ -24,7 +24,10 @@ const pure = createHigherOrderComponent( ( Wrapped ) => { if ( Wrapped.prototype instanceof Component ) { return class extends Wrapped { shouldComponentUpdate( nextProps, nextState ) { - return ! isShallowEqual( nextProps, this.props ) || ! isShallowEqual( nextState, this.state ); + return ( + ! isShallowEqual( nextProps, this.props ) || + ! isShallowEqual( nextState, this.state ) + ); } }; } diff --git a/packages/compose/src/higher-order/pure/test/index.js b/packages/compose/src/higher-order/pure/test/index.js index 2cb40cd7015e26..c938216aa4e103 100644 --- a/packages/compose/src/higher-order/pure/test/index.js +++ b/packages/compose/src/higher-order/pure/test/index.js @@ -32,15 +32,17 @@ describe( 'pure', () => { it( 'class component should rerender if the props or state change', () => { let i = 0; - const MyComp = pure( class extends Component { - constructor() { - super( ...arguments ); - this.state = {}; + const MyComp = pure( + class extends Component { + constructor() { + super( ...arguments ); + this.state = {}; + } + render() { + return <p>{ ++i }</p>; + } } - render() { - return <p>{ ++i }</p>; - } - } ); + ); const wrapper = mount( <MyComp /> ); wrapper.update(); // Updating with same props doesn't rerender expect( wrapper.html() ).toBe( '<p>1</p>' ); diff --git a/packages/compose/src/higher-order/with-global-events/index.js b/packages/compose/src/higher-order/with-global-events/index.js index ac8aabd4639f6a..6b2bc53ebeda57 100644 --- a/packages/compose/src/higher-order/with-global-events/index.js +++ b/packages/compose/src/higher-order/with-global-events/index.js @@ -78,7 +78,12 @@ function withGlobalEvents( eventTypesToHandlers ) { } render() { - return <WrappedComponent { ...this.props.ownProps } ref={ this.handleRef } />; + return ( + <WrappedComponent + { ...this.props.ownProps } + ref={ this.handleRef } + /> + ); } } diff --git a/packages/compose/src/higher-order/with-global-events/listener.js b/packages/compose/src/higher-order/with-global-events/listener.js index fa469e7ce1a0da..53fa9fec3f4a1b 100644 --- a/packages/compose/src/higher-order/with-global-events/listener.js +++ b/packages/compose/src/higher-order/with-global-events/listener.js @@ -26,7 +26,10 @@ class Listener { } remove( eventType, instance ) { - this.listeners[ eventType ] = without( this.listeners[ eventType ], instance ); + this.listeners[ eventType ] = without( + this.listeners[ eventType ], + instance + ); if ( ! this.listeners[ eventType ].length ) { // Removing last listener for this type, so unbind event. diff --git a/packages/compose/src/higher-order/with-global-events/test/index.js b/packages/compose/src/higher-order/with-global-events/test/index.js index 4001f219ad594f..44e31e623dc1b9 100644 --- a/packages/compose/src/higher-order/with-global-events/test/index.js +++ b/packages/compose/src/higher-order/with-global-events/test/index.js @@ -64,22 +64,30 @@ describe( 'withGlobalEvents', () => { props.ref = () => {}; - wrapper = TestRenderer.create( <EnhancedComponent { ...props }>Hello</EnhancedComponent> ); + wrapper = TestRenderer.create( + <EnhancedComponent { ...props }>Hello</EnhancedComponent> + ); } it( 'renders with original component', () => { mountEnhancedComponent(); - expect( wrapper.root.findByType( 'div' ).children[ 0 ] ).toBe( 'Hello' ); + expect( wrapper.root.findByType( 'div' ).children[ 0 ] ).toBe( + 'Hello' + ); } ); it( 'binds events from passed object', () => { mountEnhancedComponent(); // Get the HOC wrapper instance - const hocInstance = wrapper.root.findByType( OriginalComponent ).parent.instance; + const hocInstance = wrapper.root.findByType( OriginalComponent ).parent + .instance; - expect( Listener._instance.add ).toHaveBeenCalledWith( 'resize', hocInstance ); + expect( Listener._instance.add ).toHaveBeenCalledWith( + 'resize', + hocInstance + ); } ); it( 'handles events', () => { @@ -91,7 +99,9 @@ describe( 'withGlobalEvents', () => { Listener._instance.handleEvent( event ); - expect( OriginalComponent.prototype.handleResize ).toHaveBeenCalledWith( event ); + expect( OriginalComponent.prototype.handleResize ).toHaveBeenCalledWith( + event + ); expect( onResize ).toHaveBeenCalledWith( event ); } ); } ); diff --git a/packages/compose/src/higher-order/with-global-events/test/listener.js b/packages/compose/src/higher-order/with-global-events/test/listener.js index 53f5db9a3298de..75755aafaacfbe 100644 --- a/packages/compose/src/higher-order/with-global-events/test/listener.js +++ b/packages/compose/src/higher-order/with-global-events/test/listener.js @@ -28,7 +28,10 @@ describe( 'Listener', () => { it( 'adds an event listener on first listener', () => { listener.add( 'resize', createHandler() ); - expect( window.addEventListener ).toHaveBeenCalledWith( 'resize', expect.any( Function ) ); + expect( window.addEventListener ).toHaveBeenCalledWith( + 'resize', + expect.any( Function ) + ); } ); it( 'does not add event listener on subsequent listeners', () => { @@ -45,7 +48,10 @@ describe( 'Listener', () => { listener.add( 'resize', handler ); listener.remove( 'resize', handler ); - expect( window.removeEventListener ).toHaveBeenCalledWith( 'resize', expect.any( Function ) ); + expect( window.removeEventListener ).toHaveBeenCalledWith( + 'resize', + expect.any( Function ) + ); } ); it( 'does not remove event listener on remaining listeners', () => { diff --git a/packages/compose/src/higher-order/with-safe-timeout/index.js b/packages/compose/src/higher-order/with-safe-timeout/index.js index 43d4e9e780d5b7..66c969954c9b93 100644 --- a/packages/compose/src/higher-order/with-safe-timeout/index.js +++ b/packages/compose/src/higher-order/with-safe-timeout/index.js @@ -21,46 +21,43 @@ import createHigherOrderComponent from '../../utils/create-higher-order-componen * * @return {WPComponent} Wrapped component. */ -const withSafeTimeout = createHigherOrderComponent( - ( OriginalComponent ) => { - return class WrappedComponent extends Component { - constructor() { - super( ...arguments ); - this.timeouts = []; - this.setTimeout = this.setTimeout.bind( this ); - this.clearTimeout = this.clearTimeout.bind( this ); - } - - componentWillUnmount() { - this.timeouts.forEach( clearTimeout ); - } - - setTimeout( fn, delay ) { - const id = setTimeout( () => { - fn(); - this.clearTimeout( id ); - }, delay ); - this.timeouts.push( id ); - return id; - } - - clearTimeout( id ) { - clearTimeout( id ); - this.timeouts = without( this.timeouts, id ); - } - - render() { - return ( - <OriginalComponent - { ...this.props } - setTimeout={ this.setTimeout } - clearTimeout={ this.clearTimeout } - /> - ); - } - }; - }, - 'withSafeTimeout' -); +const withSafeTimeout = createHigherOrderComponent( ( OriginalComponent ) => { + return class WrappedComponent extends Component { + constructor() { + super( ...arguments ); + this.timeouts = []; + this.setTimeout = this.setTimeout.bind( this ); + this.clearTimeout = this.clearTimeout.bind( this ); + } + + componentWillUnmount() { + this.timeouts.forEach( clearTimeout ); + } + + setTimeout( fn, delay ) { + const id = setTimeout( () => { + fn(); + this.clearTimeout( id ); + }, delay ); + this.timeouts.push( id ); + return id; + } + + clearTimeout( id ) { + clearTimeout( id ); + this.timeouts = without( this.timeouts, id ); + } + + render() { + return ( + <OriginalComponent + { ...this.props } + setTimeout={ this.setTimeout } + clearTimeout={ this.clearTimeout } + /> + ); + } + }; +}, 'withSafeTimeout' ); export default withSafeTimeout; diff --git a/packages/compose/src/higher-order/with-state/test/index.js b/packages/compose/src/higher-order/with-state/test/index.js index 89c36403b51f86..1d07baea062545 100644 --- a/packages/compose/src/higher-order/with-state/test/index.js +++ b/packages/compose/src/higher-order/with-state/test/index.js @@ -26,14 +26,23 @@ const getTestComponent = ( WrappedComponent ) => { describe( 'withState', () => { it( 'should pass initial state and allow updates', () => { - const EnhancedComponent = withState( { count: 0 } )( ( { count, setState } ) => ( - <button onClick={ () => setState( ( state ) => ( { count: state.count + 1 } ) ) }> + const EnhancedComponent = withState( { + count: 0, + } )( ( { count, setState } ) => ( + <button + onClick={ () => + setState( ( state ) => ( { count: state.count + 1 } ) ) + } + > { count } </button> ) ); - const wrapper = TestUtils.renderIntoDocument( getTestComponent( EnhancedComponent ) ); - const buttonElement = () => TestUtils.findRenderedDOMComponentWithTag( wrapper, 'button' ); + const wrapper = TestUtils.renderIntoDocument( + getTestComponent( EnhancedComponent ) + ); + const buttonElement = () => + TestUtils.findRenderedDOMComponentWithTag( wrapper, 'button' ); expect( buttonElement().outerHTML ).toBe( '<button>0</button>' ); TestUtils.Simulate.click( buttonElement() ); diff --git a/packages/compose/src/hooks/use-dragging/index.js b/packages/compose/src/hooks/use-dragging/index.js index 411327e89c1cd3..3cb7a955fec363 100644 --- a/packages/compose/src/hooks/use-dragging/index.js +++ b/packages/compose/src/hooks/use-dragging/index.js @@ -20,41 +20,34 @@ export default function useDragging( { onDragStart, onDragMove, onDragEnd } ) { onDragMove, onDragEnd, } ); - useIsomorphicLayoutEffect( - () => { - eventsRef.current.onDragStart = onDragStart; - eventsRef.current.onDragMove = onDragMove; - eventsRef.current.onDragEnd = onDragEnd; - }, - [ onDragStart, onDragMove, onDragEnd ] - ); + useIsomorphicLayoutEffect( () => { + eventsRef.current.onDragStart = onDragStart; + eventsRef.current.onDragMove = onDragMove; + eventsRef.current.onDragEnd = onDragEnd; + }, [ onDragStart, onDragMove, onDragEnd ] ); const onMouseMove = useCallback( - ( ...args ) => ( eventsRef.current.onDragMove && eventsRef.current.onDragMove( ...args ) ), - [] - ); - const endDrag = useCallback( - ( ...args ) => { - if ( eventsRef.current.onDragEnd ) { - eventsRef.current.onDragEnd( ...args ); - } - document.removeEventListener( 'mousemove', onMouseMove ); - document.removeEventListener( 'mouseup', endDrag ); - setIsDragging( false ); - }, - [] - ); - const startDrag = useCallback( - ( ...args ) => { - if ( eventsRef.current.onDragStart ) { - eventsRef.current.onDragStart( ...args ); - } - document.addEventListener( 'mousemove', onMouseMove ); - document.addEventListener( 'mouseup', endDrag ); - setIsDragging( true ); - }, + ( ...args ) => + eventsRef.current.onDragMove && + eventsRef.current.onDragMove( ...args ), [] ); + const endDrag = useCallback( ( ...args ) => { + if ( eventsRef.current.onDragEnd ) { + eventsRef.current.onDragEnd( ...args ); + } + document.removeEventListener( 'mousemove', onMouseMove ); + document.removeEventListener( 'mouseup', endDrag ); + setIsDragging( false ); + }, [] ); + const startDrag = useCallback( ( ...args ) => { + if ( eventsRef.current.onDragStart ) { + eventsRef.current.onDragStart( ...args ); + } + document.addEventListener( 'mousemove', onMouseMove ); + document.addEventListener( 'mouseup', endDrag ); + setIsDragging( true ); + }, [] ); // Remove the global events when unmounting if needed. useEffect( () => { diff --git a/packages/compose/src/hooks/use-keyboard-shortcut/index.js b/packages/compose/src/hooks/use-keyboard-shortcut/index.js index d1004dba15a41b..666e086c1b1808 100644 --- a/packages/compose/src/hooks/use-keyboard-shortcut/index.js +++ b/packages/compose/src/hooks/use-keyboard-shortcut/index.js @@ -31,8 +31,10 @@ import { useEffect } from '@wordpress/element'; function isAppleOS( _window = window ) { const { platform } = _window.navigator; - return platform.indexOf( 'Mac' ) !== -1 || - includes( [ 'iPad', 'iPhone' ], platform ); + return ( + platform.indexOf( 'Mac' ) !== -1 || + includes( [ 'iPad', 'iPhone' ], platform ) + ); } /** @@ -42,12 +44,16 @@ function isAppleOS( _window = window ) { * @param {Function} callback Shortcut callback. * @param {WPKeyboardShortcutConfig} options Shortcut options. */ -function useKeyboardShortcut( shortcuts, callback, { - bindGlobal = false, - eventName = 'keydown', - isDisabled = false, // This is important for performance considerations. - target, -} = {} ) { +function useKeyboardShortcut( + shortcuts, + callback, + { + bindGlobal = false, + eventName = 'keydown', + isDisabled = false, // This is important for performance considerations. + target, + } = {} +) { useEffect( () => { if ( isDisabled ) { return; @@ -58,18 +64,21 @@ function useKeyboardShortcut( shortcuts, callback, { // Determines whether a key is a modifier by the length of the string. // E.g. if I add a pass a shortcut Shift+Cmd+M, it'll determine that // the modifiers are Shift and Cmd because they're not a single character. - const modifiers = new Set( keys.filter( ( value ) => value.length > 1 ) ); + const modifiers = new Set( + keys.filter( ( value ) => value.length > 1 ) + ); const hasAlt = modifiers.has( 'alt' ); const hasShift = modifiers.has( 'shift' ); // This should be better moved to the shortcut registration instead. if ( - isAppleOS() && ( - ( modifiers.size === 1 && hasAlt ) || - ( modifiers.size === 2 && hasAlt && hasShift ) - ) + isAppleOS() && + ( ( modifiers.size === 1 && hasAlt ) || + ( modifiers.size === 2 && hasAlt && hasShift ) ) ) { - throw new Error( `Cannot bind ${ shortcut }. Alt and Shift+Alt modifiers are reserved for character input.` ); + throw new Error( + `Cannot bind ${ shortcut }. Alt and Shift+Alt modifiers are reserved for character input.` + ); } const bindFn = bindGlobal ? 'bindGlobal' : 'bind'; diff --git a/packages/compose/src/hooks/use-media-query/index.js b/packages/compose/src/hooks/use-media-query/index.js index 8cee42e0f4e3b2..e863503ba940bc 100644 --- a/packages/compose/src/hooks/use-media-query/index.js +++ b/packages/compose/src/hooks/use-media-query/index.js @@ -15,7 +15,8 @@ export default function useMediaQuery( query ) { if ( ! query ) { return; } - const updateMatch = () => setMatch( window.matchMedia( query ).matches ); + const updateMatch = () => + setMatch( window.matchMedia( query ).matches ); updateMatch(); const list = window.matchMedia( query ); list.addListener( updateMatch ); diff --git a/packages/compose/src/hooks/use-media-query/test/index.js b/packages/compose/src/hooks/use-media-query/test/index.js index 8ca771748f0215..a256a4b440bec4 100644 --- a/packages/compose/src/hooks/use-media-query/test/index.js +++ b/packages/compose/src/hooks/use-media-query/test/index.js @@ -34,7 +34,11 @@ describe( 'useMediaQuery', () => { }; it( 'should return true when query matches', async () => { - global.matchMedia.mockReturnValue( { addListener, removeListener, matches: true } ); + global.matchMedia.mockReturnValue( { + addListener, + removeListener, + matches: true, + } ); let root; @@ -51,9 +55,21 @@ describe( 'useMediaQuery', () => { } ); it( 'should correctly update the value when the query evaluation matches', async () => { - global.matchMedia.mockReturnValueOnce( { addListener, removeListener, matches: true } ); - global.matchMedia.mockReturnValueOnce( { addListener, removeListener, matches: true } ); - global.matchMedia.mockReturnValueOnce( { addListener, removeListener, matches: false } ); + global.matchMedia.mockReturnValueOnce( { + addListener, + removeListener, + matches: true, + } ); + global.matchMedia.mockReturnValueOnce( { + addListener, + removeListener, + matches: true, + } ); + global.matchMedia.mockReturnValueOnce( { + addListener, + removeListener, + matches: false, + } ); let root, updateMatchFunction; await act( async () => { @@ -76,7 +92,11 @@ describe( 'useMediaQuery', () => { } ); it( 'should return false when the query does not matches', async () => { - global.matchMedia.mockReturnValue( { addListener, removeListener, matches: false } ); + global.matchMedia.mockReturnValue( { + addListener, + removeListener, + matches: false, + } ); let root; await act( async () => { root = create( <TestComponent query="(min-width: 782px)" /> ); @@ -90,7 +110,11 @@ describe( 'useMediaQuery', () => { } ); it( 'should not call matchMedia if a query is not passed', async () => { - global.matchMedia.mockReturnValue( { addListener, removeListener, matches: false } ); + global.matchMedia.mockReturnValue( { + addListener, + removeListener, + matches: false, + } ); let root; await act( async () => { root = create( <TestComponent /> ); diff --git a/packages/compose/src/hooks/use-reduced-motion/index.js b/packages/compose/src/hooks/use-reduced-motion/index.js index 5c3da690b1fe91..8b2224c868beb9 100644 --- a/packages/compose/src/hooks/use-reduced-motion/index.js +++ b/packages/compose/src/hooks/use-reduced-motion/index.js @@ -18,8 +18,8 @@ const IS_IE = * @return {boolean} Reduced motion preference value. */ const useReducedMotion = - process.env.FORCE_REDUCED_MOTION || IS_IE ? - () => true : - () => useMediaQuery( '(prefers-reduced-motion: reduce)' ); + process.env.FORCE_REDUCED_MOTION || IS_IE + ? () => true + : () => useMediaQuery( '(prefers-reduced-motion: reduce)' ); export default useReducedMotion; diff --git a/packages/compose/src/hooks/use-viewport-match/index.js b/packages/compose/src/hooks/use-viewport-match/index.js index f2201bdc7742e5..fc1500056c1e71 100644 --- a/packages/compose/src/hooks/use-viewport-match/index.js +++ b/packages/compose/src/hooks/use-viewport-match/index.js @@ -48,8 +48,8 @@ const CONDITIONS = { * @type {Object<WPViewportOperator,Function>} */ const OPERATOR_EVALUATORS = { - '>=': ( breakpointValue, width ) => ( width >= breakpointValue ), - '<': ( breakpointValue, width ) => ( width < breakpointValue ), + '>=': ( breakpointValue, width ) => width >= breakpointValue, + '<': ( breakpointValue, width ) => width < breakpointValue, }; const ViewportMatchWidthContext = createContext( null ); @@ -71,14 +71,20 @@ const ViewportMatchWidthContext = createContext( null ); */ const useViewportMatch = ( breakpoint, operator = '>=' ) => { const simulatedWidth = useContext( ViewportMatchWidthContext ); - const mediaQuery = ! simulatedWidth && `(${ CONDITIONS[ operator ] }: ${ BREAKPOINTS[ breakpoint ] }px)`; + const mediaQuery = + ! simulatedWidth && + `(${ CONDITIONS[ operator ] }: ${ BREAKPOINTS[ breakpoint ] }px)`; const mediaQueryResult = useMediaQuery( mediaQuery ); if ( simulatedWidth ) { - return OPERATOR_EVALUATORS[ operator ]( BREAKPOINTS[ breakpoint ], simulatedWidth ); + return OPERATOR_EVALUATORS[ operator ]( + BREAKPOINTS[ breakpoint ], + simulatedWidth + ); } return mediaQueryResult; }; -useViewportMatch.__experimentalWidthProvider = ViewportMatchWidthContext.Provider; +useViewportMatch.__experimentalWidthProvider = + ViewportMatchWidthContext.Provider; export default useViewportMatch; diff --git a/packages/compose/src/hooks/use-viewport-match/test/index.js b/packages/compose/src/hooks/use-viewport-match/test/index.js index eb36ffe929e556..271c2aea9e3a8b 100644 --- a/packages/compose/src/hooks/use-viewport-match/test/index.js +++ b/packages/compose/src/hooks/use-viewport-match/test/index.js @@ -88,24 +88,32 @@ describe( 'useViewportMatch', () => { const WidthProvider = useViewportMatch.__experimentalWidthProvider; await act( async () => { - root = create( <WidthProvider value={ 300 }>{ innerElement }</WidthProvider> ); + root = create( + <WidthProvider value={ 300 }>{ innerElement }</WidthProvider> + ); } ); expect( root.toJSON() ).toBe( 'useViewportMatch: false' ); await act( async () => { - root.update( <WidthProvider value={ 1200 }>{ innerElement }</WidthProvider> ); + root.update( + <WidthProvider value={ 1200 }>{ innerElement }</WidthProvider> + ); } ); expect( root.toJSON() ).toBe( 'useViewportMatch: false' ); await act( async () => { - root.update( <WidthProvider value={ 1300 }>{ innerElement }</WidthProvider> ); + root.update( + <WidthProvider value={ 1300 }>{ innerElement }</WidthProvider> + ); } ); expect( root.toJSON() ).toBe( 'useViewportMatch: true' ); await act( async () => { - root.update( <WidthProvider value={ 1300 }> - <TestComponent breakpoint="wide" operator="<" /> - </WidthProvider> ); + root.update( + <WidthProvider value={ 1300 }> + <TestComponent breakpoint="wide" operator="<" /> + </WidthProvider> + ); } ); expect( root.toJSON() ).toBe( 'useViewportMatch: false' ); diff --git a/packages/compose/src/index.native.js b/packages/compose/src/index.native.js index f410b60094d005..6c1e89a35f8791 100644 --- a/packages/compose/src/index.native.js +++ b/packages/compose/src/index.native.js @@ -1,4 +1,3 @@ - // Web exports export * from './index.js'; diff --git a/packages/compose/src/utils/create-higher-order-component/index.js b/packages/compose/src/utils/create-higher-order-component/index.js index 448193468c94eb..7366fb0c3e06fe 100644 --- a/packages/compose/src/utils/create-higher-order-component/index.js +++ b/packages/compose/src/utils/create-higher-order-component/index.js @@ -14,11 +14,20 @@ import { camelCase, upperFirst } from 'lodash'; * * @return {WPComponent} Component class with generated display name assigned. */ -function createHigherOrderComponent( mapComponentToEnhancedComponent, modifierName ) { +function createHigherOrderComponent( + mapComponentToEnhancedComponent, + modifierName +) { return ( OriginalComponent ) => { - const EnhancedComponent = mapComponentToEnhancedComponent( OriginalComponent ); - const { displayName = OriginalComponent.name || 'Component' } = OriginalComponent; - EnhancedComponent.displayName = `${ upperFirst( camelCase( modifierName ) ) }(${ displayName })`; + const EnhancedComponent = mapComponentToEnhancedComponent( + OriginalComponent + ); + const { + displayName = OriginalComponent.name || 'Component', + } = OriginalComponent; + EnhancedComponent.displayName = `${ upperFirst( + camelCase( modifierName ) + ) }(${ displayName })`; return EnhancedComponent; }; diff --git a/packages/compose/src/utils/create-higher-order-component/test/index.js b/packages/compose/src/utils/create-higher-order-component/test/index.js index 61890a2436dc8f..43fe5dd1146905 100644 --- a/packages/compose/src/utils/create-higher-order-component/test/index.js +++ b/packages/compose/src/utils/create-higher-order-component/test/index.js @@ -24,7 +24,9 @@ describe( 'createHigherOrderComponent', () => { 'with-one-two_threeFOUR' )( () => <div /> ); - expect( TestComponent.displayName ).toBe( 'WithOneTwoThreeFour(Component)' ); + expect( TestComponent.displayName ).toBe( + 'WithOneTwoThreeFour(Component)' + ); } ); it( 'should use function name', () => { @@ -50,7 +52,9 @@ describe( 'createHigherOrderComponent', () => { 'withTest' )( SomeAnotherComponent ); - expect( TestComponent.displayName ).toBe( 'WithTest(SomeAnotherComponent)' ); + expect( TestComponent.displayName ).toBe( + 'WithTest(SomeAnotherComponent)' + ); } ); it( 'should use displayName property', () => { @@ -65,6 +69,8 @@ describe( 'createHigherOrderComponent', () => { 'withTest' )( SomeYetAnotherComponent ); - expect( TestComponent.displayName ).toBe( 'WithTest(CustomDisplayName)' ); + expect( TestComponent.displayName ).toBe( + 'WithTest(CustomDisplayName)' + ); } ); } ); diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 681d4000998f6f..cec043e732e27e 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -6,10 +6,7 @@ import { castArray, get, isEqual, find } from 'lodash'; /** * Internal dependencies */ -import { - receiveItems, - receiveQueriedItems, -} from './queried-data'; +import { receiveItems, receiveQueriedItems } from './queried-data'; import { getKindEntities, DEFAULT_ENTITY_KEY } from './entities'; import { select, apiFetch } from './controls'; @@ -68,7 +65,13 @@ export function addEntities( entities ) { * * @return {Object} Action object. */ -export function receiveEntityRecords( kind, name, records, query, invalidateCache = false ) { +export function receiveEntityRecords( + kind, + name, + records, + query, + invalidateCache = false +) { // Auto drafts should not have titles, but some plugins rely on them so we can't filter this // on the server. if ( kind === 'postType' ) { @@ -138,7 +141,9 @@ export function receiveEmbedPreview( url, preview ) { export function* editEntityRecord( kind, name, recordId, edits, options = {} ) { const entity = yield select( 'getEntity', kind, name ); if ( ! entity ) { - throw new Error( `The entity being edited (${ kind }, ${ name }) does not have a loaded config.` ); + throw new Error( + `The entity being edited (${ kind }, ${ name }) does not have a loaded config.` + ); } const { transientEdits = {}, mergedEdits = {} } = entity; const record = yield select( 'getRawEntityRecord', kind, name, recordId ); @@ -158,9 +163,9 @@ export function* editEntityRecord( kind, name, recordId, edits, options = {} ) { edits: Object.keys( edits ).reduce( ( acc, key ) => { const recordValue = record[ key ]; const editedRecordValue = editedRecord[ key ]; - const value = mergedEdits[ key ] ? - { ...editedRecordValue, ...edits[ key ] } : - edits[ key ]; + const value = mergedEdits[ key ] + ? { ...editedRecordValue, ...edits[ key ] } + : edits[ key ]; acc[ key ] = isEqual( recordValue, value ) ? undefined : value; return acc; }, {} ), @@ -269,7 +274,13 @@ export function* saveEntityRecord( } } - yield { type: 'SAVE_ENTITY_RECORD_START', kind, name, recordId, isAutosave }; + yield { + type: 'SAVE_ENTITY_RECORD_START', + kind, + name, + recordId, + isAutosave, + }; let updatedRecord; let error; let persistedEntity; @@ -320,27 +331,45 @@ export function* saveEntityRecord( // when its update is requested by the author and the post had // draft or auto-draft status. if ( persistedRecord.id === updatedRecord.id ) { - let newRecord = { ...persistedRecord, ...data, ...updatedRecord }; + let newRecord = { + ...persistedRecord, + ...data, + ...updatedRecord, + }; newRecord = Object.keys( newRecord ).reduce( ( acc, key ) => { // These properties are persisted in autosaves. if ( [ 'title', 'excerpt', 'content' ].includes( key ) ) { // Edits should be the "raw" attribute values. - acc[ key ] = get( newRecord[ key ], 'raw', newRecord[ key ] ); + acc[ key ] = get( + newRecord[ key ], + 'raw', + newRecord[ key ] + ); } else if ( key === 'status' ) { // Status is only persisted in autosaves when going from // "auto-draft" to "draft". acc[ key ] = persistedRecord.status === 'auto-draft' && - newRecord.status === 'draft' ? - newRecord.status : - persistedRecord.status; + newRecord.status === 'draft' + ? newRecord.status + : persistedRecord.status; } else { // These properties are not persisted in autosaves. - acc[ key ] = get( persistedRecord[ key ], 'raw', persistedRecord[ key ] ); + acc[ key ] = get( + persistedRecord[ key ], + 'raw', + persistedRecord[ key ] + ); } return acc; }, {} ); - yield receiveEntityRecords( kind, name, newRecord, undefined, true ); + yield receiveEntityRecords( + kind, + name, + newRecord, + undefined, + true + ); } else { yield receiveAutosaves( persistedRecord.id, updatedRecord ); } @@ -348,7 +377,11 @@ export function* saveEntityRecord( // Auto drafts should be converted to drafts on explicit saves and we should not respect their default title, // but some plugins break with this behavior so we can't filter it on the server. let data = record; - if ( kind === 'postType' && persistedRecord && persistedRecord.status === 'auto-draft' ) { + if ( + kind === 'postType' && + persistedRecord && + persistedRecord.status === 'auto-draft' + ) { if ( ! data.status ) { data = { ...data, status: 'draft' }; } @@ -371,14 +404,26 @@ export function* saveEntityRecord( name, recordId ); - yield receiveEntityRecords( kind, name, { ...persistedEntity, ...data }, undefined, true ); + yield receiveEntityRecords( + kind, + name, + { ...persistedEntity, ...data }, + undefined, + true + ); updatedRecord = yield apiFetch( { path, method: recordId ? 'PUT' : 'POST', data, } ); - yield receiveEntityRecords( kind, name, updatedRecord, undefined, true ); + yield receiveEntityRecords( + kind, + name, + updatedRecord, + undefined, + true + ); } } catch ( _error ) { error = _error; @@ -386,14 +431,25 @@ export function* saveEntityRecord( // If we got to the point in the try block where we made an optimistic update, // we need to roll it back here. if ( persistedEntity && currentEdits ) { - yield receiveEntityRecords( kind, name, persistedEntity, undefined, true ); + yield receiveEntityRecords( + kind, + name, + persistedEntity, + undefined, + true + ); yield editEntityRecord( kind, name, recordId, { ...currentEdits, - ...( yield select( 'getEntityRecordEdits', kind, name, recordId ) ), + ...( yield select( + 'getEntityRecordEdits', + kind, + name, + recordId + ) ), }, { undoIgnore: true } ); @@ -420,7 +476,9 @@ export function* saveEntityRecord( * @param {Object} options Saving options. */ export function* saveEditedEntityRecord( kind, name, recordId, options ) { - if ( ! ( yield select( 'hasEditsForEntityRecord', kind, name, recordId ) ) ) { + if ( + ! ( yield select( 'hasEditsForEntityRecord', kind, name, recordId ) ) + ) { return; } const edits = yield select( diff --git a/packages/core-data/src/controls.js b/packages/core-data/src/controls.js index fe163c118c6499..404b142c8adb93 100644 --- a/packages/core-data/src/controls.js +++ b/packages/core-data/src/controls.js @@ -55,13 +55,17 @@ const controls = { return triggerApiFetch( request ); }, - SELECT: createRegistryControl( ( registry ) => ( { selectorName, args } ) => { - return registry.select( 'core' )[ selectorName ]( ...args ); - } ), + SELECT: createRegistryControl( + ( registry ) => ( { selectorName, args } ) => { + return registry.select( 'core' )[ selectorName ]( ...args ); + } + ), RESOLVE_SELECT: createRegistryControl( ( registry ) => ( { selectorName, args } ) => { - return registry.__experimentalResolveSelect( 'core' )[ selectorName ]( ...args ); + return registry + .__experimentalResolveSelect( 'core' ) + [ selectorName ]( ...args ); } ), }; diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index 00cfff80b4de9f..bc8b56c0276de2 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -14,9 +14,26 @@ export const DEFAULT_ENTITY_KEY = 'id'; export const defaultEntities = [ { name: 'site', kind: 'root', baseURL: '/wp/v2/settings' }, { name: 'postType', kind: 'root', key: 'slug', baseURL: '/wp/v2/types' }, - { name: 'media', kind: 'root', baseURL: '/wp/v2/media', plural: 'mediaItems' }, - { name: 'taxonomy', kind: 'root', key: 'slug', baseURL: '/wp/v2/taxonomies', plural: 'taxonomies' }, - { name: 'widgetArea', kind: 'root', baseURL: '/__experimental/widget-areas', plural: 'widgetAreas', transientEdits: { blocks: true } }, + { + name: 'media', + kind: 'root', + baseURL: '/wp/v2/media', + plural: 'mediaItems', + }, + { + name: 'taxonomy', + kind: 'root', + key: 'slug', + baseURL: '/wp/v2/taxonomies', + plural: 'taxonomies', + }, + { + name: 'widgetArea', + kind: 'root', + baseURL: '/__experimental/widget-areas', + plural: 'widgetAreas', + transientEdits: { blocks: true }, + }, { name: 'user', kind: 'root', baseURL: '/wp/v2/users', plural: 'users' }, ]; @@ -53,7 +70,9 @@ function* loadPostTypeEntities() { * @return {Promise} Entities promise */ function* loadTaxonomyEntities() { - const taxonomies = yield apiFetch( { path: '/wp/v2/taxonomies?context=edit' } ); + const taxonomies = yield apiFetch( { + path: '/wp/v2/taxonomies?context=edit', + } ); return map( taxonomies, ( taxonomy, name ) => { return { kind: 'taxonomy', @@ -73,11 +92,20 @@ function* loadTaxonomyEntities() { * * @return {string} Method name */ -export const getMethodName = ( kind, name, prefix = 'get', usePlural = false ) => { +export const getMethodName = ( + kind, + name, + prefix = 'get', + usePlural = false +) => { const entity = find( defaultEntities, { kind, name } ); const kindPrefix = kind === 'root' ? '' : upperFirst( camelCase( kind ) ); - const nameSuffix = upperFirst( camelCase( name ) ) + ( usePlural ? 's' : '' ); - const suffix = usePlural && entity.plural ? upperFirst( camelCase( entity.plural ) ) : nameSuffix; + const nameSuffix = + upperFirst( camelCase( name ) ) + ( usePlural ? 's' : '' ); + const suffix = + usePlural && entity.plural + ? upperFirst( camelCase( entity.plural ) ) + : nameSuffix; return `${ prefix }${ kindPrefix }${ suffix }`; }; diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js index d080d77b06ce22..0a0d3829aa4e5b 100644 --- a/packages/core-data/src/entity-provider.js +++ b/packages/core-data/src/entity-provider.js @@ -130,18 +130,21 @@ export function __experimentalUseEntitySaving( kind, type, props ) { const [ isDirty, isSaving, _select ] = useSelect( ( select ) => { - const { getEntityRecordNonTransientEdits, isSavingEntityRecord } = select( - 'core' - ); + const { + getEntityRecordNonTransientEdits, + isSavingEntityRecord, + } = select( 'core' ); const editKeys = Object.keys( getEntityRecordNonTransientEdits( kind, type, id ) ); return [ - props ? - editKeys.some( ( key ) => - typeof props === 'string' ? key === props : props.includes( key ) - ) : - editKeys.length > 0, + props + ? editKeys.some( ( key ) => + typeof props === 'string' + ? key === props + : props.includes( key ) + ) + : editKeys.length > 0, isSavingEntityRecord( kind, type, id ), select, ]; @@ -162,12 +165,15 @@ export function __experimentalUseEntitySaving( kind, type, props ) { if ( typeof props === 'string' ) { filteredEdits = { [ props ]: filteredEdits[ props ] }; } else if ( props ) { - filteredEdits = Object.keys( filteredEdits ).reduce( ( acc, key ) => { - if ( props.includes( key ) ) { - acc[ key ] = filteredEdits[ key ]; - } - return acc; - }, {} ); + filteredEdits = Object.keys( filteredEdits ).reduce( + ( acc, key ) => { + if ( props.includes( key ) ) { + acc[ key ] = filteredEdits[ key ]; + } + return acc; + }, + {} + ); } saveEntityRecord( kind, type, { id, ...filteredEdits } ); }, [ kind, type, id, props, _select ] ); @@ -206,7 +212,9 @@ export function useEntityBlockEditor( const id = useEntityId( kind, type ); const initialBlocks = useMemo( () => { if ( initialEdits ) { - editEntityRecord( kind, type, id, initialEdits, { undoIgnore: true } ); + editEntityRecord( kind, type, id, initialEdits, { + undoIgnore: true, + } ); } // Guard against other instances that might have diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index 783e2bbdd41759..4cafd782d2b4ef 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -21,23 +21,34 @@ import { REDUCER_KEY } from './name'; const entitySelectors = defaultEntities.reduce( ( result, entity ) => { const { kind, name } = entity; - result[ getMethodName( kind, name ) ] = ( state, key ) => selectors.getEntityRecord( state, kind, name, key ); - result[ getMethodName( kind, name, 'get', true ) ] = ( state, ...args ) => selectors.getEntityRecords( state, kind, name, ...args ); + result[ getMethodName( kind, name ) ] = ( state, key ) => + selectors.getEntityRecord( state, kind, name, key ); + result[ getMethodName( kind, name, 'get', true ) ] = ( state, ...args ) => + selectors.getEntityRecords( state, kind, name, ...args ); return result; }, {} ); const entityResolvers = defaultEntities.reduce( ( result, entity ) => { const { kind, name } = entity; - result[ getMethodName( kind, name ) ] = ( key ) => resolvers.getEntityRecord( kind, name, key ); + result[ getMethodName( kind, name ) ] = ( key ) => + resolvers.getEntityRecord( kind, name, key ); const pluralMethodName = getMethodName( kind, name, 'get', true ); - result[ pluralMethodName ] = ( ...args ) => resolvers.getEntityRecords( kind, name, ...args ); - result[ pluralMethodName ].shouldInvalidate = ( action, ...args ) => resolvers.getEntityRecords.shouldInvalidate( action, kind, name, ...args ); + result[ pluralMethodName ] = ( ...args ) => + resolvers.getEntityRecords( kind, name, ...args ); + result[ pluralMethodName ].shouldInvalidate = ( action, ...args ) => + resolvers.getEntityRecords.shouldInvalidate( + action, + kind, + name, + ...args + ); return result; }, {} ); const entityActions = defaultEntities.reduce( ( result, entity ) => { const { kind, name } = entity; - result[ getMethodName( kind, name, 'save' ) ] = ( key ) => actions.saveEntityRecord( kind, name, key ); + result[ getMethodName( kind, name, 'save' ) ] = ( key ) => + actions.saveEntityRecord( kind, name, key ); return result; }, {} ); diff --git a/packages/core-data/src/queried-data/get-query-parts.js b/packages/core-data/src/queried-data/get-query-parts.js index de8a5311619ddd..c659ea1ae2edbf 100644 --- a/packages/core-data/src/queried-data/get-query-parts.js +++ b/packages/core-data/src/queried-data/get-query-parts.js @@ -61,10 +61,9 @@ export function getQueryParts( query ) { // should accept a key value pair, which may optimize its // implementation for our use here, vs. iterating an object // with only a single key. - parts.stableKey += ( + parts.stableKey += ( parts.stableKey ? '&' : '' ) + - addQueryArgs( '', { [ key ]: value } ).slice( 1 ) - ); + addQueryArgs( '', { [ key ]: value } ).slice( 1 ); } } diff --git a/packages/core-data/src/queried-data/reducer.js b/packages/core-data/src/queried-data/reducer.js index 36da4465bb922d..64dd6c25bd38b0 100644 --- a/packages/core-data/src/queried-data/reducer.js +++ b/packages/core-data/src/queried-data/reducer.js @@ -46,14 +46,13 @@ export function getMergedItemIds( itemIds, nextItemIds, page, perPage ) { for ( let i = 0; i < size; i++ ) { // Preserve existing item ID except for subset of range of next items. - const isInNextItemsRange = ( + const isInNextItemsRange = i >= nextItemIdsStartIndex && - i < nextItemIdsStartIndex + nextItemIds.length - ); + i < nextItemIdsStartIndex + nextItemIds.length; - mergedItemIds[ i ] = isInNextItemsRange ? - nextItemIds[ i - nextItemIdsStartIndex ] : - itemIds[ i ]; + mergedItemIds[ i ] = isInNextItemsRange + ? nextItemIds[ i - nextItemIdsStartIndex ] + : itemIds[ i ]; } return mergedItemIds; @@ -76,7 +75,10 @@ function items( state = {}, action ) { ...state, ...action.items.reduce( ( accumulator, value ) => { const itemId = value[ key ]; - accumulator[ itemId ] = conservativeMapItem( state[ itemId ], value ); + accumulator[ itemId ] = conservativeMapItem( + state[ itemId ], + value + ); return accumulator; }, {} ), }; diff --git a/packages/core-data/src/queried-data/selectors.js b/packages/core-data/src/queried-data/selectors.js index c545f07a434c40..694c852c90f869 100644 --- a/packages/core-data/src/queried-data/selectors.js +++ b/packages/core-data/src/queried-data/selectors.js @@ -39,10 +39,10 @@ function getQueriedItemsUncached( state, query ) { } const startOffset = perPage === -1 ? 0 : ( page - 1 ) * perPage; - const endOffset = perPage === -1 ? itemIds.length : Math.min( - startOffset + perPage, - itemIds.length - ); + const endOffset = + perPage === -1 + ? itemIds.length + : Math.min( startOffset + perPage, itemIds.length ); const items = []; for ( let i = startOffset; i < endOffset; i++ ) { diff --git a/packages/core-data/src/queried-data/test/reducer.js b/packages/core-data/src/queried-data/test/reducer.js index 018c0e99803e74..d18322a14f087b 100644 --- a/packages/core-data/src/queried-data/test/reducer.js +++ b/packages/core-data/src/queried-data/test/reducer.js @@ -6,9 +6,7 @@ import deepFreeze from 'deep-freeze'; /** * Internal dependencies */ -import reducer, { - getMergedItemIds, -} from '../reducer'; +import reducer, { getMergedItemIds } from '../reducer'; describe( 'getMergedItemIds', () => { it( 'should receive a page', () => { @@ -35,57 +33,21 @@ describe( 'getMergedItemIds', () => { ] ); const result = getMergedItemIds( original, [ 1, 2, 3 ], 1, 3 ); - expect( result ).toEqual( [ - 1, - 2, - 3, - 4, - 5, - 6, - ] ); + expect( result ).toEqual( [ 1, 2, 3, 4, 5, 6 ] ); } ); it( 'should replace with new page', () => { - const original = deepFreeze( [ - 1, - 2, - 3, - 4, - 5, - 6, - ] ); + const original = deepFreeze( [ 1, 2, 3, 4, 5, 6 ] ); const result = getMergedItemIds( original, [ 'replaced', 5, 6 ], 2, 3 ); - expect( result ).toEqual( [ - 1, - 2, - 3, - 'replaced', - 5, - 6, - ] ); + expect( result ).toEqual( [ 1, 2, 3, 'replaced', 5, 6 ] ); } ); it( 'should append a new partial page', () => { - const original = deepFreeze( [ - 1, - 2, - 3, - 4, - 5, - 6, - ] ); + const original = deepFreeze( [ 1, 2, 3, 4, 5, 6 ] ); const result = getMergedItemIds( original, [ 7 ], 3, 3 ); - expect( result ).toEqual( [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - ] ); + expect( result ).toEqual( [ 1, 2, 3, 4, 5, 6, 7 ] ); } ); } ); @@ -107,9 +69,7 @@ describe( 'reducer', () => { const state = reducer( original, { type: 'RECEIVE_ITEMS', query: { s: 'a', page: 1, per_page: 3 }, - items: [ - { id: 1, name: 'abc' }, - ], + items: [ { id: 1, name: 'abc' } ], } ); expect( state ).toEqual( { @@ -129,9 +89,7 @@ describe( 'reducer', () => { } ); const state = reducer( original, { type: 'RECEIVE_ITEMS', - items: [ - { id: 1, name: 'abc' }, - ], + items: [ { id: 1, name: 'abc' } ], } ); expect( state ).toEqual( { diff --git a/packages/core-data/src/queried-data/test/selectors.js b/packages/core-data/src/queried-data/test/selectors.js index 4d6b4f59509ff3..060d316c3e38ed 100644 --- a/packages/core-data/src/queried-data/test/selectors.js +++ b/packages/core-data/src/queried-data/test/selectors.js @@ -28,10 +28,7 @@ describe( 'getQueriedItems', () => { const result = getQueriedItems( state ); - expect( result ).toEqual( [ - { id: 1 }, - { id: 2 }, - ] ); + expect( result ).toEqual( [ { id: 1 }, { id: 2 } ] ); } ); it( 'should cache on query by state', () => { diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js index 7cfb80e90ecb58..932ae2959a0476 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.js @@ -57,7 +57,10 @@ export function users( state = { byId: {}, queries: {} }, action ) { }, queries: { ...state.queries, - [ action.queryID ]: map( action.users, ( user ) => user.id ), + [ action.queryID ]: map( + action.users, + ( user ) => user.id + ), }, }; } @@ -134,12 +137,13 @@ function entity( entityConfig ) { return flowRight( [ // Limit to matching action type so we don't attempt to replace action on // an unhandled action. - ifMatchingAction( ( action ) => ( - action.name && - action.kind && - action.name === entityConfig.name && - action.kind === entityConfig.kind - ) ), + ifMatchingAction( + ( action ) => + action.name && + action.kind && + action.name === entityConfig.name && + action.kind === entityConfig.kind + ), // Inject the entity config into the action. replaceAction( ( action ) => { @@ -164,19 +168,29 @@ function entity( entityConfig ) { continue; } - const nextEdits = Object.keys( edits ).reduce( ( acc, key ) => { - // If the edited value is still different to the persisted value, - // keep the edited value in edits. - if ( - // Edits are the "raw" attribute values, but records may have - // objects with more properties, so we use `get` here for the - // comparison. - ! isEqual( edits[ key ], get( record[ key ], 'raw', record[ key ] ) ) - ) { - acc[ key ] = edits[ key ]; - } - return acc; - }, {} ); + const nextEdits = Object.keys( edits ).reduce( + ( acc, key ) => { + // If the edited value is still different to the persisted value, + // keep the edited value in edits. + if ( + // Edits are the "raw" attribute values, but records may have + // objects with more properties, so we use `get` here for the + // comparison. + ! isEqual( + edits[ key ], + get( + record[ key ], + 'raw', + record[ key ] + ) + ) + ) { + acc[ key ] = edits[ key ]; + } + return acc; + }, + {} + ); if ( Object.keys( nextEdits ).length ) { nextState[ recordId ] = nextEdits; @@ -215,7 +229,8 @@ function entity( entityConfig ) { return { ...state, [ action.recordId ]: { - pending: action.type === 'SAVE_ENTITY_RECORD_START', + pending: + action.type === 'SAVE_ENTITY_RECORD_START', error: action.error, isAutosave: action.isAutosave, }, @@ -239,10 +254,7 @@ function entity( entityConfig ) { export function entitiesConfig( state = defaultEntities, action ) { switch ( action.type ) { case 'ADD_ENTITIES': - return [ - ...state, - ...action.entities, - ]; + return [ ...state, ...action.entities ]; } return state; @@ -263,18 +275,25 @@ export const entities = ( state = {}, action ) => { let entitiesDataReducer = state.reducer; if ( ! entitiesDataReducer || newConfig !== state.config ) { const entitiesByKind = groupBy( newConfig, 'kind' ); - entitiesDataReducer = combineReducers( Object.entries( entitiesByKind ).reduce( ( memo, [ kind, subEntities ] ) => { - const kindReducer = combineReducers( subEntities.reduce( - ( kindMemo, entityConfig ) => ( { - ...kindMemo, - [ entityConfig.name ]: entity( entityConfig ), - } ), + entitiesDataReducer = combineReducers( + Object.entries( entitiesByKind ).reduce( + ( memo, [ kind, subEntities ] ) => { + const kindReducer = combineReducers( + subEntities.reduce( + ( kindMemo, entityConfig ) => ( { + ...kindMemo, + [ entityConfig.name ]: entity( entityConfig ), + } ), + {} + ) + ); + + memo[ kind ] = kindReducer; + return memo; + }, {} - ) ); - - memo[ kind ] = kindReducer; - return memo; - }, {} ) ); + ) + ); } const newData = entitiesDataReducer( state.data, action ); @@ -311,19 +330,27 @@ export function undo( state = UNDO_INITIAL_STATE, action ) { case 'CREATE_UNDO_LEVEL': let isCreateUndoLevel = action.type === 'CREATE_UNDO_LEVEL'; const isUndoOrRedo = - ! isCreateUndoLevel && ( action.meta.isUndo || action.meta.isRedo ); + ! isCreateUndoLevel && + ( action.meta.isUndo || action.meta.isRedo ); if ( isCreateUndoLevel ) { action = lastEditAction; } else if ( ! isUndoOrRedo ) { // Don't lose the last edit cache if the new one only has transient edits. // Transient edits don't create new levels so updating the cache would make // us skip an edit later when creating levels explicitly. - if ( Object.keys( action.edits ).some( ( key ) => ! action.transientEdits[ key ] ) ) { + if ( + Object.keys( action.edits ).some( + ( key ) => ! action.transientEdits[ key ] + ) + ) { lastEditAction = action; } else { lastEditAction = { ...action, - edits: { ...( lastEditAction && lastEditAction.edits ), ...action.edits }, + edits: { + ...( lastEditAction && lastEditAction.edits ), + ...action.edits, + }, }; } } @@ -331,7 +358,8 @@ export function undo( state = UNDO_INITIAL_STATE, action ) { let nextState; if ( isUndoOrRedo ) { nextState = [ ...state ]; - nextState.offset = state.offset + ( action.meta.isUndo ? -1 : 1 ); + nextState.offset = + state.offset + ( action.meta.isUndo ? -1 : 1 ); if ( state.flattenedUndo ) { // The first undo in a sequence of undos might happen while we have @@ -355,16 +383,22 @@ export function undo( state = UNDO_INITIAL_STATE, action ) { // are merged. They are defined in the entity's config. if ( ! isCreateUndoLevel && - ! Object.keys( action.edits ).some( ( key ) => ! action.transientEdits[ key ] ) + ! Object.keys( action.edits ).some( + ( key ) => ! action.transientEdits[ key ] + ) ) { nextState = [ ...state ]; - nextState.flattenedUndo = { ...state.flattenedUndo, ...action.edits }; + nextState.flattenedUndo = { + ...state.flattenedUndo, + ...action.edits, + }; nextState.offset = state.offset; return nextState; } // Clear potential redos, because this only supports linear history. - nextState = nextState || state.slice( 0, state.offset || undefined ); + nextState = + nextState || state.slice( 0, state.offset || undefined ); nextState.offset = nextState.offset || 0; nextState.pop(); if ( ! isCreateUndoLevel ) { @@ -372,14 +406,17 @@ export function undo( state = UNDO_INITIAL_STATE, action ) { kind: action.meta.undo.kind, name: action.meta.undo.name, recordId: action.meta.undo.recordId, - edits: { ...state.flattenedUndo, ...action.meta.undo.edits }, + edits: { + ...state.flattenedUndo, + ...action.meta.undo.edits, + }, } ); } // When an edit is a function it's an optimization to avoid running some expensive operation. // We can't rely on the function references being the same so we opt out of comparing them here. - const comparisonUndoEdits = Object.values( action.meta.undo.edits ).filter( - ( edit ) => typeof edit !== 'function' - ); + const comparisonUndoEdits = Object.values( + action.meta.undo.edits + ).filter( ( edit ) => typeof edit !== 'function' ); const comparisonEdits = Object.values( action.edits ).filter( ( edit ) => typeof edit !== 'function' ); @@ -388,9 +425,9 @@ export function undo( state = UNDO_INITIAL_STATE, action ) { kind: action.kind, name: action.name, recordId: action.recordId, - edits: isCreateUndoLevel ? - { ...state.flattenedUndo, ...action.edits } : - action.edits, + edits: isCreateUndoLevel + ? { ...state.flattenedUndo, ...action.edits } + : action.edits, } ); } return nextState; diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 670cb4adf2a750..b492a63e67e99e 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -28,7 +28,9 @@ import { apiFetch, resolveSelect } from './controls'; * Requests authors from the REST API. */ export function* getAuthors() { - const users = yield apiFetch( { path: '/wp/v2/users/?who=authors&per_page=-1' } ); + const users = yield apiFetch( { + path: '/wp/v2/users/?who=authors&per_page=-1', + } ); yield receiveUserQuery( 'authors', users ); } @@ -53,7 +55,9 @@ export function* getEntityRecord( kind, name, key = '' ) { if ( ! entity ) { return; } - const record = yield apiFetch( { path: `${ entity.baseURL }/${ key }?context=edit` } ); + const record = yield apiFetch( { + path: `${ entity.baseURL }/${ key }?context=edit`, + } ); yield receiveEntityRecords( kind, name, record ); } @@ -91,7 +95,9 @@ getEntityRecords.shouldInvalidate = ( action, kind, name ) => { * Requests theme supports data from the index. */ export function* getThemeSupports() { - const activeThemes = yield apiFetch( { path: '/wp/v2/themes?status=active' } ); + const activeThemes = yield apiFetch( { + path: '/wp/v2/themes?status=active', + } ); yield receiveThemeSupports( activeThemes[ 0 ].theme_supports ); } @@ -102,7 +108,9 @@ export function* getThemeSupports() { */ export function* getEmbedPreview( url ) { try { - const embedProxyResponse = yield apiFetch( { path: addQueryArgs( '/oembed/1.0/proxy', { url } ) } ); + const embedProxyResponse = yield apiFetch( { + path: addQueryArgs( '/oembed/1.0/proxy', { url } ), + } ); yield receiveEmbedPreview( url, embedProxyResponse ); } catch ( error ) { // Embed API 404s if the URL cannot be embedded, so we have to catch the error from the apiRequest here. @@ -187,8 +195,13 @@ export function* canUser( action, resource, id ) { * @param {number} postId The id of the parent post. */ export function* getAutosaves( postType, postId ) { - const { rest_base: restBase } = yield resolveSelect( 'getPostType', postType ); - const autosaves = yield apiFetch( { path: `/wp/v2/${ restBase }/${ postId }/autosaves?context=edit` } ); + const { rest_base: restBase } = yield resolveSelect( + 'getPostType', + postType + ); + const autosaves = yield apiFetch( { + path: `/wp/v2/${ restBase }/${ postId }/autosaves?context=edit`, + } ); if ( autosaves && autosaves.length ) { yield receiveAutosaves( postId, autosaves ); diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js index 495ebbbf94d795..ba49b799a2b3e1 100644 --- a/packages/core-data/src/selectors.js +++ b/packages/core-data/src/selectors.js @@ -25,9 +25,15 @@ import { getQueriedItems } from './queried-data'; * * @return {boolean} Whether a request is in progress for an embed preview. */ -export const isRequestingEmbedPreview = createRegistrySelector( ( select ) => ( state, url ) => { - return select( 'core/data' ).isResolving( REDUCER_KEY, 'getEmbedPreview', [ url ] ); -} ); +export const isRequestingEmbedPreview = createRegistrySelector( + ( select ) => ( state, url ) => { + return select( 'core/data' ).isResolving( + REDUCER_KEY, + 'getEmbedPreview', + [ url ] + ); + } +); /** * Returns all available authors. @@ -104,7 +110,13 @@ export function getEntity( state, kind, name ) { * @return {Object?} Record. */ export function getEntityRecord( state, kind, name, key ) { - return get( state.entities.data, [ kind, name, 'queriedData', 'items', key ] ); + return get( state.entities.data, [ + kind, + name, + 'queriedData', + 'items', + key, + ] ); } /** @@ -137,13 +149,17 @@ export const getRawEntityRecord = createSelector( const record = getEntityRecord( state, kind, name, key ); return ( record && - Object.keys( record ).reduce( ( accumulator, _key ) => { - // Because edits are the "raw" attribute values, - // we return those from record selectors to make rendering, - // comparisons, and joins with edits easier. - accumulator[ _key ] = get( record[ _key ], 'raw', record[ _key ] ); - return accumulator; - }, {} ) + Object.keys( record ).reduce( ( accumulator, _key ) => { + // Because edits are the "raw" attribute values, + // we return those from record selectors to make rendering, + // comparisons, and joins with edits easier. + accumulator[ _key ] = get( + record[ _key ], + 'raw', + record[ _key ] + ); + return accumulator; + }, {} ) ); }, ( state ) => [ state.entities.data ] @@ -160,7 +176,11 @@ export const getRawEntityRecord = createSelector( * @return {Array} Records. */ export function getEntityRecords( state, kind, name, query ) { - const queriedState = get( state.entities.data, [ kind, name, 'queriedData' ] ); + const queriedState = get( state.entities.data, [ + kind, + name, + 'queriedData', + ] ); if ( ! queriedState ) { return []; } @@ -184,7 +204,9 @@ export const getEntityRecordChangesByRecord = createSelector( } = state; return Object.keys( data ).reduce( ( acc, kind ) => { Object.keys( data[ kind ] ).forEach( ( name ) => { - const editsKeys = Object.keys( data[ kind ][ name ].edits ).filter( ( editsKey ) => + const editsKeys = Object.keys( + data[ kind ][ name ].edits + ).filter( ( editsKey ) => hasEditsForEntityRecord( state, kind, name, editsKey ) ); if ( editsKeys.length ) { @@ -197,7 +219,12 @@ export const getEntityRecordChangesByRecord = createSelector( editsKeys.forEach( ( editsKey ) => ( acc[ kind ][ name ][ editsKey ] = { - rawRecord: getRawEntityRecord( state, kind, name, editsKey ), + rawRecord: getRawEntityRecord( + state, + kind, + name, + editsKey + ), edits: getEntityRecordNonTransientEdits( state, kind, @@ -274,8 +301,9 @@ export const getEntityRecordNonTransientEdits = createSelector( export function hasEditsForEntityRecord( state, kind, name, recordId ) { return ( isSavingEntityRecord( state, kind, name, recordId ) || - Object.keys( getEntityRecordNonTransientEdits( state, kind, name, recordId ) ) - .length > 0 + Object.keys( + getEntityRecordNonTransientEdits( state, kind, name, recordId ) + ).length > 0 ); } @@ -345,7 +373,13 @@ export function isSavingEntityRecord( state, kind, name, recordId ) { * @return {Object?} The entity record's save error. */ export function getLastEntitySaveError( state, kind, name, recordId ) { - return get( state.entities.data, [ kind, name, 'saving', recordId, 'error' ] ); + return get( state.entities.data, [ + kind, + name, + 'saving', + recordId, + 'error', + ] ); } /** @@ -544,9 +578,14 @@ export function getAutosave( state, postType, postId, authorId ) { * * @return {boolean} True if the REST request was completed. False otherwise. */ -export const hasFetchedAutosaves = createRegistrySelector( ( select ) => ( state, postType, postId ) => { - return select( REDUCER_KEY ).hasFinishedResolution( 'getAutosaves', [ postType, postId ] ); -} ); +export const hasFetchedAutosaves = createRegistrySelector( + ( select ) => ( state, postType, postId ) => { + return select( REDUCER_KEY ).hasFinishedResolution( 'getAutosaves', [ + postType, + postId, + ] ); + } +); /** * Returns a new reference when edited values have changed. This is useful in @@ -568,5 +607,5 @@ export const hasFetchedAutosaves = createRegistrySelector( ( select ) => ( state */ export const getReferenceByDistinctEdits = createSelector( () => [], - ( state ) => [ state.undo.length, state.undo.offset ], + ( state ) => [ state.undo.length, state.undo.offset ] ); diff --git a/packages/core-data/src/test/actions.js b/packages/core-data/src/test/actions.js index 23e9463b03ebb0..34b4597e2b96f7 100644 --- a/packages/core-data/src/test/actions.js +++ b/packages/core-data/src/test/actions.js @@ -33,7 +33,9 @@ describe( 'editEntityRecord', () => { describe( 'saveEntityRecord', () => { it( 'triggers a POST request for a new record', async () => { const post = { title: 'new post' }; - const entities = [ { name: 'post', kind: 'postType', baseURL: '/wp/v2/posts' } ]; + const entities = [ + { name: 'post', kind: 'postType', baseURL: '/wp/v2/posts' }, + ]; const fulfillment = saveEntityRecord( 'postType', 'post', post ); // Trigger generator fulfillment.next(); @@ -45,7 +47,9 @@ describe( 'saveEntityRecord', () => { // Should select getEntityRecordNoResolver selector (as opposed to getEntityRecord) // see https://github.com/WordPress/gutenberg/pull/19752#discussion_r368498318. expect( fulfillment.next().value.type ).toBe( 'SELECT' ); - expect( fulfillment.next().value.selectorName ).toBe( 'getEntityRecordNoResolver' ); + expect( fulfillment.next().value.selectorName ).toBe( + 'getEntityRecordNoResolver' + ); expect( fulfillment.next().value.type ).toBe( 'SELECT' ); expect( fulfillment.next().value.type ).toBe( 'RECEIVE_ITEMS' ); const { value: apiFetchAction } = fulfillment.next( {} ); @@ -66,13 +70,17 @@ describe( 'saveEntityRecord', () => { true ) ); - expect( fulfillment.next().value.type ).toBe( 'SAVE_ENTITY_RECORD_FINISH' ); + expect( fulfillment.next().value.type ).toBe( + 'SAVE_ENTITY_RECORD_FINISH' + ); expect( fulfillment.next().value ).toBe( updatedRecord ); } ); it( 'triggers a PUT request for an existing record', async () => { const post = { id: 10, title: 'new post' }; - const entities = [ { name: 'post', kind: 'postType', baseURL: '/wp/v2/posts' } ]; + const entities = [ + { name: 'post', kind: 'postType', baseURL: '/wp/v2/posts' }, + ]; const fulfillment = saveEntityRecord( 'postType', 'post', post ); // Trigger generator fulfillment.next(); @@ -92,13 +100,24 @@ describe( 'saveEntityRecord', () => { } ); // Provide response and trigger action const { value: received } = fulfillment.next( post ); - expect( received ).toEqual( receiveEntityRecords( 'postType', 'post', post, undefined, true ) ); - expect( fulfillment.next().value.type ).toBe( 'SAVE_ENTITY_RECORD_FINISH' ); + expect( received ).toEqual( + receiveEntityRecords( 'postType', 'post', post, undefined, true ) + ); + expect( fulfillment.next().value.type ).toBe( + 'SAVE_ENTITY_RECORD_FINISH' + ); } ); it( 'triggers a PUT request for an existing record with a custom key', async () => { const postType = { slug: 'page', title: 'Pages' }; - const entities = [ { name: 'postType', kind: 'root', baseURL: '/wp/v2/types', key: 'slug' } ]; + const entities = [ + { + name: 'postType', + kind: 'root', + baseURL: '/wp/v2/types', + key: 'slug', + }, + ]; const fulfillment = saveEntityRecord( 'root', 'postType', postType ); // Trigger generator fulfillment.next(); @@ -118,8 +137,18 @@ describe( 'saveEntityRecord', () => { } ); // Provide response and trigger action const { value: received } = fulfillment.next( postType ); - expect( received ).toEqual( receiveEntityRecords( 'root', 'postType', postType, undefined, true ) ); - expect( fulfillment.next().value.type ).toBe( 'SAVE_ENTITY_RECORD_FINISH' ); + expect( received ).toEqual( + receiveEntityRecords( + 'root', + 'postType', + postType, + undefined, + true + ) + ); + expect( fulfillment.next().value.type ).toBe( + 'SAVE_ENTITY_RECORD_FINISH' + ); } ); } ); diff --git a/packages/core-data/src/test/entities.js b/packages/core-data/src/test/entities.js index cd6361987af8d4..e84e99ffb191f1 100644 --- a/packages/core-data/src/test/entities.js +++ b/packages/core-data/src/test/entities.js @@ -60,11 +60,13 @@ describe( 'getKindEntities', () => { } ); it( 'should fetch and add the entities', async () => { - const fetchedEntities = [ { - baseURL: '/wp/v2/posts', - kind: 'postType', - name: 'post', - } ]; + const fetchedEntities = [ + { + baseURL: '/wp/v2/posts', + kind: 'postType', + name: 'post', + }, + ]; const fulfillment = getKindEntities( 'postType' ); // Start the generator fulfillment.next(); diff --git a/packages/core-data/src/test/reducer.js b/packages/core-data/src/test/reducer.js index c585e4f4415a91..070d317ee0d6de 100644 --- a/packages/core-data/src/test/reducer.js +++ b/packages/core-data/src/test/reducer.js @@ -42,14 +42,20 @@ describe( 'entities', () => { it( 'returns the default state for all defined entities', () => { const state = entities( undefined, {} ); - expect( state.data.root.postType.queriedData ).toEqual( { items: {}, queries: {} } ); + expect( state.data.root.postType.queriedData ).toEqual( { + items: {}, + queries: {}, + } ); } ); it( 'returns with received post types by slug', () => { const originalState = deepFreeze( {} ); const state = entities( originalState, { type: 'RECEIVE_ITEMS', - items: [ { slug: 'b', title: 'beach' }, { slug: 's', title: 'sun' } ], + items: [ + { slug: 'b', title: 'beach' }, + { slug: 's', title: 'sun' }, + ], kind: 'root', name: 'postType', } ); @@ -139,7 +145,9 @@ describe( 'undo', () => { // the action to move the offset. lastEdits = undoState[ - undoState.length + undoState.offset - ( args[ 0 ] === 'isUndo' ? 2 : 0 ) + undoState.length + + undoState.offset - + ( args[ 0 ] === 'isUndo' ? 2 : 0 ) ].edits; action = { type: 'EDIT_ENTITY_RECORD', @@ -268,7 +276,9 @@ describe( 'undo', () => { { transientValue: true } ); undoState = createNextUndoState( 'isCreate' ); - expectedUndoState[ expectedUndoState.length - 1 ].edits.transientValue = 2; + expectedUndoState[ + expectedUndoState.length - 1 + ].edits.transientValue = 2; expect( undoState ).toEqual( expectedUndoState ); // Check that undo levels are created with the latest action, @@ -359,37 +369,44 @@ describe( 'autosaves', () => { } ); it( 'returns the current state with the new autosaves merged in, keyed by the parent post id', () => { - const existingAutosaves = [ { - title: { - raw: 'Some', - }, - content: { - raw: 'other', - }, - excerpt: { - raw: 'autosave', + const existingAutosaves = [ + { + title: { + raw: 'Some', + }, + content: { + raw: 'other', + }, + excerpt: { + raw: 'autosave', + }, + status: 'publish', }, - status: 'publish', - } ]; + ]; - const newAutosaves = [ { - title: { - raw: 'The Title', - }, - content: { - raw: 'The Content', - }, - excerpt: { - raw: 'The Excerpt', + const newAutosaves = [ + { + title: { + raw: 'The Title', + }, + content: { + raw: 'The Content', + }, + excerpt: { + raw: 'The Excerpt', + }, + status: 'draft', }, - status: 'draft', - } ]; - - const state = autosaves( { 1: existingAutosaves }, { - type: 'RECEIVE_AUTOSAVES', - postId: 2, - autosaves: newAutosaves, - } ); + ]; + + const state = autosaves( + { 1: existingAutosaves }, + { + type: 'RECEIVE_AUTOSAVES', + postId: 2, + autosaves: newAutosaves, + } + ); expect( state ).toEqual( { 1: existingAutosaves, @@ -398,37 +415,44 @@ describe( 'autosaves', () => { } ); it( 'overwrites any existing state if new autosaves are received with the same post id', () => { - const existingAutosaves = [ { - title: { - raw: 'Some', - }, - content: { - raw: 'other', - }, - excerpt: { - raw: 'autosave', + const existingAutosaves = [ + { + title: { + raw: 'Some', + }, + content: { + raw: 'other', + }, + excerpt: { + raw: 'autosave', + }, + status: 'publish', }, - status: 'publish', - } ]; + ]; - const newAutosaves = [ { - title: { - raw: 'The Title', - }, - content: { - raw: 'The Content', - }, - excerpt: { - raw: 'The Excerpt', + const newAutosaves = [ + { + title: { + raw: 'The Title', + }, + content: { + raw: 'The Content', + }, + excerpt: { + raw: 'The Excerpt', + }, + status: 'draft', }, - status: 'draft', - } ]; - - const state = autosaves( { 1: existingAutosaves }, { - type: 'RECEIVE_AUTOSAVES', - postId: 1, - autosaves: newAutosaves, - } ); + ]; + + const state = autosaves( + { 1: existingAutosaves }, + { + type: 'RECEIVE_AUTOSAVES', + postId: 1, + autosaves: newAutosaves, + } + ); expect( state ).toEqual( { 1: newAutosaves, @@ -445,10 +469,13 @@ describe( 'currentUser', () => { it( 'returns the current user', () => { const currentUserData = { id: 1 }; - const state = currentUser( {}, { - type: 'RECEIVE_CURRENT_USER', - currentUser: currentUserData, - } ); + const state = currentUser( + {}, + { + type: 'RECEIVE_CURRENT_USER', + currentUser: currentUserData, + } + ); expect( state ).toEqual( currentUserData ); } ); @@ -456,10 +483,13 @@ describe( 'currentUser', () => { it( 'overwrites any existing current user state', () => { const currentUserData = { id: 2 }; - const state = currentUser( { id: 1 }, { - type: 'RECEIVE_CURRENT_USER', - currentUser: currentUserData, - } ); + const state = currentUser( + { id: 1 }, + { + type: 'RECEIVE_CURRENT_USER', + currentUser: currentUserData, + } + ); expect( state ).toEqual( currentUserData ); } ); diff --git a/packages/core-data/src/test/resolvers.js b/packages/core-data/src/test/resolvers.js index c91babdfb59562..74891e8fe17869 100644 --- a/packages/core-data/src/test/resolvers.js +++ b/packages/core-data/src/test/resolvers.js @@ -1,24 +1,43 @@ /** * Internal dependencies */ -import { getEntityRecord, getEntityRecords, getEmbedPreview, canUser, getAutosaves, getCurrentUser } from '../resolvers'; -import { receiveEntityRecords, receiveEmbedPreview, receiveUserPermission, receiveAutosaves, receiveCurrentUser } from '../actions'; +import { + getEntityRecord, + getEntityRecords, + getEmbedPreview, + canUser, + getAutosaves, + getCurrentUser, +} from '../resolvers'; +import { + receiveEntityRecords, + receiveEmbedPreview, + receiveUserPermission, + receiveAutosaves, + receiveCurrentUser, +} from '../actions'; import { apiFetch } from '../controls'; describe( 'getEntityRecord', () => { const POST_TYPE = { slug: 'post' }; it( 'yields with requested post type', async () => { - const entities = [ { name: 'postType', kind: 'root', baseURL: '/wp/v2/types' } ]; + const entities = [ + { name: 'postType', kind: 'root', baseURL: '/wp/v2/types' }, + ]; const fulfillment = getEntityRecord( 'root', 'postType', 'post' ); // Trigger generator fulfillment.next(); // Provide entities and trigger apiFetch const { value: apiFetchAction } = fulfillment.next( entities ); - expect( apiFetchAction.request ).toEqual( { path: '/wp/v2/types/post?context=edit' } ); + expect( apiFetchAction.request ).toEqual( { + path: '/wp/v2/types/post?context=edit', + } ); // Provide response and trigger action const { value: received } = fulfillment.next( POST_TYPE ); - expect( received ).toEqual( receiveEntityRecords( 'root', 'postType', POST_TYPE ) ); + expect( received ).toEqual( + receiveEntityRecords( 'root', 'postType', POST_TYPE ) + ); } ); } ); @@ -38,10 +57,19 @@ describe( 'getEntityRecords', () => { fulfillment.next(); // Provide entities and trigger apiFetch const { value: apiFetchAction } = fulfillment.next( entities ); - expect( apiFetchAction.request ).toEqual( { path: '/wp/v2/types?context=edit' } ); + expect( apiFetchAction.request ).toEqual( { + path: '/wp/v2/types?context=edit', + } ); // Provide response and trigger action const { value: received } = fulfillment.next( POST_TYPES ); - expect( received ).toEqual( receiveEntityRecords( 'root', 'postType', Object.values( POST_TYPES ), {} ) ); + expect( received ).toEqual( + receiveEntityRecords( + 'root', + 'postType', + Object.values( POST_TYPES ), + {} + ) + ); } ); } ); @@ -56,8 +84,11 @@ describe( 'getEmbedPreview', () => { // Trigger generator fulfillment.next(); // Provide apiFetch response and trigger Action - const received = ( await fulfillment.next( SUCCESSFUL_EMBED_RESPONSE ) ).value; - expect( received ).toEqual( receiveEmbedPreview( EMBEDDABLE_URL, SUCCESSFUL_EMBED_RESPONSE ) ); + const received = ( await fulfillment.next( SUCCESSFUL_EMBED_RESPONSE ) ) + .value; + expect( received ).toEqual( + receiveEmbedPreview( EMBEDDABLE_URL, SUCCESSFUL_EMBED_RESPONSE ) + ); } ); it( 'yields false if the URL cannot be embedded', async () => { @@ -66,7 +97,9 @@ describe( 'getEmbedPreview', () => { fulfillment.next(); // Provide invalid response and trigger Action const received = ( await fulfillment.throw( { status: 404 } ) ).value; - expect( received ).toEqual( receiveEmbedPreview( UNEMBEDDABLE_URL, UNEMBEDDABLE_RESPONSE ) ); + expect( received ).toEqual( + receiveEmbedPreview( UNEMBEDDABLE_URL, UNEMBEDDABLE_RESPONSE ) + ); } ); } ); @@ -76,11 +109,13 @@ describe( 'canUser', () => { let received = generator.next(); expect( received.done ).toBe( false ); - expect( received.value ).toEqual( apiFetch( { - path: '/wp/v2/media', - method: 'OPTIONS', - parse: false, - } ) ); + expect( received.value ).toEqual( + apiFetch( { + path: '/wp/v2/media', + method: 'OPTIONS', + parse: false, + } ) + ); received = generator.throw( { status: 404 } ); expect( received.done ).toBe( true ); @@ -92,11 +127,13 @@ describe( 'canUser', () => { let received = generator.next(); expect( received.done ).toBe( false ); - expect( received.value ).toEqual( apiFetch( { - path: '/wp/v2/media', - method: 'OPTIONS', - parse: false, - } ) ); + expect( received.value ).toEqual( + apiFetch( { + path: '/wp/v2/media', + method: 'OPTIONS', + parse: false, + } ) + ); received = generator.next( { headers: { @@ -104,7 +141,9 @@ describe( 'canUser', () => { }, } ); expect( received.done ).toBe( false ); - expect( received.value ).toEqual( receiveUserPermission( 'create/media', false ) ); + expect( received.value ).toEqual( + receiveUserPermission( 'create/media', false ) + ); received = generator.next(); expect( received.done ).toBe( true ); @@ -116,11 +155,13 @@ describe( 'canUser', () => { let received = generator.next(); expect( received.done ).toBe( false ); - expect( received.value ).toEqual( apiFetch( { - path: '/wp/v2/media', - method: 'OPTIONS', - parse: false, - } ) ); + expect( received.value ).toEqual( + apiFetch( { + path: '/wp/v2/media', + method: 'OPTIONS', + parse: false, + } ) + ); received = generator.next( { headers: { @@ -128,7 +169,9 @@ describe( 'canUser', () => { }, } ); expect( received.done ).toBe( false ); - expect( received.value ).toEqual( receiveUserPermission( 'create/media', true ) ); + expect( received.value ).toEqual( + receiveUserPermission( 'create/media', true ) + ); received = generator.next(); expect( received.done ).toBe( true ); @@ -140,11 +183,13 @@ describe( 'canUser', () => { let received = generator.next(); expect( received.done ).toBe( false ); - expect( received.value ).toEqual( apiFetch( { - path: '/wp/v2/blocks/123', - method: 'GET', - parse: false, - } ) ); + expect( received.value ).toEqual( + apiFetch( { + path: '/wp/v2/blocks/123', + method: 'GET', + parse: false, + } ) + ); received = generator.next( { headers: { @@ -152,7 +197,9 @@ describe( 'canUser', () => { }, } ); expect( received.done ).toBe( false ); - expect( received.value ).toEqual( receiveUserPermission( 'update/blocks/123', true ) ); + expect( received.value ).toEqual( + receiveUserPermission( 'update/blocks/123', true ) + ); received = generator.next(); expect( received.done ).toBe( true ); @@ -161,11 +208,13 @@ describe( 'canUser', () => { } ); describe( 'getAutosaves', () => { - const SUCCESSFUL_RESPONSE = [ { - title: 'test title', - excerpt: 'test excerpt', - content: 'test content', - } ]; + const SUCCESSFUL_RESPONSE = [ + { + title: 'test title', + excerpt: 'test excerpt', + content: 'test content', + }, + ]; it( 'yields with fetched autosaves', async () => { const postType = 'post'; @@ -180,11 +229,16 @@ describe( 'getAutosaves', () => { // Trigger generator with the postEntity and assert that correct path is formed // in the apiFetch request. const { value: apiFetchAction } = fulfillment.next( postEntity ); - expect( apiFetchAction.request ).toEqual( { path: `/wp/v2/${ restBase }/${ postId }/autosaves?context=edit` } ); + expect( apiFetchAction.request ).toEqual( { + path: `/wp/v2/${ restBase }/${ postId }/autosaves?context=edit`, + } ); // Provide apiFetch response and trigger Action - const received = ( await fulfillment.next( SUCCESSFUL_RESPONSE ) ).value; - expect( received ).toEqual( receiveAutosaves( 1, SUCCESSFUL_RESPONSE ) ); + const received = ( await fulfillment.next( SUCCESSFUL_RESPONSE ) ) + .value; + expect( received ).toEqual( + receiveAutosaves( 1, SUCCESSFUL_RESPONSE ) + ); } ); it( ' yields undefined if no autosaves exist for the post', async () => { @@ -200,7 +254,9 @@ describe( 'getAutosaves', () => { // Trigger generator with the postEntity and assert that correct path is formed // in the apiFetch request. const { value: apiFetchAction } = fulfillment.next( postEntity ); - expect( apiFetchAction.request ).toEqual( { path: `/wp/v2/${ restBase }/${ postId }/autosaves?context=edit` } ); + expect( apiFetchAction.request ).toEqual( { + path: `/wp/v2/${ restBase }/${ postId }/autosaves?context=edit`, + } ); // Provide apiFetch response and trigger Action const received = ( await fulfillment.next( [] ) ).value; @@ -220,7 +276,8 @@ describe( 'getCurrentUser', () => { fulfillment.next(); // Provide apiFetch response and trigger Action - const received = ( await fulfillment.next( SUCCESSFUL_RESPONSE ) ).value; + const received = ( await fulfillment.next( SUCCESSFUL_RESPONSE ) ) + .value; expect( received ).toEqual( receiveCurrentUser( SUCCESSFUL_RESPONSE ) ); } ); } ); diff --git a/packages/core-data/src/test/selectors.js b/packages/core-data/src/test/selectors.js index 07a2540d6dd4eb..52aff59e26446f 100644 --- a/packages/core-data/src/test/selectors.js +++ b/packages/core-data/src/test/selectors.js @@ -22,50 +22,52 @@ import { } from '../selectors'; // getEntityRecord and getEntityRecordNoResolver selectors share the same tests -describe.each( [ - [ getEntityRecord ], - [ getEntityRecordNoResolver ], -] )( '%p', ( selector ) => { - it( 'should return undefined for unknown record’s key', () => { - const state = deepFreeze( { - entities: { - data: { - root: { - postType: { - queriedData: { - items: {}, - queries: {}, +describe.each( [ [ getEntityRecord ], [ getEntityRecordNoResolver ] ] )( + '%p', + ( selector ) => { + it( 'should return undefined for unknown record’s key', () => { + const state = deepFreeze( { + entities: { + data: { + root: { + postType: { + queriedData: { + items: {}, + queries: {}, + }, }, }, }, }, - }, + } ); + expect( selector( state, 'root', 'postType', 'post' ) ).toBe( + undefined + ); } ); - expect( selector( state, 'root', 'postType', 'post' ) ).toBe( undefined ); - } ); - it( 'should return a record by key', () => { - const state = deepFreeze( { - entities: { - data: { - root: { - postType: { - queriedData: { - items: { - post: { slug: 'post' }, + it( 'should return a record by key', () => { + const state = deepFreeze( { + entities: { + data: { + root: { + postType: { + queriedData: { + items: { + post: { slug: 'post' }, + }, + queries: {}, }, - queries: {}, }, }, }, }, - }, - } ); - expect( selector( state, 'root', 'postType', 'post' ) ).toEqual( { - slug: 'post', + } ); + expect( selector( state, 'root', 'postType', 'post' ) ).toEqual( { + slug: 'post', + } ); } ); - } ); -} ); + } +); describe( 'getEntityRecords', () => { it( 'should return an null by default', () => { @@ -131,7 +133,9 @@ describe( 'getEntityRecordChangesByRecord', () => { items: { someKey: { someProperty: 'somePersistedValue', - someRawProperty: { raw: 'somePersistedRawValue' }, + someRawProperty: { + raw: 'somePersistedRawValue', + }, }, }, }, @@ -139,7 +143,8 @@ describe( 'getEntityRecordChangesByRecord', () => { someKey: { someProperty: 'someEditedValue', someRawProperty: 'someEditedRawValue', - someTransientEditProperty: 'someEditedTransientEditValue', + someTransientEditProperty: + 'someEditedTransientEditValue', }, }, }, @@ -172,7 +177,12 @@ describe( 'getEntityRecordNonTransientEdits', () => { entities: { config: [], data: {} }, } ); expect( - getEntityRecordNonTransientEdits( state, 'someKind', 'someName', 'someId' ) + getEntityRecordNonTransientEdits( + state, + 'someKind', + 'someName', + 'someId' + ) ).toEqual( {} ); } ); } ); @@ -182,14 +192,18 @@ describe( 'getEmbedPreview()', () => { let state = deepFreeze( { embedPreviews: {}, } ); - expect( getEmbedPreview( state, 'http://example.com/' ) ).toBe( undefined ); + expect( getEmbedPreview( state, 'http://example.com/' ) ).toBe( + undefined + ); state = deepFreeze( { embedPreviews: { 'http://example.com/': { data: 42 }, }, } ); - expect( getEmbedPreview( state, 'http://example.com/' ) ).toEqual( { data: 42 } ); + expect( getEmbedPreview( state, 'http://example.com/' ) ).toEqual( { + data: 42, + } ); } ); } ); @@ -197,10 +211,15 @@ describe( 'isPreviewEmbedFallback()', () => { it( 'returns true if the preview html is just a single link', () => { const state = deepFreeze( { embedPreviews: { - 'http://example.com/': { html: '<a href="http://example.com/">http://example.com/</a>' }, + 'http://example.com/': { + html: + '<a href="http://example.com/">http://example.com/</a>', + }, }, } ); - expect( isPreviewEmbedFallback( state, 'http://example.com/' ) ).toEqual( true ); + expect( + isPreviewEmbedFallback( state, 'http://example.com/' ) + ).toEqual( true ); } ); } ); @@ -232,7 +251,12 @@ describe( 'canUser', () => { } ); describe( 'getAutosave', () => { - const testAutosave = { author: 1, title: { raw: '' }, excerpt: { raw: '' }, content: { raw: '' } }; + const testAutosave = { + author: 1, + title: { raw: '' }, + excerpt: { raw: '' }, + content: { raw: '' }, + }; it( 'returns undefined if no autosaves exist for the post id in state', () => { const postType = 'post'; @@ -284,7 +308,12 @@ describe( 'getAutosave', () => { const postType = 'post'; const postId = 1; const author = 2; - const expectedAutosave = { author, title: { raw: '' }, excerpt: { raw: '' }, content: { raw: '' } }; + const expectedAutosave = { + author, + title: { raw: '' }, + excerpt: { raw: '' }, + content: { raw: '' }, + }; const state = { autosaves: { [ postId ]: [ testAutosave, expectedAutosave ], @@ -302,7 +331,9 @@ describe( 'getAutosaves', () => { it( 'returns undefined for the provided post id if no autosaves exist for it in state', () => { const postType = 'post'; const postId = 2; - const autosaves = [ { title: { raw: '' }, excerpt: { raw: '' }, content: { raw: '' } } ]; + const autosaves = [ + { title: { raw: '' }, excerpt: { raw: '' }, content: { raw: '' } }, + ]; const state = { autosaves: { 1: autosaves, @@ -317,7 +348,9 @@ describe( 'getAutosaves', () => { it( 'returns the autosaves for the provided post id when they exist in state', () => { const postType = 'post'; const postId = 1; - const autosaves = [ { title: { raw: '' }, excerpt: { raw: '' }, content: { raw: '' } } ]; + const autosaves = [ + { title: { raw: '' }, excerpt: { raw: '' }, content: { raw: '' } }, + ]; const state = { autosaves: { 1: autosaves, @@ -353,21 +386,29 @@ describe( 'getCurrentUser', () => { describe( 'getReferenceByDistinctEdits', () => { it( 'should return referentially equal values across empty states', () => { const state = { undo: [] }; - expect( getReferenceByDistinctEdits( state ) ).toBe( getReferenceByDistinctEdits( state ) ); + expect( getReferenceByDistinctEdits( state ) ).toBe( + getReferenceByDistinctEdits( state ) + ); const beforeState = { undo: [] }; const afterState = { undo: [] }; - expect( getReferenceByDistinctEdits( beforeState ) ).toBe( getReferenceByDistinctEdits( afterState ) ); + expect( getReferenceByDistinctEdits( beforeState ) ).toBe( + getReferenceByDistinctEdits( afterState ) + ); } ); it( 'should return referentially equal values across unchanging non-empty state', () => { const undoStates = [ {} ]; const state = { undo: undoStates }; - expect( getReferenceByDistinctEdits( state ) ).toBe( getReferenceByDistinctEdits( state ) ); + expect( getReferenceByDistinctEdits( state ) ).toBe( + getReferenceByDistinctEdits( state ) + ); const beforeState = { undo: undoStates }; const afterState = { undo: undoStates }; - expect( getReferenceByDistinctEdits( beforeState ) ).toBe( getReferenceByDistinctEdits( afterState ) ); + expect( getReferenceByDistinctEdits( beforeState ) ).toBe( + getReferenceByDistinctEdits( afterState ) + ); } ); describe( 'when adding edits', () => { @@ -376,7 +417,9 @@ describe( 'getReferenceByDistinctEdits', () => { beforeState.undo.offset = 0; const afterState = { undo: [ {}, {} ] }; afterState.undo.offset = 1; - expect( getReferenceByDistinctEdits( beforeState ) ).not.toBe( getReferenceByDistinctEdits( afterState ) ); + expect( getReferenceByDistinctEdits( beforeState ) ).not.toBe( + getReferenceByDistinctEdits( afterState ) + ); } ); } ); @@ -386,8 +429,9 @@ describe( 'getReferenceByDistinctEdits', () => { beforeState.undo.offset = 1; const afterState = { undo: [ {}, {} ] }; afterState.undo.offset = 0; - expect( getReferenceByDistinctEdits( beforeState ) ).not.toBe( getReferenceByDistinctEdits( afterState ) ); + expect( getReferenceByDistinctEdits( beforeState ) ).not.toBe( + getReferenceByDistinctEdits( afterState ) + ); } ); } ); } ); - diff --git a/packages/core-data/src/utils/on-sub-key.js b/packages/core-data/src/utils/on-sub-key.js index 0c525da246f42f..24adf06b773ebc 100644 --- a/packages/core-data/src/utils/on-sub-key.js +++ b/packages/core-data/src/utils/on-sub-key.js @@ -6,7 +6,10 @@ * * @return {Function} Higher-order reducer. */ -export const onSubKey = ( actionProperty ) => ( reducer ) => ( state = {}, action ) => { +export const onSubKey = ( actionProperty ) => ( reducer ) => ( + state = {}, + action +) => { // Retrieve subkey from action. Do not track if undefined; useful for cases // where reducer is scoped by action shape. const key = action[ actionProperty ]; diff --git a/packages/core-data/src/utils/test/on-sub-key.js b/packages/core-data/src/utils/test/on-sub-key.js index 22fe81c047bc0e..4707e7380988c2 100644 --- a/packages/core-data/src/utils/test/on-sub-key.js +++ b/packages/core-data/src/utils/test/on-sub-key.js @@ -11,7 +11,9 @@ import onSubKey from '../on-sub-key'; describe( 'onSubKey', () => { function createEnhancedReducer( actionProperty ) { const enhanceReducer = onSubKey( actionProperty ); - return enhanceReducer( ( state, action ) => 'Called by ' + action.caller ); + return enhanceReducer( + ( state, action ) => 'Called by ' + action.caller + ); } it( 'should default to an empty object', () => { diff --git a/packages/core-data/src/utils/test/replace-action.js b/packages/core-data/src/utils/test/replace-action.js index 8007e3ef502fd5..e04d9947f83ba1 100644 --- a/packages/core-data/src/utils/test/replace-action.js +++ b/packages/core-data/src/utils/test/replace-action.js @@ -6,11 +6,15 @@ import replaceAction from '../replace-action'; describe( 'replaceAction', () => { function createEnhancedReducer( replacer ) { const enhanceReducer = replaceAction( replacer ); - return enhanceReducer( ( state, action ) => 'Called by ' + action.after ); + return enhanceReducer( + ( state, action ) => 'Called by ' + action.after + ); } it( 'should replace the action passed to the reducer', () => { - const reducer = createEnhancedReducer( ( action ) => ( { after: action.before } ) ); + const reducer = createEnhancedReducer( ( action ) => ( { + after: action.before, + } ) ); const state = reducer( undefined, { before: 'foo' } ); expect( state ).toBe( 'Called by foo' ); diff --git a/packages/create-block/CHANGELOG.md b/packages/create-block/CHANGELOG.md index 872fdec9f6fdeb..4caef89dc97206 100644 --- a/packages/create-block/CHANGELOG.md +++ b/packages/create-block/CHANGELOG.md @@ -11,4 +11,38 @@ ### Internal -- Imported from `create-wordpress-block` npm packgage ([#19773](https://github.com/WordPress/gutenberg/pull/19773)). +- Relocated npm packge from `create-wordpress-block` to `@wordpress/create-block` ([#19773](https://github.com/WordPress/gutenberg/pull/19773)). + +## 0.5.0 (2020-01-08) + +### New Features + +- Update templates to include WordPress plugin metadata by default. + +## 0.4.3 (2020-01-08) + +### Bug Fix + +- Print available commands only for ESNext template. + +## 0.4.0 (2019-12-17) + +### New Features + +- Add full support for ESNext template, including `wp-scripts` bootstrapping. + +### Enhancements + +- Improve the feedback shared on the console while scaffolding a block. + +## 0.3.2 (2019-12-16) + +### Bug Fix + +- Fix the paths pointing to the JS build file listed in PHP file in the ESNext template. + +## 0.3.0 (2019-12-16) + +### New Features + +- Added support for template types. `esnext` becomes the default one. `es5` is still available as an option. diff --git a/packages/create-block/lib/init-wp-scripts.js b/packages/create-block/lib/init-wp-scripts.js index 2ce2ecf57d0775..1ff5d986a16ba4 100644 --- a/packages/create-block/lib/init-wp-scripts.js +++ b/packages/create-block/lib/init-wp-scripts.js @@ -10,7 +10,13 @@ const writePkg = require( 'write-pkg' ); */ const { info } = require( './log' ); -module.exports = async function( { author, description, license, slug, version } ) { +module.exports = async function( { + author, + description, + license, + slug, + version, +} ) { const cwd = join( process.cwd(), slug ); info( '' ); diff --git a/packages/create-block/lib/prompts.js b/packages/create-block/lib/prompts.js index e0e407148f1649..99d49120e2d6ed 100644 --- a/packages/create-block/lib/prompts.js +++ b/packages/create-block/lib/prompts.js @@ -6,7 +6,8 @@ const { upperFirst } = require( 'lodash' ); const slug = { type: 'input', name: 'slug', - message: 'The block slug used for identification (also the plugin and output folder name):', + message: + 'The block slug used for identification (also the plugin and output folder name):', validate( input ) { if ( ! /^[a-z][a-z0-9\-]*$/.test( input ) ) { return 'Invalid block slug specified. Block slug can contain only lowercase alphanumeric characters or dashes, and start with a letter.'; @@ -19,7 +20,8 @@ const slug = { const namespace = { type: 'input', name: 'namespace', - message: 'The internal namespace for the block name (something unique for your products):', + message: + 'The internal namespace for the block name (something unique for your products):', validate( input ) { if ( ! /^[a-z][a-z0-9\-]*$/.test( input ) ) { return 'Invalid block namespace specified. Block namespace can contain only lowercase alphanumeric characters or dashes, and start with a letter.'; @@ -50,7 +52,8 @@ const description = { const dashicon = { type: 'input', name: 'dashicon', - message: 'The dashicon to make it easier to identify your block (optional):', + message: + 'The dashicon to make it easier to identify your block (optional):', validate( input ) { if ( ! /^[a-z][a-z0-9\-]*$/.test( input ) ) { return 'Invalid dashicon name specified. Visit https://developer.wordpress.org/resource/dashicons/ to discover available names.'; diff --git a/packages/create-block/lib/scaffold.js b/packages/create-block/lib/scaffold.js index 7374fbf4f82ecc..85ff546595afd7 100644 --- a/packages/create-block/lib/scaffold.js +++ b/packages/create-block/lib/scaffold.js @@ -16,7 +16,17 @@ const { hasWPScriptsEnabled, getOutputFiles } = require( './templates' ); module.exports = async function( templateName, - { namespace, slug, title, description, dashicon, category, author, license, version } + { + namespace, + slug, + title, + description, + dashicon, + category, + author, + license, + version, + } ) { slug = slug.toLowerCase(); namespace = namespace.toLowerCase(); @@ -41,11 +51,17 @@ module.exports = async function( await Promise.all( getOutputFiles( templateName ).map( async ( file ) => { const template = await readFile( - join( __dirname, `templates/${ templateName }/${ file }.mustache` ), + join( + __dirname, + `templates/${ templateName }/${ file }.mustache` + ), 'utf8' ); // Output files can have names that depend on the slug provided. - const outputFilePath = `${ slug }/${ file.replace( /\$slug/g, slug ) }`; + const outputFilePath = `${ slug }/${ file.replace( + /\$slug/g, + slug + ) }`; await makeDir( dirname( outputFilePath ) ); writeFile( outputFilePath, render( template, view ) ); } ) @@ -56,7 +72,9 @@ module.exports = async function( } info( '' ); - success( `Done: block "${ title }" bootstrapped in the "${ slug }" folder.` ); + success( + `Done: block "${ title }" bootstrapped in the "${ slug }" folder.` + ); if ( hasWPScriptsEnabled( templateName ) ) { info( '' ); info( 'Inside that directory, you can run several commands:' ); diff --git a/packages/create-block/lib/templates.js b/packages/create-block/lib/templates.js index 4a4b7c4b50c6a6..1517fbdb20d5f5 100644 --- a/packages/create-block/lib/templates.js +++ b/packages/create-block/lib/templates.js @@ -17,14 +17,21 @@ const templates = { namespace, slug: 'es5-example', title: 'ES5 Example', - description: 'Example block written with ES5 standard and no JSX – no build step required.', + description: + 'Example block written with ES5 standard and no JSX – no build step required.', dashicon, category, author, license, version, }, - outputFiles: [ '.editorconfig', 'editor.css', 'index.js', '$slug.php', 'style.css' ], + outputFiles: [ + '.editorconfig', + 'editor.css', + 'index.js', + '$slug.php', + 'style.css', + ], }, esnext: { defaultValues: { @@ -54,7 +61,9 @@ const templates = { const getTemplate = ( templateName ) => { if ( ! templates[ templateName ] ) { throw new CliError( - `Invalid template type name. Allowed values: ${ Object.keys( templates ).join( ', ' ) }.` + `Invalid template type name. Allowed values: ${ Object.keys( + templates + ).join( ', ' ) }.` ); } return templates[ templateName ]; diff --git a/packages/custom-templated-path-webpack-plugin/index.js b/packages/custom-templated-path-webpack-plugin/index.js index b802335e0309b1..7d7bd317aa48bf 100644 --- a/packages/custom-templated-path-webpack-plugin/index.js +++ b/packages/custom-templated-path-webpack-plugin/index.js @@ -23,7 +23,10 @@ class CustomTemplatedPathPlugin { this.handlers = []; for ( const [ key, handler ] of Object.entries( handlers ) ) { - const regexp = new RegExp( `\\[${ escapeStringRegexp( key ) }\\]`, 'gi' ); + const regexp = new RegExp( + `\\[${ escapeStringRegexp( key ) }\\]`, + 'gi' + ); this.handlers.push( [ regexp, handler ] ); } } @@ -34,18 +37,27 @@ class CustomTemplatedPathPlugin { * @param {Object} compiler Webpack compiler */ apply( compiler ) { - compiler.hooks.compilation.tap( 'CustomTemplatedPathPlugin', ( compilation ) => { - compilation.mainTemplate.hooks.assetPath.tap( 'CustomTemplatedPathPlugin', ( path, data ) => { - for ( let i = 0; i < this.handlers.length; i++ ) { - const [ regexp, handler ] = this.handlers[ i ]; - if ( regexp.test( path ) ) { - path = path.replace( regexp, handler( path, data ) ); - } - } + compiler.hooks.compilation.tap( + 'CustomTemplatedPathPlugin', + ( compilation ) => { + compilation.mainTemplate.hooks.assetPath.tap( + 'CustomTemplatedPathPlugin', + ( path, data ) => { + for ( let i = 0; i < this.handlers.length; i++ ) { + const [ regexp, handler ] = this.handlers[ i ]; + if ( regexp.test( path ) ) { + path = path.replace( + regexp, + handler( path, data ) + ); + } + } - return path; - } ); - } ); + return path; + } + ); + } + ); } } diff --git a/packages/data-controls/src/index.js b/packages/data-controls/src/index.js index e32f16392e5371..c0c1fe21807864 100644 --- a/packages/data-controls/src/index.js +++ b/packages/data-controls/src/index.js @@ -127,9 +127,9 @@ export const controls = { SELECT: createRegistryControl( ( registry ) => ( { storeKey, selectorName, args } ) => { return registry[ - registry.select( storeKey )[ selectorName ].hasResolver ? - '__experimentalResolveSelect' : - 'select' + registry.select( storeKey )[ selectorName ].hasResolver + ? '__experimentalResolveSelect' + : 'select' ]( storeKey )[ selectorName ]( ...args ); } ), diff --git a/packages/data-controls/src/test/index.js b/packages/data-controls/src/test/index.js index a543e090d79a4c..f7c1fe297487d0 100644 --- a/packages/data-controls/src/test/index.js +++ b/packages/data-controls/src/test/index.js @@ -55,9 +55,11 @@ describe( 'controls', () => { return stores[ storeKey ]; }, }; - const getSelectorArgs = ( storeKey, selectorName, ...args ) => ( - { storeKey, selectorName, args } - ); + const getSelectorArgs = ( storeKey, selectorName, ...args ) => ( { + storeKey, + selectorName, + args, + } ); beforeEach( () => { selectorWithUndefinedResolver.mockReturnValue( 'foo' ); selectorWithFalseResolver.mockReturnValue( 'bar' ); @@ -72,20 +74,18 @@ describe( 'controls', () => { } ); it( 'invokes selector with undefined resolver', () => { const testControl = controls.SELECT( registryMock ); - const value = testControl( getSelectorArgs( - 'mockStore', - 'selectorWithUndefinedResolver' - ) ); + const value = testControl( + getSelectorArgs( 'mockStore', 'selectorWithUndefinedResolver' ) + ); expect( value ).toBe( 'foo' ); expect( selectorWithUndefinedResolver ).toHaveBeenCalled(); expect( hasFinishedResolution ).not.toHaveBeenCalled(); } ); it( 'invokes selector with resolver set to false', () => { const testControl = controls.SELECT( registryMock ); - const value = testControl( getSelectorArgs( - 'mockStore', - 'selectorWithFalseResolver' - ) ); + const value = testControl( + getSelectorArgs( 'mockStore', 'selectorWithFalseResolver' ) + ); expect( value ).toBe( 'bar' ); expect( selectorWithFalseResolver ).toHaveBeenCalled(); expect( hasFinishedResolution ).not.toHaveBeenCalled(); @@ -93,23 +93,24 @@ describe( 'controls', () => { describe( 'invokes selector with resolver set to true', () => { const testControl = controls.SELECT( registryMock ); it( 'returns a promise', async () => { - const value = testControl( getSelectorArgs( - 'mockStore', - 'selectorWithResolver' - ) ); + const value = testControl( + getSelectorArgs( 'mockStore', 'selectorWithResolver' ) + ); await expect( value ).resolves.toBe( 'resolved' ); expect( selectorWithResolver ).toHaveBeenCalled(); } ); - it( 'selector with resolver resolves to expected result when ' + - 'finished', async () => { - const value = testControl( getSelectorArgs( - 'mockStore', - 'selectorWithResolver' - ) ); - hasFinishedResolution.mockReturnValue( true ); - expect( selectorWithResolver ).toHaveBeenCalled(); - await expect( value ).resolves.toBe( 'resolved' ); - } ); + it( + 'selector with resolver resolves to expected result when ' + + 'finished', + async () => { + const value = testControl( + getSelectorArgs( 'mockStore', 'selectorWithResolver' ) + ); + hasFinishedResolution.mockReturnValue( true ); + expect( selectorWithResolver ).toHaveBeenCalled(); + await expect( value ).resolves.toBe( 'resolved' ); + } + ); } ); } ); describe( 'DISPATCH', () => { diff --git a/packages/data/src/components/use-dispatch/test/use-dispatch.js b/packages/data/src/components/use-dispatch/test/use-dispatch.js index 8770d04144b06d..226c7d80e51ea7 100644 --- a/packages/data/src/components/use-dispatch/test/use-dispatch.js +++ b/packages/data/src/components/use-dispatch/test/use-dispatch.js @@ -42,8 +42,9 @@ describe( 'useDispatch', () => { const testInstance = testRenderer.root; - expect( testInstance.findByType( TestComponent ).props.dispatch ) - .toBe( registry.dispatch ); + expect( testInstance.findByType( TestComponent ).props.dispatch ).toBe( + registry.dispatch + ); } ); it( 'returns expected action creators from store for given storeName', () => { const noop = () => ( { type: '__INERT__' } ); @@ -63,7 +64,7 @@ describe( 'useDispatch', () => { act( () => { testRenderer = TestRenderer.create( - <RegistryProvider value={ registry } > + <RegistryProvider value={ registry }> <TestComponent /> </RegistryProvider> ); diff --git a/packages/data/src/components/use-dispatch/use-dispatch-with-map.js b/packages/data/src/components/use-dispatch/use-dispatch-with-map.js index 057ddab06191e9..eda70d5846cffa 100644 --- a/packages/data/src/components/use-dispatch/use-dispatch-with-map.js +++ b/packages/data/src/components/use-dispatch/use-dispatch-with-map.js @@ -6,7 +6,12 @@ import { mapValues } from 'lodash'; /** * WordPress dependencies */ -import { useMemo, useRef, useEffect, useLayoutEffect } from '@wordpress/element'; +import { + useMemo, + useRef, + useEffect, + useLayoutEffect, +} from '@wordpress/element'; /** * Internal dependencies @@ -52,19 +57,18 @@ const useDispatchWithMap = ( dispatchMap, deps ) => { registry.dispatch, registry ); - return mapValues( - currentDispatchProps, - ( dispatcher, propName ) => { - if ( typeof dispatcher !== 'function' ) { - // eslint-disable-next-line no-console - console.warn( - `Property ${ propName } returned from dispatchMap in useDispatchWithMap must be a function.` - ); - } - return ( ...args ) => currentDispatchMap - .current( registry.dispatch, registry )[ propName ]( ...args ); + return mapValues( currentDispatchProps, ( dispatcher, propName ) => { + if ( typeof dispatcher !== 'function' ) { + // eslint-disable-next-line no-console + console.warn( + `Property ${ propName } returned from dispatchMap in useDispatchWithMap must be a function.` + ); } - ); + return ( ...args ) => + currentDispatchMap + .current( registry.dispatch, registry ) + [ propName ]( ...args ); + } ); }, [ registry, ...deps ] ); }; diff --git a/packages/data/src/components/use-select/index.js b/packages/data/src/components/use-select/index.js index 97b8e207dd11dc..cb8a56b9133c6d 100644 --- a/packages/data/src/components/use-select/index.js +++ b/packages/data/src/components/use-select/index.js @@ -97,7 +97,10 @@ export default function useSelect( _mapSelect, deps ) { let mapOutput; try { - if ( latestMapSelect.current !== mapSelect || latestMapOutputError.current ) { + if ( + latestMapSelect.current !== mapSelect || + latestMapOutputError.current + ) { mapOutput = mapSelect( registry.select, registry ); } else { mapOutput = latestMapOutput.current; @@ -138,7 +141,9 @@ export default function useSelect( _mapSelect, deps ) { registry.select, registry ); - if ( isShallowEqual( latestMapOutput.current, newMapOutput ) ) { + if ( + isShallowEqual( latestMapOutput.current, newMapOutput ) + ) { return; } latestMapOutput.current = newMapOutput; diff --git a/packages/data/src/components/use-select/test/index.js b/packages/data/src/components/use-select/test/index.js index 1aa2ad0d7b9d70..78a81ba3d47986 100644 --- a/packages/data/src/components/use-select/test/index.js +++ b/packages/data/src/components/use-select/test/index.js @@ -18,11 +18,9 @@ describe( 'useSelect', () => { const getTestComponent = ( mapSelectSpy, dependencyKey ) => ( props ) => { const dependencies = props[ dependencyKey ]; - mapSelectSpy.mockImplementation( - ( select ) => ( { - results: select( 'testStore' ).testSelector( props.keyName ), - } ) - ); + mapSelectSpy.mockImplementation( ( select ) => ( { + results: select( 'testStore' ).testSelector( props.keyName ), + } ) ); const data = useSelect( mapSelectSpy, [ dependencies ] ); return <div>{ data.results }</div>; }; @@ -35,9 +33,9 @@ describe( 'useSelect', () => { }, } ); const selectSpy = jest.fn(); - const TestComponent = jest.fn().mockImplementation( - getTestComponent( selectSpy, 'keyName' ) - ); + const TestComponent = jest + .fn() + .mockImplementation( getTestComponent( selectSpy, 'keyName' ) ); let renderer; act( () => { renderer = TestRenderer.create( @@ -69,13 +67,11 @@ describe( 'useSelect', () => { const selectSpyFoo = jest.fn().mockImplementation( () => 'foo' ); const selectSpyBar = jest.fn().mockImplementation( () => 'bar' ); - const TestComponent = jest.fn().mockImplementation( - ( props ) => { - const mapSelect = props.change ? selectSpyFoo : selectSpyBar; - const data = useSelect( mapSelect, [ props.keyName ] ); - return <div>{ data }</div>; - } - ); + const TestComponent = jest.fn().mockImplementation( ( props ) => { + const mapSelect = props.change ? selectSpyFoo : selectSpyBar; + const data = useSelect( mapSelect, [ props.keyName ] ); + return <div>{ data }</div>; + } ); let renderer; act( () => { renderer = TestRenderer.create( @@ -138,8 +134,8 @@ describe( 'useSelect', () => { return <div data={ data } />; }; let subscribedSpy, TestComponent; - const mapSelectSpy = jest.fn( - ( select ) => select( 'testStore' ).testSelector() + const mapSelectSpy = jest.fn( ( select ) => + select( 'testStore' ).testSelector() ); const selectorSpy = jest.fn(); const subscribeCallback = ( subscription ) => { @@ -162,61 +158,49 @@ describe( 'useSelect', () => { } ); it.each( [ - [ - 'boolean', - [ false, true ], - ], - [ - 'number', - [ 10, 20 ], - ], - [ - 'string', - [ 'bar', 'cheese' ], - ], + [ 'boolean', [ false, true ] ], + [ 'number', [ 10, 20 ] ], + [ 'string', [ 'bar', 'cheese' ] ], [ 'array', - [ [ 10, 20 ], [ 10, 30 ] ], - ], - [ - 'object', - [ { foo: 'bar' }, { foo: 'cheese' } ], + [ + [ 10, 20 ], + [ 10, 30 ], + ], ], - [ - 'null', - [ null, undefined ], - ], - [ - 'undefined', - [ undefined, 42 ], - ], - ] )( 'renders as expected with %s return values', ( - type, - testValues, - ) => { - const [ valueA, valueB ] = testValues; - selectorSpy.mockReturnValue( valueA ); - let renderer; - act( () => { - renderer = TestRenderer.create( - <RegistryProvider value={ registry }> - <TestComponent /> - </RegistryProvider> + [ 'object', [ { foo: 'bar' }, { foo: 'cheese' } ] ], + [ 'null', [ null, undefined ] ], + [ 'undefined', [ undefined, 42 ] ], + ] )( + 'renders as expected with %s return values', + ( type, testValues ) => { + const [ valueA, valueB ] = testValues; + selectorSpy.mockReturnValue( valueA ); + let renderer; + act( () => { + renderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <TestComponent /> + </RegistryProvider> + ); + } ); + const testInstance = renderer.root; + // ensure expected state was rendered. + expect( testInstance.findByType( 'div' ).props.data ).toEqual( + valueA ); - } ); - const testInstance = renderer.root; - // ensure expected state was rendered. - expect( testInstance.findByType( 'div' ).props.data ).toEqual( valueA ); - - // Update the returned value from the selector and trigger the - // subscription which should in turn trigger a re-render. - act( () => { - selectorSpy.mockReturnValue( valueB ); - subscribedSpy(); - } ); - expect( testInstance.findByType( 'div' ).props.data ).toEqual( valueB ); - expect( mapSelectSpy ).toHaveBeenCalledTimes( 3 ); - } ); + + // Update the returned value from the selector and trigger the + // subscription which should in turn trigger a re-render. + act( () => { + selectorSpy.mockReturnValue( valueB ); + subscribedSpy(); + } ); + expect( testInstance.findByType( 'div' ).props.data ).toEqual( + valueB + ); + expect( mapSelectSpy ).toHaveBeenCalledTimes( 3 ); + } + ); } ); } ); - diff --git a/packages/data/src/components/with-dispatch/index.js b/packages/data/src/components/with-dispatch/index.js index 3e6c2194dec5f4..d122530c307bd8 100644 --- a/packages/data/src/components/with-dispatch/index.js +++ b/packages/data/src/components/with-dispatch/index.js @@ -87,17 +87,15 @@ import { useDispatchWithMap } from '../use-dispatch'; * * @return {WPComponent} Enhanced component with merged dispatcher props. */ -const withDispatch = ( mapDispatchToProps ) => createHigherOrderComponent( - ( WrappedComponent ) => ( ownProps ) => { - const mapDispatch = ( dispatch, registry ) => mapDispatchToProps( - dispatch, - ownProps, - registry - ); - const dispatchProps = useDispatchWithMap( mapDispatch, [] ); - return <WrappedComponent { ...ownProps } { ...dispatchProps } />; - }, - 'withDispatch' -); +const withDispatch = ( mapDispatchToProps ) => + createHigherOrderComponent( + ( WrappedComponent ) => ( ownProps ) => { + const mapDispatch = ( dispatch, registry ) => + mapDispatchToProps( dispatch, ownProps, registry ); + const dispatchProps = useDispatchWithMap( mapDispatch, [] ); + return <WrappedComponent { ...ownProps } { ...dispatchProps } />; + }, + 'withDispatch' + ); export default withDispatch; diff --git a/packages/data/src/components/with-dispatch/test/index.js b/packages/data/src/components/with-dispatch/test/index.js index 186098bd3facb1..b28114fe295e04 100644 --- a/packages/data/src/components/with-dispatch/test/index.js +++ b/packages/data/src/components/with-dispatch/test/index.js @@ -34,13 +34,15 @@ describe( 'withDispatch', () => { return { increment: () => { - const actionReturnedFromDispatch = Promise.resolve( _dispatch( 'counter' ).increment( count ) ); - return expect( actionReturnedFromDispatch ).resolves.toEqual( - { - type: 'increment', - count, - } + const actionReturnedFromDispatch = Promise.resolve( + _dispatch( 'counter' ).increment( count ) ); + return expect( + actionReturnedFromDispatch + ).resolves.toEqual( { + type: 'increment', + count, + } ); }, }; } )( ( props ) => <button onClick={ props.increment } /> ); @@ -55,7 +57,8 @@ describe( 'withDispatch', () => { } ); const testInstance = testRenderer.root; - const incrementBeforeSetProps = testInstance.findByType( 'button' ).props.onClick; + const incrementBeforeSetProps = testInstance.findByType( 'button' ) + .props.onClick; // Verify that dispatch respects props at the time of being invoked by // changing props after the initial mount. @@ -68,7 +71,9 @@ describe( 'withDispatch', () => { } ); // Function value reference should not have changed in props update. - expect( testInstance.findByType( 'button' ).props.onClick ).toBe( incrementBeforeSetProps ); + expect( testInstance.findByType( 'button' ).props.onClick ).toBe( + incrementBeforeSetProps + ); act( () => { incrementBeforeSetProps(); @@ -140,66 +145,75 @@ describe( 'withDispatch', () => { expect( secondRegistryAction ).toHaveBeenCalledTimes( 2 ); } ); - it( 'always calls select with the latest state in the handler passed to ' + - 'the component', () => { - const store = registry.registerStore( 'counter', { - reducer: ( state = 0, action ) => { - if ( action.type === 'update' ) { - return action.count; - } - return state; - }, - actions: { - update: ( count ) => ( { type: 'update', count } ), - }, - selectors: { - getCount: ( state ) => state, - }, - } ); - - const Component = withDispatch( ( _dispatch, ownProps, { select: _select } ) => { - const outerCount = _select( 'counter' ).getCount(); - return { - update: () => { - const innerCount = _select( 'counter' ).getCount(); - expect( innerCount ).toBe( outerCount ); - const actionReturnedFromDispatch = Promise.resolve( - _dispatch( 'counter' ).update( innerCount + 1 ) - ); - return expect( actionReturnedFromDispatch ).resolves.toEqual( { - type: 'update', - count: innerCount + 1, - } ); + it( + 'always calls select with the latest state in the handler passed to ' + + 'the component', + () => { + const store = registry.registerStore( 'counter', { + reducer: ( state = 0, action ) => { + if ( action.type === 'update' ) { + return action.count; + } + return state; }, - }; - } )( ( props ) => <button onClick={ props.update } /> ); - - let testRenderer; - act( () => { - testRenderer = TestRenderer.create( - <RegistryProvider value={ registry }> - <Component /> - </RegistryProvider> - ); - } ); - - const counterUpdateHandler = testRenderer.root.findByType( 'button' ).props.onClick; - - act( () => { - counterUpdateHandler(); - } ); - expect( store.getState() ).toBe( 1 ); - - act( () => { - counterUpdateHandler(); - } ); - expect( store.getState() ).toBe( 2 ); - - act( () => { - counterUpdateHandler(); - } ); - expect( store.getState() ).toBe( 3 ); - } ); + actions: { + update: ( count ) => ( { type: 'update', count } ), + }, + selectors: { + getCount: ( state ) => state, + }, + } ); + + const Component = withDispatch( + ( _dispatch, ownProps, { select: _select } ) => { + const outerCount = _select( 'counter' ).getCount(); + return { + update: () => { + const innerCount = _select( 'counter' ).getCount(); + expect( innerCount ).toBe( outerCount ); + const actionReturnedFromDispatch = Promise.resolve( + _dispatch( 'counter' ).update( innerCount + 1 ) + ); + return expect( + actionReturnedFromDispatch + ).resolves.toEqual( { + type: 'update', + count: innerCount + 1, + } ); + }, + }; + } + )( ( props ) => <button onClick={ props.update } /> ); + + let testRenderer; + act( () => { + testRenderer = TestRenderer.create( + <RegistryProvider value={ registry }> + <Component /> + </RegistryProvider> + ); + } ); + + const counterUpdateHandler = testRenderer.root.findByType( + 'button' + ).props.onClick; + + act( () => { + counterUpdateHandler(); + } ); + expect( store.getState() ).toBe( 1 ); + + act( () => { + counterUpdateHandler(); + } ); + expect( store.getState() ).toBe( 2 ); + + act( () => { + counterUpdateHandler(); + } ); + expect( store.getState() ).toBe( 3 ); + } + ); it( 'warns when mapDispatchToProps returns non-function property', () => { const Component = withDispatch( () => { diff --git a/packages/data/src/components/with-registry/index.js b/packages/data/src/components/with-registry/index.js index 32d1c8e1a40fd5..faf3da8c009add 100644 --- a/packages/data/src/components/with-registry/index.js +++ b/packages/data/src/components/with-registry/index.js @@ -20,10 +20,7 @@ const withRegistry = createHigherOrderComponent( ( OriginalComponent ) => ( props ) => ( <RegistryConsumer> { ( registry ) => ( - <OriginalComponent - { ...props } - registry={ registry } - /> + <OriginalComponent { ...props } registry={ registry } /> ) } </RegistryConsumer> ), diff --git a/packages/data/src/components/with-select/index.js b/packages/data/src/components/with-select/index.js index aeb86761494e44..8c0801b0855b7d 100644 --- a/packages/data/src/components/with-select/index.js +++ b/packages/data/src/components/with-select/index.js @@ -47,20 +47,16 @@ import useSelect from '../use-select'; * * @return {WPComponent} Enhanced component with merged state data props. */ -const withSelect = ( mapSelectToProps ) => createHigherOrderComponent( - ( WrappedComponent ) => pure( - ( ownProps ) => { - const mapSelect = - ( select, registry ) => mapSelectToProps( - select, - ownProps, - registry - ); - const mergeProps = useSelect( mapSelect ); - return <WrappedComponent { ...ownProps } { ...mergeProps } />; - } - ), - 'withSelect' -); +const withSelect = ( mapSelectToProps ) => + createHigherOrderComponent( + ( WrappedComponent ) => + pure( ( ownProps ) => { + const mapSelect = ( select, registry ) => + mapSelectToProps( select, ownProps, registry ); + const mergeProps = useSelect( mapSelect ); + return <WrappedComponent { ...ownProps } { ...mergeProps } />; + } ), + 'withSelect' + ); export default withSelect; diff --git a/packages/data/src/components/with-select/test/index.js b/packages/data/src/components/with-select/test/index.js index 3b2049826c63b5..0eaf393a64b16f 100644 --- a/packages/data/src/components/with-select/test/index.js +++ b/packages/data/src/components/with-select/test/index.js @@ -38,15 +38,21 @@ describe( 'withSelect', () => { // including both `withSelect` and `select` in the same scope, which // shouldn't occur for a typical component, and if it did might wrongly // encourage the developer to use `select` within the component itself. - const mapSelectToProps = jest.fn().mockImplementation( ( _select, ownProps ) => ( { - data: _select( 'reactReducer' ).reactSelector( ownProps.keyName ), - } ) ); - - const OriginalComponent = jest.fn().mockImplementation( ( props ) => ( - <div>{ props.data }</div> - ) ); - - const DataBoundComponent = withSelect( mapSelectToProps )( OriginalComponent ); + const mapSelectToProps = jest + .fn() + .mockImplementation( ( _select, ownProps ) => ( { + data: _select( 'reactReducer' ).reactSelector( + ownProps.keyName + ), + } ) ); + + const OriginalComponent = jest + .fn() + .mockImplementation( ( props ) => <div>{ props.data }</div> ); + + const DataBoundComponent = withSelect( mapSelectToProps )( + OriginalComponent + ); let testRenderer; act( () => { testRenderer = TestRenderer.create( @@ -85,19 +91,23 @@ describe( 'withSelect', () => { }, } ); - const mapSelectToProps = jest.fn().mockImplementation( ( _select ) => ( { - count: _select( 'counter' ).getCount(), - } ) ); + const mapSelectToProps = jest + .fn() + .mockImplementation( ( _select ) => ( { + count: _select( 'counter' ).getCount(), + } ) ); - const mapDispatchToProps = jest.fn().mockImplementation( ( _dispatch ) => ( { - increment: _dispatch( 'counter' ).increment, - } ) ); + const mapDispatchToProps = jest + .fn() + .mockImplementation( ( _dispatch ) => ( { + increment: _dispatch( 'counter' ).increment, + } ) ); - const OriginalComponent = jest.fn().mockImplementation( ( props ) => ( - <button onClick={ props.increment }> - { props.count } - </button> - ) ); + const OriginalComponent = jest + .fn() + .mockImplementation( ( props ) => ( + <button onClick={ props.increment }>{ props.count }</button> + ) ); const DataBoundComponent = compose( [ withSelect( mapSelectToProps ), @@ -181,13 +191,17 @@ describe( 'withSelect', () => { const renderSpy = jest.spyOn( OriginalComponent.prototype, 'render' ); - const mapSelectToProps = jest.fn().mockImplementation( ( _select ) => ( { - count: _select( 'counter' ).getCount(), - } ) ); + const mapSelectToProps = jest + .fn() + .mockImplementation( ( _select ) => ( { + count: _select( 'counter' ).getCount(), + } ) ); - const mapDispatchToProps = jest.fn().mockImplementation( ( _dispatch ) => ( { - increment: _dispatch( 'counter' ).increment, - } ) ); + const mapDispatchToProps = jest + .fn() + .mockImplementation( ( _dispatch ) => ( { + increment: _dispatch( 'counter' ).increment, + } ) ); const DataBoundComponent = compose( [ withSelect( mapSelectToProps ), @@ -195,11 +209,12 @@ describe( 'withSelect', () => { ] )( OriginalComponent ); let testRenderer, testInstance; - const createTestRenderer = () => TestRenderer.create( - <RegistryProvider value={ testRegistry }> - <DataBoundComponent /> - </RegistryProvider> - ); + const createTestRenderer = () => + TestRenderer.create( + <RegistryProvider value={ testRegistry }> + <DataBoundComponent /> + </RegistryProvider> + ); act( () => { testRenderer = createTestRenderer(); } ); @@ -243,15 +258,19 @@ describe( 'withSelect', () => { }, } ); - const mapSelectToProps = jest.fn().mockImplementation( ( _select, ownProps ) => ( { - count: _select( 'counter' ).getCount( ownProps.offset ), - } ) ); + const mapSelectToProps = jest + .fn() + .mockImplementation( ( _select, ownProps ) => ( { + count: _select( 'counter' ).getCount( ownProps.offset ), + } ) ); - const OriginalComponent = jest.fn().mockImplementation( ( props ) => ( - <div>{ props.count }</div> - ) ); + const OriginalComponent = jest + .fn() + .mockImplementation( ( props ) => <div>{ props.count }</div> ); - const DataBoundComponent = withSelect( mapSelectToProps )( OriginalComponent ); + const DataBoundComponent = withSelect( mapSelectToProps )( + OriginalComponent + ); let testRenderer; act( () => { @@ -298,7 +317,9 @@ describe( 'withSelect', () => { withSelect( mapSelectToProps ), ] )( OriginalComponent ); - const Parent = ( props ) => <DataBoundComponent propName={ props.propName } />; + const Parent = ( props ) => ( + <DataBoundComponent propName={ props.propName } /> + ); let testRenderer; act( () => { @@ -338,13 +359,17 @@ describe( 'withSelect', () => { }, } ); - const mapSelectToProps = jest.fn().mockImplementation( ( _select ) => ( { - value: _select( 'demo' ).getUnchangingValue(), - } ) ); + const mapSelectToProps = jest + .fn() + .mockImplementation( ( _select ) => ( { + value: _select( 'demo' ).getUnchangingValue(), + } ) ); const OriginalComponent = jest.fn().mockImplementation( () => <div /> ); - const DataBoundComponent = withSelect( mapSelectToProps )( OriginalComponent ); + const DataBoundComponent = withSelect( mapSelectToProps )( + OriginalComponent + ); act( () => { TestRenderer.create( @@ -453,16 +478,23 @@ describe( 'withSelect', () => { }, } ); - const mapSelectToProps = jest.fn().mockImplementation( ( _select, ownProps ) => { - return { - [ ownProps.propName ]: _select( 'demo' ).getValue(), - }; - } ); + const mapSelectToProps = jest + .fn() + .mockImplementation( ( _select, ownProps ) => { + return { + [ ownProps.propName ]: _select( 'demo' ).getValue(), + }; + } ); - const OriginalComponent = jest.fn() - .mockImplementation( ( props ) => <div>{ JSON.stringify( props ) }</div> ); + const OriginalComponent = jest + .fn() + .mockImplementation( ( props ) => ( + <div>{ JSON.stringify( props ) }</div> + ) ); - const DataBoundComponent = withSelect( mapSelectToProps )( OriginalComponent ); + const DataBoundComponent = withSelect( mapSelectToProps )( + OriginalComponent + ); let testRenderer; act( () => { @@ -480,8 +512,12 @@ describe( 'withSelect', () => { expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 1 ); - expect( JSON.parse( testInstance.findByType( 'div' ).props.children ) ) - .toEqual( { foo: 'OK', propName: 'foo' } ); + expect( + JSON.parse( testInstance.findByType( 'div' ).props.children ) + ).toEqual( { + foo: 'OK', + propName: 'foo', + } ); act( () => { testRenderer.update( @@ -493,8 +529,12 @@ describe( 'withSelect', () => { expect( mapSelectToProps ).toHaveBeenCalledTimes( 3 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 2 ); - expect( JSON.parse( testInstance.findByType( 'div' ).props.children ) ) - .toEqual( { bar: 'OK', propName: 'bar' } ); + expect( + JSON.parse( testInstance.findByType( 'div' ).props.children ) + ).toEqual( { + bar: 'OK', + propName: 'bar', + } ); } ); it( 'allows undefined return from mapSelectToProps', () => { @@ -505,19 +545,25 @@ describe( 'withSelect', () => { }, } ); - const mapSelectToProps = jest.fn().mockImplementation( ( _select, ownProps ) => { - if ( ownProps.pass ) { - return { - count: _select( 'demo' ).getValue(), - }; - } - } ); + const mapSelectToProps = jest + .fn() + .mockImplementation( ( _select, ownProps ) => { + if ( ownProps.pass ) { + return { + count: _select( 'demo' ).getValue(), + }; + } + } ); - const OriginalComponent = jest.fn().mockImplementation( ( - ( props ) => <div>{ props.count || 'Unknown' }</div> - ) ); + const OriginalComponent = jest + .fn() + .mockImplementation( ( props ) => ( + <div>{ props.count || 'Unknown' }</div> + ) ); - const DataBoundComponent = withSelect( mapSelectToProps )( OriginalComponent ); + const DataBoundComponent = withSelect( mapSelectToProps )( + OriginalComponent + ); let testRenderer; act( () => { @@ -534,7 +580,9 @@ describe( 'withSelect', () => { // - 1 on effect before subscription set. expect( mapSelectToProps ).toHaveBeenCalledTimes( 2 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 1 ); - expect( testInstance.findByType( 'div' ).props.children ).toBe( 'Unknown' ); + expect( testInstance.findByType( 'div' ).props.children ).toBe( + 'Unknown' + ); act( () => { testRenderer.update( @@ -558,14 +606,15 @@ describe( 'withSelect', () => { expect( mapSelectToProps ).toHaveBeenCalledTimes( 4 ); expect( OriginalComponent ).toHaveBeenCalledTimes( 3 ); - expect( testInstance.findByType( 'div' ).props.children ).toBe( 'Unknown' ); + expect( testInstance.findByType( 'div' ).props.children ).toBe( + 'Unknown' + ); } ); it( 'should limit unnecessary selections run on children', () => { registry.registerStore( 'childRender', { - reducer: ( state = true, action ) => ( - action.type === 'TOGGLE_RENDER' ? ! state : state - ), + reducer: ( state = true, action ) => + action.type === 'TOGGLE_RENDER' ? ! state : state, selectors: { getValue: ( state ) => state, }, @@ -575,17 +624,27 @@ describe( 'withSelect', () => { } ); const childMapSelectToProps = jest.fn(); - const parentMapSelectToProps = jest.fn().mockImplementation( ( _select ) => ( - { isRenderingChild: _select( 'childRender' ).getValue() } - ) ); - - const ChildOriginalComponent = jest.fn().mockImplementation( () => <div /> ); - const ParentOriginalComponent = jest.fn().mockImplementation( ( props ) => ( - <div>{ props.isRenderingChild ? <Child /> : null }</div> - ) ); - - const Child = withSelect( childMapSelectToProps )( ChildOriginalComponent ); - const Parent = withSelect( parentMapSelectToProps )( ParentOriginalComponent ); + const parentMapSelectToProps = jest + .fn() + .mockImplementation( ( _select ) => ( { + isRenderingChild: _select( 'childRender' ).getValue(), + } ) ); + + const ChildOriginalComponent = jest + .fn() + .mockImplementation( () => <div /> ); + const ParentOriginalComponent = jest + .fn() + .mockImplementation( ( props ) => ( + <div>{ props.isRenderingChild ? <Child /> : null }</div> + ) ); + + const Child = withSelect( childMapSelectToProps )( + ChildOriginalComponent + ); + const Parent = withSelect( parentMapSelectToProps )( + ParentOriginalComponent + ); act( () => { TestRenderer.create( @@ -626,15 +685,19 @@ describe( 'withSelect', () => { }, } ); - const mapSelectToProps = jest.fn().mockImplementation( ( _select ) => ( { - value: _select( 'demo' ).getValue(), - } ) ); + const mapSelectToProps = jest + .fn() + .mockImplementation( ( _select ) => ( { + value: _select( 'demo' ).getValue(), + } ) ); - const OriginalComponent = jest.fn().mockImplementation( ( props ) => ( - <div>{ props.value }</div> - ) ); + const OriginalComponent = jest + .fn() + .mockImplementation( ( props ) => <div>{ props.value }</div> ); - const DataBoundComponent = withSelect( mapSelectToProps )( OriginalComponent ); + const DataBoundComponent = withSelect( mapSelectToProps )( + OriginalComponent + ); let testRenderer; act( () => { diff --git a/packages/data/src/factory.js b/packages/data/src/factory.js index 2bb74724f56c92..cdbdfa4f9a45d5 100644 --- a/packages/data/src/factory.js +++ b/packages/data/src/factory.js @@ -13,7 +13,8 @@ import defaultRegistry from './default-registry'; * @return {Function} marked registry selector. */ export function createRegistrySelector( registrySelector ) { - const selector = ( ...args ) => registrySelector( selector.registry.select )( ...args ); + const selector = ( ...args ) => + registrySelector( selector.registry.select )( ...args ); /** * Flag indicating to selector registration mapping that the selector should diff --git a/packages/data/src/index.js b/packages/data/src/index.js index 0ed5123c67a7f1..b4f2f71e6319bd 100644 --- a/packages/data/src/index.js +++ b/packages/data/src/index.js @@ -109,7 +109,8 @@ export const select = defaultRegistry.select; * * @return {Object} Object containing the store's promise-wrapped selectors. */ -export const __experimentalResolveSelect = defaultRegistry.__experimentalResolveSelect; +export const __experimentalResolveSelect = + defaultRegistry.__experimentalResolveSelect; /** * Given the name of a registered store, returns an object of the store's action creators. diff --git a/packages/data/src/namespace-store/index.js b/packages/data/src/namespace-store/index.js index bc41587569bbba..47c0f6530ac092 100644 --- a/packages/data/src/namespace-store/index.js +++ b/packages/data/src/namespace-store/index.js @@ -2,11 +2,7 @@ * External dependencies */ import { createStore, applyMiddleware } from 'redux'; -import { - flowRight, - get, - mapValues, -} from 'lodash'; +import { flowRight, get, mapValues } from 'lodash'; import combineReducers from 'turbo-combine-reducers'; /** @@ -43,20 +39,30 @@ export default function createNamespace( key, options, registry ) { const store = createReduxStore( key, options, registry ); let resolvers; - const actions = mapActions( { - ...metadataActions, - ...options.actions, - }, store ); - let selectors = mapSelectors( { - ...mapValues( metadataSelectors, ( selector ) => ( state, ...args ) => selector( state.metadata, ...args ) ), - ...mapValues( options.selectors, ( selector ) => { - if ( selector.isRegistrySelector ) { - selector.registry = registry; - } + const actions = mapActions( + { + ...metadataActions, + ...options.actions, + }, + store + ); + let selectors = mapSelectors( + { + ...mapValues( + metadataSelectors, + ( selector ) => ( state, ...args ) => + selector( state.metadata, ...args ) + ), + ...mapValues( options.selectors, ( selector ) => { + if ( selector.isRegistrySelector ) { + selector.registry = registry; + } - return ( state, ...args ) => selector( state.root, ...args ); - } ), - }, store ); + return ( state, ...args ) => selector( state.root, ...args ); + } ), + }, + store + ); if ( options.resolvers ) { const result = mapResolvers( options.resolvers, selectors, store ); resolvers = result.resolvers; @@ -74,18 +80,20 @@ export default function createNamespace( key, options, registry ) { // Customize subscribe behavior to call listeners only on effective change, // not on every dispatch. - const subscribe = store && function( listener ) { - let lastState = store.__unstableOriginalGetState(); - store.subscribe( () => { - const state = store.__unstableOriginalGetState(); - const hasChanged = state !== lastState; - lastState = state; + const subscribe = + store && + function( listener ) { + let lastState = store.__unstableOriginalGetState(); + store.subscribe( () => { + const state = store.__unstableOriginalGetState(); + const hasChanged = state !== lastState; + lastState = state; - if ( hasChanged ) { - listener(); - } - } ); - }; + if ( hasChanged ) { + listener(); + } + } ); + }; // This can be simplified to just { subscribe, getSelectors, getActions } // Once we remove the use function. @@ -125,11 +133,17 @@ function createReduxStore( key, options, registry ) { middlewares.push( createReduxRoutineMiddleware( normalizedControls ) ); } - const enhancers = [ - applyMiddleware( ...middlewares ), - ]; - if ( typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__ ) { - enhancers.push( window.__REDUX_DEVTOOLS_EXTENSION__( { name: key, instanceId: key } ) ); + const enhancers = [ applyMiddleware( ...middlewares ) ]; + if ( + typeof window !== 'undefined' && + window.__REDUX_DEVTOOLS_EXTENSION__ + ) { + enhancers.push( + window.__REDUX_DEVTOOLS_EXTENSION__( { + name: key, + instanceId: key, + } ) + ); } const { reducer, initialState } = options; @@ -222,18 +236,36 @@ function mapResolvers( resolvers, selectors, store ) { const selectorResolver = ( ...args ) => { async function fulfillSelector() { const state = store.getState(); - if ( typeof resolver.isFulfilled === 'function' && resolver.isFulfilled( state, ...args ) ) { + if ( + typeof resolver.isFulfilled === 'function' && + resolver.isFulfilled( state, ...args ) + ) { return; } const { metadata } = store.__unstableOriginalGetState(); - if ( metadataSelectors.hasStartedResolution( metadata, selectorName, args ) ) { + if ( + metadataSelectors.hasStartedResolution( + metadata, + selectorName, + args + ) + ) { return; } - store.dispatch( metadataActions.startResolution( selectorName, args ) ); - await fulfillResolver( store, mappedResolvers, selectorName, ...args ); - store.dispatch( metadataActions.finishResolution( selectorName, args ) ); + store.dispatch( + metadataActions.startResolution( selectorName, args ) + ); + await fulfillResolver( + store, + mappedResolvers, + selectorName, + ...args + ); + store.dispatch( + metadataActions.finishResolution( selectorName, args ) + ); } fulfillSelector( ...args ); diff --git a/packages/data/src/namespace-store/metadata/reducer.js b/packages/data/src/namespace-store/metadata/reducer.js index 17d271ec9d365d..d576c5944f95d7 100644 --- a/packages/data/src/namespace-store/metadata/reducer.js +++ b/packages/data/src/namespace-store/metadata/reducer.js @@ -20,25 +20,25 @@ import { onSubKey } from './utils'; * * @return {Object} Next state. */ -const subKeysIsResolved = flowRight( [ - onSubKey( 'selectorName' ), -] )( ( state = new EquivalentKeyMap(), action ) => { - switch ( action.type ) { - case 'START_RESOLUTION': - case 'FINISH_RESOLUTION': { - const isStarting = action.type === 'START_RESOLUTION'; - const nextState = new EquivalentKeyMap( state ); - nextState.set( action.args, isStarting ); - return nextState; - } - case 'INVALIDATE_RESOLUTION': { - const nextState = new EquivalentKeyMap( state ); - nextState.delete( action.args ); - return nextState; +const subKeysIsResolved = flowRight( [ onSubKey( 'selectorName' ) ] )( + ( state = new EquivalentKeyMap(), action ) => { + switch ( action.type ) { + case 'START_RESOLUTION': + case 'FINISH_RESOLUTION': { + const isStarting = action.type === 'START_RESOLUTION'; + const nextState = new EquivalentKeyMap( state ); + nextState.set( action.args, isStarting ); + return nextState; + } + case 'INVALIDATE_RESOLUTION': { + const nextState = new EquivalentKeyMap( state ); + nextState.delete( action.args ); + return nextState; + } } + return state; } - return state; -} ); +); /** * Reducer function returning next state for selector resolution, object form: @@ -55,9 +55,9 @@ const isResolved = ( state = {}, action ) => { case 'INVALIDATE_RESOLUTION_FOR_STORE': return {}; case 'INVALIDATE_RESOLUTION_FOR_STORE_SELECTOR': - return has( state, [ action.selectorName ] ) ? - omit( state, [ action.selectorName ] ) : - state; + return has( state, [ action.selectorName ] ) + ? omit( state, [ action.selectorName ] ) + : state; case 'START_RESOLUTION': case 'FINISH_RESOLUTION': case 'INVALIDATE_RESOLUTION': diff --git a/packages/data/src/namespace-store/metadata/test/reducer.js b/packages/data/src/namespace-store/metadata/test/reducer.js index d1af3f7804ea9c..41708eb29176d8 100644 --- a/packages/data/src/namespace-store/metadata/test/reducer.js +++ b/packages/data/src/namespace-store/metadata/test/reducer.js @@ -85,39 +85,45 @@ describe( 'reducer', () => { expect( state.getFoo.get( [ 'block' ] ) ).toBe( true ); } ); - it( 'should remove invalidation for store level and leave others ' + - 'intact', () => { - const original = reducer( undefined, { - type: 'FINISH_RESOLUTION', - selectorName: 'getFoo', - args: [ 'post' ], - } ); - const state = reducer( deepFreeze( original ), { - type: 'INVALIDATE_RESOLUTION_FOR_STORE', - } ); + it( + 'should remove invalidation for store level and leave others ' + + 'intact', + () => { + const original = reducer( undefined, { + type: 'FINISH_RESOLUTION', + selectorName: 'getFoo', + args: [ 'post' ], + } ); + const state = reducer( deepFreeze( original ), { + type: 'INVALIDATE_RESOLUTION_FOR_STORE', + } ); - expect( state ).toEqual( {} ); - } ); + expect( state ).toEqual( {} ); + } + ); - it( 'should remove invalidation for store and selector name level and ' + - 'leave other selectors at store level intact', () => { - const original = reducer( undefined, { - type: 'FINISH_RESOLUTION', - selectorName: 'getFoo', - args: [ 'post' ], - } ); - let state = reducer( deepFreeze( original ), { - type: 'FINISH_RESOLUTION', - selectorName: 'getBar', - args: [ 'postBar' ], - } ); - state = reducer( deepFreeze( state ), { - type: 'INVALIDATE_RESOLUTION_FOR_STORE_SELECTOR', - selectorName: 'getBar', - } ); + it( + 'should remove invalidation for store and selector name level and ' + + 'leave other selectors at store level intact', + () => { + const original = reducer( undefined, { + type: 'FINISH_RESOLUTION', + selectorName: 'getFoo', + args: [ 'post' ], + } ); + let state = reducer( deepFreeze( original ), { + type: 'FINISH_RESOLUTION', + selectorName: 'getBar', + args: [ 'postBar' ], + } ); + state = reducer( deepFreeze( state ), { + type: 'INVALIDATE_RESOLUTION_FOR_STORE_SELECTOR', + selectorName: 'getBar', + } ); - expect( state.getBar ).toBeUndefined(); - // { getFoo: EquivalentKeyMap( [] => false ) } - expect( state.getFoo.get( [ 'post' ] ) ).toBe( false ); - } ); + expect( state.getBar ).toBeUndefined(); + // { getFoo: EquivalentKeyMap( [] => false ) } + expect( state.getFoo.get( [ 'post' ] ) ).toBe( false ); + } + ); } ); diff --git a/packages/data/src/namespace-store/metadata/test/utils.js b/packages/data/src/namespace-store/metadata/test/utils.js index 97490250d4d3a8..8b96312a7574e2 100644 --- a/packages/data/src/namespace-store/metadata/test/utils.js +++ b/packages/data/src/namespace-store/metadata/test/utils.js @@ -6,7 +6,9 @@ import { onSubKey } from '../utils'; describe( 'onSubKey', () => { function createEnhancedReducer( actionProperty ) { const enhanceReducer = onSubKey( actionProperty ); - return enhanceReducer( ( state, action ) => 'Called by ' + action.caller ); + return enhanceReducer( + ( state, action ) => 'Called by ' + action.caller + ); } it( 'should default to an empty object', () => { diff --git a/packages/data/src/namespace-store/metadata/utils.js b/packages/data/src/namespace-store/metadata/utils.js index d793855683d65d..d405d93edf39ed 100644 --- a/packages/data/src/namespace-store/metadata/utils.js +++ b/packages/data/src/namespace-store/metadata/utils.js @@ -6,7 +6,10 @@ * * @return {Function} Higher-order reducer. */ -export const onSubKey = ( actionProperty ) => ( reducer ) => ( state = {}, action ) => { +export const onSubKey = ( actionProperty ) => ( reducer ) => ( + state = {}, + action +) => { // Retrieve subkey from action. Do not track if undefined; useful for cases // where reducer is scoped by action shape. const key = action[ actionProperty ]; diff --git a/packages/data/src/namespace-store/test/index.js b/packages/data/src/namespace-store/test/index.js index 62cca98f4c31f2..5f0eb4cb792d39 100644 --- a/packages/data/src/namespace-store/test/index.js +++ b/packages/data/src/namespace-store/test/index.js @@ -14,7 +14,7 @@ describe( 'controls', () => { describe( 'should call registry-aware controls', () => { it( 'registers multiple selectors to the public API', () => { const action1 = jest.fn( () => ( { type: 'NOTHING' } ) ); - const action2 = function * () { + const action2 = function*() { yield { type: 'DISPATCH', store: 'store1', action: 'action1' }; }; registry.registerStore( 'store1', { @@ -29,9 +29,11 @@ describe( 'controls', () => { action2, }, controls: { - DISPATCH: createRegistryControl( ( reg ) => ( { store, action } ) => { - return reg.dispatch( store )[ action ](); - } ), + DISPATCH: createRegistryControl( + ( reg ) => ( { store, action } ) => { + return reg.dispatch( store )[ action ](); + } + ), }, } ); @@ -58,22 +60,30 @@ describe( 'controls', () => { getItems: ( state ) => state, }, resolvers: { - * getItems() { + *getItems() { yield actions.wait(); yield actions.receive( [ 1, 2, 3 ] ); }, }, controls: { WAIT() { - return new Promise( ( resolve ) => process.nextTick( resolve ) ); + return new Promise( ( resolve ) => + process.nextTick( resolve ) + ); }, }, } ); registry.subscribe( () => { - const isFinished = registry.select( 'store' ).hasFinishedResolution( 'getItems' ); + const isFinished = registry + .select( 'store' ) + .hasFinishedResolution( 'getItems' ); if ( isFinished ) { - expect( registry.select( 'store' ).getItems() ).toEqual( [ 1, 2, 3 ] ); + expect( registry.select( 'store' ).getItems() ).toEqual( [ + 1, + 2, + 3, + ] ); done(); } } ); @@ -89,13 +99,17 @@ describe( 'controls', () => { getItem: ( state ) => state, }, resolvers: { - * getItems() { + *getItems() { yield 'foo'; }, }, } ); - expect( registry.select( 'store' ).getItems.hasResolver ).toBe( true ); - expect( registry.select( 'store' ).getItem.hasResolver ).toBe( false ); + expect( registry.select( 'store' ).getItems.hasResolver ).toBe( + true + ); + expect( registry.select( 'store' ).getItem.hasResolver ).toBe( + false + ); } ); it( 'when custom store does not have resolvers defined', () => { registry.registerStore( 'store', { @@ -104,7 +118,9 @@ describe( 'controls', () => { getItems: ( state ) => state, }, } ); - expect( registry.select( 'store' ).getItems.hasResolver ).toBe( false ); + expect( registry.select( 'store' ).getItems.hasResolver ).toBe( + false + ); } ); } ); describe( 'various action types have expected response and resolve as expected with controls middleware', () => { @@ -135,43 +151,54 @@ describe( 'controls', () => { actions, } ); } ); - it( 'action generator returning a yielded promise control descriptor ' + - 'resolves as expected', async () => { - const withPromise = registry.dispatch( 'store' ).withPromise(); - await expect( withPromise ).resolves.toEqual( 10 ); - } ); - it( 'action generator yielding normal action objects resolves as ' + - 'expected', async () => { - const withNormal = registry.dispatch( 'store' ).withNormal(); - await expect( withNormal ).resolves.toBeUndefined(); - } ); + it( + 'action generator returning a yielded promise control descriptor ' + + 'resolves as expected', + async () => { + const withPromise = registry.dispatch( 'store' ).withPromise(); + await expect( withPromise ).resolves.toEqual( 10 ); + } + ); + it( + 'action generator yielding normal action objects resolves as ' + + 'expected', + async () => { + const withNormal = registry.dispatch( 'store' ).withNormal(); + await expect( withNormal ).resolves.toBeUndefined(); + } + ); it( 'action generator returning a non action like value', async () => { - const withNonActionLikeValue = registry.dispatch( 'store' ) + const withNonActionLikeValue = registry + .dispatch( 'store' ) .withNonActionLikeValue(); await expect( withNonActionLikeValue ).resolves.toEqual( 10 ); } ); - it( 'normal dispatch action throwing error because no action ' + - 'returned', () => { - const testDispatch = () => registry.dispatch( 'store' ).normalShouldFail(); - expect( testDispatch ).toThrow( - 'Actions must be plain objects. Use custom middleware for async actions.' - ); - } ); + it( + 'normal dispatch action throwing error because no action ' + + 'returned', + () => { + const testDispatch = () => + registry.dispatch( 'store' ).normalShouldFail(); + expect( testDispatch ).toThrow( + 'Actions must be plain objects. Use custom middleware for async actions.' + ); + } + ); it( 'returns action object for normal dispatch action', async () => { - await expect( registry.dispatch( 'store' ).normal() ) - .resolves - .toEqual( { type: 'NORMAL' } ); + await expect( + registry.dispatch( 'store' ).normal() + ).resolves.toEqual( { type: 'NORMAL' } ); } ); } ); describe( 'action type resolves as expected with just promise middleware', () => { const actions = { normal: () => ( { type: 'NORMAL' } ), - withPromiseAndAction: () => new Promise( - ( resolve ) => resolve( { type: 'WITH_PROMISE' } ) - ), - withPromiseAndNonAction: () => new Promise( - ( resolve ) => resolve( 10 ) - ), + withPromiseAndAction: () => + new Promise( ( resolve ) => + resolve( { type: 'WITH_PROMISE' } ) + ), + withPromiseAndNonAction: () => + new Promise( ( resolve ) => resolve( 10 ) ), }; beforeEach( () => { registry.registerStore( 'store', { @@ -180,18 +207,24 @@ describe( 'controls', () => { } ); } ); it( 'normal action returns action object', async () => { - await expect( registry.dispatch( 'store' ).normal() ) - .resolves - .toEqual( { type: 'NORMAL' } ); - } ); - it( 'action with promise resolving to action returning ' + - 'action object', async () => { - await expect( registry.dispatch( 'store' ).withPromiseAndAction() ) - .resolves - .toEqual( { type: 'WITH_PROMISE' } ); + await expect( + registry.dispatch( 'store' ).normal() + ).resolves.toEqual( { type: 'NORMAL' } ); } ); + it( + 'action with promise resolving to action returning ' + + 'action object', + async () => { + await expect( + registry.dispatch( 'store' ).withPromiseAndAction() + ).resolves.toEqual( { + type: 'WITH_PROMISE', + } ); + } + ); it( 'action with promise returning non action throws error', async () => { - const dispatchedAction = registry.dispatch( 'store' ) + const dispatchedAction = registry + .dispatch( 'store' ) .withPromiseAndNonAction(); await expect( dispatchedAction ).rejects.toThrow( 'Actions must be plain objects. Use custom middleware for async actions.' diff --git a/packages/data/src/plugins/persistence/index.js b/packages/data/src/plugins/persistence/index.js index 75506ef5dfa7e6..30ac60abbc3cc7 100644 --- a/packages/data/src/plugins/persistence/index.js +++ b/packages/data/src/plugins/persistence/index.js @@ -142,19 +142,29 @@ const persistencePlugin = function( registry, pluginOptions ) { // to leverage its behavior of returning the same object when none // of the property values changes. This allows a strict reference // equality to bypass a persistence set on an unchanging state. - const reducers = keys.reduce( ( accumulator, key ) => Object.assign( accumulator, { - [ key ]: ( state, action ) => action.nextState[ key ], - } ), {} ); - - getPersistedState = withLazySameState( combineReducers( reducers ) ); + const reducers = keys.reduce( + ( accumulator, key ) => + Object.assign( accumulator, { + [ key ]: ( state, action ) => action.nextState[ key ], + } ), + {} + ); + + getPersistedState = withLazySameState( + combineReducers( reducers ) + ); } else { getPersistedState = ( state, action ) => action.nextState; } - let lastState = getPersistedState( undefined, { nextState: getState() } ); + let lastState = getPersistedState( undefined, { + nextState: getState(), + } ); return () => { - const state = getPersistedState( lastState, { nextState: getState() } ); + const state = getPersistedState( lastState, { + nextState: getState(), + } ); if ( state !== lastState ) { persistence.set( reducerKey, state ); lastState = state; @@ -175,7 +185,10 @@ const persistencePlugin = function( registry, pluginOptions ) { type: '@@WP/PERSISTENCE_RESTORE', } ); - if ( isPlainObject( initialState ) && isPlainObject( persistedState ) ) { + if ( + isPlainObject( initialState ) && + isPlainObject( persistedState ) + ) { // If state is an object, ensure that: // - Other keys are left intact when persisting only a // subset of keys. @@ -196,11 +209,13 @@ const persistencePlugin = function( registry, pluginOptions ) { const store = registry.registerStore( reducerKey, options ); - store.subscribe( createPersistOnChange( - store.getState, - reducerKey, - options.persist - ) ); + store.subscribe( + createPersistOnChange( + store.getState, + reducerKey, + options.persist + ) + ); return store; }, @@ -218,7 +233,11 @@ persistencePlugin.__unstableMigrate = ( pluginOptions ) => { const state = persistence.get(); // Migrate 'insertUsage' from 'core/editor' to 'core/block-editor' - const insertUsage = get( state, [ 'core/editor', 'preferences', 'insertUsage' ] ); + const insertUsage = get( state, [ + 'core/editor', + 'preferences', + 'insertUsage', + ] ); if ( insertUsage ) { persistence.set( 'core/block-editor', { preferences: { @@ -228,8 +247,17 @@ persistencePlugin.__unstableMigrate = ( pluginOptions ) => { } // Migrate 'areTipsEnabled' from 'core/nux' to 'showWelcomeGuide' in 'core/edit-post' - const areTipsEnabled = get( state, [ 'core/nux', 'preferences', 'areTipsEnabled' ] ); - const hasWelcomeGuide = has( state, [ 'core/edit-post', 'preferences', 'features', 'welcomeGuide' ] ); + const areTipsEnabled = get( state, [ + 'core/nux', + 'preferences', + 'areTipsEnabled', + ] ); + const hasWelcomeGuide = has( state, [ + 'core/edit-post', + 'preferences', + 'features', + 'welcomeGuide', + ] ); if ( areTipsEnabled !== undefined && ! hasWelcomeGuide ) { persistence.set( 'core/edit-post', diff --git a/packages/data/src/plugins/persistence/test/index.js b/packages/data/src/plugins/persistence/test/index.js index b66ebe5f8154dd..4d91648ce81da3 100644 --- a/packages/data/src/plugins/persistence/test/index.js +++ b/packages/data/src/plugins/persistence/test/index.js @@ -6,10 +6,7 @@ import deepFreeze from 'deep-freeze'; /** * Internal dependencies */ -import plugin, { - createPersistenceInterface, - withLazySameState, -} from '../'; +import plugin, { createPersistenceInterface, withLazySameState } from '../'; import objectStorage from '../storage/object'; import { createRegistry } from '../../../'; @@ -29,7 +26,10 @@ describe( 'persistence', () => { // TODO: Remove the `use` function in favor of `registerGenericStore` registry = createRegistry() .use( ( originalRegistry ) => { - originalRegisterStore = jest.spyOn( originalRegistry, 'registerStore' ); + originalRegisterStore = jest.spyOn( + originalRegistry, + 'registerStore' + ); return {}; } ) .use( plugin, { storage: objectStorage } ); @@ -78,21 +78,27 @@ describe( 'persistence', () => { }, } ); - expect( registry.select( 'test' ).getState() ).toEqual( { a: 1, b: null } ); + expect( registry.select( 'test' ).getState() ).toEqual( { + a: 1, + b: null, + } ); } ); it( 'should merge persisted value with default if object-like', () => { - const DEFAULT_STATE = deepFreeze( { preferences: { useFoo: true, useBar: true } } ); + const DEFAULT_STATE = deepFreeze( { + preferences: { useFoo: true, useBar: true }, + } ); registry = createRegistry().use( plugin, { storage: { - getItem: () => JSON.stringify( { - test: { - preferences: { - useFoo: false, + getItem: () => + JSON.stringify( { + test: { + preferences: { + useFoo: false, + }, }, - }, - } ), + } ), setItem() {}, }, } ); @@ -129,7 +135,9 @@ describe( 'persistence', () => { }, } ); - expect( registry.select( 'test' ).getState() ).toEqual( { persisted: true } ); + expect( registry.select( 'test' ).getState() ).toEqual( { + persisted: true, + } ); } ); it( 'should defer to persisted state if mismatch of object-like (initial object-like)', () => { @@ -154,9 +162,10 @@ describe( 'persistence', () => { it( 'should be reasonably tolerant to a non-object persisted state', () => { registry = createRegistry().use( plugin, { storage: { - getItem: () => JSON.stringify( { - test: 1, - } ), + getItem: () => + JSON.stringify( { + test: 1, + } ), setItem() {}, }, } ); @@ -248,7 +257,10 @@ describe( 'persistence', () => { registry.dispatch( 'test' ).setState( { ok: true } ); - expect( objectStorage.setItem ).toHaveBeenCalledWith( 'WP_DATA', '{"test":{"ok":true}}' ); + expect( objectStorage.setItem ).toHaveBeenCalledWith( + 'WP_DATA', + '{"test":{"ok":true}}' + ); } ); it( 'should persist a subset of keys', () => { @@ -269,7 +281,10 @@ describe( 'persistence', () => { registry.dispatch( 'test' ).setState( { foo: 1, baz: 2 } ); - expect( objectStorage.setItem ).toHaveBeenCalledWith( 'WP_DATA', '{"test":{"foo":1}}' ); + expect( objectStorage.setItem ).toHaveBeenCalledWith( + 'WP_DATA', + '{"test":{"foo":1}}' + ); } ); it( 'should not persist an unchanging subset', () => { @@ -306,7 +321,10 @@ describe( 'persistence', () => { let get, set; beforeEach( () => { - ( { get, set } = createPersistenceInterface( { storage, storageKey } ) ); + ( { get, set } = createPersistenceInterface( { + storage, + storageKey, + } ) ); } ); describe( 'get', () => { @@ -328,22 +346,33 @@ describe( 'persistence', () => { it( 'sets JSON by object', () => { set( 'test', {} ); - expect( objectStorage.setItem ).toHaveBeenCalledWith( storageKey, '{"test":{}}' ); + expect( objectStorage.setItem ).toHaveBeenCalledWith( + storageKey, + '{"test":{}}' + ); } ); it( 'merges to existing', () => { set( 'test1', {} ); set( 'test2', {} ); - expect( objectStorage.setItem ).toHaveBeenCalledWith( storageKey, '{"test1":{}}' ); - expect( objectStorage.setItem ).toHaveBeenCalledWith( storageKey, '{"test1":{},"test2":{}}' ); + expect( objectStorage.setItem ).toHaveBeenCalledWith( + storageKey, + '{"test1":{}}' + ); + expect( objectStorage.setItem ).toHaveBeenCalledWith( + storageKey, + '{"test1":{},"test2":{}}' + ); } ); } ); } ); describe( 'withLazySameState', () => { it( 'should call the original reducer if action.nextState differs from state', () => { - const reducer = jest.fn().mockImplementation( ( state, action ) => action.nextState ); + const reducer = jest + .fn() + .mockImplementation( ( state, action ) => action.nextState ); const enhanced = withLazySameState( reducer ); reducer.mockClear(); @@ -355,7 +384,9 @@ describe( 'persistence', () => { } ); it( 'should not call the original reducer if action.nextState equals state', () => { - const reducer = jest.fn().mockImplementation( ( state, action ) => action.nextState ); + const reducer = jest + .fn() + .mockImplementation( ( state, action ) => action.nextState ); const enhanced = withLazySameState( reducer ); reducer.mockClear(); diff --git a/packages/data/src/registry.js b/packages/data/src/registry.js index 4d6511c101cf35..adb11b40c75dfa 100644 --- a/packages/data/src/registry.js +++ b/packages/data/src/registry.js @@ -1,11 +1,7 @@ /** * External dependencies */ -import { - omit, - without, - mapValues, -} from 'lodash'; +import { omit, without, mapValues } from 'lodash'; import memize from 'memize'; /** @@ -93,22 +89,23 @@ export function createRegistry( storeConfigs = {}, parent = null ) { const getResolveSelectors = memize( ( selectors ) => { return mapValues( - omit( - selectors, - [ - 'getIsResolving', - 'hasStartedResolution', - 'hasFinishedResolution', - 'isResolving', - 'getCachedResolvers', - ] - ), + omit( selectors, [ + 'getIsResolving', + 'hasStartedResolution', + 'hasFinishedResolution', + 'isResolving', + 'getCachedResolvers', + ] ), ( selector, selectorName ) => { return ( ...args ) => { return new Promise( ( resolve ) => { - const hasFinished = () => selectors - .hasFinishedResolution( selectorName, args ); - const getResult = () => selector.apply( null, args ); + const hasFinished = () => + selectors.hasFinishedResolution( + selectorName, + args + ); + const getResult = () => + selector.apply( null, args ); // trigger the selector (to trigger the resolver) const result = getResult(); @@ -241,8 +238,8 @@ export function createRegistry( storeConfigs = {}, parent = null ) { registerGenericStore( 'core/data', createCoreDataStore( registry ) ); - Object.entries( storeConfigs ).forEach( - ( [ name, config ] ) => registry.registerStore( name, config ) + Object.entries( storeConfigs ).forEach( ( [ name, config ] ) => + registry.registerStore( name, config ) ); if ( parent ) { diff --git a/packages/data/src/resolvers-cache-middleware.js b/packages/data/src/resolvers-cache-middleware.js index 0a51fb02f2015b..85f0f8a65cd25a 100644 --- a/packages/data/src/resolvers-cache-middleware.js +++ b/packages/data/src/resolvers-cache-middleware.js @@ -15,25 +15,40 @@ import { get } from 'lodash'; * * @return {Function} Middleware function. */ -const createResolversCacheMiddleware = ( registry, reducerKey ) => () => ( next ) => ( action ) => { - const resolvers = registry.select( 'core/data' ).getCachedResolvers( reducerKey ); - Object.entries( resolvers ).forEach( ( [ selectorName, resolversByArgs ] ) => { - const resolver = get( registry.stores, [ reducerKey, 'resolvers', selectorName ] ); - if ( ! resolver || ! resolver.shouldInvalidate ) { - return; - } - resolversByArgs.forEach( ( value, args ) => { - // resolversByArgs is the map Map([ args ] => boolean) storing the cache resolution status for a given selector. - // If the value is false it means this resolver has finished its resolution which means we need to invalidate it, - // if it's true it means it's inflight and the invalidation is not necessary. - if ( value !== false || ! resolver.shouldInvalidate( action, ...args ) ) { +const createResolversCacheMiddleware = ( registry, reducerKey ) => () => ( + next +) => ( action ) => { + const resolvers = registry + .select( 'core/data' ) + .getCachedResolvers( reducerKey ); + Object.entries( resolvers ).forEach( + ( [ selectorName, resolversByArgs ] ) => { + const resolver = get( registry.stores, [ + reducerKey, + 'resolvers', + selectorName, + ] ); + if ( ! resolver || ! resolver.shouldInvalidate ) { return; } + resolversByArgs.forEach( ( value, args ) => { + // resolversByArgs is the map Map([ args ] => boolean) storing the cache resolution status for a given selector. + // If the value is false it means this resolver has finished its resolution which means we need to invalidate it, + // if it's true it means it's inflight and the invalidation is not necessary. + if ( + value !== false || + ! resolver.shouldInvalidate( action, ...args ) + ) { + return; + } - // Trigger cache invalidation - registry.dispatch( 'core/data' ).invalidateResolution( reducerKey, selectorName, args ); - } ); - } ); + // Trigger cache invalidation + registry + .dispatch( 'core/data' ) + .invalidateResolution( reducerKey, selectorName, args ); + } ); + } + ); return next( action ); }; diff --git a/packages/data/src/store/index.js b/packages/data/src/store/index.js index 9a14aac264508e..8310610cb59364 100644 --- a/packages/data/src/store/index.js +++ b/packages/data/src/store/index.js @@ -1,4 +1,3 @@ - function createCoreDataStore( registry ) { const getCoreDataSelector = ( selectorName ) => ( reducerKey, ...args ) => { return registry.select( reducerKey )[ selectorName ]( ...args ); @@ -16,10 +15,13 @@ function createCoreDataStore( registry ) { 'hasFinishedResolution', 'isResolving', 'getCachedResolvers', - ].reduce( ( memo, selectorName ) => ( { - ...memo, - [ selectorName ]: getCoreDataSelector( selectorName ), - } ), {} ); + ].reduce( + ( memo, selectorName ) => ( { + ...memo, + [ selectorName ]: getCoreDataSelector( selectorName ), + } ), + {} + ); }, getActions() { @@ -29,10 +31,13 @@ function createCoreDataStore( registry ) { 'invalidateResolution', 'invalidateResolutionForStore', 'invalidateResolutionForStoreSelector', - ].reduce( ( memo, actionName ) => ( { - ...memo, - [ actionName ]: getCoreDataAction( actionName ), - } ), {} ); + ].reduce( + ( memo, actionName ) => ( { + ...memo, + [ actionName ]: getCoreDataAction( actionName ), + } ), + {} + ); }, subscribe() { diff --git a/packages/data/src/test/registry.js b/packages/data/src/test/registry.js index 9be52ac7d022b1..02242ecc5b283c 100644 --- a/packages/data/src/test/registry.js +++ b/packages/data/src/test/registry.js @@ -53,9 +53,21 @@ describe( 'createRegistry', () => { } ); it( 'should throw if not all required config elements are present', () => { - expect( () => registry.registerGenericStore( 'grocer', {} ) ).toThrow(); - expect( () => registry.registerGenericStore( 'grocer', { getSelectors, getActions } ) ).toThrow(); - expect( () => registry.registerGenericStore( 'grocer', { getActions, subscribe } ) ).toThrow(); + expect( () => + registry.registerGenericStore( 'grocer', {} ) + ).toThrow(); + expect( () => + registry.registerGenericStore( 'grocer', { + getSelectors, + getActions, + } ) + ).toThrow(); + expect( () => + registry.registerGenericStore( 'grocer', { + getActions, + subscribe, + } ) + ).toThrow(); } ); describe( 'getSelectors', () => { @@ -77,10 +89,18 @@ describe( 'createRegistry', () => { getSelectors = () => ( { getPrice, getQuantity } ); - registry.registerGenericStore( 'grocer', { getSelectors, getActions, subscribe } ); + registry.registerGenericStore( 'grocer', { + getSelectors, + getActions, + subscribe, + } ); - expect( registry.select( 'grocer' ).getPrice ).toEqual( getPrice ); - expect( registry.select( 'grocer' ).getQuantity ).toEqual( getQuantity ); + expect( registry.select( 'grocer' ).getPrice ).toEqual( + getPrice + ); + expect( registry.select( 'grocer' ).getQuantity ).toEqual( + getQuantity + ); } ); } ); @@ -98,26 +118,36 @@ describe( 'createRegistry', () => { getActions = () => { return { - setPrice: ( ...args ) => dispatch( setPrice( ...args ) ), - setQuantity: ( ...args ) => dispatch( setQuantity( ...args ) ), + setPrice: ( ...args ) => + dispatch( setPrice( ...args ) ), + setQuantity: ( ...args ) => + dispatch( setQuantity( ...args ) ), }; }; - registry.registerGenericStore( 'grocer', { getSelectors, getActions, subscribe } ); + registry.registerGenericStore( 'grocer', { + getSelectors, + getActions, + subscribe, + } ); expect( dispatch ).not.toHaveBeenCalled(); registry.dispatch( 'grocer' ).setPrice( 'broccoli', 3 ); expect( dispatch ).toHaveBeenCalledTimes( 1 ); - expect( dispatch ).toHaveBeenCalledWith( - { type: 'SET_PRICE', itemName: 'broccoli', price: 3 } - ); + expect( dispatch ).toHaveBeenCalledWith( { + type: 'SET_PRICE', + itemName: 'broccoli', + price: 3, + } ); registry.dispatch( 'grocer' ).setQuantity( 'lettuce', 8 ); expect( dispatch ).toHaveBeenCalledTimes( 2 ); - expect( dispatch ).toHaveBeenCalledWith( - { type: 'SET_QUANTITY', itemName: 'lettuce', quantity: 8 } - ); + expect( dispatch ).toHaveBeenCalledWith( { + type: 'SET_QUANTITY', + itemName: 'lettuce', + quantity: 8, + } ); } ); } ); @@ -134,7 +164,11 @@ describe( 'createRegistry', () => { }; const unsubscribe = registry.subscribe( registryListener ); - registry.registerGenericStore( 'grocer', { getSelectors, getActions, subscribe } ); + registry.registerGenericStore( 'grocer', { + getSelectors, + getActions, + subscribe, + } ); expect( registryListener ).not.toHaveBeenCalled(); storeChanged(); @@ -172,12 +206,18 @@ describe( 'createRegistry', () => { } ); expect( store.getState() ).toEqual( { ribs: 6, chicken: 4 } ); - expect( registry.dispatch( 'butcher' ) ).toHaveProperty( 'startSale' ); + expect( registry.dispatch( 'butcher' ) ).toHaveProperty( + 'startSale' + ); expect( registry.select( 'butcher' ) ).toHaveProperty( 'getPrice' ); - expect( registry.select( 'butcher' ).getPrice( 'chicken' ) ).toBe( 4 ); + expect( registry.select( 'butcher' ).getPrice( 'chicken' ) ).toBe( + 4 + ); expect( registry.select( 'butcher' ).getPrice( 'ribs' ) ).toBe( 6 ); registry.dispatch( 'butcher' ).startSale( 'chicken' ); - expect( registry.select( 'butcher' ).getPrice( 'chicken' ) ).toBe( 2 ); + expect( registry.select( 'butcher' ).getPrice( 'chicken' ) ).toBe( + 2 + ); expect( registry.select( 'butcher' ).getPrice( 'ribs' ) ).toBe( 6 ); } ); @@ -185,10 +225,14 @@ describe( 'createRegistry', () => { const reducer1 = () => 'chicken'; const reducer2 = () => 'ribs'; - const store = registry.registerStore( 'red1', { reducer: reducer1 } ); + const store = registry.registerStore( 'red1', { + reducer: reducer1, + } ); expect( store.getState() ).toEqual( 'chicken' ); - const store2 = registry.registerStore( 'red2', { reducer: reducer2 } ); + const store2 = registry.registerStore( 'red2', { + reducer: reducer2, + } ); expect( store2.getState() ).toEqual( 'ribs' ); } ); @@ -315,7 +359,10 @@ describe( 'createRegistry', () => { const promise = subscribeUntil( [ () => registry.select( 'demo' ).getValue() === 'OK', - () => registry.select( 'core/data' ).hasFinishedResolution( 'demo', 'getValue' ), + () => + registry + .select( 'core/data' ) + .hasFinishedResolution( 'demo', 'getValue' ), ] ); registry.select( 'demo' ).getValue(); @@ -338,7 +385,10 @@ describe( 'createRegistry', () => { const promise = subscribeUntil( [ () => registry.select( 'demo' ).getValue() === 'OK', - () => registry.select( 'core/data' ).hasFinishedResolution( 'demo', 'getValue' ), + () => + registry + .select( 'core/data' ) + .hasFinishedResolution( 'demo', 'getValue' ), ] ); registry.select( 'demo' ).getValue(); @@ -375,7 +425,9 @@ describe( 'createRegistry', () => { it( 'should not dispatch resolved promise action on subsequent selector calls', () => { registry.registerStore( 'demo', { reducer: ( state = 'NOTOK', action ) => { - return action.type === 'SET_OK' && state === 'NOTOK' ? 'OK' : 'NOTOK'; + return action.type === 'SET_OK' && state === 'NOTOK' + ? 'OK' + : 'NOTOK'; }, selectors: { getValue: ( state ) => state, @@ -385,7 +437,9 @@ describe( 'createRegistry', () => { }, } ); - const promise = subscribeUntil( () => registry.select( 'demo' ).getValue() === 'OK' ); + const promise = subscribeUntil( + () => registry.select( 'demo' ).getValue() === 'OK' + ); registry.select( 'demo' ).getValue(); registry.select( 'demo' ).getValue(); @@ -393,10 +447,12 @@ describe( 'createRegistry', () => { return promise; } ); - it( 'should invalidate the resolver\'s resolution cache', async () => { + it( "should invalidate the resolver's resolution cache", async () => { registry.registerStore( 'demo', { reducer: ( state = 'NOTOK', action ) => { - return action.type === 'SET_OK' && state === 'NOTOK' ? 'OK' : 'NOTOK'; + return action.type === 'SET_OK' && state === 'NOTOK' + ? 'OK' + : 'NOTOK'; }, selectors: { getValue: ( state ) => state, @@ -404,7 +460,8 @@ describe( 'createRegistry', () => { resolvers: { getValue: { fulfill: () => Promise.resolve( { type: 'SET_OK' } ), - shouldInvalidate: ( action ) => action.type === 'INVALIDATE', + shouldInvalidate: ( action ) => + action.type === 'INVALIDATE', }, }, actions: { @@ -412,14 +469,18 @@ describe( 'createRegistry', () => { }, } ); - let promise = subscribeUntil( () => registry.select( 'demo' ).getValue() === 'OK' ); + let promise = subscribeUntil( + () => registry.select( 'demo' ).getValue() === 'OK' + ); registry.select( 'demo' ).getValue(); // Triggers resolver switches to OK await promise; // Invalidate the cache registry.dispatch( 'demo' ).invalidate(); - promise = subscribeUntil( () => registry.select( 'demo' ).getValue() === 'NOTOK' ); + promise = subscribeUntil( + () => registry.select( 'demo' ).getValue() === 'NOTOK' + ); registry.select( 'demo' ).getValue(); // Triggers the resolver again and switch to NOTOK await promise; } ); @@ -437,10 +498,14 @@ describe( 'createRegistry', () => { }, } ); - expect( registry.select( 'reducer1' ).selector1() ).toEqual( 'result1' ); + expect( registry.select( 'reducer1' ).selector1() ).toEqual( + 'result1' + ); expect( selector1 ).toHaveBeenCalledWith( store.getState() ); - expect( registry.select( 'reducer1' ).selector2() ).toEqual( 'result2' ); + expect( registry.select( 'reducer1' ).selector2() ).toEqual( + 'result2' + ); expect( selector2 ).toHaveBeenCalledWith( store.getState() ); } ); @@ -462,7 +527,9 @@ describe( 'createRegistry', () => { }, } ); - expect( registry.select( 'reducer2' ).selector2() ).toEqual( 'result1' ); + expect( registry.select( 'reducer2' ).selector2() ).toEqual( + 'result1' + ); } ); it( 'should run the registry selector from a non-registry selector', () => { @@ -485,11 +552,15 @@ describe( 'createRegistry', () => { }, } ); - expect( registry.select( 'reducer2' ).selector3() ).toEqual( 'result1' ); + expect( registry.select( 'reducer2' ).selector3() ).toEqual( + 'result1' + ); } ); it( 'gracefully stubs select on selector calls', () => { - const selector = createRegistrySelector( ( select ) => () => select ); + const selector = createRegistrySelector( ( select ) => () => + select + ); const maybeSelect = selector(); @@ -508,7 +579,9 @@ describe( 'createRegistry', () => { }, } ); const unsubscribe = registry.subscribe( () => { - incrementedValue = registry.select( 'myAwesomeReducer' ).globalSelector(); + incrementedValue = registry + .select( 'myAwesomeReducer' ) + .globalSelector(); } ); const action = { type: 'dummy' }; @@ -552,7 +625,9 @@ describe( 'createRegistry', () => { const secondListener = jest.fn(); subscribeWithUnsubscribe( firstListener ); - const secondUnsubscribe = subscribeWithUnsubscribe( secondListener ); + const secondUnsubscribe = subscribeWithUnsubscribe( + secondListener + ); store.dispatch( { type: 'dummy' } ); @@ -587,13 +662,13 @@ describe( 'createRegistry', () => { }, } ); // state = 1 - const dispatchResult = await registry.dispatch( 'counter' ).increment(); - await expect( dispatchResult ).toEqual( - { - type: 'increment', - count: 1, - } - ); + const dispatchResult = await registry + .dispatch( 'counter' ) + .increment(); + await expect( dispatchResult ).toEqual( { + type: 'increment', + count: 1, + } ); registry.dispatch( 'counter' ).increment( 4 ); // state = 5 expect( store.getState() ).toBe( 5 ); } ); @@ -650,7 +725,11 @@ describe( 'createRegistry', () => { const getSelectors = () => ( { mySelector } ); const getActions = () => ( { myAction } ); const subscribe = () => {}; - registry.registerGenericStore( 'store', { getSelectors, getActions, subscribe } ); + registry.registerGenericStore( 'store', { + getSelectors, + getActions, + subscribe, + } ); const subRegistry = createRegistry( {}, registry ); subRegistry.select( 'store' ).mySelector(); @@ -666,7 +745,11 @@ describe( 'createRegistry', () => { const getSelectors = () => ( { mySelector } ); const getActions = () => ( { myAction } ); const subscribe = () => {}; - registry.registerGenericStore( 'store', { getSelectors, getActions, subscribe } ); + registry.registerGenericStore( 'store', { + getSelectors, + getActions, + subscribe, + } ); const subRegistry = createRegistry( {}, registry ); const mySelector2 = jest.fn(); diff --git a/packages/date/src/index.js b/packages/date/src/index.js index 54fe5e633d8d29..6251e1c82c04bf 100644 --- a/packages/date/src/index.js +++ b/packages/date/src/index.js @@ -14,9 +14,43 @@ const WP_ZONE = 'WP'; let settings = { l10n: { locale: 'en', - months: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ], - monthsShort: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ], - weekdays: [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ], + months: [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ], + monthsShort: [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ], + weekdays: [ + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + ], weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ], meridiem: { am: 'am', pm: 'pm', AM: 'AM', PM: 'PM' }, relative: { @@ -64,9 +98,13 @@ export function setSettings( dateSettings ) { weekdaysShort: dateSettings.l10n.weekdaysShort, meridiem( hour, minute, isLowercase ) { if ( hour < 12 ) { - return isLowercase ? dateSettings.l10n.meridiem.am : dateSettings.l10n.meridiem.AM; + return isLowercase + ? dateSettings.l10n.meridiem.am + : dateSettings.l10n.meridiem.AM; } - return isLowercase ? dateSettings.l10n.meridiem.pm : dateSettings.l10n.meridiem.PM; + return isLowercase + ? dateSettings.l10n.meridiem.pm + : dateSettings.l10n.meridiem.PM; }, longDateFormat: { LT: dateSettings.formats.time, @@ -96,12 +134,14 @@ export function __experimentalGetSettings() { function setupWPTimezone() { // Create WP timezone based off dateSettings. - momentLib.tz.add( momentLib.tz.pack( { - name: WP_ZONE, - abbrs: [ WP_ZONE ], - untils: [ null ], - offsets: [ -settings.timezone.offset * 60 || 0 ], - } ) ); + momentLib.tz.add( + momentLib.tz.pack( { + name: WP_ZONE, + abbrs: [ WP_ZONE ], + untils: [ null ], + offsets: [ -settings.timezone.offset * 60 || 0 ], + } ) + ); } // Date constants. @@ -221,11 +261,10 @@ const formatMap = { minutes = parseInt( timezoned.format( 'm' ), 10 ), hours = parseInt( timezoned.format( 'H' ), 10 ); return parseInt( - ( - seconds + - ( minutes * MINUTE_IN_SECONDS ) + - ( hours * HOUR_IN_SECONDS ) - ) / 86.4, + ( seconds + + minutes * MINUTE_IN_SECONDS + + hours * HOUR_IN_SECONDS ) / + 86.4, 10 ); }, @@ -264,7 +303,11 @@ const formatMap = { const offset = momentDate.format( 'Z' ); const sign = offset[ 0 ] === '-' ? -1 : 1; const parts = offset.substring( 1 ).split( ':' ); - return sign * ( ( parts[ 0 ] * HOUR_IN_MINUTES ) + parts[ 1 ] ) * MINUTE_IN_SECONDS; + return ( + sign * + ( parts[ 0 ] * HOUR_IN_MINUTES + parts[ 1 ] ) * + MINUTE_IN_SECONDS + ); }, // Full date/time c: 'YYYY-MM-DDTHH:mm:ssZ', // .toISOString diff --git a/packages/date/src/test/index.js b/packages/date/src/test/index.js index b5e17280d67829..69368b1fb630b5 100644 --- a/packages/date/src/test/index.js +++ b/packages/date/src/test/index.js @@ -1,19 +1,24 @@ /** * Internal dependencies */ -import { isInTheFuture, getDate, setSettings, __experimentalGetSettings } from '../'; +import { + isInTheFuture, + getDate, + setSettings, + __experimentalGetSettings, +} from '../'; describe( 'isInTheFuture', () => { it( 'should return true if the date is in the future', () => { // Create a Date object 1 minute in the future. - const date = new Date( Number( getDate() ) + ( 1000 * 60 ) ); + const date = new Date( Number( getDate() ) + 1000 * 60 ); expect( isInTheFuture( date ) ).toBe( true ); } ); it( 'should return true if the date is in the past', () => { // Create a Date object 1 minute in the past. - const date = new Date( Number( getDate() ) - ( 1000 * 60 ) ); + const date = new Date( Number( getDate() ) - 1000 * 60 ); expect( isInTheFuture( date ) ).toBe( false ); } ); @@ -27,11 +32,11 @@ describe( 'isInTheFuture', () => { timezone: { offset: '4', string: '' }, } ); // Create a Date object 1 minute in the past. - let date = new Date( Number( getDate() ) - ( 1000 * 60 ) ); + let date = new Date( Number( getDate() ) - 1000 * 60 ); expect( isInTheFuture( date ) ).toBe( false ); // Create a Date object 1 minute in the future. - date = new Date( Number( getDate() ) + ( 1000 * 60 ) ); + date = new Date( Number( getDate() ) + 1000 * 60 ); expect( isInTheFuture( date ) ).toBe( true ); // Restore default settings diff --git a/packages/dependency-extraction-webpack-plugin/index.js b/packages/dependency-extraction-webpack-plugin/index.js index 58987ce517d9ad..da909d7c5258f1 100644 --- a/packages/dependency-extraction-webpack-plugin/index.js +++ b/packages/dependency-extraction-webpack-plugin/index.js @@ -9,7 +9,10 @@ const { RawSource } = require( 'webpack-sources' ); /** * Internal dependencies */ -const { defaultRequestToExternal, defaultRequestToHandle } = require( './util' ); +const { + defaultRequestToExternal, + defaultRequestToHandle, +} = require( './util' ); class DependencyExtractionWebpackPlugin { constructor( options ) { @@ -30,7 +33,10 @@ class DependencyExtractionWebpackPlugin { this.externalizedDeps = new Set(); // Offload externalization work to the ExternalsPlugin. - this.externalsPlugin = new ExternalsPlugin( 'this', this.externalizeWpDeps.bind( this ) ); + this.externalsPlugin = new ExternalsPlugin( + 'this', + this.externalizeWpDeps.bind( this ) + ); } externalizeWpDeps( context, request, callback ) { @@ -42,7 +48,10 @@ class DependencyExtractionWebpackPlugin { } // Cascade to default if unhandled and enabled - if ( typeof externalRequest === 'undefined' && this.options.useDefaults ) { + if ( + typeof externalRequest === 'undefined' && + this.options.useDefaults + ) { externalRequest = defaultRequestToExternal( request ); } @@ -78,7 +87,9 @@ class DependencyExtractionWebpackPlugin { stringify( asset ) { if ( this.options.outputFormat === 'php' ) { - return `<?php return ${ json2php( JSON.parse( JSON.stringify( asset ) ) ) };`; + return `<?php return ${ json2php( + JSON.parse( JSON.stringify( asset ) ) + ) };`; } return JSON.stringify( asset ); @@ -94,7 +105,10 @@ class DependencyExtractionWebpackPlugin { const { injectPolyfill, outputFormat } = this.options; // Process each entry point independently. - for ( const [ entrypointName, entrypoint ] of compilation.entrypoints.entries() ) { + for ( const [ + entrypointName, + entrypoint, + ] of compilation.entrypoints.entries() ) { const entrypointExternalizedWpDeps = new Set(); if ( injectPolyfill ) { entrypointExternalizedWpDeps.add( 'wp-polyfill' ); @@ -104,8 +118,12 @@ class DependencyExtractionWebpackPlugin { for ( const chunk of entrypoint.chunks ) { for ( const { userRequest } of chunk.modulesIterable ) { if ( this.externalizedDeps.has( userRequest ) ) { - const scriptDependency = this.mapRequestToDependency( userRequest ); - entrypointExternalizedWpDeps.add( scriptDependency ); + const scriptDependency = this.mapRequestToDependency( + userRequest + ); + entrypointExternalizedWpDeps.add( + scriptDependency + ); } } } @@ -114,7 +132,9 @@ class DependencyExtractionWebpackPlugin { // Get a stable, stringified representation of the WordPress script asset. const assetString = this.stringify( { - dependencies: Array.from( entrypointExternalizedWpDeps ).sort(), + dependencies: Array.from( + entrypointExternalizedWpDeps + ).sort(), version: runtimeChunk.hash, } ); @@ -130,10 +150,15 @@ class DependencyExtractionWebpackPlugin { .update( assetString ) .digest( 'hex' ), } ) - .replace( /\.js$/i, '.asset.' + ( outputFormat === 'php' ? 'php' : 'json' ) ); + .replace( + /\.js$/i, + '.asset.' + ( outputFormat === 'php' ? 'php' : 'json' ) + ); // Add source and file into compilation for webpack to output. - compilation.assets[ assetFilename ] = new RawSource( assetString ); + compilation.assets[ assetFilename ] = new RawSource( + assetString + ); runtimeChunk.files.push( assetFilename ); } } ); diff --git a/packages/dependency-extraction-webpack-plugin/test/build.js b/packages/dependency-extraction-webpack-plugin/test/build.js index 28c9391226b31d..12216abc9687c7 100644 --- a/packages/dependency-extraction-webpack-plugin/test/build.js +++ b/packages/dependency-extraction-webpack-plugin/test/build.js @@ -46,16 +46,20 @@ describe.each( configFixtures )( 'Webpack `%s`', ( configCase ) => { webpack( options, ( err, stats ) => { expect( err ).toBeNull(); - const assetFiles = glob( `${ outputDirectory }/*.asset.@(json|php)` ); + const assetFiles = glob( + `${ outputDirectory }/*.asset.@(json|php)` + ); const expectedLength = - typeof options.entry === 'object' ? Object.keys( options.entry ).length : 1; + typeof options.entry === 'object' + ? Object.keys( options.entry ).length + : 1; expect( assetFiles ).toHaveLength( expectedLength ); // Asset files should match. assetFiles.forEach( ( assetFile ) => { - expect( fs.readFileSync( assetFile, 'utf-8' ) ).toMatchSnapshot( - 'Asset file should match snapshot' - ); + expect( + fs.readFileSync( assetFile, 'utf-8' ) + ).toMatchSnapshot( 'Asset file should match snapshot' ); } ); // Webpack stats external modules should match. @@ -67,7 +71,9 @@ describe.each( configFixtures )( 'Webpack `%s`', ( configCase ) => { request: module.request, userRequest: module.userRequest, } ) ); - expect( externalModules ).toMatchSnapshot( 'External modules should match snapshot' ); + expect( externalModules ).toMatchSnapshot( + 'External modules should match snapshot' + ); resolve(); } ); diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/no-default/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/no-default/webpack.config.js index 2b009fba2cc4fe..4a751034233d0c 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/no-default/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/no-default/webpack.config.js @@ -1,5 +1,7 @@ const DependencyExtractionWebpackPlugin = require( '../../..' ); module.exports = { - plugins: [ new DependencyExtractionWebpackPlugin( { useDefaults: false } ) ], + plugins: [ + new DependencyExtractionWebpackPlugin( { useDefaults: false } ), + ], }; diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/output-format-json/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/output-format-json/webpack.config.js index 4ab7d1646d4e95..e4713c3a3778f8 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/output-format-json/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/output-format-json/webpack.config.js @@ -1,5 +1,7 @@ const DependencyExtractionWebpackPlugin = require( '../../..' ); module.exports = { - plugins: [ new DependencyExtractionWebpackPlugin( { outputFormat: 'json' } ) ], + plugins: [ + new DependencyExtractionWebpackPlugin( { outputFormat: 'json' } ), + ], }; diff --git a/packages/dependency-extraction-webpack-plugin/test/util.js b/packages/dependency-extraction-webpack-plugin/test/util.js index f7d5f166b7d56a..65bd0110ff105f 100644 --- a/packages/dependency-extraction-webpack-plugin/test/util.js +++ b/packages/dependency-extraction-webpack-plugin/test/util.js @@ -1,7 +1,10 @@ /** * Internal dependencies */ -const { defaultRequestToExternal, defaultRequestToHandle } = require( '../util' ); +const { + defaultRequestToExternal, + defaultRequestToHandle, +} = require( '../util' ); describe( 'defaultRequestToExternal', () => { test( 'Returns undefined on unrecognized request', () => { @@ -13,14 +16,16 @@ describe( 'defaultRequestToExternal', () => { } ); test( 'Handles known @wordpress request', () => { - expect( defaultRequestToExternal( '@wordpress/i18n' ) ).toEqual( [ 'wp', 'i18n' ] ); + expect( defaultRequestToExternal( '@wordpress/i18n' ) ).toEqual( [ + 'wp', + 'i18n', + ] ); } ); test( 'Handles future @wordpress namespace packages', () => { - expect( defaultRequestToExternal( '@wordpress/some-future-package' ) ).toEqual( [ - 'wp', - 'someFuturePackage', - ] ); + expect( + defaultRequestToExternal( '@wordpress/some-future-package' ) + ).toEqual( [ 'wp', 'someFuturePackage' ] ); } ); } ); @@ -34,8 +39,8 @@ describe( 'defaultRequestToHandle', () => { } ); test( 'Handles @wordpress request', () => { - expect( defaultRequestToHandle( '@wordpress/some-future-package' ) ).toBe( - 'wp-some-future-package' - ); + expect( + defaultRequestToHandle( '@wordpress/some-future-package' ) + ).toBe( 'wp-some-future-package' ); } ); } ); diff --git a/packages/dependency-extraction-webpack-plugin/util.js b/packages/dependency-extraction-webpack-plugin/util.js index a5d36c8853163b..5185c24682fb6d 100644 --- a/packages/dependency-extraction-webpack-plugin/util.js +++ b/packages/dependency-extraction-webpack-plugin/util.js @@ -40,7 +40,10 @@ function defaultRequestToExternal( request ) { } if ( request.startsWith( WORDPRESS_NAMESPACE ) ) { - return [ 'wp', camelCaseDash( request.substring( WORDPRESS_NAMESPACE.length ) ) ]; + return [ + 'wp', + camelCaseDash( request.substring( WORDPRESS_NAMESPACE.length ) ), + ]; } } @@ -83,7 +86,9 @@ function defaultRequestToHandle( request ) { * @return {string} Camel-cased string. */ function camelCaseDash( string ) { - return string.replace( /-([a-z])/g, ( match, letter ) => letter.toUpperCase() ); + return string.replace( /-([a-z])/g, ( match, letter ) => + letter.toUpperCase() + ); } module.exports = { diff --git a/packages/deprecated/src/index.js b/packages/deprecated/src/index.js index 633f54c9aea658..2c067a82916d55 100644 --- a/packages/deprecated/src/index.js +++ b/packages/deprecated/src/index.js @@ -37,17 +37,15 @@ export const logged = Object.create( null ); * ``` */ export default function deprecated( feature, options = {} ) { - const { - version, - alternative, - plugin, - link, - hint, - } = options; + const { version, alternative, plugin, link, hint } = options; const pluginMessage = plugin ? ` from ${ plugin }` : ''; - const versionMessage = version ? ` and will be removed${ pluginMessage } in version ${ version }` : ''; - const useInsteadMessage = alternative ? ` Please use ${ alternative } instead.` : ''; + const versionMessage = version + ? ` and will be removed${ pluginMessage } in version ${ version }` + : ''; + const useInsteadMessage = alternative + ? ` Please use ${ alternative } instead.` + : ''; const linkMessage = link ? ` See: ${ link }` : ''; const hintMessage = hint ? ` Note: ${ hint }` : ''; const message = `${ feature } is deprecated${ versionMessage }.${ useInsteadMessage }${ linkMessage }${ hintMessage }`; diff --git a/packages/deprecated/src/test/index.js b/packages/deprecated/src/test/index.js index 6ef419bdeb43c6..94a4823dcd4a94 100644 --- a/packages/deprecated/src/test/index.js +++ b/packages/deprecated/src/test/index.js @@ -18,9 +18,7 @@ describe( 'deprecated', () => { it( 'should show a deprecation warning', () => { deprecated( 'Eating meat' ); - expect( console ).toHaveWarnedWith( - 'Eating meat is deprecated.' - ); + expect( console ).toHaveWarnedWith( 'Eating meat is deprecated.' ); } ); it( 'should show a deprecation warning with a version', () => { @@ -32,7 +30,10 @@ describe( 'deprecated', () => { } ); it( 'should show a deprecation warning with an alternative', () => { - deprecated( 'Eating meat', { version: '2020.01.01', alternative: 'vegetables' } ); + deprecated( 'Eating meat', { + version: '2020.01.01', + alternative: 'vegetables', + } ); expect( console ).toHaveWarnedWith( 'Eating meat is deprecated and will be removed in version 2020.01.01. Please use vegetables instead.' diff --git a/packages/docgen/bin/cli.js b/packages/docgen/bin/cli.js index e5e67d5d89f3cb..7707b417d2f52d 100755 --- a/packages/docgen/bin/cli.js +++ b/packages/docgen/bin/cli.js @@ -4,40 +4,53 @@ const docgen = require( '../src' ); const optionator = require( 'optionator' )( { prepend: 'Usage: node <path-to-docgen> <relative-path-to-entry-point>', - options: [ { - option: 'formatter', - type: 'String', - description: 'A custom function to format the generated documentation. By default, a Markdown formatter will be used.', - }, { - option: 'output', - type: 'String', - description: 'Output file to contain the API documentation.', - }, { - option: 'ignore', - type: 'RegExp', - description: 'A regular expression used to ignore symbols whose name match it.', - }, { - option: 'to-section', - type: 'String', - description: 'Append generated documentation to this section in the Markdown output. To be used by the default Markdown formatter.', - dependsOn: 'output', - }, { - option: 'to-token', - type: 'Boolean', - description: 'Embed generated documentation within this token in the Markdown output. To be used by the default Markdown formatter.', - dependsOn: 'output', - }, { - option: 'use-token', - type: 'String', - default: 'Autogenerated API docs', - description: 'Add this string to the start/end tokens.', - dependsOn: 'to-token', - }, { - option: 'debug', - type: 'Boolean', - default: false, - description: 'Run in debug mode, which outputs some intermediate files useful for debugging.', - } ], + options: [ + { + option: 'formatter', + type: 'String', + description: + 'A custom function to format the generated documentation. By default, a Markdown formatter will be used.', + }, + { + option: 'output', + type: 'String', + description: 'Output file to contain the API documentation.', + }, + { + option: 'ignore', + type: 'RegExp', + description: + 'A regular expression used to ignore symbols whose name match it.', + }, + { + option: 'to-section', + type: 'String', + description: + 'Append generated documentation to this section in the Markdown output. To be used by the default Markdown formatter.', + dependsOn: 'output', + }, + { + option: 'to-token', + type: 'Boolean', + description: + 'Embed generated documentation within this token in the Markdown output. To be used by the default Markdown formatter.', + dependsOn: 'output', + }, + { + option: 'use-token', + type: 'String', + default: 'Autogenerated API docs', + description: 'Add this string to the start/end tokens.', + dependsOn: 'to-token', + }, + { + option: 'debug', + type: 'Boolean', + default: false, + description: + 'Run in debug mode, which outputs some intermediate files useful for debugging.', + }, + ], } ); const options = optionator.parseArgv( process.argv ); diff --git a/packages/docgen/src/engine.js b/packages/docgen/src/engine.js index e2510af0517dad..55b50c8c0b7bba 100644 --- a/packages/docgen/src/engine.js +++ b/packages/docgen/src/engine.js @@ -9,36 +9,40 @@ const { flatten } = require( 'lodash' ); */ const getIntermediateRepresentation = require( './get-intermediate-representation' ); -const getAST = ( source ) => espree.parse( source, { - attachComment: true, - loc: true, - ecmaVersion: 2018, - ecmaFeatures: { - jsx: true, - }, - sourceType: 'module', -} ); +const getAST = ( source ) => + espree.parse( source, { + attachComment: true, + loc: true, + ecmaVersion: 2018, + ecmaFeatures: { + jsx: true, + }, + sourceType: 'module', + } ); -const getExportTokens = ( ast ) => ast.body.filter( - ( node ) => [ - 'ExportNamedDeclaration', - 'ExportDefaultDeclaration', - 'ExportAllDeclaration', - ].some( ( declaration ) => declaration === node.type ) -); +const getExportTokens = ( ast ) => + ast.body.filter( ( node ) => + [ + 'ExportNamedDeclaration', + 'ExportDefaultDeclaration', + 'ExportAllDeclaration', + ].some( ( declaration ) => declaration === node.type ) + ); const engine = ( path, code, getIRFromPath = () => {} ) => { const result = {}; result.ast = getAST( code ); result.tokens = getExportTokens( result.ast ); - result.ir = flatten( result.tokens.map( - ( token ) => getIntermediateRepresentation( - path, - token, - result.ast, - getIRFromPath + result.ir = flatten( + result.tokens.map( ( token ) => + getIntermediateRepresentation( + path, + token, + result.ast, + getIRFromPath + ) ) - ) ); + ); return result; }; diff --git a/packages/docgen/src/get-export-entries.js b/packages/docgen/src/get-export-entries.js index a69811b2b6545d..95c6a3931817cb 100644 --- a/packages/docgen/src/get-export-entries.js +++ b/packages/docgen/src/get-export-entries.js @@ -37,34 +37,40 @@ module.exports = function( token ) { } return name; }; - return [ { - localName: getLocalName( token ), - exportName: 'default', - module: null, - lineStart: token.loc.start.line, - lineEnd: token.loc.end.line, - } ]; + return [ + { + localName: getLocalName( token ), + exportName: 'default', + module: null, + lineStart: token.loc.start.line, + lineEnd: token.loc.end.line, + }, + ]; } if ( token.type === 'ExportAllDeclaration' ) { - return [ { - localName: '*', - exportName: null, - module: token.source.value, - lineStart: token.loc.start.line, - lineEnd: token.loc.end.line, - } ]; + return [ + { + localName: '*', + exportName: null, + module: token.source.value, + lineStart: token.loc.start.line, + lineEnd: token.loc.end.line, + }, + ]; } const name = []; if ( token.declaration === null ) { - token.specifiers.forEach( ( specifier ) => name.push( { - localName: specifier.local.name, - exportName: specifier.exported.name, - module: get( token.source, [ 'value' ], null ), - lineStart: specifier.loc.start.line, - lineEnd: specifier.loc.end.line, - } ) ); + token.specifiers.forEach( ( specifier ) => + name.push( { + localName: specifier.local.name, + exportName: specifier.exported.name, + module: get( token.source, [ 'value' ], null ), + lineStart: specifier.loc.start.line, + lineEnd: specifier.loc.end.line, + } ) + ); return name; } diff --git a/packages/docgen/src/get-intermediate-representation.js b/packages/docgen/src/get-intermediate-representation.js index eee7775d69979a..725cdc1ab8e5fe 100644 --- a/packages/docgen/src/get-intermediate-representation.js +++ b/packages/docgen/src/get-intermediate-representation.js @@ -15,12 +15,10 @@ const NAMESPACE_EXPORT = '*'; const DEFAULT_EXPORT = 'default'; const hasClassWithName = ( node, name ) => - node.type === 'ClassDeclaration' && - node.id.name === name; + node.type === 'ClassDeclaration' && node.id.name === name; const hasFunctionWithName = ( node, name ) => - node.type === 'FunctionDeclaration' && - node.id.name === name; + node.type === 'FunctionDeclaration' && node.id.name === name; const hasVariableWithName = ( node, name ) => node.type === 'VariableDeclaration' && @@ -34,11 +32,10 @@ const hasVariableWithName = ( node, name ) => } ); const hasNamedExportWithName = ( node, name ) => - node.type === 'ExportNamedDeclaration' && ( - ( node.declaration && hasClassWithName( node.declaration, name ) ) || + node.type === 'ExportNamedDeclaration' && + ( ( node.declaration && hasClassWithName( node.declaration, name ) ) || ( node.declaration && hasFunctionWithName( node.declaration, name ) ) || - ( node.declaration && hasVariableWithName( node.declaration, name ) ) - ); + ( node.declaration && hasVariableWithName( node.declaration, name ) ) ); const hasImportWithName = ( node, name ) => node.type === 'ImportDeclaration' && @@ -49,10 +46,16 @@ const isImportDeclaration = ( node ) => node.type === 'ImportDeclaration'; const someImportMatchesName = ( name, token ) => { let matches = false; token.specifiers.forEach( ( specifier ) => { - if ( ( specifier.type === 'ImportDefaultSpecifier' ) && ( name === 'default' ) ) { + if ( + specifier.type === 'ImportDefaultSpecifier' && + name === 'default' + ) { matches = true; } - if ( ( specifier.type === 'ImportSpecifier' ) && ( name === specifier.imported.name ) ) { + if ( + specifier.type === 'ImportSpecifier' && + name === specifier.imported.name + ) { matches = true; } } ); @@ -61,7 +64,8 @@ const someImportMatchesName = ( name, token ) => { const someEntryMatchesName = ( name, entry, token ) => ( token.type === 'ExportNamedDeclaration' && entry.localName === name ) || - ( token.type === 'ImportDeclaration' && someImportMatchesName( name, token ) ); + ( token.type === 'ImportDeclaration' && + someImportMatchesName( name, token ) ); const getJSDocFromDependency = ( token, entry, parseDependency ) => { let doc; @@ -69,7 +73,9 @@ const getJSDocFromDependency = ( token, entry, parseDependency ) => { if ( entry.localName === NAMESPACE_EXPORT ) { doc = ir.filter( ( { name } ) => name !== DEFAULT_EXPORT ); } else { - doc = ir.find( ( { name } ) => someEntryMatchesName( name, entry, token ) ); + doc = ir.find( ( { name } ) => + someEntryMatchesName( name, entry, token ) + ); } return doc; }; @@ -78,18 +84,20 @@ const getJSDoc = ( token, entry, ast, parseDependency ) => { let doc; if ( entry.localName !== NAMESPACE_EXPORT ) { doc = getJSDocFromToken( token ); - if ( ( doc !== undefined ) ) { + if ( doc !== undefined ) { return doc; } } if ( entry && entry.module === null ) { const candidates = ast.body.filter( ( node ) => { - return hasClassWithName( node, entry.localName ) || + return ( + hasClassWithName( node, entry.localName ) || hasFunctionWithName( node, entry.localName ) || hasVariableWithName( node, entry.localName ) || hasNamedExportWithName( node, entry.localName ) || - hasImportWithName( node, entry.localName ); + hasImportWithName( node, entry.localName ) + ); } ); if ( candidates.length !== 1 ) { return doc; @@ -121,7 +129,12 @@ const getJSDoc = ( token, entry, ast, parseDependency ) => { * * @return {Object} Intermediate Representation in JSON. */ -module.exports = function( path, token, ast = { body: [] }, parseDependency = () => {} ) { +module.exports = function( + path, + token, + ast = { body: [] }, + parseDependency = () => {} +) { const exportEntries = getExportEntries( token ); const ir = []; exportEntries.forEach( ( entry ) => { diff --git a/packages/docgen/src/get-type-as-string.js b/packages/docgen/src/get-type-as-string.js index 64bfaefd00d529..6022ded3e90320 100644 --- a/packages/docgen/src/get-type-as-string.js +++ b/packages/docgen/src/get-type-as-string.js @@ -18,9 +18,12 @@ const getType = function( param, defaultValue ) { } else if ( param.type === 'NullableType' ) { return `?${ getType( param.expression, defaultValue ) }`; } else if ( param.type === 'TypeApplication' ) { - return `${ getType( param.expression, defaultValue ) }<${ - param.applications.map( ( application ) => getType( application ) ).join( ',' ) - }>`; + return `${ getType( + param.expression, + defaultValue + ) }<${ param.applications + .map( ( application ) => getType( application ) ) + .join( ',' ) }>`; } else if ( param.type === 'OptionalType' ) { return `[${ getType( param.expression, defaultValue ) }]`; } diff --git a/packages/docgen/src/index.js b/packages/docgen/src/index.js index 475a458955d070..c4c34ded985378 100644 --- a/packages/docgen/src/index.js +++ b/packages/docgen/src/index.js @@ -51,7 +51,11 @@ const processFile = ( rootDir, inputFile ) => { const data = fs.readFileSync( inputFile, 'utf8' ); currentFileStack.push( inputFile ); const relativePath = path.relative( rootDir, inputFile ); - const result = engine( relativePath, data, getIRFromRelativePath( rootDir, last( currentFileStack ) ) ); + const result = engine( + relativePath, + data, + getIRFromRelativePath( rootDir, last( currentFileStack ) ) + ); currentFileStack.pop(); return result; } catch ( e ) { @@ -61,7 +65,13 @@ const processFile = ( rootDir, inputFile ) => { } }; -const runCustomFormatter = ( customFormatterFile, rootDir, doc, symbols, headingTitle ) => { +const runCustomFormatter = ( + customFormatterFile, + rootDir, + doc, + symbols, + headingTitle +) => { try { const customFormatter = require( customFormatterFile ); const output = customFormatter( rootDir, doc, symbols, headingTitle ); @@ -97,9 +107,9 @@ module.exports = function( sourceFile, options ) { const ast = inputBase + '-ast.json'; const tokens = inputBase + '-exports.json'; const ir = inputBase + '-ir.json'; - const doc = options.output ? - path.join( processDir, options.output ) : - inputBase + '-api.md'; + const doc = options.output + ? path.join( processDir, options.output ) + : inputBase + '-api.md'; // Process const result = processFile( processDir, sourceFile ); @@ -117,14 +127,22 @@ module.exports = function( sourceFile, options ) { // Ouput if ( result === undefined ) { - process.stdout.write( '\nFile was processed, but contained no ES6 module exports:' ); + process.stdout.write( + '\nFile was processed, but contained no ES6 module exports:' + ); process.stdout.write( `\n${ sourceFile }` ); process.stdout.write( '\n\n' ); process.exit( 0 ); } if ( options.formatter ) { - runCustomFormatter( path.join( processDir, options.formatter ), processDir, doc, filteredIR, 'API' ); + runCustomFormatter( + path.join( processDir, options.formatter ), + processDir, + doc, + filteredIR, + 'API' + ); } else { defaultMarkdownFormatter( options, processDir, doc, filteredIR, 'API' ); } diff --git a/packages/docgen/src/is-symbol-private.js b/packages/docgen/src/is-symbol-private.js index a26974f3d42559..7c7634e5cf4451 100644 --- a/packages/docgen/src/is-symbol-private.js +++ b/packages/docgen/src/is-symbol-private.js @@ -11,6 +11,7 @@ const getSymbolTagsByName = require( './get-symbol-tags-by-name' ); * * @return {boolean} Whether symbol is private. */ -const isSymbolPrivate = ( symbol ) => getSymbolTagsByName( symbol, 'private' ).length > 0; +const isSymbolPrivate = ( symbol ) => + getSymbolTagsByName( symbol, 'private' ).length > 0; module.exports = isSymbolPrivate; diff --git a/packages/docgen/src/markdown/embed.js b/packages/docgen/src/markdown/embed.js index 9b52064c3377a4..d75cc17847e18b 100644 --- a/packages/docgen/src/markdown/embed.js +++ b/packages/docgen/src/markdown/embed.js @@ -5,7 +5,10 @@ const { findLast } = require( 'lodash' ); const getHeadingIndex = ( ast, index ) => { const astBeforeIndex = ast.children.slice( 0, index ); - const lastHeading = findLast( astBeforeIndex, ( node ) => node.type === 'heading' ); + const lastHeading = findLast( + astBeforeIndex, + ( node ) => node.type === 'heading' + ); return lastHeading ? lastHeading.depth : 1; }; @@ -42,7 +45,11 @@ const embed = function( token, targetAst, newContentAst ) { node.depth = headingIndex + 1; } } ); - targetAst.children.splice( startIndex + 1, endIndex - startIndex - 1, newContentAst ); + targetAst.children.splice( + startIndex + 1, + endIndex - startIndex - 1, + newContentAst + ); return true; } return false; diff --git a/packages/docgen/src/markdown/formatter.js b/packages/docgen/src/markdown/formatter.js index 13f5b3ff0f298c..c6be6698f42f22 100644 --- a/packages/docgen/src/markdown/formatter.js +++ b/packages/docgen/src/markdown/formatter.js @@ -4,14 +4,13 @@ const getSymbolTagsByName = require( '../get-symbol-tags-by-name' ); const cleanSpaces = ( paragraph ) => - paragraph ? - paragraph.split( '\n' ).map( - ( sentence ) => sentence.trim() - ).reduce( - ( acc, current ) => acc + ' ' + current, - '' - ).trim() : - ''; + paragraph + ? paragraph + .split( '\n' ) + .map( ( sentence ) => sentence.trim() ) + .reduce( ( acc, current ) => acc + ' ' + current, '' ) + .trim() + : ''; const formatTag = ( title, tags, formatter, docs ) => { if ( tags && tags.length > 0 ) { @@ -30,18 +29,21 @@ const formatExamples = ( tags, docs ) => { docs.push( '*Usage*' ); docs.push( '\n' ); docs.push( '\n' ); - docs.push( ...tags.map( - ( tag ) => `${ tag.description }` - ).join( '\n\n' ) ); + docs.push( + ...tags.map( ( tag ) => `${ tag.description }` ).join( '\n\n' ) + ); } }; const formatDeprecated = ( tags, docs ) => { if ( tags && tags.length > 0 ) { docs.push( '\n' ); - docs.push( ...tags.map( - ( tag ) => `\n> **Deprecated** ${ cleanSpaces( tag.description ) }` - ) ); + docs.push( + ...tags.map( + ( tag ) => + `\n> **Deprecated** ${ cleanSpaces( tag.description ) }` + ) + ); } }; @@ -63,7 +65,13 @@ const getTypeOutput = ( type ) => { return type ? `\`${ type }\`` : '(unknown type)'; }; -module.exports = function( rootDir, docPath, symbols, headingTitle, headingStartIndex ) { +module.exports = function( + rootDir, + docPath, + symbols, + headingTitle, + headingStartIndex +) { const docs = []; let headingIndex = headingStartIndex || 1; if ( headingTitle ) { @@ -86,7 +94,10 @@ module.exports = function( rootDir, docPath, symbols, headingTitle, headingStart if ( symbols && symbols.length > 0 ) { symbols.forEach( ( symbol ) => { docs.push( getSymbolHeading( symbol.name ) ); - formatDeprecated( getSymbolTagsByName( symbol, 'deprecated' ), docs ); + formatDeprecated( + getSymbolTagsByName( symbol, 'deprecated' ), + docs + ); formatDescription( symbol.description, docs ); formatTag( 'Related', @@ -98,19 +109,28 @@ module.exports = function( rootDir, docPath, symbols, headingTitle, headingStart formatTag( 'Type', getSymbolTagsByName( symbol, 'type' ), - ( tag ) => `\n- ${ getTypeOutput( tag.type ) } ${ cleanSpaces( tag.description ) }`, + ( tag ) => + `\n- ${ getTypeOutput( tag.type ) } ${ cleanSpaces( + tag.description + ) }`, docs ); formatTag( 'Parameters', getSymbolTagsByName( symbol, 'param' ), - ( tag ) => `\n- *${ tag.name }* ${ getTypeOutput( tag.type ) }: ${ cleanSpaces( tag.description ) }`, + ( tag ) => + `\n- *${ tag.name }* ${ getTypeOutput( + tag.type + ) }: ${ cleanSpaces( tag.description ) }`, docs ); formatTag( 'Returns', getSymbolTagsByName( symbol, 'return' ), - ( tag ) => `\n- ${ getTypeOutput( tag.type ) }: ${ cleanSpaces( tag.description ) }`, + ( tag ) => + `\n- ${ getTypeOutput( tag.type ) }: ${ cleanSpaces( + tag.description + ) }`, docs ); formatTag( @@ -122,7 +142,10 @@ module.exports = function( rootDir, docPath, symbols, headingTitle, headingStart formatTag( 'Properties', getSymbolTagsByName( symbol, 'property' ), - ( tag ) => `\n- *${ tag.name }* ${ getTypeOutput( tag.type ) }: ${ cleanSpaces( tag.description ) }`, + ( tag ) => + `\n- *${ tag.name }* ${ getTypeOutput( + tag.type + ) }: ${ cleanSpaces( tag.description ) }`, docs ); docs.push( '\n' ); diff --git a/packages/docgen/src/markdown/index.js b/packages/docgen/src/markdown/index.js index b73041903bc7a2..fc0a09a15a3986 100644 --- a/packages/docgen/src/markdown/index.js +++ b/packages/docgen/src/markdown/index.js @@ -15,19 +15,39 @@ const embed = require( './embed' ); const appendOrEmbedContents = ( { options, newContents } ) => { return function transform( targetAst, file, next ) { - if ( options.toSection && ! inject( options.toSection, targetAst, newContents ) ) { - return next( new Error( `Heading ${ options.toSection } not found.` ) ); - } else if ( options.toToken && ! embed( options.useToken, targetAst, newContents ) ) { - return next( new Error( `Start and/or end tokens for ${ options.useToken } not found.` ) ); + if ( + options.toSection && + ! inject( options.toSection, targetAst, newContents ) + ) { + return next( + new Error( `Heading ${ options.toSection } not found.` ) + ); + } else if ( + options.toToken && + ! embed( options.useToken, targetAst, newContents ) + ) { + return next( + new Error( + `Start and/or end tokens for ${ options.useToken } not found.` + ) + ); } next(); }; }; -module.exports = function( options, processDir, doc, filteredIR, headingTitle ) { +module.exports = function( + options, + processDir, + doc, + filteredIR, + headingTitle +) { if ( options.toSection || options.toToken ) { const currentReadmeFile = fs.readFileSync( options.output, 'utf8' ); - const newContents = unified().use( remarkParser ).parse( formatter( processDir, doc, filteredIR, null ) ); + const newContents = unified() + .use( remarkParser ) + .parse( formatter( processDir, doc, filteredIR, null ) ); remark() .use( { settings: { commonmark: true } } ) .use( appendOrEmbedContents, { options, newContents } ) diff --git a/packages/docgen/src/test/fixtures/default-undocumented-oneliner/code.js b/packages/docgen/src/test/fixtures/default-undocumented-oneliner/code.js index 11e8966dd62d1c..4e9e2fe5a3279c 100644 --- a/packages/docgen/src/test/fixtures/default-undocumented-oneliner/code.js +++ b/packages/docgen/src/test/fixtures/default-undocumented-oneliner/code.js @@ -1,2 +1,2 @@ // This comment should be ignored -export default function() { } +export default function() {} diff --git a/packages/docgen/src/test/fixtures/named-default-exported/module-code.js b/packages/docgen/src/test/fixtures/named-default-exported/module-code.js index 92127f1f850593..7c73c90be0adbd 100644 --- a/packages/docgen/src/test/fixtures/named-default-exported/module-code.js +++ b/packages/docgen/src/test/fixtures/named-default-exported/module-code.js @@ -1,4 +1,4 @@ /** * Module declaration. */ -export default function( ) {} +export default function() {} diff --git a/packages/docgen/src/test/fixtures/named-default/module-code.js b/packages/docgen/src/test/fixtures/named-default/module-code.js index 92127f1f850593..7c73c90be0adbd 100644 --- a/packages/docgen/src/test/fixtures/named-default/module-code.js +++ b/packages/docgen/src/test/fixtures/named-default/module-code.js @@ -1,4 +1,4 @@ /** * Module declaration. */ -export default function( ) {} +export default function() {} diff --git a/packages/docgen/src/test/fixtures/named-identifier-destructuring/code.js b/packages/docgen/src/test/fixtures/named-identifier-destructuring/code.js index d5e1b5d46160d9..5dcad05eb2e4ef 100644 --- a/packages/docgen/src/test/fixtures/named-identifier-destructuring/code.js +++ b/packages/docgen/src/test/fixtures/named-identifier-destructuring/code.js @@ -1,6 +1,6 @@ /** * My declaration example. */ -const { someDeclaration } = { someDeclaration: () => { } }; +const { someDeclaration } = { someDeclaration: () => {} }; export { someDeclaration as myDeclaration }; diff --git a/packages/docgen/src/test/fixtures/named-identifiers-and-inline/code.js b/packages/docgen/src/test/fixtures/named-identifiers-and-inline/code.js index 10b836d597856f..f22043cac6503d 100644 --- a/packages/docgen/src/test/fixtures/named-identifiers-and-inline/code.js +++ b/packages/docgen/src/test/fixtures/named-identifiers-and-inline/code.js @@ -14,4 +14,3 @@ export { functionDeclaration, ClassDeclaration }; * Variable declaration example. */ export const variableDeclaration = true; - diff --git a/packages/docgen/src/test/fixtures/named-variable/code.js b/packages/docgen/src/test/fixtures/named-variable/code.js index e89c8a9751394c..8b6dce4c578e43 100644 --- a/packages/docgen/src/test/fixtures/named-variable/code.js +++ b/packages/docgen/src/test/fixtures/named-variable/code.js @@ -2,4 +2,3 @@ * My declaration example. */ export const myDeclaration = true; - diff --git a/packages/docgen/src/test/fixtures/named-variables/code.js b/packages/docgen/src/test/fixtures/named-variables/code.js index 8f56e0aff46e5a..4f49ac1e883e11 100644 --- a/packages/docgen/src/test/fixtures/named-variables/code.js +++ b/packages/docgen/src/test/fixtures/named-variables/code.js @@ -3,4 +3,3 @@ */ export const firstDeclaration = true, secondDeclaration = 42; - diff --git a/packages/docgen/src/test/formatter-markdown.js b/packages/docgen/src/test/formatter-markdown.js index 9e6d669d4fa0af..e88fc75ce17f7e 100644 --- a/packages/docgen/src/test/formatter-markdown.js +++ b/packages/docgen/src/test/formatter-markdown.js @@ -7,26 +7,33 @@ describe( 'Formatter', () => { it( 'returns markdown', () => { const rootDir = '/home/my-path'; const docPath = '/home/my-path/docs'; - const docs = formatter( rootDir, docPath + '-api.md', [ { - path: docPath + '-code.js', - description: 'My declaration example.', - tags: [ + const docs = formatter( + rootDir, + docPath + '-api.md', + [ { - title: 'param', - description: 'First declaration parameter.', - type: 'number', - name: 'firstParam', - }, - { - title: 'return', - description: 'The result of the declaration.', - type: 'number', + path: docPath + '-code.js', + description: 'My declaration example.', + tags: [ + { + title: 'param', + description: 'First declaration parameter.', + type: 'number', + name: 'firstParam', + }, + { + title: 'return', + description: 'The result of the declaration.', + type: 'number', + }, + ], + name: 'myDeclaration', + lineStart: 1, + lineEnd: 2, }, ], - name: 'myDeclaration', - lineStart: 1, - lineEnd: 2, - } ], 'API docs' ); + 'API docs' + ); expect( docs ).toBe( '# API docs\n\n<a name="myDeclaration" href="#myDeclaration">#</a> **myDeclaration**\n\nMy declaration example.\n\n*Parameters*\n\n- *firstParam* `number`: First declaration parameter.\n\n*Returns*\n\n- `number`: The result of the declaration.\n' ); @@ -35,22 +42,30 @@ describe( 'Formatter', () => { it( 'handles unknown types from parse error', () => { const rootDir = '/home/my-path'; const docPath = '/home/my-path/docs'; - const docs = formatter( rootDir, docPath + '-api.md', [ { - path: null, - name: 'default', - description: 'Function invoking callback after delay with current timestamp in milliseconds\nsince epoch.', - tags: [ + const docs = formatter( + rootDir, + docPath + '-api.md', + [ { - description: 'Callback function.', - errors: [ 'unexpected token' ], - name: 'callback', - title: 'param', - type: null, + path: null, + name: 'default', + description: + 'Function invoking callback after delay with current timestamp in milliseconds\nsince epoch.', + tags: [ + { + description: 'Callback function.', + errors: [ 'unexpected token' ], + name: 'callback', + title: 'param', + type: null, + }, + ], + lineStart: 7, + lineEnd: 9, }, ], - lineStart: 7, - lineEnd: 9, - } ], 'API docs' ); + 'API docs' + ); expect( docs ).toBe( '# API docs\n\n<a name="default" href="#default">#</a> **default**\n\nFunction invoking callback after delay with current timestamp in milliseconds\nsince epoch.\n\n*Parameters*\n\n- *callback* (unknown type): Callback function.\n' ); diff --git a/packages/docgen/src/test/get-export-entries.js b/packages/docgen/src/test/get-export-entries.js index b1e06f9d145a9c..10bc46916494bc 100644 --- a/packages/docgen/src/test/get-export-entries.js +++ b/packages/docgen/src/test/get-export-entries.js @@ -12,7 +12,10 @@ const getExportEntries = require( '../get-export-entries' ); describe( 'Export entries', function() { it( 'default class (anonymous)', () => { const token = fs.readFileSync( - path.join( __dirname, './fixtures/default-class-anonymous/exports.json' ), + path.join( + __dirname, + './fixtures/default-class-anonymous/exports.json' + ), 'utf-8' ); const name = getExportEntries( JSON.parse( token ) ); @@ -28,7 +31,10 @@ describe( 'Export entries', function() { it( 'default class (named)', function() { const token = fs.readFileSync( - path.join( __dirname, './fixtures/default-class-named/exports.json' ), + path.join( + __dirname, + './fixtures/default-class-named/exports.json' + ), 'utf-8' ); const name = getExportEntries( JSON.parse( token ) ); @@ -44,7 +50,10 @@ describe( 'Export entries', function() { it( 'default function (anonymous)', function() { const token = fs.readFileSync( - path.join( __dirname, './fixtures/default-function-anonymous/exports.json' ), + path.join( + __dirname, + './fixtures/default-function-anonymous/exports.json' + ), 'utf-8' ); const name = getExportEntries( JSON.parse( token ) ); @@ -60,7 +69,10 @@ describe( 'Export entries', function() { it( 'default function (named)', function() { const token = fs.readFileSync( - path.join( __dirname, './fixtures/default-function-named/exports.json' ), + path.join( + __dirname, + './fixtures/default-function-named/exports.json' + ), 'utf-8' ); const name = getExportEntries( JSON.parse( token ) ); @@ -76,7 +88,10 @@ describe( 'Export entries', function() { it( 'default identifier', function() { const token = fs.readFileSync( - path.join( __dirname, './fixtures/default-identifier/exports.json' ), + path.join( + __dirname, + './fixtures/default-identifier/exports.json' + ), 'utf-8' ); const name = getExportEntries( JSON.parse( token ) ); @@ -92,7 +107,10 @@ describe( 'Export entries', function() { it( 'default import (named)', function() { const token = fs.readFileSync( - path.join( __dirname, './fixtures/default-import-named/exports.json' ), + path.join( + __dirname, + './fixtures/default-import-named/exports.json' + ), 'utf-8' ); const name = getExportEntries( JSON.parse( token ) ); @@ -108,7 +126,10 @@ describe( 'Export entries', function() { it( 'default import (default)', function() { const token = fs.readFileSync( - path.join( __dirname, './fixtures/default-import-default/exports.json' ), + path.join( + __dirname, + './fixtures/default-import-default/exports.json' + ), 'utf-8' ); const name = getExportEntries( JSON.parse( token ) ); @@ -124,7 +145,10 @@ describe( 'Export entries', function() { it( 'default named export', function() { const tokens = fs.readFileSync( - path.join( __dirname, './fixtures/default-named-export/exports.json' ), + path.join( + __dirname, + './fixtures/default-named-export/exports.json' + ), 'utf-8' ); const namedExport = getExportEntries( JSON.parse( tokens )[ 0 ] ); @@ -197,7 +221,10 @@ describe( 'Export entries', function() { it( 'named default (exported)', function() { const token = fs.readFileSync( - path.join( __dirname, './fixtures/named-default-exported/exports.json' ), + path.join( + __dirname, + './fixtures/named-default-exported/exports.json' + ), 'utf-8' ); const name = getExportEntries( JSON.parse( token ) ); @@ -242,7 +269,10 @@ describe( 'Export entries', function() { lineEnd: 6, } ); const tokenObject = fs.readFileSync( - path.join( __dirname, './fixtures/named-identifier-destructuring/exports.json' ), + path.join( + __dirname, + './fixtures/named-identifier-destructuring/exports.json' + ), 'utf-8' ); const nameObject = getExportEntries( JSON.parse( tokenObject ) ); @@ -263,44 +293,82 @@ describe( 'Export entries', function() { ); const name = getExportEntries( JSON.parse( token ) ); expect( name ).toHaveLength( 3 ); - expect( name[ 0 ] ).toEqual( - { localName: 'functionDeclaration', exportName: 'functionDeclaration', module: null, lineStart: 16, lineEnd: 16 } - ); - expect( name[ 1 ] ).toEqual( - { localName: 'variableDeclaration', exportName: 'variableDeclaration', module: null, lineStart: 16, lineEnd: 16 }, - ); - expect( name[ 2 ] ).toEqual( - { localName: 'ClassDeclaration', exportName: 'ClassDeclaration', module: null, lineStart: 16, lineEnd: 16 }, - ); + expect( name[ 0 ] ).toEqual( { + localName: 'functionDeclaration', + exportName: 'functionDeclaration', + module: null, + lineStart: 16, + lineEnd: 16, + } ); + expect( name[ 1 ] ).toEqual( { + localName: 'variableDeclaration', + exportName: 'variableDeclaration', + module: null, + lineStart: 16, + lineEnd: 16, + } ); + expect( name[ 2 ] ).toEqual( { + localName: 'ClassDeclaration', + exportName: 'ClassDeclaration', + module: null, + lineStart: 16, + lineEnd: 16, + } ); const tokenIdentifiersAndInline = fs.readFileSync( - path.join( __dirname, './fixtures/named-identifiers-and-inline/exports.json' ), + path.join( + __dirname, + './fixtures/named-identifiers-and-inline/exports.json' + ), 'utf-8' ); - const nameInline0 = getExportEntries( JSON.parse( tokenIdentifiersAndInline )[ 0 ] ); - expect( nameInline0 ).toHaveLength( 2 ); - expect( nameInline0[ 0 ] ).toEqual( - { localName: 'functionDeclaration', exportName: 'functionDeclaration', module: null, lineStart: 11, lineEnd: 11 }, + const nameInline0 = getExportEntries( + JSON.parse( tokenIdentifiersAndInline )[ 0 ] ); - expect( nameInline0[ 1 ] ).toEqual( - { localName: 'ClassDeclaration', exportName: 'ClassDeclaration', module: null, lineStart: 11, lineEnd: 11 }, + expect( nameInline0 ).toHaveLength( 2 ); + expect( nameInline0[ 0 ] ).toEqual( { + localName: 'functionDeclaration', + exportName: 'functionDeclaration', + module: null, + lineStart: 11, + lineEnd: 11, + } ); + expect( nameInline0[ 1 ] ).toEqual( { + localName: 'ClassDeclaration', + exportName: 'ClassDeclaration', + module: null, + lineStart: 11, + lineEnd: 11, + } ); + const nameInline1 = getExportEntries( + JSON.parse( tokenIdentifiersAndInline )[ 1 ] ); - const nameInline1 = getExportEntries( JSON.parse( tokenIdentifiersAndInline )[ 1 ] ); expect( nameInline1 ).toHaveLength( 1 ); - expect( nameInline1[ 0 ] ).toEqual( - { localName: 'variableDeclaration', exportName: 'variableDeclaration', module: null, lineStart: 16, lineEnd: 16 }, - ); + expect( nameInline1[ 0 ] ).toEqual( { + localName: 'variableDeclaration', + exportName: 'variableDeclaration', + module: null, + lineStart: 16, + lineEnd: 16, + } ); } ); it( 'named import namespace', function() { const token = fs.readFileSync( - path.join( __dirname, './fixtures/named-import-namespace/exports.json' ), + path.join( + __dirname, + './fixtures/named-import-namespace/exports.json' + ), 'utf-8' ); const name = getExportEntries( JSON.parse( token ) ); expect( name ).toHaveLength( 1 ); - expect( name[ 0 ] ).toEqual( - { localName: 'variables', exportName: 'variables', module: null, lineStart: 3, lineEnd: 3 }, - ); + expect( name[ 0 ] ).toEqual( { + localName: 'variables', + exportName: 'variables', + module: null, + lineStart: 3, + lineEnd: 3, + } ); } ); it( 'named variable', function() { @@ -326,12 +394,20 @@ describe( 'Export entries', function() { ); const name = getExportEntries( JSON.parse( token ) ); expect( name ).toHaveLength( 2 ); - expect( name[ 0 ] ).toEqual( - { localName: 'firstDeclaration', exportName: 'firstDeclaration', module: null, lineStart: 4, lineEnd: 5 }, - ); - expect( name[ 1 ] ).toEqual( - { localName: 'secondDeclaration', exportName: 'secondDeclaration', module: null, lineStart: 4, lineEnd: 5 }, - ); + expect( name[ 0 ] ).toEqual( { + localName: 'firstDeclaration', + exportName: 'firstDeclaration', + module: null, + lineStart: 4, + lineEnd: 5, + } ); + expect( name[ 1 ] ).toEqual( { + localName: 'secondDeclaration', + exportName: 'secondDeclaration', + module: null, + lineStart: 4, + lineEnd: 5, + } ); } ); it( 'namespace (*)', function() { diff --git a/packages/docgen/src/test/get-intermediate-representation.js b/packages/docgen/src/test/get-intermediate-representation.js index ce284139e702b4..ab1914dbd397f4 100644 --- a/packages/docgen/src/test/get-intermediate-representation.js +++ b/packages/docgen/src/test/get-intermediate-representation.js @@ -12,7 +12,10 @@ const getIntermediateRepresentation = require( '../get-intermediate-representati describe( 'Intermediate Representation', function() { it( 'undocumented', function() { const token = fs.readFileSync( - path.join( __dirname, './fixtures/default-undocumented-nocomments/exports.json' ), + path.join( + __dirname, + './fixtures/default-undocumented-nocomments/exports.json' + ), 'utf-8' ); const ir = getIntermediateRepresentation( null, JSON.parse( token ) ); @@ -26,10 +29,16 @@ describe( 'Intermediate Representation', function() { lineEnd: 3, } ); const tokenOneliner = fs.readFileSync( - path.join( __dirname, './fixtures/default-undocumented-oneliner/exports.json' ), + path.join( + __dirname, + './fixtures/default-undocumented-oneliner/exports.json' + ), 'utf-8' ); - const irOneliner = getIntermediateRepresentation( null, JSON.parse( tokenOneliner ) ); + const irOneliner = getIntermediateRepresentation( + null, + JSON.parse( tokenOneliner ) + ); expect( irOneliner ).toHaveLength( 1 ); expect( irOneliner[ 0 ] ).toEqual( { path: null, @@ -42,16 +51,22 @@ describe( 'Intermediate Representation', function() { } ); it( 'type parse error handling', () => { - const token = JSON.parse( fs.readFileSync( - path.join( __dirname, './fixtures/default-parse-error/exports.json' ), - 'utf-8' - ) )[ 0 ]; + const token = JSON.parse( + fs.readFileSync( + path.join( + __dirname, + './fixtures/default-parse-error/exports.json' + ), + 'utf-8' + ) + )[ 0 ]; const ir = getIntermediateRepresentation( null, token ); expect( ir ).toHaveLength( 1 ); expect( ir[ 0 ] ).toEqual( { path: null, name: 'default', - description: 'Function invoking callback after delay with current timestamp in milliseconds\nsince epoch.', + description: + 'Function invoking callback after delay with current timestamp in milliseconds\nsince epoch.', tags: [ { description: 'Callback function.', @@ -69,10 +84,16 @@ describe( 'Intermediate Representation', function() { describe( 'JSDoc in export statement', function() { it( 'default export', function() { const tokenClassAnonymous = fs.readFileSync( - path.join( __dirname, './fixtures/default-class-anonymous/exports.json' ), + path.join( + __dirname, + './fixtures/default-class-anonymous/exports.json' + ), 'utf-8' ); - const irClassAnonymous = getIntermediateRepresentation( null, JSON.parse( tokenClassAnonymous ) ); + const irClassAnonymous = getIntermediateRepresentation( + null, + JSON.parse( tokenClassAnonymous ) + ); expect( irClassAnonymous ).toHaveLength( 1 ); expect( irClassAnonymous[ 0 ] ).toEqual( { path: null, @@ -83,10 +104,16 @@ describe( 'Intermediate Representation', function() { lineEnd: 4, } ); const tokenClassNamed = fs.readFileSync( - path.join( __dirname, './fixtures/default-class-named/exports.json' ), + path.join( + __dirname, + './fixtures/default-class-named/exports.json' + ), 'utf-8' ); - const irClassNamed = getIntermediateRepresentation( null, JSON.parse( tokenClassNamed ) ); + const irClassNamed = getIntermediateRepresentation( + null, + JSON.parse( tokenClassNamed ) + ); expect( irClassNamed ).toHaveLength( 1 ); expect( irClassNamed[ 0 ] ).toEqual( { path: null, @@ -97,10 +124,16 @@ describe( 'Intermediate Representation', function() { lineEnd: 4, } ); const tokenFnAnonymous = fs.readFileSync( - path.join( __dirname, './fixtures/default-function-anonymous/exports.json' ), + path.join( + __dirname, + './fixtures/default-function-anonymous/exports.json' + ), 'utf-8' ); - const irFnAnonymous = getIntermediateRepresentation( null, JSON.parse( tokenFnAnonymous ) ); + const irFnAnonymous = getIntermediateRepresentation( + null, + JSON.parse( tokenFnAnonymous ) + ); expect( irFnAnonymous ).toHaveLength( 1 ); expect( irFnAnonymous[ 0 ] ).toEqual( { path: null, @@ -111,10 +144,16 @@ describe( 'Intermediate Representation', function() { lineEnd: 4, } ); const tokenFnNamed = fs.readFileSync( - path.join( __dirname, './fixtures/default-function-named/exports.json' ), + path.join( + __dirname, + './fixtures/default-function-named/exports.json' + ), 'utf-8' ); - const irFnNamed = getIntermediateRepresentation( null, JSON.parse( tokenFnNamed ) ); + const irFnNamed = getIntermediateRepresentation( + null, + JSON.parse( tokenFnNamed ) + ); expect( irFnNamed[ 0 ] ).toEqual( { path: null, name: 'default', @@ -124,10 +163,16 @@ describe( 'Intermediate Representation', function() { lineEnd: 4, } ); const tokenVariable = fs.readFileSync( - path.join( __dirname, './fixtures/default-variable/exports.json' ), + path.join( + __dirname, + './fixtures/default-variable/exports.json' + ), 'utf-8' ); - const irVar = getIntermediateRepresentation( null, JSON.parse( tokenVariable ) ); + const irVar = getIntermediateRepresentation( + null, + JSON.parse( tokenVariable ) + ); expect( irVar[ 0 ] ).toEqual( { path: null, name: 'default', @@ -142,7 +187,10 @@ describe( 'Intermediate Representation', function() { path.join( __dirname, './fixtures/named-class/exports.json' ), 'utf-8' ); - const irNamedClass = getIntermediateRepresentation( null, JSON.parse( tokenClass ) ); + const irNamedClass = getIntermediateRepresentation( + null, + JSON.parse( tokenClass ) + ); expect( irNamedClass ).toHaveLength( 1 ); expect( irNamedClass[ 0 ] ).toEqual( { path: null, @@ -153,10 +201,16 @@ describe( 'Intermediate Representation', function() { lineEnd: 4, } ); const tokenFn = fs.readFileSync( - path.join( __dirname, './fixtures/named-function/exports.json' ), + path.join( + __dirname, + './fixtures/named-function/exports.json' + ), 'utf-8' ); - const irNamedFn = getIntermediateRepresentation( null, JSON.parse( tokenFn ) ); + const irNamedFn = getIntermediateRepresentation( + null, + JSON.parse( tokenFn ) + ); expect( irNamedFn ).toHaveLength( 1 ); expect( irNamedFn[ 0 ] ).toEqual( { path: null, @@ -167,10 +221,16 @@ describe( 'Intermediate Representation', function() { lineEnd: 4, } ); const tokenVariable = fs.readFileSync( - path.join( __dirname, './fixtures/named-variable/exports.json' ), + path.join( + __dirname, + './fixtures/named-variable/exports.json' + ), 'utf-8' ); - const irNamedVar = getIntermediateRepresentation( null, JSON.parse( tokenVariable ) ); + const irNamedVar = getIntermediateRepresentation( + null, + JSON.parse( tokenVariable ) + ); expect( irNamedVar ).toHaveLength( 1 ); expect( irNamedVar[ 0 ] ).toEqual( { path: null, @@ -181,31 +241,57 @@ describe( 'Intermediate Representation', function() { lineEnd: 4, } ); const tokenVariables = fs.readFileSync( - path.join( __dirname, './fixtures/named-variables/exports.json' ), + path.join( + __dirname, + './fixtures/named-variables/exports.json' + ), 'utf-8' ); - const irNamedVars = getIntermediateRepresentation( null, JSON.parse( tokenVariables ) ); - expect( irNamedVars ).toHaveLength( 2 ); - expect( irNamedVars[ 0 ] ).toEqual( - { path: null, name: 'firstDeclaration', description: 'My declaration example.', tags: [], lineStart: 4, lineEnd: 5 }, - ); - expect( irNamedVars[ 1 ] ).toEqual( - { path: null, name: 'secondDeclaration', description: 'My declaration example.', tags: [], lineStart: 4, lineEnd: 5 }, + const irNamedVars = getIntermediateRepresentation( + null, + JSON.parse( tokenVariables ) ); + expect( irNamedVars ).toHaveLength( 2 ); + expect( irNamedVars[ 0 ] ).toEqual( { + path: null, + name: 'firstDeclaration', + description: 'My declaration example.', + tags: [], + lineStart: 4, + lineEnd: 5, + } ); + expect( irNamedVars[ 1 ] ).toEqual( { + path: null, + name: 'secondDeclaration', + description: 'My declaration example.', + tags: [], + lineStart: 4, + lineEnd: 5, + } ); } ); } ); describe( 'JSDoc in same file', function() { it( 'default export', function() { const token = fs.readFileSync( - path.join( __dirname, './fixtures/default-identifier/exports.json' ), + path.join( + __dirname, + './fixtures/default-identifier/exports.json' + ), 'utf-8' ); const ast = fs.readFileSync( - path.join( __dirname, './fixtures/default-identifier/ast.json' ), + path.join( + __dirname, + './fixtures/default-identifier/ast.json' + ), 'utf-8' ); - const irDefaultId = getIntermediateRepresentation( null, JSON.parse( token ), JSON.parse( ast ) ); + const irDefaultId = getIntermediateRepresentation( + null, + JSON.parse( token ), + JSON.parse( ast ) + ); expect( irDefaultId ).toHaveLength( 1 ); expect( irDefaultId[ 0 ] ).toEqual( { path: null, @@ -216,34 +302,65 @@ describe( 'Intermediate Representation', function() { lineEnd: 6, } ); const namedExport = fs.readFileSync( - path.join( __dirname, './fixtures/default-named-export/exports.json' ), + path.join( + __dirname, + './fixtures/default-named-export/exports.json' + ), 'utf-8' ); const namedExportAST = fs.readFileSync( - path.join( __dirname, './fixtures/default-named-export/ast.json' ), + path.join( + __dirname, + './fixtures/default-named-export/ast.json' + ), 'utf-8' ); - const irDefaultNamed0 = getIntermediateRepresentation( null, JSON.parse( namedExport )[ 0 ], JSON.parse( namedExportAST ) ); - expect( irDefaultNamed0 ).toHaveLength( 1 ); - expect( irDefaultNamed0[ 0 ] ).toEqual( - { path: null, name: 'functionDeclaration', description: 'Function declaration example.', tags: [], lineStart: 4, lineEnd: 4 } + const irDefaultNamed0 = getIntermediateRepresentation( + null, + JSON.parse( namedExport )[ 0 ], + JSON.parse( namedExportAST ) ); - const irDefaultNamed1 = getIntermediateRepresentation( null, JSON.parse( namedExport )[ 1 ], JSON.parse( namedExportAST ) ); - expect( irDefaultNamed1[ 0 ] ).toEqual( - { path: null, name: 'default', description: 'Function declaration example.', tags: [], lineStart: 6, lineEnd: 6 } + expect( irDefaultNamed0 ).toHaveLength( 1 ); + expect( irDefaultNamed0[ 0 ] ).toEqual( { + path: null, + name: 'functionDeclaration', + description: 'Function declaration example.', + tags: [], + lineStart: 4, + lineEnd: 4, + } ); + const irDefaultNamed1 = getIntermediateRepresentation( + null, + JSON.parse( namedExport )[ 1 ], + JSON.parse( namedExportAST ) ); + expect( irDefaultNamed1[ 0 ] ).toEqual( { + path: null, + name: 'default', + description: 'Function declaration example.', + tags: [], + lineStart: 6, + lineEnd: 6, + } ); } ); it( 'named export', function() { const token = fs.readFileSync( - path.join( __dirname, './fixtures/named-identifier/exports.json' ), + path.join( + __dirname, + './fixtures/named-identifier/exports.json' + ), 'utf-8' ); const ast = fs.readFileSync( path.join( __dirname, './fixtures/named-identifier/ast.json' ), 'utf-8' ); - const irNamedId = getIntermediateRepresentation( null, JSON.parse( token ), JSON.parse( ast ) ); + const irNamedId = getIntermediateRepresentation( + null, + JSON.parse( token ), + JSON.parse( ast ) + ); expect( irNamedId ).toHaveLength( 1 ); expect( irNamedId[ 0 ] ).toEqual( { path: null, @@ -254,14 +371,24 @@ describe( 'Intermediate Representation', function() { lineEnd: 6, } ); const tokenObject = fs.readFileSync( - path.join( __dirname, './fixtures/named-identifier-destructuring/exports.json' ), + path.join( + __dirname, + './fixtures/named-identifier-destructuring/exports.json' + ), 'utf-8' ); const astObject = fs.readFileSync( - path.join( __dirname, './fixtures/named-identifier-destructuring/ast.json' ), + path.join( + __dirname, + './fixtures/named-identifier-destructuring/ast.json' + ), 'utf-8' ); - const irNamedIdDestructuring = getIntermediateRepresentation( null, JSON.parse( tokenObject ), JSON.parse( astObject ) ); + const irNamedIdDestructuring = getIntermediateRepresentation( + null, + JSON.parse( tokenObject ), + JSON.parse( astObject ) + ); expect( irNamedIdDestructuring ).toHaveLength( 1 ); expect( irNamedIdDestructuring[ 0 ] ).toEqual( { path: null, @@ -272,69 +399,148 @@ describe( 'Intermediate Representation', function() { lineEnd: 6, } ); const tokens = fs.readFileSync( - path.join( __dirname, './fixtures/named-identifiers/exports.json' ), + path.join( + __dirname, + './fixtures/named-identifiers/exports.json' + ), 'utf-8' ); const asts = fs.readFileSync( path.join( __dirname, './fixtures/named-identifiers/ast.json' ), 'utf-8' ); - const irIds = getIntermediateRepresentation( null, JSON.parse( tokens ), JSON.parse( asts ) ); - expect( irIds ).toHaveLength( 3 ); - expect( irIds[ 0 ] ).toEqual( - { path: null, name: 'functionDeclaration', description: 'Function declaration example.', tags: [], lineStart: 16, lineEnd: 16 } - ); - expect( irIds[ 1 ] ).toEqual( - { path: null, name: 'variableDeclaration', description: 'Variable declaration example.', tags: [], lineStart: 16, lineEnd: 16 } - ); - expect( irIds[ 2 ] ).toEqual( - { path: null, name: 'ClassDeclaration', description: 'Class declaration example.', tags: [], lineStart: 16, lineEnd: 16 } + const irIds = getIntermediateRepresentation( + null, + JSON.parse( tokens ), + JSON.parse( asts ) ); + expect( irIds ).toHaveLength( 3 ); + expect( irIds[ 0 ] ).toEqual( { + path: null, + name: 'functionDeclaration', + description: 'Function declaration example.', + tags: [], + lineStart: 16, + lineEnd: 16, + } ); + expect( irIds[ 1 ] ).toEqual( { + path: null, + name: 'variableDeclaration', + description: 'Variable declaration example.', + tags: [], + lineStart: 16, + lineEnd: 16, + } ); + expect( irIds[ 2 ] ).toEqual( { + path: null, + name: 'ClassDeclaration', + description: 'Class declaration example.', + tags: [], + lineStart: 16, + lineEnd: 16, + } ); const foo = fs.readFileSync( - path.join( __dirname, './fixtures/named-identifiers-and-inline/exports.json' ), + path.join( + __dirname, + './fixtures/named-identifiers-and-inline/exports.json' + ), 'utf-8' ); const bar = fs.readFileSync( - path.join( __dirname, './fixtures/named-identifiers-and-inline/ast.json' ), + path.join( + __dirname, + './fixtures/named-identifiers-and-inline/ast.json' + ), 'utf-8' ); - const irIdInline0 = getIntermediateRepresentation( null, JSON.parse( foo )[ 0 ], JSON.parse( bar ) ); - expect( irIdInline0 ).toHaveLength( 2 ); - expect( irIdInline0[ 0 ] ).toEqual( - { path: null, name: 'functionDeclaration', description: 'Function declaration example.', tags: [], lineStart: 11, lineEnd: 11 } + const irIdInline0 = getIntermediateRepresentation( + null, + JSON.parse( foo )[ 0 ], + JSON.parse( bar ) ); - expect( irIdInline0[ 1 ] ).toEqual( - { path: null, name: 'ClassDeclaration', description: 'Class declaration example.', tags: [], lineStart: 11, lineEnd: 11 } - ); - const irIdInline1 = getIntermediateRepresentation( null, JSON.parse( foo )[ 1 ], JSON.parse( bar ) ); - expect( irIdInline1[ 0 ] ).toEqual( - { path: null, name: 'variableDeclaration', description: 'Variable declaration example.', tags: [], lineStart: 16, lineEnd: 16 } + expect( irIdInline0 ).toHaveLength( 2 ); + expect( irIdInline0[ 0 ] ).toEqual( { + path: null, + name: 'functionDeclaration', + description: 'Function declaration example.', + tags: [], + lineStart: 11, + lineEnd: 11, + } ); + expect( irIdInline0[ 1 ] ).toEqual( { + path: null, + name: 'ClassDeclaration', + description: 'Class declaration example.', + tags: [], + lineStart: 11, + lineEnd: 11, + } ); + const irIdInline1 = getIntermediateRepresentation( + null, + JSON.parse( foo )[ 1 ], + JSON.parse( bar ) ); + expect( irIdInline1[ 0 ] ).toEqual( { + path: null, + name: 'variableDeclaration', + description: 'Variable declaration example.', + tags: [], + lineStart: 16, + lineEnd: 16, + } ); } ); } ); describe( 'JSDoc in module dependency', function() { it( 'named export', function() { const tokenImportNamed = fs.readFileSync( - path.join( __dirname, './fixtures/named-import-named/exports.json' ), - 'utf-8' + path.join( + __dirname, + './fixtures/named-import-named/exports.json' + ), + 'utf-8' + ); + const getModuleImportNamed = () => + JSON.parse( + fs.readFileSync( + path.join( + __dirname, + './fixtures/named-identifiers/ir.json' + ), + 'utf-8' + ) + ); + const ir = getIntermediateRepresentation( + null, + JSON.parse( tokenImportNamed ), + { body: [] }, + getModuleImportNamed ); - const getModuleImportNamed = () => JSON.parse( fs.readFileSync( - path.join( __dirname, './fixtures/named-identifiers/ir.json' ), - 'utf-8' - ) ); - const ir = getIntermediateRepresentation( null, JSON.parse( tokenImportNamed ), { body: [] }, getModuleImportNamed ); expect( ir ).toHaveLength( 3 ); - expect( ir[ 0 ] ).toEqual( - { path: null, name: 'functionDeclaration', description: 'Function declaration example.', tags: [], lineStart: 2, lineEnd: 2 } - ); - expect( ir[ 1 ] ).toEqual( - { path: null, name: 'variableDeclaration', description: 'Variable declaration example.', tags: [], lineStart: 3, lineEnd: 3 } - ); - expect( ir[ 2 ] ).toEqual( - { path: null, name: 'ClassDeclaration', description: 'Class declaration example.', tags: [], lineStart: 4, lineEnd: 4 } - - ); + expect( ir[ 0 ] ).toEqual( { + path: null, + name: 'functionDeclaration', + description: 'Function declaration example.', + tags: [], + lineStart: 2, + lineEnd: 2, + } ); + expect( ir[ 1 ] ).toEqual( { + path: null, + name: 'variableDeclaration', + description: 'Variable declaration example.', + tags: [], + lineStart: 3, + lineEnd: 3, + } ); + expect( ir[ 2 ] ).toEqual( { + path: null, + name: 'ClassDeclaration', + description: 'Class declaration example.', + tags: [], + lineStart: 4, + lineEnd: 4, + } ); } ); it( 'named default export', function() { @@ -342,24 +548,53 @@ describe( 'Intermediate Representation', function() { path.join( __dirname, './fixtures/named-default/exports.json' ), 'utf-8' ); - const getModule = () => JSON.parse( fs.readFileSync( - path.join( __dirname, './fixtures/named-default/module-ir.json' ), - 'utf-8' - ) ); - const irNamedDefault = getIntermediateRepresentation( null, JSON.parse( tokenDefault ), { body: [] }, getModule ); - expect( irNamedDefault ).toHaveLength( 1 ); - expect( irNamedDefault[ 0 ] ).toEqual( - { path: null, name: 'default', description: 'Module declaration.', tags: [], lineStart: 1, lineEnd: 1 } + const getModule = () => + JSON.parse( + fs.readFileSync( + path.join( + __dirname, + './fixtures/named-default/module-ir.json' + ), + 'utf-8' + ) + ); + const irNamedDefault = getIntermediateRepresentation( + null, + JSON.parse( tokenDefault ), + { body: [] }, + getModule ); + expect( irNamedDefault ).toHaveLength( 1 ); + expect( irNamedDefault[ 0 ] ).toEqual( { + path: null, + name: 'default', + description: 'Module declaration.', + tags: [], + lineStart: 1, + lineEnd: 1, + } ); const tokenDefaultExported = fs.readFileSync( - path.join( __dirname, './fixtures/named-default-exported/exports.json' ), + path.join( + __dirname, + './fixtures/named-default-exported/exports.json' + ), 'utf-8' ); - const irNamedDefaultExported = getIntermediateRepresentation( null, JSON.parse( tokenDefaultExported ), { body: [] }, getModule ); - expect( irNamedDefaultExported ).toHaveLength( 1 ); - expect( irNamedDefaultExported[ 0 ] ).toEqual( - { path: null, name: 'moduleName', description: 'Module declaration.', tags: [], lineStart: 1, lineEnd: 1 } + const irNamedDefaultExported = getIntermediateRepresentation( + null, + JSON.parse( tokenDefaultExported ), + { body: [] }, + getModule ); + expect( irNamedDefaultExported ).toHaveLength( 1 ); + expect( irNamedDefaultExported[ 0 ] ).toEqual( { + path: null, + name: 'moduleName', + description: 'Module declaration.', + tags: [], + lineStart: 1, + lineEnd: 1, + } ); } ); it( 'namespace export', function() { @@ -367,54 +602,120 @@ describe( 'Intermediate Representation', function() { path.join( __dirname, './fixtures/namespace/exports.json' ), 'utf-8' ); - const getModule = () => JSON.parse( fs.readFileSync( - path.join( __dirname, './fixtures/namespace/module-ir.json' ), - 'utf-8' - ) ); - const irNamespace = getIntermediateRepresentation( null, JSON.parse( token ), { body: [] }, getModule ); - expect( irNamespace ).toHaveLength( 3 ); - expect( irNamespace[ 0 ] ).toEqual( - { path: null, name: 'MyClass', description: 'Named class.', tags: [], lineStart: 1, lineEnd: 1 } - ); - expect( irNamespace[ 1 ] ).toEqual( - { path: null, name: 'myFunction', description: 'Named function.', tags: [], lineStart: 1, lineEnd: 1 } - ); - expect( irNamespace[ 2 ] ).toEqual( - { path: null, name: 'myVariable', description: 'Named variable.', tags: [], lineStart: 1, lineEnd: 1 } + const getModule = () => + JSON.parse( + fs.readFileSync( + path.join( + __dirname, + './fixtures/namespace/module-ir.json' + ), + 'utf-8' + ) + ); + const irNamespace = getIntermediateRepresentation( + null, + JSON.parse( token ), + { body: [] }, + getModule ); + expect( irNamespace ).toHaveLength( 3 ); + expect( irNamespace[ 0 ] ).toEqual( { + path: null, + name: 'MyClass', + description: 'Named class.', + tags: [], + lineStart: 1, + lineEnd: 1, + } ); + expect( irNamespace[ 1 ] ).toEqual( { + path: null, + name: 'myFunction', + description: 'Named function.', + tags: [], + lineStart: 1, + lineEnd: 1, + } ); + expect( irNamespace[ 2 ] ).toEqual( { + path: null, + name: 'myVariable', + description: 'Named variable.', + tags: [], + lineStart: 1, + lineEnd: 1, + } ); const tokenCommented = fs.readFileSync( - path.join( __dirname, './fixtures/namespace-commented/exports.json' ), + path.join( + __dirname, + './fixtures/namespace-commented/exports.json' + ), 'utf-8' ); - const irNamespaceCommented = getIntermediateRepresentation( null, JSON.parse( tokenCommented ), { body: [] }, getModule ); - expect( irNamespaceCommented ).toHaveLength( 3 ); - expect( irNamespaceCommented[ 0 ] ).toEqual( - { path: null, name: 'MyClass', description: 'Named class.', tags: [], lineStart: 4, lineEnd: 4 } - ); - expect( irNamespaceCommented[ 1 ] ).toEqual( - { path: null, name: 'myFunction', description: 'Named function.', tags: [], lineStart: 4, lineEnd: 4 } - ); - expect( irNamespaceCommented[ 2 ] ).toEqual( - { path: null, name: 'myVariable', description: 'Named variable.', tags: [], lineStart: 4, lineEnd: 4 } + const irNamespaceCommented = getIntermediateRepresentation( + null, + JSON.parse( tokenCommented ), + { body: [] }, + getModule ); + expect( irNamespaceCommented ).toHaveLength( 3 ); + expect( irNamespaceCommented[ 0 ] ).toEqual( { + path: null, + name: 'MyClass', + description: 'Named class.', + tags: [], + lineStart: 4, + lineEnd: 4, + } ); + expect( irNamespaceCommented[ 1 ] ).toEqual( { + path: null, + name: 'myFunction', + description: 'Named function.', + tags: [], + lineStart: 4, + lineEnd: 4, + } ); + expect( irNamespaceCommented[ 2 ] ).toEqual( { + path: null, + name: 'myVariable', + description: 'Named variable.', + tags: [], + lineStart: 4, + lineEnd: 4, + } ); } ); } ); describe( 'JSDoc in module dependency through import', function() { it( 'default export', function() { const tokenDefault = fs.readFileSync( - path.join( __dirname, './fixtures/default-import-default/exports.json' ), + path.join( + __dirname, + './fixtures/default-import-default/exports.json' + ), 'utf-8' ); const astDefault = fs.readFileSync( - path.join( __dirname, './fixtures/default-import-default/ast.json' ), - 'utf-8' + path.join( + __dirname, + './fixtures/default-import-default/ast.json' + ), + 'utf-8' + ); + const getModuleDefault = () => + JSON.parse( + fs.readFileSync( + path.join( + __dirname, + './fixtures/default-import-default/module-ir.json' + ), + 'utf-8' + ) + ); + const irDefault = getIntermediateRepresentation( + null, + JSON.parse( tokenDefault ), + JSON.parse( astDefault ), + getModuleDefault ); - const getModuleDefault = () => JSON.parse( fs.readFileSync( - path.join( __dirname, './fixtures/default-import-default/module-ir.json' ), - 'utf-8' - ) ); - const irDefault = getIntermediateRepresentation( null, JSON.parse( tokenDefault ), JSON.parse( astDefault ), getModuleDefault ); expect( irDefault ).toHaveLength( 1 ); expect( irDefault[ 0 ] ).toEqual( { path: null, @@ -425,18 +726,35 @@ describe( 'Intermediate Representation', function() { lineEnd: 3, } ); const tokenNamed = fs.readFileSync( - path.join( __dirname, './fixtures/default-import-named/exports.json' ), + path.join( + __dirname, + './fixtures/default-import-named/exports.json' + ), 'utf-8' ); const astNamed = fs.readFileSync( - path.join( __dirname, './fixtures/default-import-named/ast.json' ), - 'utf-8' + path.join( + __dirname, + './fixtures/default-import-named/ast.json' + ), + 'utf-8' + ); + const getModuleNamed = () => + JSON.parse( + fs.readFileSync( + path.join( + __dirname, + './fixtures/default-import-named/module-ir.json' + ), + 'utf-8' + ) + ); + const irNamed = getIntermediateRepresentation( + null, + JSON.parse( tokenNamed ), + JSON.parse( astNamed ), + getModuleNamed ); - const getModuleNamed = () => JSON.parse( fs.readFileSync( - path.join( __dirname, './fixtures/default-import-named/module-ir.json' ), - 'utf-8' - ) ); - const irNamed = getIntermediateRepresentation( null, JSON.parse( tokenNamed ), JSON.parse( astNamed ), getModuleNamed ); expect( irNamed ).toHaveLength( 1 ); expect( irNamed[ 0 ] ).toEqual( { path: null, @@ -450,30 +768,56 @@ describe( 'Intermediate Representation', function() { it( 'named export', function() { const tokenImportNamespace = fs.readFileSync( - path.join( __dirname, './fixtures/named-import-namespace/exports.json' ), + path.join( + __dirname, + './fixtures/named-import-namespace/exports.json' + ), 'utf-8' ); const astImportNamespace = fs.readFileSync( - path.join( __dirname, './fixtures/named-import-namespace/ast.json' ), + path.join( + __dirname, + './fixtures/named-import-namespace/ast.json' + ), 'utf-8' ); const getModuleImportNamespace = ( filePath ) => { if ( filePath === './named-import-namespace-module' ) { - return JSON.parse( fs.readFileSync( - path.join( __dirname, './fixtures/named-import-namespace/module-ir.json' ), - 'utf-8' - ) ); + return JSON.parse( + fs.readFileSync( + path.join( + __dirname, + './fixtures/named-import-namespace/module-ir.json' + ), + 'utf-8' + ) + ); } - return JSON.parse( fs.readFileSync( - path.join( __dirname, './fixtures/default-function/ir.json' ), - 'utf-8' - ) ); + return JSON.parse( + fs.readFileSync( + path.join( + __dirname, + './fixtures/default-function/ir.json' + ), + 'utf-8' + ) + ); }; - const ir = getIntermediateRepresentation( null, JSON.parse( tokenImportNamespace ), JSON.parse( astImportNamespace ), getModuleImportNamespace ); - expect( ir ).toHaveLength( 1 ); - expect( ir[ 0 ] ).toEqual( - { path: null, name: 'variables', description: 'Undocumented declaration.', tags: [], lineStart: 3, lineEnd: 3 } + const ir = getIntermediateRepresentation( + null, + JSON.parse( tokenImportNamespace ), + JSON.parse( astImportNamespace ), + getModuleImportNamespace ); + expect( ir ).toHaveLength( 1 ); + expect( ir[ 0 ] ).toEqual( { + path: null, + name: 'variables', + description: 'Undocumented declaration.', + tags: [], + lineStart: 3, + lineEnd: 3, + } ); } ); } ); } ); diff --git a/packages/docgen/src/test/get-jsdoc-from-token.js b/packages/docgen/src/test/get-jsdoc-from-token.js index 99771bcdd18c8d..3475e69938187e 100644 --- a/packages/docgen/src/test/get-jsdoc-from-token.js +++ b/packages/docgen/src/test/get-jsdoc-from-token.js @@ -7,95 +7,100 @@ describe( 'JSDoc', () => { it( 'extracts description and tags', () => { expect( getJSDocFromToken( { - leadingComments: [ { - value: '*\n * A function that adds two parameters.\n *\n * @deprecated Use native addition instead.\n * @since v2\n *\n * @see addition\n * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators\n *\n * @param {number} firstParam The first param to add.\n * @param {number} secondParam The second param to add.\n *\n * @example\n *\n * ```js\n * const addResult = sum( 1, 3 );\n * console.log( addResult ); // will yield 4\n * ```\n *\n * @return {number} The result of adding the two params.\n ', - } ], - } ) - ).toEqual( - { - description: 'A function that adds two parameters.', - tags: [ - { - title: 'deprecated', - description: 'Use native addition instead.', - }, - { - title: 'since', - description: 'v2', - }, - { - title: 'see', - description: 'addition', - }, - { - title: 'link', - description: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators', - }, - { - title: 'param', - description: 'The first param to add.', - type: 'number', - name: 'firstParam', - }, - { - title: 'param', - description: 'The second param to add.', - type: 'number', - name: 'secondParam', - }, - { - title: 'example', - description: '```js\nconst addResult = sum( 1, 3 );\nconsole.log( addResult ); // will yield 4\n```', - }, + leadingComments: [ { - title: 'return', - description: 'The result of adding the two params.', - type: 'number', + value: + '*\n * A function that adds two parameters.\n *\n * @deprecated Use native addition instead.\n * @since v2\n *\n * @see addition\n * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators\n *\n * @param {number} firstParam The first param to add.\n * @param {number} secondParam The second param to add.\n *\n * @example\n *\n * ```js\n * const addResult = sum( 1, 3 );\n * console.log( addResult ); // will yield 4\n * ```\n *\n * @return {number} The result of adding the two params.\n ', }, ], - } - ); + } ) + ).toEqual( { + description: 'A function that adds two parameters.', + tags: [ + { + title: 'deprecated', + description: 'Use native addition instead.', + }, + { + title: 'since', + description: 'v2', + }, + { + title: 'see', + description: 'addition', + }, + { + title: 'link', + description: + 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators', + }, + { + title: 'param', + description: 'The first param to add.', + type: 'number', + name: 'firstParam', + }, + { + title: 'param', + description: 'The second param to add.', + type: 'number', + name: 'secondParam', + }, + { + title: 'example', + description: + '```js\nconst addResult = sum( 1, 3 );\nconsole.log( addResult ); // will yield 4\n```', + }, + { + title: 'return', + description: 'The result of adding the two params.', + type: 'number', + }, + ], + } ); expect( getJSDocFromToken( { - leadingComments: [ { - value: '*\n * Constant to document the meaning of life,\n * the universe and everything else.\n *\n * @type {number}\n ', - } ], - } ) - ).toEqual( - { - description: 'Constant to document the meaning of life,\nthe universe and everything else.', - tags: [ + leadingComments: [ { - title: 'type', - description: null, - type: 'number', + value: + '*\n * Constant to document the meaning of life,\n * the universe and everything else.\n *\n * @type {number}\n ', }, ], - } - ); + } ) + ).toEqual( { + description: + 'Constant to document the meaning of life,\nthe universe and everything else.', + tags: [ + { + title: 'type', + description: null, + type: 'number', + }, + ], + } ); expect( getJSDocFromToken( { - leadingComments: [ { - value: '*\n * Function invoking callback after delay with current timestamp in milliseconds since epoch.\n * @param {(timestamp:number)=>void} callback Callback function.\n ', - } ], - } ) - ).toEqual( - { - description: 'Function invoking callback after delay with current timestamp in milliseconds since epoch.', - tags: [ + leadingComments: [ { - title: 'param', - errors: [ - 'unexpected token', - ], - description: 'Callback function.', - name: 'callback', - type: null, + value: + '*\n * Function invoking callback after delay with current timestamp in milliseconds since epoch.\n * @param {(timestamp:number)=>void} callback Callback function.\n ', }, ], - } - ); + } ) + ).toEqual( { + description: + 'Function invoking callback after delay with current timestamp in milliseconds since epoch.', + tags: [ + { + title: 'param', + errors: [ 'unexpected token' ], + description: 'Callback function.', + name: 'callback', + type: null, + }, + ], + } ); } ); } ); diff --git a/packages/dom-ready/src/test/index.test.js b/packages/dom-ready/src/test/index.test.js index 36a5bbbc640563..dc1fcb882fc4f2 100644 --- a/packages/dom-ready/src/test/index.test.js +++ b/packages/dom-ready/src/test/index.test.js @@ -40,7 +40,10 @@ describe( 'domReady', () => { const callback = jest.fn( () => {} ); domReady( callback ); expect( callback ).not.toHaveBeenCalled(); - expect( addEventListener ).toHaveBeenCalledWith( 'DOMContentLoaded', callback ); + expect( addEventListener ).toHaveBeenCalledWith( + 'DOMContentLoaded', + callback + ); } ); } ); } ); diff --git a/packages/dom/src/dom.js b/packages/dom/src/dom.js index 05a2740c6b4e56..c1ae1fbc17395d 100644 --- a/packages/dom/src/dom.js +++ b/packages/dom/src/dom.js @@ -26,12 +26,7 @@ const { * @return {boolean} Whether the selection is forward. */ function isSelectionForward( selection ) { - const { - anchorNode, - focusNode, - anchorOffset, - focusOffset, - } = selection; + const { anchorNode, focusNode, anchorOffset, focusOffset } = selection; const position = anchorNode.compareDocumentPosition( focusNode ); @@ -122,20 +117,22 @@ function isEdge( container, isReverse, onlyVertical ) { return false; } - const padding = parseInt( computedStyle[ - `padding${ isReverse ? 'Top' : 'Bottom' }` - ], 10 ) || 0; + const padding = + parseInt( + computedStyle[ `padding${ isReverse ? 'Top' : 'Bottom' }` ], + 10 + ) || 0; // Calculate a buffer that is half the line height. In some browsers, the // selection rectangle may not fill the entire height of the line, so we add // 3/4 the line height to the selection rectangle to ensure that it is well // over its line boundary. - const buffer = 3 * parseInt( lineHeight, 10 ) / 4; + const buffer = ( 3 * parseInt( lineHeight, 10 ) ) / 4; const containerRect = container.getBoundingClientRect(); const originalRangeRect = getRectangleFromRange( originalRange ); - const verticalEdge = isReverse ? - containerRect.top + padding > originalRangeRect.top - buffer : - containerRect.bottom - padding < originalRangeRect.bottom + buffer; + const verticalEdge = isReverse + ? containerRect.top + padding > originalRangeRect.top - buffer + : containerRect.bottom - padding < originalRangeRect.bottom + buffer; if ( ! verticalEdge ) { return false; @@ -147,7 +144,7 @@ function isEdge( container, isReverse, onlyVertical ) { // In the case of RTL scripts, the horizontal edge is at the opposite side. const { direction } = computedStyle; - const isReverseDir = direction === 'rtl' ? ( ! isReverse ) : isReverse; + const isReverseDir = direction === 'rtl' ? ! isReverse : isReverse; // To calculate the horizontal position, we insert a test range and see if // this test range has the same horizontal position. This method proves to @@ -155,7 +152,9 @@ function isEdge( container, isReverse, onlyVertical ) { // nodes and a trailing line break element. In other words, we need to check // visual positioning, not DOM positioning. const x = isReverseDir ? containerRect.left + 1 : containerRect.right - 1; - const y = isReverse ? containerRect.top + buffer : containerRect.bottom - buffer; + const y = isReverse + ? containerRect.top + buffer + : containerRect.bottom - buffer; const testRange = hiddenCaretRangeFromPoint( document, x, y, container ); if ( ! testRange ) { @@ -213,7 +212,9 @@ export function getRectangleFromRange( range ) { // Correct invalid "BR" ranges. The cannot contain any children. if ( startContainer.nodeName === 'BR' ) { const { parentNode } = startContainer; - const index = Array.from( parentNode.childNodes ).indexOf( startContainer ); + const index = Array.from( parentNode.childNodes ).indexOf( + startContainer + ); range = document.createRange(); range.setStart( parentNode, index ); @@ -378,7 +379,12 @@ function hiddenCaretRangeFromPoint( doc, x, y, container ) { * @param {DOMRect} [rect] The rectangle to position the caret with. * @param {boolean} [mayUseScroll=true] True to allow scrolling, false to disallow. */ -export function placeCaretAtVerticalEdge( container, isReverse, rect, mayUseScroll = true ) { +export function placeCaretAtVerticalEdge( + container, + isReverse, + rect, + mayUseScroll = true +) { if ( ! container ) { return; } @@ -397,14 +403,19 @@ export function placeCaretAtVerticalEdge( container, isReverse, rect, mayUseScro const buffer = rect.height / 2; const editableRect = container.getBoundingClientRect(); const x = rect.left; - const y = isReverse ? ( editableRect.bottom - buffer ) : ( editableRect.top + buffer ); + const y = isReverse + ? editableRect.bottom - buffer + : editableRect.top + buffer; const range = hiddenCaretRangeFromPoint( document, x, y, container ); if ( ! range || ! container.contains( range.startContainer ) ) { - if ( mayUseScroll && ( - ( ! range || ! range.startContainer ) || - ! range.startContainer.contains( container ) ) ) { + if ( + mayUseScroll && + ( ! range || + ! range.startContainer || + ! range.startContainer.contains( container ) ) + ) { // Might be out of view. // Easier than attempting to calculate manually. container.scrollIntoView( isReverse ); @@ -442,7 +453,7 @@ export function isTextField( element ) { return ( ( nodeName === 'INPUT' && selectionStart !== null ) || - ( nodeName === 'TEXTAREA' ) || + nodeName === 'TEXTAREA' || contentEditable === 'true' ); } catch ( error ) { @@ -485,7 +496,10 @@ export function documentHasSelection() { */ export function isEntirelySelected( element ) { if ( includes( [ 'INPUT', 'TEXTAREA' ], element.nodeName ) ) { - return element.selectionStart === 0 && element.value.length === element.selectionEnd; + return ( + element.selectionStart === 0 && + element.value.length === element.selectionEnd + ); } if ( ! element.isContentEditable ) { @@ -511,9 +525,10 @@ export function isEntirelySelected( element ) { } const lastChild = element.lastChild; - const lastChildContentLength = lastChild.nodeType === TEXT_NODE ? - lastChild.data.length : - lastChild.childNodes.length; + const lastChildContentLength = + lastChild.nodeType === TEXT_NODE + ? lastChild.data.length + : lastChild.childNodes.length; return ( startContainer === element.firstChild && diff --git a/packages/dom/src/test/dom.js b/packages/dom/src/test/dom.js index cc88c6b23c14c0..a2352d75efbd8a 100644 --- a/packages/dom/src/test/dom.js +++ b/packages/dom/src/test/dom.js @@ -1,7 +1,12 @@ /** * Internal dependencies */ -import { isHorizontalEdge, placeCaretAtHorizontalEdge, isTextField, __unstableStripHTML as stripHTML } from '../dom'; +import { + isHorizontalEdge, + placeCaretAtHorizontalEdge, + isTextField, + __unstableStripHTML as stripHTML, +} from '../dom'; describe( 'DOM', () => { let parent; @@ -112,12 +117,7 @@ describe( 'DOM', () => { * * @type {string[]} */ - const TEXT_INPUT_TYPES = [ - 'text', - 'password', - 'search', - 'url', - ]; + const TEXT_INPUT_TYPES = [ 'text', 'password', 'search', 'url' ]; it( 'should return false for non-text input elements', () => { NON_TEXT_INPUT_TYPES.forEach( ( type ) => { @@ -138,7 +138,9 @@ describe( 'DOM', () => { } ); it( 'should return true for an textarea element', () => { - expect( isTextField( document.createElement( 'textarea' ) ) ).toBe( true ); + expect( isTextField( document.createElement( 'textarea' ) ) ).toBe( + true + ); } ); it( 'should return true for a contenteditable element', () => { @@ -150,13 +152,17 @@ describe( 'DOM', () => { } ); it( 'should return true for a normal div element', () => { - expect( isTextField( document.createElement( 'div' ) ) ).toBe( false ); + expect( isTextField( document.createElement( 'div' ) ) ).toBe( + false + ); } ); } ); describe( 'stripHTML', () => { it( 'removes any HTML from a text string', () => { - expect( stripHTML( 'This is <em>emphasized</em>' ) ).toBe( 'This is emphasized' ); + expect( stripHTML( 'This is <em>emphasized</em>' ) ).toBe( + 'This is emphasized' + ); } ); it( 'removes script tags, but does not execute them', () => { diff --git a/packages/dom/src/test/tabbable.js b/packages/dom/src/test/tabbable.js index e262fccd86d65e..47ee59aabee083 100644 --- a/packages/dom/src/test/tabbable.js +++ b/packages/dom/src/test/tabbable.js @@ -26,11 +26,7 @@ describe( 'tabbable', () => { const tabbables = find( node ); - expect( tabbables ).toEqual( [ - first, - second, - third, - ] ); + expect( tabbables ).toEqual( [ first, second, third ] ); } ); it( 'consolidates radio group to the first, if unchecked', () => { @@ -67,11 +63,7 @@ describe( 'tabbable', () => { const tabbables = find( node ); - expect( tabbables ).toEqual( [ - firstRadio, - text, - fourthRadio, - ] ); + expect( tabbables ).toEqual( [ firstRadio, text, fourthRadio ] ); } ); it( 'consolidates radio group to the checked', () => { @@ -99,10 +91,7 @@ describe( 'tabbable', () => { const tabbables = find( node ); - expect( tabbables ).toEqual( [ - text, - thirdRadio, - ] ); + expect( tabbables ).toEqual( [ text, thirdRadio ] ); } ); it( 'not consolidate unnamed radio inputs', () => { @@ -122,11 +111,7 @@ describe( 'tabbable', () => { const tabbables = find( node ); - expect( tabbables ).toEqual( [ - firstRadio, - text, - secondRadio, - ] ); + expect( tabbables ).toEqual( [ firstRadio, text, secondRadio ] ); } ); } ); } ); diff --git a/packages/dom/src/test/utils/create-element.js b/packages/dom/src/test/utils/create-element.js index 2818c19d375c10..bae4273adfa609 100644 --- a/packages/dom/src/test/utils/create-element.js +++ b/packages/dom/src/test/utils/create-element.js @@ -9,20 +9,24 @@ export default function createElement( type ) { const element = document.createElement( type ); - const ifNotHidden = ( value, elseValue ) => function() { - let isHidden = false; - let node = this; - do { - isHidden = ( - node.style.display === 'none' || - node.style.visibility === 'hidden' - ); + const ifNotHidden = ( value, elseValue ) => + function() { + let isHidden = false; + let node = this; + do { + isHidden = + node.style.display === 'none' || + node.style.visibility === 'hidden'; - node = node.parentNode; - } while ( ! isHidden && node && node.nodeType === window.Node.ELEMENT_NODE ); + node = node.parentNode; + } while ( + ! isHidden && + node && + node.nodeType === window.Node.ELEMENT_NODE + ); - return isHidden ? elseValue : value; - }; + return isHidden ? elseValue : value; + }; Object.defineProperties( element, { offsetHeight: { @@ -33,14 +37,19 @@ export default function createElement( type ) { }, } ); - element.getClientRects = ifNotHidden( [ { - width: 10, - height: 10, - top: 0, - right: 10, - bottom: 10, - left: 0, - } ], [] ); + element.getClientRects = ifNotHidden( + [ + { + width: 10, + height: 10, + top: 0, + right: 10, + bottom: 10, + left: 0, + }, + ], + [] + ); return element; } diff --git a/packages/e2e-test-utils/src/activate-plugin.js b/packages/e2e-test-utils/src/activate-plugin.js index 9d90f866ff5dd5..694321c1193bac 100644 --- a/packages/e2e-test-utils/src/activate-plugin.js +++ b/packages/e2e-test-utils/src/activate-plugin.js @@ -13,7 +13,9 @@ import { visitAdminPage } from './visit-admin-page'; export async function activatePlugin( slug ) { await switchUserToAdmin(); await visitAdminPage( 'plugins.php' ); - const disableLink = await page.$( `tr[data-slug="${ slug }"] .deactivate a` ); + const disableLink = await page.$( + `tr[data-slug="${ slug }"] .deactivate a` + ); if ( disableLink ) { await switchUserToTest(); return; diff --git a/packages/e2e-test-utils/src/click-block-toolbar-button.js b/packages/e2e-test-utils/src/click-block-toolbar-button.js index fcdd9d2af91ede..dffc3e313c5d0c 100644 --- a/packages/e2e-test-utils/src/click-block-toolbar-button.js +++ b/packages/e2e-test-utils/src/click-block-toolbar-button.js @@ -6,7 +6,7 @@ export async function clickBlockToolbarButton( buttonAriaLabel ) { const BLOCK_TOOLBAR_SELECTOR = '.block-editor-block-toolbar'; const BUTTON_SELECTOR = `${ BLOCK_TOOLBAR_SELECTOR } button[aria-label="${ buttonAriaLabel }"]`; - if ( await page.$( BLOCK_TOOLBAR_SELECTOR ) === null ) { + if ( ( await page.$( BLOCK_TOOLBAR_SELECTOR ) ) === null ) { // Move the mouse to show the block toolbar await page.mouse.move( 0, 0 ); await page.mouse.move( 10, 10 ); diff --git a/packages/e2e-test-utils/src/click-button.js b/packages/e2e-test-utils/src/click-button.js index 9c406c22b24af1..634319101e5d08 100644 --- a/packages/e2e-test-utils/src/click-button.js +++ b/packages/e2e-test-utils/src/click-button.js @@ -4,6 +4,8 @@ * @param {string} buttonText The text that appears on the button to click. */ export async function clickButton( buttonText ) { - const button = await page.waitForXPath( `//button[contains(text(), '${ buttonText }')]` ); + const button = await page.waitForXPath( + `//button[contains(text(), '${ buttonText }')]` + ); await button.click(); } diff --git a/packages/e2e-test-utils/src/click-on-close-modal-button.js b/packages/e2e-test-utils/src/click-on-close-modal-button.js index 491037f3ba9af3..dfd01c5d4ae48f 100644 --- a/packages/e2e-test-utils/src/click-on-close-modal-button.js +++ b/packages/e2e-test-utils/src/click-on-close-modal-button.js @@ -4,8 +4,7 @@ * @param {?string} modalClassName Class name for the modal to close */ export async function clickOnCloseModalButton( modalClassName ) { - let closeButtonClassName = - '.components-modal__header .components-button'; + let closeButtonClassName = '.components-modal__header .components-button'; if ( modalClassName ) { closeButtonClassName = `${ modalClassName } ${ closeButtonClassName }`; diff --git a/packages/e2e-test-utils/src/click-on-more-menu-item.js b/packages/e2e-test-utils/src/click-on-more-menu-item.js index 28b9acabf15653..564ef6b715e6cb 100644 --- a/packages/e2e-test-utils/src/click-on-more-menu-item.js +++ b/packages/e2e-test-utils/src/click-on-more-menu-item.js @@ -17,16 +17,20 @@ export async function clickOnMoreMenuItem( buttonLabel ) { await toggleMoreMenu(); const moreMenuContainerSelector = '//*[contains(concat(" ", @class, " "), " edit-post-more-menu__content ")]'; - let elementToClick = first( await page.$x( - `${ moreMenuContainerSelector }//button[contains(text(), "${ buttonLabel }")]` - ) ); + let elementToClick = first( + await page.$x( + `${ moreMenuContainerSelector }//button[contains(text(), "${ buttonLabel }")]` + ) + ); // If button is not found, the label should be on the info wrapper. if ( ! elementToClick ) { - elementToClick = first( await page.$x( - moreMenuContainerSelector + - '//button' + - `/*[contains(concat(" ", @class, " "), " components-menu-item__info-wrapper ")][contains(text(), "${ buttonLabel }")]` - ) ); + elementToClick = first( + await page.$x( + moreMenuContainerSelector + + '//button' + + `/*[contains(concat(" ", @class, " "), " components-menu-item__info-wrapper ")][contains(text(), "${ buttonLabel }")]` + ) + ); } await elementToClick.click(); } diff --git a/packages/e2e-test-utils/src/create-new-post.js b/packages/e2e-test-utils/src/create-new-post.js index a83577de62ddf3..7e5b8a9a287109 100644 --- a/packages/e2e-test-utils/src/create-new-post.js +++ b/packages/e2e-test-utils/src/create-new-post.js @@ -30,11 +30,13 @@ export async function createNewPost( { await visitAdminPage( 'post-new.php', query ); const isWelcomeGuideActive = await page.evaluate( () => - wp.data.select( 'core/edit-post' ).isFeatureActive( 'welcomeGuide' ) ); + wp.data.select( 'core/edit-post' ).isFeatureActive( 'welcomeGuide' ) + ); if ( showWelcomeGuide !== isWelcomeGuideActive ) { await page.evaluate( () => - wp.data.dispatch( 'core/edit-post' ).toggleFeature( 'welcomeGuide' ) ); + wp.data.dispatch( 'core/edit-post' ).toggleFeature( 'welcomeGuide' ) + ); await page.reload(); } diff --git a/packages/e2e-test-utils/src/drag-and-resize.js b/packages/e2e-test-utils/src/drag-and-resize.js index f8f0b3e996b261..f3c45eec479ec6 100644 --- a/packages/e2e-test-utils/src/drag-and-resize.js +++ b/packages/e2e-test-utils/src/drag-and-resize.js @@ -16,8 +16,8 @@ export async function dragAndResize( element, delta ) { height: elementHeight, } = await element.boundingBox(); - const originX = elementX + ( elementWidth / 2 ); - const originY = elementY + ( elementHeight / 2 ); + const originX = elementX + elementWidth / 2; + const originY = elementY + elementHeight / 2; await page.mouse.move( originX, originY ); await page.mouse.down(); diff --git a/packages/e2e-test-utils/src/ensure-sidebar-opened.js b/packages/e2e-test-utils/src/ensure-sidebar-opened.js index 977a4325105637..124a5e28b7ae9b 100644 --- a/packages/e2e-test-utils/src/ensure-sidebar-opened.js +++ b/packages/e2e-test-utils/src/ensure-sidebar-opened.js @@ -10,6 +10,8 @@ export async function ensureSidebarOpened() { try { return page.$eval( '.edit-post-sidebar', () => {} ); } catch ( error ) { - return page.click( '.edit-post-header__settings [aria-label="Settings"]' ); + return page.click( + '.edit-post-header__settings [aria-label="Settings"]' + ); } } diff --git a/packages/e2e-test-utils/src/find-sidebar-panel-toggle-button-with-title.js b/packages/e2e-test-utils/src/find-sidebar-panel-toggle-button-with-title.js index 916ddc53f4b6b4..cf9c2e57b9a83b 100644 --- a/packages/e2e-test-utils/src/find-sidebar-panel-toggle-button-with-title.js +++ b/packages/e2e-test-utils/src/find-sidebar-panel-toggle-button-with-title.js @@ -13,5 +13,9 @@ import { first } from 'lodash'; * @return {?ElementHandle} Object that represents an in-page DOM element. */ export async function findSidebarPanelToggleButtonWithTitle( panelTitle ) { - return first( await page.$x( `//div[contains(@class,"edit-post-sidebar")]//button[@class="components-button components-panel__body-toggle"][contains(text(),"${ panelTitle }")]` ) ); + return first( + await page.$x( + `//div[contains(@class,"edit-post-sidebar")]//button[@class="components-button components-panel__body-toggle"][contains(text(),"${ panelTitle }")]` + ) + ); } diff --git a/packages/e2e-test-utils/src/find-sidebar-panel-with-title.js b/packages/e2e-test-utils/src/find-sidebar-panel-with-title.js index fea4bf968f0a46..485bf3d9f0bb64 100644 --- a/packages/e2e-test-utils/src/find-sidebar-panel-with-title.js +++ b/packages/e2e-test-utils/src/find-sidebar-panel-with-title.js @@ -13,8 +13,13 @@ import { first } from 'lodash'; * @return {Promise<ElementHandle|undefined>} Object that represents an in-page DOM element. */ export async function findSidebarPanelWithTitle( panelTitle ) { - const classSelect = ( className ) => `[contains(concat(" ", @class, " "), " ${ className } ")]`; - const buttonSelector = `//div${ classSelect( 'edit-post-sidebar' ) }//button${ classSelect( 'components-button' ) }${ classSelect( 'components-panel__body-toggle' ) }[contains(text(),"${ panelTitle }")]`; + const classSelect = ( className ) => + `[contains(concat(" ", @class, " "), " ${ className } ")]`; + const buttonSelector = `//div${ classSelect( + 'edit-post-sidebar' + ) }//button${ classSelect( 'components-button' ) }${ classSelect( + 'components-panel__body-toggle' + ) }[contains(text(),"${ panelTitle }")]`; const panelSelector = `${ buttonSelector }/ancestor::*[contains(concat(" ", @class, " "), " components-panel__body ")]`; return first( await page.$x( panelSelector ) ); } diff --git a/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js b/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js index ef564d81b38660..eb2802e879b559 100644 --- a/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js +++ b/packages/e2e-test-utils/src/get-all-block-inserter-item-titles.js @@ -14,11 +14,9 @@ export async function getAllBlockInserterItemTitles() { document.querySelectorAll( '.block-editor-inserter__results .block-editor-block-types-list__item-title' ) - ).map( - ( inserterItem ) => { - return inserterItem.innerText; - } - ); + ).map( ( inserterItem ) => { + return inserterItem.innerText; + } ); } ); return sortBy( uniq( inserterItemTitles ) ); } diff --git a/packages/e2e-test-utils/src/get-available-block-transforms.js b/packages/e2e-test-utils/src/get-available-block-transforms.js index d3d1192798a8ef..1b3074f0c501a7 100644 --- a/packages/e2e-test-utils/src/get-available-block-transforms.js +++ b/packages/e2e-test-utils/src/get-available-block-transforms.js @@ -10,16 +10,14 @@ import { hasBlockSwitcher } from './has-block-switcher'; * @return {Promise} Promise resolving with an array containing all possible block transforms */ export const getAvailableBlockTransforms = async () => { - if ( ! await hasBlockSwitcher() ) { + if ( ! ( await hasBlockSwitcher() ) ) { return []; } - await page.click( '.block-editor-block-toolbar .block-editor-block-switcher' ); + await page.click( + '.block-editor-block-toolbar .block-editor-block-switcher' + ); return page.evaluate( ( buttonSelector ) => { - return Array.from( - document.querySelectorAll( - buttonSelector - ) - ).map( + return Array.from( document.querySelectorAll( buttonSelector ) ).map( ( button ) => { return button.textContent; } diff --git a/packages/e2e-test-utils/src/get-block-setting.js b/packages/e2e-test-utils/src/get-block-setting.js index e0adf27eaebedf..f4a486894b5d3e 100644 --- a/packages/e2e-test-utils/src/get-block-setting.js +++ b/packages/e2e-test-utils/src/get-block-setting.js @@ -7,8 +7,14 @@ * @return {Promise} Promise resolving with a string containing the block title. */ export async function getBlockSetting( blockName, setting ) { - return page.evaluate( ( _blockName, _setting ) => { - const blockType = wp.data.select( 'core/blocks' ).getBlockType( _blockName ); - return blockType && blockType[ _setting ]; - }, blockName, setting ); + return page.evaluate( + ( _blockName, _setting ) => { + const blockType = wp.data + .select( 'core/blocks' ) + .getBlockType( _blockName ); + return blockType && blockType[ _setting ]; + }, + blockName, + setting + ); } diff --git a/packages/e2e-test-utils/src/index.js b/packages/e2e-test-utils/src/index.js index adb2b2867f737d..136496058223b4 100644 --- a/packages/e2e-test-utils/src/index.js +++ b/packages/e2e-test-utils/src/index.js @@ -27,7 +27,10 @@ export { installPlugin } from './install-plugin'; export { isCurrentURL } from './is-current-url'; export { isInDefaultBlock } from './is-in-default-block'; export { loginUser } from './login-user'; -export { enableFocusLossObservation, disableFocusLossObservation } from './observe-focus-loss'; +export { + enableFocusLossObservation, + disableFocusLossObservation, +} from './observe-focus-loss'; export { openAllBlockInserterCategories } from './open-all-block-inserter-categories'; export { openDocumentSettingsSidebar } from './open-document-settings-sidebar'; export { openGlobalBlockInserter } from './open-global-block-inserter'; diff --git a/packages/e2e-test-utils/src/insert-block.js b/packages/e2e-test-utils/src/insert-block.js index 452f0432d0ed97..4190d7c1136ce3 100644 --- a/packages/e2e-test-utils/src/insert-block.js +++ b/packages/e2e-test-utils/src/insert-block.js @@ -13,13 +13,13 @@ import { searchForBlock } from './search-for-block'; export async function insertBlock( searchTerm, panelName = null ) { await searchForBlock( searchTerm ); if ( panelName ) { - const panelButton = ( await page.$x( - `//button[contains(text(), '${ panelName }')]` - ) )[ 0 ]; + const panelButton = ( + await page.$x( `//button[contains(text(), '${ panelName }')]` ) + )[ 0 ]; await panelButton.click(); } - const insertButton = ( await page.$x( - `//button//span[contains(text(), '${ searchTerm }')]` - ) )[ 0 ]; + const insertButton = ( + await page.$x( `//button//span[contains(text(), '${ searchTerm }')]` ) + )[ 0 ]; await insertButton.click(); } diff --git a/packages/e2e-test-utils/src/install-plugin.js b/packages/e2e-test-utils/src/install-plugin.js index 37a47ae382a246..6fa47ed53d8e40 100644 --- a/packages/e2e-test-utils/src/install-plugin.js +++ b/packages/e2e-test-utils/src/install-plugin.js @@ -13,7 +13,12 @@ import { visitAdminPage } from './visit-admin-page'; */ export async function installPlugin( slug, searchTerm ) { await switchUserToAdmin(); - await visitAdminPage( 'plugin-install.php', 's=' + encodeURIComponent( searchTerm || slug ) + '&tab=search&type=term' ); + await visitAdminPage( + 'plugin-install.php', + 's=' + + encodeURIComponent( searchTerm || slug ) + + '&tab=search&type=term' + ); await page.click( `.install-now[data-slug="${ slug }"]` ); await page.waitForSelector( `.activate-now[data-slug="${ slug }"]` ); await switchUserToTest(); diff --git a/packages/e2e-test-utils/src/is-in-default-block.js b/packages/e2e-test-utils/src/is-in-default-block.js index 7558a7b0767e3b..21d9a19bf22dd7 100644 --- a/packages/e2e-test-utils/src/is-in-default-block.js +++ b/packages/e2e-test-utils/src/is-in-default-block.js @@ -10,11 +10,15 @@ export function isInDefaultBlock() { if ( ! activeElement ) { return false; } - const closestElementWithDataTpe = activeElement.closest( '[data-type]' ); + const closestElementWithDataTpe = activeElement.closest( + '[data-type]' + ); if ( ! closestElementWithDataTpe ) { return false; } - const activeBlockName = closestElementWithDataTpe.getAttribute( 'data-type' ); + const activeBlockName = closestElementWithDataTpe.getAttribute( + 'data-type' + ); const defaultBlockName = window.wp.blocks.getDefaultBlockName(); return activeBlockName === defaultBlockName; diff --git a/packages/e2e-test-utils/src/login-user.js b/packages/e2e-test-utils/src/login-user.js index d06878d69186ed..aeecfd9d5cc612 100644 --- a/packages/e2e-test-utils/src/login-user.js +++ b/packages/e2e-test-utils/src/login-user.js @@ -12,11 +12,12 @@ import { pressKeyWithModifier } from './press-key-with-modifier'; * @param {?string} username String to be used as user credential. * @param {?string} password String to be used as user credential. */ -export async function loginUser( username = WP_USERNAME, password = WP_PASSWORD ) { +export async function loginUser( + username = WP_USERNAME, + password = WP_PASSWORD +) { if ( ! isCurrentURL( 'wp-login.php' ) ) { - await page.goto( - createURL( 'wp-login.php' ) - ); + await page.goto( createURL( 'wp-login.php' ) ); } await page.focus( '#user_login' ); @@ -26,5 +27,8 @@ export async function loginUser( username = WP_USERNAME, password = WP_PASSWORD await pressKeyWithModifier( 'primary', 'a' ); await page.type( '#user_pass', password ); - await Promise.all( [ page.waitForNavigation(), page.click( '#wp-submit' ) ] ); + await Promise.all( [ + page.waitForNavigation(), + page.click( '#wp-submit' ), + ] ); } diff --git a/packages/e2e-test-utils/src/mocks/create-embedding-matcher.js b/packages/e2e-test-utils/src/mocks/create-embedding-matcher.js index 9673ea04706d4a..403b3a3911220d 100644 --- a/packages/e2e-test-utils/src/mocks/create-embedding-matcher.js +++ b/packages/e2e-test-utils/src/mocks/create-embedding-matcher.js @@ -13,7 +13,9 @@ import { createURLMatcher } from './create-url-matcher'; function parameterEquals( parameterName, value ) { return ( request ) => { const url = request.url(); - const match = new RegExp( `.*${ parameterName }=([^&]+).*` ).exec( url ); + const match = new RegExp( `.*${ parameterName }=([^&]+).*` ).exec( + url + ); if ( ! match ) { return false; } diff --git a/packages/e2e-test-utils/src/mocks/create-json-response.js b/packages/e2e-test-utils/src/mocks/create-json-response.js index f7bddf0befe64c..4836ecbc720be4 100644 --- a/packages/e2e-test-utils/src/mocks/create-json-response.js +++ b/packages/e2e-test-utils/src/mocks/create-json-response.js @@ -10,5 +10,6 @@ import { getJSONResponse } from '../shared/get-json-response'; * @return {Promise} Promise that responds to a request with the mock JSON response. */ export function createJSONResponse( mockResponse ) { - return async ( request ) => request.respond( getJSONResponse( mockResponse ) ); + return async ( request ) => + request.respond( getJSONResponse( mockResponse ) ); } diff --git a/packages/e2e-test-utils/src/mocks/mock-or-transform.js b/packages/e2e-test-utils/src/mocks/mock-or-transform.js index 12cf8af07b24d2..5b2a33de38cca8 100644 --- a/packages/e2e-test-utils/src/mocks/mock-or-transform.js +++ b/packages/e2e-test-utils/src/mocks/mock-or-transform.js @@ -35,7 +35,9 @@ export function mockOrTransform( if ( mockCheck( responseObject ) ) { request.respond( getJSONResponse( mock ) ); } else { - request.respond( getJSONResponse( responseObjectTransform( responseObject ) ) ); + request.respond( + getJSONResponse( responseObjectTransform( responseObject ) ) + ); } }; } diff --git a/packages/e2e-test-utils/src/observe-focus-loss.js b/packages/e2e-test-utils/src/observe-focus-loss.js index 1266d0997c1f29..e89cb298273dfb 100644 --- a/packages/e2e-test-utils/src/observe-focus-loss.js +++ b/packages/e2e-test-utils/src/observe-focus-loss.js @@ -5,7 +5,10 @@ export async function enableFocusLossObservation() { await page.evaluate( () => { if ( window._detectFocusLoss ) { - document.body.removeEventListener( 'focusout', window._detectFocusLoss ); + document.body.removeEventListener( + 'focusout', + window._detectFocusLoss + ); } window._detectFocusLoss = ( event ) => { @@ -24,7 +27,10 @@ export async function enableFocusLossObservation() { export async function disableFocusLossObservation() { await page.evaluate( () => { if ( window._detectFocusLoss ) { - document.body.removeEventListener( 'focusout', window._detectFocusLoss ); + document.body.removeEventListener( + 'focusout', + window._detectFocusLoss + ); } } ); } diff --git a/packages/e2e-test-utils/src/open-all-block-inserter-categories.js b/packages/e2e-test-utils/src/open-all-block-inserter-categories.js index f022a0921ed1ad..9b31a0cd36c4ae 100644 --- a/packages/e2e-test-utils/src/open-all-block-inserter-categories.js +++ b/packages/e2e-test-utils/src/open-all-block-inserter-categories.js @@ -2,7 +2,8 @@ * Opens all block inserter categories. */ export async function openAllBlockInserterCategories() { - const notOppenedCategorySelector = '.block-editor-inserter__results .components-panel__body:not(.is-opened)'; + const notOppenedCategorySelector = + '.block-editor-inserter__results .components-panel__body:not(.is-opened)'; let categoryPanel = await page.$( notOppenedCategorySelector ); while ( categoryPanel !== null ) { await categoryPanel.click(); diff --git a/packages/e2e-test-utils/src/press-key-with-modifier.js b/packages/e2e-test-utils/src/press-key-with-modifier.js index c36b316cea3ef4..abb7f168e60901 100644 --- a/packages/e2e-test-utils/src/press-key-with-modifier.js +++ b/packages/e2e-test-utils/src/press-key-with-modifier.js @@ -32,7 +32,8 @@ async function emulateSelectAll() { key: isMac ? 'Meta' : 'Control', code: isMac ? 'MetaLeft' : 'ControlLeft', location: window.KeyboardEvent.DOM_KEY_LOCATION_LEFT, - getModifierState: ( keyArg ) => keyArg === ( isMac ? 'Meta' : 'Control' ), + getModifierState: ( keyArg ) => + keyArg === ( isMac ? 'Meta' : 'Control' ), ctrlKey: ! isMac, metaKey: isMac, charCode: 0, @@ -47,7 +48,8 @@ async function emulateSelectAll() { key: 'a', code: 'KeyA', location: window.KeyboardEvent.DOM_KEY_LOCATION_STANDARD, - getModifierState: ( keyArg ) => keyArg === ( isMac ? 'Meta' : 'Control' ), + getModifierState: ( keyArg ) => + keyArg === ( isMac ? 'Meta' : 'Control' ), ctrlKey: ! isMac, metaKey: isMac, charCode: 0, @@ -55,10 +57,9 @@ async function emulateSelectAll() { which: 65, } ); - const wasPrevented = ( + const wasPrevented = ! document.activeElement.dispatchEvent( preventableEvent ) || - preventableEvent.defaultPrevented - ); + preventableEvent.defaultPrevented; if ( ! wasPrevented ) { document.execCommand( 'selectall', false, null ); @@ -102,10 +103,12 @@ async function emulateClipboard( type ) { window._clipboardData.setData( 'text/html', html ); } - document.activeElement.dispatchEvent( new ClipboardEvent( _type, { - bubbles: true, - clipboardData: window._clipboardData, - } ) ); + document.activeElement.dispatchEvent( + new ClipboardEvent( _type, { + bubbles: true, + clipboardData: window._clipboardData, + } ) + ); }, type ); } @@ -136,10 +139,11 @@ export async function pressKeyWithModifier( modifier, key ) { const isAppleOS = () => process.platform === 'darwin'; const overWrittenModifiers = { ...modifiers, - shiftAlt: ( _isApple ) => _isApple() ? [ SHIFT, ALT ] : [ SHIFT, CTRL ], + shiftAlt: ( _isApple ) => + _isApple() ? [ SHIFT, ALT ] : [ SHIFT, CTRL ], }; const mappedModifiers = overWrittenModifiers[ modifier ]( isAppleOS ); - const ctrlSwap = ( mod ) => mod === CTRL ? 'control' : mod; + const ctrlSwap = ( mod ) => ( mod === CTRL ? 'control' : mod ); await Promise.all( mappedModifiers.map( async ( mod ) => { diff --git a/packages/e2e-test-utils/src/set-browser-viewport.js b/packages/e2e-test-utils/src/set-browser-viewport.js index ddf65df5a83180..3408dfe03903eb 100644 --- a/packages/e2e-test-utils/src/set-browser-viewport.js +++ b/packages/e2e-test-utils/src/set-browser-viewport.js @@ -43,9 +43,10 @@ const PREDEFINED_DIMENSIONS = { * @param {WPViewport} viewport Viewport name or dimensions object to assign. */ export async function setBrowserViewport( viewport ) { - const dimensions = typeof viewport === 'string' ? - PREDEFINED_DIMENSIONS[ viewport ] : - viewport; + const dimensions = + typeof viewport === 'string' + ? PREDEFINED_DIMENSIONS[ viewport ] + : viewport; await page.setViewport( dimensions ); await waitForWindowDimensions( dimensions.width, dimensions.height ); diff --git a/packages/e2e-test-utils/src/shared/config.js b/packages/e2e-test-utils/src/shared/config.js index f098b78c528791..3365722bd90721 100644 --- a/packages/e2e-test-utils/src/shared/config.js +++ b/packages/e2e-test-utils/src/shared/config.js @@ -9,9 +9,4 @@ const { WP_BASE_URL = 'http://localhost:8889', } = process.env; -export { - WP_ADMIN_USER, - WP_USERNAME, - WP_PASSWORD, - WP_BASE_URL, -}; +export { WP_ADMIN_USER, WP_USERNAME, WP_PASSWORD, WP_BASE_URL }; diff --git a/packages/e2e-test-utils/src/toggle-screen-option.js b/packages/e2e-test-utils/src/toggle-screen-option.js index d66f557fec1751..bada7373cafbcd 100644 --- a/packages/e2e-test-utils/src/toggle-screen-option.js +++ b/packages/e2e-test-utils/src/toggle-screen-option.js @@ -13,7 +13,9 @@ import { clickOnMoreMenuItem } from './click-on-more-menu-item'; */ export async function toggleScreenOption( label, shouldBeChecked = undefined ) { await clickOnMoreMenuItem( 'Options' ); - const [ handle ] = await page.$x( `//label[contains(text(), "${ label }")]` ); + const [ handle ] = await page.$x( + `//label[contains(text(), "${ label }")]` + ); const isChecked = await page.evaluate( ( element ) => element.control.checked, diff --git a/packages/e2e-test-utils/src/transform-block-to.js b/packages/e2e-test-utils/src/transform-block-to.js index 2bf0b5d559a354..018b387c23581f 100644 --- a/packages/e2e-test-utils/src/transform-block-to.js +++ b/packages/e2e-test-utils/src/transform-block-to.js @@ -6,12 +6,16 @@ export async function transformBlockTo( name ) { await page.mouse.move( 0, 0 ); await page.mouse.move( 10, 10 ); - const switcherToggle = await page.waitForSelector( '.block-editor-block-switcher__toggle' ); + const switcherToggle = await page.waitForSelector( + '.block-editor-block-switcher__toggle' + ); await switcherToggle.click(); // Find the block button option within the switcher popover. const switcher = await page.$( '.block-editor-block-switcher__container' ); - const insertButton = ( await switcher.$x( `//button[.='${ name }']` ) )[ 0 ]; + const insertButton = ( + await switcher.$x( `//button[.='${ name }']` ) + )[ 0 ]; // Clicks may fail if the button is out of view. Assure it is before click. await insertButton.evaluate( ( element ) => element.scrollIntoView() ); diff --git a/packages/e2e-test-utils/src/visit-admin-page.js b/packages/e2e-test-utils/src/visit-admin-page.js index 12a8f327e544b8..ac74c4ce688182 100644 --- a/packages/e2e-test-utils/src/visit-admin-page.js +++ b/packages/e2e-test-utils/src/visit-admin-page.js @@ -17,9 +17,7 @@ import { loginUser } from './login-user'; * @param {string} query String to be serialized as query portion of URL. */ export async function visitAdminPage( adminPath, query ) { - await page.goto( - createURL( join( 'wp-admin', adminPath ), query ) - ); + await page.goto( createURL( join( 'wp-admin', adminPath ), query ) ); if ( isCurrentURL( 'wp-login.php' ) ) { await loginUser(); diff --git a/packages/e2e-test-utils/src/wp-data-select.js b/packages/e2e-test-utils/src/wp-data-select.js index 05b92b0a6dbbec..43ddafcd52abf3 100644 --- a/packages/e2e-test-utils/src/wp-data-select.js +++ b/packages/e2e-test-utils/src/wp-data-select.js @@ -10,7 +10,9 @@ export function wpDataSelect( store, selector, ...parameters ) { return page.evaluate( ( _store, _selector, ..._parameters ) => { - return window.wp.data.select( _store )[ _selector ]( ..._parameters ); + return window.wp.data + .select( _store ) + [ _selector ]( ..._parameters ); }, store, selector, diff --git a/packages/e2e-tests/config/gutenberg-phase.js b/packages/e2e-tests/config/gutenberg-phase.js index 1b6117b3236a6a..2e119cd3e89f07 100644 --- a/packages/e2e-tests/config/gutenberg-phase.js +++ b/packages/e2e-tests/config/gutenberg-phase.js @@ -2,5 +2,8 @@ global.process.env = { ...global.process.env, // Inject the `GUTENBERG_PHASE` global, used for feature flagging. // eslint-disable-next-line @wordpress/gutenberg-phase - GUTENBERG_PHASE: parseInt( process.env.npm_package_config_GUTENBERG_PHASE, 10 ), + GUTENBERG_PHASE: parseInt( + process.env.npm_package_config_GUTENBERG_PHASE, + 10 + ), }; diff --git a/packages/e2e-tests/config/setup-test-framework.js b/packages/e2e-tests/config/setup-test-framework.js index edc2c521ee7bec..d4f547a7190cc4 100644 --- a/packages/e2e-tests/config/setup-test-framework.js +++ b/packages/e2e-tests/config/setup-test-framework.js @@ -139,7 +139,9 @@ function observeConsoleLogging() { // See: https://core.trac.wordpress.org/ticket/37000 // See: https://www.chromestatus.com/feature/5088147346030592 // See: https://www.chromestatus.com/feature/5633521622188032 - if ( text.includes( 'A cookie associated with a cross-site resource' ) ) { + if ( + text.includes( 'A cookie associated with a cross-site resource' ) + ) { return; } @@ -151,7 +153,10 @@ function observeConsoleLogging() { // Network errors are ignored only if we are intentionally testing // offline mode. - if ( text.includes( 'net::ERR_INTERNET_DISCONNECTED' ) && isOfflineMode() ) { + if ( + text.includes( 'net::ERR_INTERNET_DISCONNECTED' ) && + isOfflineMode() + ) { return; } @@ -184,7 +189,11 @@ function observeConsoleLogging() { // correctly. Instead, the logic here synchronously inspects the // internal object shape of the JSHandle to find the error text. If it // cannot be found, the default text value is used instead. - text = get( message.args(), [ 0, '_remoteObject', 'description' ], text ); + text = get( + message.args(), + [ 0, '_remoteObject', 'description' ], + text + ); // Disable reason: We intentionally bubble up the console message // which, unless the test explicitly anticipates the logging via @@ -202,7 +211,7 @@ function observeConsoleLogging() { * @return {?Promise} Promise resolving once Axe texts are finished. */ async function runAxeTestsForBlockEditor() { - if ( ! await page.$( '.block-editor' ) ) { + if ( ! ( await page.$( '.block-editor' ) ) ) { return; } diff --git a/packages/e2e-tests/experimental-features.js b/packages/e2e-tests/experimental-features.js index 4b06a761c865a0..bbbd3bb6bf7435 100644 --- a/packages/e2e-tests/experimental-features.js +++ b/packages/e2e-tests/experimental-features.js @@ -10,14 +10,16 @@ async function setExperimentalFeaturesState( features, enable ) { } ); await visitAdminPage( '/admin.php', query ); - await Promise.all( features.map( async ( feature ) => { - await page.waitForSelector( feature ); - const checkedSelector = `${ feature }[checked=checked]`; - const isChecked = !! ( await page.$( checkedSelector ) ); - if ( ( ! isChecked && enable ) || ( isChecked && ! enable ) ) { - await page.click( feature ); - } - } ) ); + await Promise.all( + features.map( async ( feature ) => { + await page.waitForSelector( feature ); + const checkedSelector = `${ feature }[checked=checked]`; + const isChecked = !! ( await page.$( checkedSelector ) ); + if ( ( ! isChecked && enable ) || ( isChecked && ! enable ) ) { + await page.click( feature ); + } + } ) + ); await Promise.all( [ page.waitForNavigation( { waitUntil: 'networkidle0' } ), page.click( '#submit' ), diff --git a/packages/e2e-tests/fixtures/block-transforms.js b/packages/e2e-tests/fixtures/block-transforms.js index 3677373fbeb301..30a22bfc3e7c98 100644 --- a/packages/e2e-tests/fixtures/block-transforms.js +++ b/packages/e2e-tests/fixtures/block-transforms.js @@ -1,209 +1,127 @@ export const EXPECTED_TRANSFORMS = { core__archives: { originalBlock: 'Archives', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__archives__showPostCounts: { originalBlock: 'Archives', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__audio: { originalBlock: 'Audio', - availableTransforms: [ - 'File', - 'Group', - ], + availableTransforms: [ 'File', 'Group' ], }, core__button__center: { originalBlock: 'Button', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__button__squared: { originalBlock: 'Button', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__buttons: { originalBlock: 'Buttons', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__calendar: { originalBlock: 'Calendar', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, 'core__media-text': { originalBlock: 'Media & Text', - availableTransforms: [ - 'Group', - 'Image', - ], + availableTransforms: [ 'Group', 'Image' ], }, 'core__media-text__image-alt-no-align': { originalBlock: 'Media & Text', - availableTransforms: [ - 'Group', - 'Image', - ], + availableTransforms: [ 'Group', 'Image' ], }, 'core__media-text__image-fill-no-focal-point-selected': { originalBlock: 'Media & Text', - availableTransforms: [ - 'Group', - 'Image', - ], + availableTransforms: [ 'Group', 'Image' ], }, 'core__media-text__image-fill-with-focal-point-selected': { originalBlock: 'Media & Text', - availableTransforms: [ - 'Group', - 'Image', - ], + availableTransforms: [ 'Group', 'Image' ], }, 'core__media-text__is-stacked-on-mobile': { originalBlock: 'Media & Text', - availableTransforms: [ - 'Group', - 'Video', - ], + availableTransforms: [ 'Group', 'Video' ], }, 'core__media-text__media-right-custom-width': { originalBlock: 'Media & Text', - availableTransforms: [ - 'Group', - 'Video', - ], + availableTransforms: [ 'Group', 'Video' ], }, 'core__media-text__vertical-align-bottom': { originalBlock: 'Media & Text', - availableTransforms: [ - 'Group', - 'Image', - ], + availableTransforms: [ 'Group', 'Image' ], }, 'core__media-text__video': { originalBlock: 'Media & Text', - availableTransforms: [ - 'Group', - 'Video', - ], + availableTransforms: [ 'Group', 'Video' ], }, core__categories: { originalBlock: 'Categories', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__code: { originalBlock: 'Code', - availableTransforms: [ - 'Group', - 'Preformatted', - ], + availableTransforms: [ 'Group', 'Preformatted' ], }, core__columns: { originalBlock: 'Columns', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__cover: { - availableTransforms: [ - 'Group', - 'Image', - ], + availableTransforms: [ 'Group', 'Image' ], originalBlock: 'Cover', }, core__cover__gradient: { - availableTransforms: [ - 'Group', - 'Image', - 'Video', - ], + availableTransforms: [ 'Group', 'Image', 'Video' ], originalBlock: 'Cover', }, 'core__cover__gradient-image': { - availableTransforms: [ - 'Group', - 'Image', - ], + availableTransforms: [ 'Group', 'Image' ], originalBlock: 'Cover', }, 'core__cover__gradient-video': { - availableTransforms: [ - 'Group', - 'Video', - ], + availableTransforms: [ 'Group', 'Video' ], originalBlock: 'Cover', }, core__cover__video: { - availableTransforms: [ - 'Group', - 'Video', - ], + availableTransforms: [ 'Group', 'Video' ], originalBlock: 'Cover', }, 'core__cover__video-overlay': { - availableTransforms: [ - 'Group', - 'Video', - ], + availableTransforms: [ 'Group', 'Video' ], originalBlock: 'Cover', }, core__embed: { originalBlock: 'Embed', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, 'core__file__new-window': { originalBlock: 'File', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, 'core__file__no-download-button': { originalBlock: 'File', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, 'core__file__no-text-link': { originalBlock: 'File', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__gallery: { originalBlock: 'Gallery', - availableTransforms: [ - 'Group', - 'Image', - ], + availableTransforms: [ 'Group', 'Image' ], }, 'core__gallery-with-caption': { originalBlock: 'Gallery', - availableTransforms: [ - 'Group', - 'Image', - ], + availableTransforms: [ 'Group', 'Image' ], }, core__gallery__columns: { originalBlock: 'Gallery', - availableTransforms: [ - 'Group', - 'Image', - ], + availableTransforms: [ 'Group', 'Image' ], }, core__group: { originalBlock: 'Group', @@ -211,33 +129,19 @@ export const EXPECTED_TRANSFORMS = { }, 'core__heading__h2-color': { originalBlock: 'Heading', - availableTransforms: [ - 'Quote', - 'Group', - 'Paragraph', - ], + availableTransforms: [ 'Quote', 'Group', 'Paragraph' ], }, 'core__heading__h4-em': { originalBlock: 'Heading', - availableTransforms: [ - 'Quote', - 'Group', - 'Paragraph', - ], + availableTransforms: [ 'Quote', 'Group', 'Paragraph' ], }, core__heading__h2: { originalBlock: 'Heading', - availableTransforms: [ - 'Quote', - 'Group', - 'Paragraph', - ], + availableTransforms: [ 'Quote', 'Group', 'Paragraph' ], }, core__html: { originalBlock: 'Custom HTML', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__image: { originalBlock: 'Image', @@ -311,65 +215,43 @@ export const EXPECTED_TRANSFORMS = { }, 'core__latest-comments': { originalBlock: 'Latest Comments', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, 'core__latest-posts': { originalBlock: 'Latest Posts', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, 'core__latest-posts__displayPostDate': { originalBlock: 'Latest Posts', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, 'core__legacy-widget': { originalBlock: 'Legacy Widget (Experimental)', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__list__ul: { originalBlock: 'List', - availableTransforms: [ - 'Group', - 'Paragraph', - 'Quote', - ], + availableTransforms: [ 'Group', 'Paragraph', 'Quote' ], }, core__more: { originalBlock: 'More', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, 'core__more__custom-text-teaser': { originalBlock: 'More', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__navigation: { originalBlock: 'Navigation', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, 'core__navigation-link': { originalBlock: 'Navigation Link', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__nextpage: { originalBlock: 'Page Break', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, 'core__paragraph__align-right': { originalBlock: 'Paragraph', @@ -383,44 +265,28 @@ export const EXPECTED_TRANSFORMS = { ], }, 'core__post-content': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Post Content', }, 'core__post-title': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Post Title', }, core__preformatted: { originalBlock: 'Preformatted', - availableTransforms: [ - 'Group', - 'Paragraph', - ], + availableTransforms: [ 'Group', 'Paragraph' ], }, core__pullquote: { originalBlock: 'Pullquote', - availableTransforms: [ - 'Quote', - 'Group', - ], + availableTransforms: [ 'Quote', 'Group' ], }, 'core__pullquote__main-color': { originalBlock: 'Pullquote', - availableTransforms: [ - 'Quote', - 'Group', - ], + availableTransforms: [ 'Quote', 'Group' ], }, 'core__pullquote__multi-paragraph': { originalBlock: 'Pullquote', - availableTransforms: [ - 'Quote', - 'Group', - ], + availableTransforms: [ 'Quote', 'Group' ], }, 'core__quote__style-1': { originalBlock: 'Quote', @@ -444,330 +310,218 @@ export const EXPECTED_TRANSFORMS = { }, core__rss: { originalBlock: 'RSS', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__search: { originalBlock: 'Search', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, 'core__search__custom-text': { originalBlock: 'Search', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__separator: { originalBlock: 'Separator', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__shortcode: { originalBlock: 'Shortcode', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, 'core__site-title': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Site Title', }, 'core__social-link-amazon': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Amazon', }, 'core__social-link-bandcamp': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Bandcamp', }, 'core__social-link-behance': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Behance', }, 'core__social-link-chain': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Link', }, 'core__social-link-codepen': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'CodePen', }, 'core__social-link-deviantart': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'DeviantArt', }, 'core__social-link-dribbble': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Dribbble', }, 'core__social-link-dropbox': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Dropbox', }, 'core__social-link-etsy': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Etsy', }, 'core__social-link-facebook': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Facebook', }, 'core__social-link-feed': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'RSS Feed', }, 'core__social-link-fivehundredpx': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: '500px', }, 'core__social-link-flickr': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Flickr', }, 'core__social-link-foursquare': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Foursquare', }, 'core__social-link-github': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'GitHub', }, 'core__social-link-goodreads': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Goodreads', }, 'core__social-link-google': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Google', }, 'core__social-link-instagram': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Instagram', }, 'core__social-link-lastfm': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Last.fm', }, 'core__social-link-linkedin': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'LinkedIn', }, 'core__social-link-mail': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Mail', }, 'core__social-link-mastodon': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Mastodon', }, 'core__social-link-medium': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Medium', }, 'core__social-link-meetup': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Meetup', }, 'core__social-link-pinterest': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Pinterest', }, 'core__social-link-pocket': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Pocket', }, 'core__social-link-reddit': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Reddit', }, 'core__social-link-skype': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Skype', }, 'core__social-link-snapchat': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Snapchat', }, 'core__social-link-soundcloud': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Soundcloud', }, 'core__social-link-spotify': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Spotify', }, 'core__social-link-tumblr': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Tumblr', }, 'core__social-link-twitch': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Twitch', }, 'core__social-link-twitter': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Twitter', }, 'core__social-link-vimeo': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Vimeo', }, 'core__social-link-vk': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'VK', }, 'core__social-link-wordpress': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'WordPress', }, 'core__social-link-yelp': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'Yelp', }, 'core__social-link-youtube': { - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], originalBlock: 'YouTube', }, 'core__social-links': { originalBlock: 'Social links', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__spacer: { originalBlock: 'Spacer', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__table: { originalBlock: 'Table', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__table__caption: { originalBlock: 'Table', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, 'core__table__scope-attribute': { originalBlock: 'Table', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, 'core__tag-cloud': { originalBlock: 'Tag Cloud', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, 'core__tag-cloud__showTagCounts': { originalBlock: 'Tag Cloud', - availableTransforms: [ - 'Group', - ], + availableTransforms: [ 'Group' ], }, core__verse: { originalBlock: 'Verse', - availableTransforms: [ - 'Group', - 'Paragraph', - ], + availableTransforms: [ 'Group', 'Paragraph' ], }, core__video: { originalBlock: 'Video', - availableTransforms: [ - 'Cover', - 'File', - 'Group', - 'Media & Text', - ], + availableTransforms: [ 'Cover', 'File', 'Group', 'Media & Text' ], }, }; diff --git a/packages/e2e-tests/fixtures/utils.js b/packages/e2e-tests/fixtures/utils.js index 344e6d4a0504a7..ff96e72d9a011e 100644 --- a/packages/e2e-tests/fixtures/utils.js +++ b/packages/e2e-tests/fixtures/utils.js @@ -9,20 +9,14 @@ const FIXTURES_DIR = path.join( __dirname, 'blocks' ); function readFixtureFile( fixturesDir, filename ) { try { - return fs.readFileSync( - path.join( fixturesDir, filename ), - 'utf8' - ); + return fs.readFileSync( path.join( fixturesDir, filename ), 'utf8' ); } catch ( err ) { return null; } } function writeFixtureFile( fixturesDir, filename, content ) { - fs.writeFileSync( - path.join( fixturesDir, filename ), - content - ); + fs.writeFileSync( path.join( fixturesDir, filename ), content ); } export function blockNameToFixtureBasename( blockName ) { @@ -37,7 +31,8 @@ export function getAvailableBlockFixturesBasenames() { // - fixture.serialized.html : re-serialized content // Get the "base" name for each fixture first. return uniq( - fs.readdirSync( FIXTURES_DIR ) + fs + .readdirSync( FIXTURES_DIR ) .filter( ( f ) => /(\.html|\.json)$/.test( f ) ) .map( ( f ) => f.replace( /\..+$/, '' ) ) ); diff --git a/packages/e2e-tests/jest.config.js b/packages/e2e-tests/jest.config.js index 22bccf74e3d1a1..6b82ce764de14a 100644 --- a/packages/e2e-tests/jest.config.js +++ b/packages/e2e-tests/jest.config.js @@ -1,8 +1,6 @@ module.exports = { ...require( '@wordpress/scripts/config/jest-e2e.config' ), - setupFiles: [ - '<rootDir>/config/gutenberg-phase.js', - ], + setupFiles: [ '<rootDir>/config/gutenberg-phase.js' ], setupFilesAfterEnv: [ '<rootDir>/config/setup-test-framework.js', '@wordpress/jest-console', diff --git a/packages/e2e-tests/jest.performance.config.js b/packages/e2e-tests/jest.performance.config.js index cc011820f08871..e84d03483ba957 100644 --- a/packages/e2e-tests/jest.performance.config.js +++ b/packages/e2e-tests/jest.performance.config.js @@ -1,11 +1,7 @@ module.exports = { ...require( '@wordpress/scripts/config/jest-e2e.config' ), - testMatch: [ - '**/performance/*.test.js', - ], - setupFiles: [ - '<rootDir>/config/gutenberg-phase.js', - ], + testMatch: [ '**/performance/*.test.js' ], + setupFiles: [ '<rootDir>/config/gutenberg-phase.js' ], setupFilesAfterEnv: [ '<rootDir>/config/setup-test-framework.js', '@wordpress/jest-console', @@ -16,8 +12,5 @@ module.exports = { 'node_modules', 'scripts/config/puppeteer.config.js', ], - reporters: [ - 'default', - '<rootDir>/config/performance-reporter.js', - ], + reporters: [ 'default', '<rootDir>/config/performance-reporter.js' ], }; diff --git a/packages/e2e-tests/specs/editor/blocks/buttons.test.js b/packages/e2e-tests/specs/editor/blocks/buttons.test.js index bd975d6bdf1697..3938bc2edb6c1d 100644 --- a/packages/e2e-tests/specs/editor/blocks/buttons.test.js +++ b/packages/e2e-tests/specs/editor/blocks/buttons.test.js @@ -38,7 +38,9 @@ describe( 'Buttons', () => { await page.keyboard.press( 'Enter' ); // Make sure that the dialog is still opened, and that focus is retained // within (focusing on the link preview). - await page.waitForSelector( ':focus.block-editor-link-control__search-item-title' ); + await page.waitForSelector( + ':focus.block-editor-link-control__search-item-title' + ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); diff --git a/packages/e2e-tests/specs/editor/blocks/classic.test.js b/packages/e2e-tests/specs/editor/blocks/classic.test.js index 29170335d900f1..456ce33f557930 100644 --- a/packages/e2e-tests/specs/editor/blocks/classic.test.js +++ b/packages/e2e-tests/specs/editor/blocks/classic.test.js @@ -49,14 +49,23 @@ describe( 'Classic', () => { // Wait for media modal to appear and upload image. await page.waitForSelector( '.media-modal input[type=file]' ); const inputElement = await page.$( '.media-modal input[type=file]' ); - const testImagePath = path.join( __dirname, '..', '..', '..', 'assets', '10x10_e2e_test_image_z9T8jK.png' ); + const testImagePath = path.join( + __dirname, + '..', + '..', + '..', + 'assets', + '10x10_e2e_test_image_z9T8jK.png' + ); const filename = uuid(); const tmpFileName = path.join( os.tmpdir(), filename + '.png' ); fs.copyFileSync( testImagePath, tmpFileName ); await inputElement.uploadFile( tmpFileName ); // Wait for upload. - await page.waitForSelector( `.media-modal li[aria-label="${ filename }"]` ); + await page.waitForSelector( + `.media-modal li[aria-label="${ filename }"]` + ); // Insert the uploaded image. await page.click( '.media-modal button.media-button-insert' ); @@ -67,7 +76,9 @@ describe( 'Classic', () => { // Move focus away. await pressKeyWithModifier( 'shift', 'Tab' ); - const regExp = new RegExp( `test<img class="alignnone size-full wp-image-\\d+" src="[^"]+\\/${ filename }\\.png" alt="" width="10" height="10" \\/>` ); + const regExp = new RegExp( + `test<img class="alignnone size-full wp-image-\\d+" src="[^"]+\\/${ filename }\\.png" alt="" width="10" height="10" \\/>` + ); expect( await getEditedPostContent() ).toMatch( regExp ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/blocks/columns.test.js b/packages/e2e-tests/specs/editor/blocks/columns.test.js index 7b8070e6881097..6255f6c5c5ac8d 100644 --- a/packages/e2e-tests/specs/editor/blocks/columns.test.js +++ b/packages/e2e-tests/specs/editor/blocks/columns.test.js @@ -18,7 +18,11 @@ describe( 'Columns', () => { await insertBlock( 'Columns' ); await page.click( '[aria-label="Two columns; equal split"]' ); await page.click( '[aria-label="Block navigation"]' ); - const columnBlockMenuItem = ( await page.$x( '//button[contains(concat(" ", @class, " "), " block-editor-block-navigation__item-button ")][text()="Column"]' ) )[ 0 ]; + const columnBlockMenuItem = ( + await page.$x( + '//button[contains(concat(" ", @class, " "), " block-editor-block-navigation__item-button ")][text()="Column"]' + ) + )[ 0 ]; await columnBlockMenuItem.click(); await openGlobalBlockInserter(); await openAllBlockInserterCategories(); diff --git a/packages/e2e-tests/specs/editor/blocks/heading.test.js b/packages/e2e-tests/specs/editor/blocks/heading.test.js index 23149b198adb88..58d328be5e1d52 100644 --- a/packages/e2e-tests/specs/editor/blocks/heading.test.js +++ b/packages/e2e-tests/specs/editor/blocks/heading.test.js @@ -11,8 +11,10 @@ import { describe( 'Heading', () => { const CUSTOM_COLOR_TEXT = 'Custom Color'; const CUSTOM_COLOR_BUTTON_X_SELECTOR = `//button[contains(text(),'${ CUSTOM_COLOR_TEXT }')]`; - const COLOR_INPUT_FIELD_SELECTOR = '.components-color-palette__picker .components-text-control__input'; - const COLOR_PANEL_TOGGLE_X_SELECTOR = '//button[./span[contains(text(),\'Color settings\')]]'; + const COLOR_INPUT_FIELD_SELECTOR = + '.components-color-palette__picker .components-text-control__input'; + const COLOR_PANEL_TOGGLE_X_SELECTOR = + "//button[./span[contains(text(),'Color settings')]]"; beforeEach( async () => { await createNewPost(); @@ -54,7 +56,9 @@ describe( 'Heading', () => { it( 'it should correctly apply custom colors', async () => { await clickBlockAppender(); await page.keyboard.type( '### Heading' ); - const [ colorPanelToggle ] = await page.$x( COLOR_PANEL_TOGGLE_X_SELECTOR ); + const [ colorPanelToggle ] = await page.$x( + COLOR_PANEL_TOGGLE_X_SELECTOR + ); await colorPanelToggle.click(); const [ customTextColorButton ] = await page.$x( @@ -66,21 +70,27 @@ describe( 'Heading', () => { await pressKeyWithModifier( 'primary', 'A' ); await page.keyboard.type( '#7700ff' ); await page.click( '[data-type="core/heading"] h3' ); - await page.waitForSelector( '.component-color-indicator[aria-label="(Color: #7700ff)"]' ); + await page.waitForSelector( + '.component-color-indicator[aria-label="(Color: #7700ff)"]' + ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); it( 'it should correctly apply named colors', async () => { await clickBlockAppender(); await page.keyboard.type( '## Heading' ); - const [ colorPanelToggle ] = await page.$x( COLOR_PANEL_TOGGLE_X_SELECTOR ); + const [ colorPanelToggle ] = await page.$x( + COLOR_PANEL_TOGGLE_X_SELECTOR + ); await colorPanelToggle.click(); const colorButtonSelector = `//button[@aria-label='Color: Luminous vivid orange']`; const [ colorButton ] = await page.$x( colorButtonSelector ); await colorButton.click(); await page.click( '[data-type="core/heading"] h2' ); - await page.waitForXPath( `${ colorButtonSelector }[@aria-pressed='true']` ); + await page.waitForXPath( + `${ colorButtonSelector }[@aria-pressed='true']` + ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); } ); diff --git a/packages/e2e-tests/specs/editor/blocks/html.test.js b/packages/e2e-tests/specs/editor/blocks/html.test.js index 69370b4f622083..34be8f5a6299c5 100644 --- a/packages/e2e-tests/specs/editor/blocks/html.test.js +++ b/packages/e2e-tests/specs/editor/blocks/html.test.js @@ -19,7 +19,9 @@ describe( 'HTML block', () => { await page.keyboard.press( 'Enter' ); await page.keyboard.type( '<p>Pythagorean theorem: ' ); await page.keyboard.press( 'Enter' ); - await page.keyboard.type( '<var>a</var><sup>2</sup> + <var>b</var><sup>2</sup> = <var>c</var><sup>2</sup> </p>' ); + await page.keyboard.type( + '<var>a</var><sup>2</sup> + <var>b</var><sup>2</sup> = <var>c</var><sup>2</sup> </p>' + ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); diff --git a/packages/e2e-tests/specs/editor/blocks/navigation.test.js b/packages/e2e-tests/specs/editor/blocks/navigation.test.js index 42c108f0bcaf12..fd3ab2202f636a 100644 --- a/packages/e2e-tests/specs/editor/blocks/navigation.test.js +++ b/packages/e2e-tests/specs/editor/blocks/navigation.test.js @@ -1,4 +1,3 @@ - /** * WordPress dependencies */ @@ -7,9 +6,9 @@ import { createNewPost, getEditedPostContent, insertBlock, - pressKeyWithModifier, setUpResponseMocking, clickBlockToolbarButton, + pressKeyWithModifier, } from '@wordpress/e2e-test-utils'; async function mockPagesResponse( pages ) { @@ -25,7 +24,12 @@ async function mockPagesResponse( pages ) { await setUpResponseMocking( [ { - match: ( request ) => request.url().includes( `rest_route=${ encodeURIComponent( '/wp/v2/pages' ) }` ), + match: ( request ) => + request + .url() + .includes( + `rest_route=${ encodeURIComponent( '/wp/v2/pages' ) }` + ), onRequestMatch: createJSONResponse( mappedPages ), }, ] ); @@ -42,7 +46,9 @@ async function mockSearchResponse( items ) { await setUpResponseMocking( [ { - match: ( request ) => request.url().includes( `rest_route` ) && request.url().includes( `search` ), + match: ( request ) => + request.url().includes( `rest_route` ) && + request.url().includes( `search` ), onRequestMatch: createJSONResponse( mappedItems ), }, ] ); @@ -52,26 +58,28 @@ async function updateActiveNavigationLink( { url, label } ) { if ( url ) { await page.type( 'input[placeholder="Search or type url"]', url ); // Wait for the autocomplete suggestion item to appear. - await page.waitForXPath( `//span[@class="block-editor-link-control__search-item-title"]/mark[text()="${ url }"]` ); + await page.waitForXPath( + `//span[@class="block-editor-link-control__search-item-title"]/mark[text()="${ url }"]` + ); // Navigate to the first suggestion. await page.keyboard.press( 'ArrowDown' ); // Select the suggestion. await page.keyboard.press( 'Enter' ); - // Make sure that the dialog is still opened, and that focus is retained - // within (focusing on the link preview). - await page.waitForSelector( ':focus.block-editor-link-control__search-item-title' ); } if ( label ) { - await page.click( '.is-selected .wp-block-navigation-link__label' ); - - // Ideally this would be `await pressKeyWithModifier( 'primary', 'a' )` - // to select all text like other tests do. - // Unfortunately, these tests don't seem to pass on Travis CI when - // using that approach, while using `Home` and `End` they do pass. - await page.keyboard.press( 'Home' ); - await pressKeyWithModifier( 'shift', 'End' ); - await page.keyboard.press( 'Backspace' ); + // With https://github.com/WordPress/gutenberg/pull/19686, we're auto-selecting the label if the label is URL-ish. + // In this case, it means we have to select and delete the label if it's _not_ the url. + if ( label !== url ) { + // Ideally this would be `await pressKeyWithModifier( 'primary', 'a' )` + // to select all text like other tests do. + // Unfortunately, these tests don't seem to pass on Travis CI when + // using that approach, while using `Home` and `End` they do pass. + await page.keyboard.press( 'Home' ); + await pressKeyWithModifier( 'shift', 'End' ); + await page.keyboard.press( 'Backspace' ); + } + await page.keyboard.type( label ); } } @@ -108,8 +116,12 @@ describe( 'Navigation', () => { // Create an empty nav block. The 'create' button is disabled until pages are loaded, // so we must wait for it to become not-disabled. - await page.waitForXPath( '//button[text()="Create from all top-level pages"][not(@disabled)]' ); - const [ createFromExistingButton ] = await page.$x( '//button[text()="Create from all top-level pages"][not(@disabled)]' ); + await page.waitForXPath( + '//button[text()="Create from all top-level pages"][not(@disabled)]' + ); + const [ createFromExistingButton ] = await page.$x( + '//button[text()="Create from all top-level pages"][not(@disabled)]' + ); await createFromExistingButton.click(); // Snapshot should contain the mocked pages. @@ -122,11 +134,16 @@ describe( 'Navigation', () => { // Create an empty nav block. await page.waitForSelector( '.wp-block-navigation-placeholder' ); - const [ createEmptyButton ] = await page.$x( '//button[text()="Create empty"]' ); + const [ createEmptyButton ] = await page.$x( + '//button[text()="Create empty"]' + ); await createEmptyButton.click(); // Add a link to the default Navigation Link block. - await updateActiveNavigationLink( { url: 'https://wordpress.org', label: 'WP' } ); + await updateActiveNavigationLink( { + url: 'https://wordpress.org', + label: 'WP', + } ); // Move the mouse to reveal the block movers. Without this the test seems to fail. await page.mouse.move( 100, 100 ); @@ -139,15 +156,18 @@ describe( 'Navigation', () => { // After adding a new block, search input should be shown immediately. // Verify that Escape would close the popover. // Regression: https://github.com/WordPress/gutenberg/pull/19885 - const isInURLInput = await page.evaluate( () => ( - !! document.activeElement.closest( '.block-editor-url-input' ) - ) ); + const isInURLInput = await page.evaluate( + () => !! document.activeElement.closest( '.block-editor-url-input' ) + ); expect( isInURLInput ).toBe( true ); await page.keyboard.press( 'Escape' ); - const isInLinkRichText = await page.evaluate( () => ( - document.activeElement.classList.contains( 'rich-text' ) && - !! document.activeElement.closest( '.block-editor-block-list__block' ) - ) ); + const isInLinkRichText = await page.evaluate( + () => + document.activeElement.classList.contains( 'rich-text' ) && + !! document.activeElement.closest( + '.block-editor-block-list__block' + ) + ); expect( isInLinkRichText ).toBe( true ); // Now, trigger the link dialog once more. @@ -155,10 +175,15 @@ describe( 'Navigation', () => { // For the second nav link block use an existing internal page. // Mock the api response so that it's consistent. - await mockSearchResponse( [ { title: 'Contact Us', slug: 'contact-us' } ] ); + await mockSearchResponse( [ + { title: 'Contact Us', slug: 'contact-us' }, + ] ); // Add a link to the default Navigation Link block. - await updateActiveNavigationLink( { url: 'Contact Us', label: 'Get in touch' } ); + await updateActiveNavigationLink( { + url: 'Contact Us', + label: 'Get in touch', + } ); // Expect a Navigation Block with two Navigation Links in the snapshot. expect( await getEditedPostContent() ).toMatchSnapshot(); diff --git a/packages/e2e-tests/specs/editor/blocks/spacer.test.js b/packages/e2e-tests/specs/editor/blocks/spacer.test.js index bd02e7a8505599..4850d73570e964 100644 --- a/packages/e2e-tests/specs/editor/blocks/spacer.test.js +++ b/packages/e2e-tests/specs/editor/blocks/spacer.test.js @@ -28,11 +28,15 @@ describe( 'Spacer', () => { await page.keyboard.type( '/spacer' ); await page.keyboard.press( 'Enter' ); - const resizableHandle = await page.$( '.block-library-spacer__resize-container .components-resizable-box__handle' ); + const resizableHandle = await page.$( + '.block-library-spacer__resize-container .components-resizable-box__handle' + ); await dragAndResize( resizableHandle, { x: 0, y: 50 } ); expect( await getEditedPostContent() ).toMatchSnapshot(); - const selectedSpacer = await page.$( '[data-type="core/spacer"].is-selected' ); + const selectedSpacer = await page.$( + '[data-type="core/spacer"].is-selected' + ); expect( selectedSpacer ).not.toBe( null ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/blocks/table.test.js b/packages/e2e-tests/specs/editor/blocks/table.test.js index 99c4a195e458bb..680416980067d5 100644 --- a/packages/e2e-tests/specs/editor/blocks/table.test.js +++ b/packages/e2e-tests/specs/editor/blocks/table.test.js @@ -35,23 +35,31 @@ describe( 'Table', () => { await insertBlock( 'Table' ); // Check for existence of the column count field. - const columnCountLabel = await page.$x( "//div[@data-type='core/table']//label[text()='Column Count']" ); + const columnCountLabel = await page.$x( + "//div[@data-type='core/table']//label[text()='Column Count']" + ); expect( columnCountLabel ).toHaveLength( 1 ); // Modify the column count. await columnCountLabel[ 0 ].click(); - const currentColumnCount = await page.evaluate( () => document.activeElement.value ); + const currentColumnCount = await page.evaluate( + () => document.activeElement.value + ); expect( currentColumnCount ).toBe( '2' ); await page.keyboard.press( 'Backspace' ); await page.keyboard.type( '5' ); // Check for existence of the row count field. - const rowCountLabel = await page.$x( "//div[@data-type='core/table']//label[text()='Row Count']" ); + const rowCountLabel = await page.$x( + "//div[@data-type='core/table']//label[text()='Row Count']" + ); expect( rowCountLabel ).toHaveLength( 1 ); // Modify the row count. await rowCountLabel[ 0 ].click(); - const currentRowCount = await page.evaluate( () => document.activeElement.value ); + const currentRowCount = await page.evaluate( + () => document.activeElement.value + ); expect( currentRowCount ).toBe( '2' ); await page.keyboard.press( 'Backspace' ); await page.keyboard.type( '10' ); @@ -141,8 +149,12 @@ describe( 'Table', () => { await clickButton( createButtonLabel ); // Toggle on the switches and add some content. - const headerSwitch = await page.$x( "//label[text()='Header section']" ); - const footerSwitch = await page.$x( "//label[text()='Footer section']" ); + const headerSwitch = await page.$x( + "//label[text()='Header section']" + ); + const footerSwitch = await page.$x( + "//label[text()='Footer section']" + ); await headerSwitch[ 0 ].click(); await footerSwitch[ 0 ].click(); @@ -168,7 +180,9 @@ describe( 'Table', () => { it( 'allows columns to be aligned', async () => { await insertBlock( 'Table' ); - const [ columnCountLabel ] = await page.$x( "//div[@data-type='core/table']//label[text()='Column Count']" ); + const [ columnCountLabel ] = await page.$x( + "//div[@data-type='core/table']//label[text()='Column Count']" + ); await columnCountLabel.click(); await page.keyboard.press( 'Backspace' ); await page.keyboard.type( '4' ); @@ -208,7 +222,9 @@ describe( 'Table', () => { await clickButton( createButtonLabel ); // Enable fixed width as it exascerbates the amount of empty space around the RichText. - const [ fixedWidthSwitch ] = await page.$x( "//label[text()='Fixed width table cells']" ); + const [ fixedWidthSwitch ] = await page.$x( + "//label[text()='Fixed width table cells']" + ); await fixedWidthSwitch.click(); // Add multiple new lines to the first cell to make it taller. @@ -217,7 +233,9 @@ describe( 'Table', () => { // Get the bounding client rect for the second cell. const { x: secondCellX, y: secondCellY } = await page.evaluate( () => { - const secondCell = document.querySelectorAll( '.wp-block-table td' )[ 1 ]; + const secondCell = document.querySelectorAll( + '.wp-block-table td' + )[ 1 ]; // Page.evaluate can only return a serializable value to the // parent process, so destructure and restructure the result // into an object. diff --git a/packages/e2e-tests/specs/editor/plugins/align-hook.test.js b/packages/e2e-tests/specs/editor/plugins/align-hook.test.js index 16a61e53f568bf..7a4bb5253582fa 100644 --- a/packages/e2e-tests/specs/editor/plugins/align-hook.test.js +++ b/packages/e2e-tests/specs/editor/plugins/align-hook.test.js @@ -21,7 +21,8 @@ const alignLabels = { }; describe( 'Align Hook Works As Expected', () => { - const CHANGE_ALIGNMENT_BUTTON_SELECTOR = '.block-editor-block-toolbar .components-dropdown-menu__toggle[aria-label="Change alignment"]'; + const CHANGE_ALIGNMENT_BUTTON_SELECTOR = + '.block-editor-block-toolbar .components-dropdown-menu__toggle[aria-label="Change alignment"]'; beforeAll( async () => { await activatePlugin( 'gutenberg-test-align-hook' ); @@ -43,24 +44,19 @@ describe( 'Align Hook Works As Expected', () => { document.querySelectorAll( '.components-dropdown-menu__menu button' ) - ).map( - ( button ) => { - return button.innerText; - } - ); + ).map( ( button ) => { + return button.innerText; + } ); } ); return buttonLabels; }; const createShowsTheExpectedButtonsTest = ( blockName, buttonLabels ) => { - it( 'Shows the expected buttons on the alignment toolbar', - async () => { - await insertBlock( blockName ); + it( 'Shows the expected buttons on the alignment toolbar', async () => { + await insertBlock( blockName ); - expect( - await getAlignmentToolbarLabels() - ).toEqual( buttonLabels ); - } ); + expect( await getAlignmentToolbarLabels() ).toEqual( buttonLabels ); + } ); }; const createDoesNotApplyAlignmentByDefaultTest = ( blockName ) => { @@ -69,7 +65,9 @@ describe( 'Align Hook Works As Expected', () => { // verify no alignment button is in pressed state await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); - const pressedButtons = await page.$$( '.components-dropdown-menu__menu button.is-active' ); + const pressedButtons = await page.$$( + '.components-dropdown-menu__menu button.is-active' + ); expect( pressedButtons ).toHaveLength( 0 ); } ); }; @@ -81,77 +79,77 @@ describe( 'Align Hook Works As Expected', () => { expect( blocks[ 0 ].isValid ).toBeTruthy(); }; - const createCorrectlyAppliesAndRemovesAlignmentTest = ( blockName, alignment ) => { - it( 'Correctly applies the selected alignment and correctly removes the alignment', - async () => { - const BUTTON_XPATH = `//button[contains(@class,'components-dropdown-menu__menu-item') and contains(text(), '${ alignLabels[ alignment ] }')]`; - const BUTTON_PRESSED_SELECTOR = '.components-dropdown-menu__menu button.is-active'; - // set the specified alignment. - await insertBlock( blockName ); - await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); - await ( await page.$x( BUTTON_XPATH ) )[ 0 ].click(); - - // verify the button of the specified alignment is pressed. - await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); - let pressedButtons = await page.$$( BUTTON_PRESSED_SELECTOR ); - expect( pressedButtons ).toHaveLength( 1 ); - - let htmlMarkup = await getEditedPostContent(); - - // verify the markup of the selected alignment was generated. - expect( htmlMarkup ).toMatchSnapshot(); - - // verify the markup can be correctly parsed - await verifyMarkupIsValid( htmlMarkup ); - - await selectBlockByClientId( - ( await getAllBlocks() )[ 0 ].clientId - ); - - // remove the alignment. - await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); - await ( await page.$x( BUTTON_XPATH ) )[ 0 ].click(); - - // verify no alignment button is in pressed state. - await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); - pressedButtons = await page.$$( BUTTON_PRESSED_SELECTOR ); - expect( pressedButtons ).toHaveLength( 0 ); - - // verify alignment markup was removed from the block. - htmlMarkup = await getEditedPostContent(); - expect( htmlMarkup ).toMatchSnapshot(); - - // verify the markup when no alignment is set is valid - await verifyMarkupIsValid( htmlMarkup ); - - await selectBlockByClientId( - ( await getAllBlocks() )[ 0 ].clientId - ); - - // verify no alignment button is in pressed state after parsing the block. - await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); - pressedButtons = await page.$$( BUTTON_PRESSED_SELECTOR ); - expect( pressedButtons ).toHaveLength( 0 ); - } - ); + const createCorrectlyAppliesAndRemovesAlignmentTest = ( + blockName, + alignment + ) => { + it( 'Correctly applies the selected alignment and correctly removes the alignment', async () => { + const BUTTON_XPATH = `//button[contains(@class,'components-dropdown-menu__menu-item') and contains(text(), '${ alignLabels[ alignment ] }')]`; + const BUTTON_PRESSED_SELECTOR = + '.components-dropdown-menu__menu button.is-active'; + // set the specified alignment. + await insertBlock( blockName ); + await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); + await ( await page.$x( BUTTON_XPATH ) )[ 0 ].click(); + + // verify the button of the specified alignment is pressed. + await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); + let pressedButtons = await page.$$( BUTTON_PRESSED_SELECTOR ); + expect( pressedButtons ).toHaveLength( 1 ); + + let htmlMarkup = await getEditedPostContent(); + + // verify the markup of the selected alignment was generated. + expect( htmlMarkup ).toMatchSnapshot(); + + // verify the markup can be correctly parsed + await verifyMarkupIsValid( htmlMarkup ); + + await selectBlockByClientId( + ( await getAllBlocks() )[ 0 ].clientId + ); + + // remove the alignment. + await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); + await ( await page.$x( BUTTON_XPATH ) )[ 0 ].click(); + + // verify no alignment button is in pressed state. + await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); + pressedButtons = await page.$$( BUTTON_PRESSED_SELECTOR ); + expect( pressedButtons ).toHaveLength( 0 ); + + // verify alignment markup was removed from the block. + htmlMarkup = await getEditedPostContent(); + expect( htmlMarkup ).toMatchSnapshot(); + + // verify the markup when no alignment is set is valid + await verifyMarkupIsValid( htmlMarkup ); + + await selectBlockByClientId( + ( await getAllBlocks() )[ 0 ].clientId + ); + + // verify no alignment button is in pressed state after parsing the block. + await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); + pressedButtons = await page.$$( BUTTON_PRESSED_SELECTOR ); + expect( pressedButtons ).toHaveLength( 0 ); + } ); }; describe( 'Block with no alignment set', () => { const BLOCK_NAME = 'Test No Alignment Set'; - it( 'Shows no alignment buttons on the alignment toolbar', - async () => { - await insertBlock( BLOCK_NAME ); - const changeAlignmentButton = await page.$( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); - expect( changeAlignmentButton ).toBe( null ); - } - ); - - it( 'Does not save any alignment related attribute or class', - async () => { - await insertBlock( BLOCK_NAME ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } - ); + it( 'Shows no alignment buttons on the alignment toolbar', async () => { + await insertBlock( BLOCK_NAME ); + const changeAlignmentButton = await page.$( + CHANGE_ALIGNMENT_BUTTON_SELECTOR + ); + expect( changeAlignmentButton ).toBe( null ); + } ); + + it( 'Does not save any alignment related attribute or class', async () => { + await insertBlock( BLOCK_NAME ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); } ); describe( 'Block with align true', () => { @@ -185,7 +183,8 @@ describe( 'Align Hook Works As Expected', () => { describe( 'Block with default align', () => { const BLOCK_NAME = 'Test Default Align'; - const SELECTED_ALIGNMENT_CONTROL_SELECTOR = '//div[contains(@class, "components-dropdown-menu__menu")]//button[contains(@class, "is-active")][text()="Align right"]'; + const SELECTED_ALIGNMENT_CONTROL_SELECTOR = + '//div[contains(@class, "components-dropdown-menu__menu")]//button[contains(@class, "is-active")][text()="Align right"]'; createShowsTheExpectedButtonsTest( BLOCK_NAME, [ alignLabels.left, alignLabels.center, @@ -198,24 +197,26 @@ describe( 'Align Hook Works As Expected', () => { await insertBlock( BLOCK_NAME ); // verify the correct alignment button is pressed await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); - const selectedAlignmentControls = await page.$x( SELECTED_ALIGNMENT_CONTROL_SELECTOR ); + const selectedAlignmentControls = await page.$x( + SELECTED_ALIGNMENT_CONTROL_SELECTOR + ); expect( selectedAlignmentControls ).toHaveLength( 1 ); } ); - it( 'The default markup does not contain the alignment attribute but contains the alignment class', - async () => { - await insertBlock( BLOCK_NAME ); - const markup = await getEditedPostContent(); - expect( markup ).not.toContain( '"align":"right"' ); - expect( markup ).toContain( 'alignright' ); - } - ); + it( 'The default markup does not contain the alignment attribute but contains the alignment class', async () => { + await insertBlock( BLOCK_NAME ); + const markup = await getEditedPostContent(); + expect( markup ).not.toContain( '"align":"right"' ); + expect( markup ).toContain( 'alignright' ); + } ); it( 'Can remove the default alignment and the align attribute equals none but alignnone class is not applied', async () => { await insertBlock( BLOCK_NAME ); // remove the alignment. await page.click( CHANGE_ALIGNMENT_BUTTON_SELECTOR ); - const [ selectedAlignmentControl ] = await page.$x( SELECTED_ALIGNMENT_CONTROL_SELECTOR ); + const [ selectedAlignmentControl ] = await page.$x( + SELECTED_ALIGNMENT_CONTROL_SELECTOR + ); await selectedAlignmentControl.click(); const markup = await getEditedPostContent(); expect( markup ).toContain( '"align":""' ); diff --git a/packages/e2e-tests/specs/editor/plugins/allowed-blocks.test.js b/packages/e2e-tests/specs/editor/plugins/allowed-blocks.test.js index bed48d6e93ff2c..5a638574a0fc01 100644 --- a/packages/e2e-tests/specs/editor/plugins/allowed-blocks.test.js +++ b/packages/e2e-tests/specs/editor/plugins/allowed-blocks.test.js @@ -25,37 +25,32 @@ describe( 'Allowed Blocks Filter', () => { it( 'should restrict the allowed blocks in the inserter', async () => { // The paragraph block is available. await searchForBlock( 'Paragraph' ); - const paragraphBlockButton = ( await page.$x( - `//button//span[contains(text(), 'Paragraph')]` - ) )[ 0 ]; + const paragraphBlockButton = ( + await page.$x( `//button//span[contains(text(), 'Paragraph')]` ) + )[ 0 ]; expect( paragraphBlockButton ).not.toBeNull(); await paragraphBlockButton.click(); // The gallery block is not available. await searchForBlock( 'Gallery' ); - const galleryBlockButton = ( await page.$x( - `//button//span[contains(text(), 'Gallery')]` - ) )[ 0 ]; + const galleryBlockButton = ( + await page.$x( `//button//span[contains(text(), 'Gallery')]` ) + )[ 0 ]; expect( galleryBlockButton ).toBeUndefined(); } ); it( 'should remove not allowed blocks from the block manager', async () => { await clickOnMoreMenuItem( 'Block Manager' ); - const BLOCK_LABEL_SELECTOR = '.edit-post-manage-blocks-modal__checklist-item .components-checkbox-control__label'; + const BLOCK_LABEL_SELECTOR = + '.edit-post-manage-blocks-modal__checklist-item .components-checkbox-control__label'; await page.waitForSelector( BLOCK_LABEL_SELECTOR ); - const blocks = await page.evaluate( - ( selector ) => { - return Array.from( document.querySelectorAll( selector ) ).map( - ( element ) => ( ( element.innerText || '' ).trim() ) - ).sort(); - }, - BLOCK_LABEL_SELECTOR - ); - expect( blocks ).toEqual( [ - 'Image', - 'Paragraph', - ] ); + const blocks = await page.evaluate( ( selector ) => { + return Array.from( document.querySelectorAll( selector ) ) + .map( ( element ) => ( element.innerText || '' ).trim() ) + .sort(); + }, BLOCK_LABEL_SELECTOR ); + expect( blocks ).toEqual( [ 'Image', 'Paragraph' ] ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/plugins/annotations.test.js b/packages/e2e-tests/specs/editor/plugins/annotations.test.js index 4425e8c7e8f539..a14fa73ae8cd91 100644 --- a/packages/e2e-tests/specs/editor/plugins/annotations.test.js +++ b/packages/e2e-tests/specs/editor/plugins/annotations.test.js @@ -11,7 +11,11 @@ import { const clickOnBlockSettingsMenuItem = async ( buttonLabel ) => { await clickBlockToolbarButton( 'More options' ); - const itemButton = ( await page.$x( `//*[contains(@class, "block-editor-block-settings-menu__popover")]//button[contains(text(), '${ buttonLabel }')]` ) )[ 0 ]; + const itemButton = ( + await page.$x( + `//*[contains(@class, "block-editor-block-settings-menu__popover")]//button[contains(text(), '${ buttonLabel }')]` + ) + )[ 0 ]; await itemButton.click(); }; @@ -47,9 +51,13 @@ describe( 'Using Plugins API', () => { await page.keyboard.type( end + '' ); // Click add annotation button. - const addAnnotationButton = ( await page.$x( "//button[contains(text(), 'Add annotation')]" ) )[ 0 ]; + const addAnnotationButton = ( + await page.$x( "//button[contains(text(), 'Add annotation')]" ) + )[ 0 ]; await addAnnotationButton.click(); - await page.evaluate( () => document.querySelector( '[contenteditable]' ).focus() ); + await page.evaluate( () => + document.querySelector( '[contenteditable]' ).focus() + ); } /** @@ -59,9 +67,13 @@ describe( 'Using Plugins API', () => { */ async function removeAnnotations() { // Click remove annotations button. - const addAnnotationButton = ( await page.$x( "//button[contains(text(), 'Remove annotations')]" ) )[ 0 ]; + const addAnnotationButton = ( + await page.$x( "//button[contains(text(), 'Remove annotations')]" ) + )[ 0 ]; await addAnnotationButton.click(); - await page.evaluate( () => document.querySelector( '[contenteditable]' ).focus() ); + await page.evaluate( () => + document.querySelector( '[contenteditable]' ).focus() + ); } /** @@ -91,7 +103,9 @@ describe( 'Using Plugins API', () => { describe( 'Annotations', () => { it( 'Allows a block to be annotated', async () => { - await page.keyboard.type( 'Title' + '\n' + 'Paragraph to annotate' ); + await page.keyboard.type( + 'Title' + '\n' + 'Paragraph to annotate' + ); await clickOnMoreMenuItem( 'Annotations Sidebar' ); @@ -108,7 +122,9 @@ describe( 'Using Plugins API', () => { await clickOnBlockSettingsMenuItem( 'Edit as HTML' ); - const htmlContent = await page.$$( '.block-editor-block-list__block-html-textarea' ); + const htmlContent = await page.$$( + '.block-editor-block-list__block-html-textarea' + ); const html = await page.evaluate( ( el ) => { return el.innerHTML; }, htmlContent[ 0 ] ); diff --git a/packages/e2e-tests/specs/editor/plugins/block-icons.test.js b/packages/e2e-tests/specs/editor/plugins/block-icons.test.js index 3035a6ddf1aeac..517340e987b80a 100644 --- a/packages/e2e-tests/specs/editor/plugins/block-icons.test.js +++ b/packages/e2e-tests/specs/editor/plugins/block-icons.test.js @@ -10,7 +10,8 @@ import { searchForBlock, } from '@wordpress/e2e-test-utils'; -const INSERTER_BUTTON_SELECTOR = '.components-popover__content .block-editor-block-types-list__item'; +const INSERTER_BUTTON_SELECTOR = + '.components-popover__content .block-editor-block-types-list__item'; const INSERTER_ICON_WRAPPER_SELECTOR = `${ INSERTER_BUTTON_SELECTOR } .block-editor-block-types-list__item-icon`; const INSERTER_ICON_SELECTOR = `${ INSERTER_BUTTON_SELECTOR } .block-editor-block-icon`; const INSPECTOR_ICON_SELECTOR = '.edit-post-sidebar .block-editor-block-icon'; @@ -37,14 +38,19 @@ async function getFirstInserterIcon() { async function selectFirstBlock() { await pressKeyWithModifier( 'access', 'o' ); - const navButtons = await page.$$( '.block-editor-block-navigation__item-button' ); + const navButtons = await page.$$( + '.block-editor-block-navigation__item-button' + ); await navButtons[ 0 ].click(); } describe( 'Correctly Renders Block Icons on Inserter and Inspector', () => { const dashIconRegex = /<svg.*?class=".*?dashicons-cart.*?">.*?<\/svg>/; - const circleString = '<circle cx="10" cy="10" r="10" fill="red" stroke="blue" stroke-width="10"></circle>'; - const svgIcon = new RegExp( `<svg.*?viewBox="0 0 20 20".*?>${ circleString }</svg>` ); + const circleString = + '<circle cx="10" cy="10" r="10" fill="red" stroke="blue" stroke-width="10"></circle>'; + const svgIcon = new RegExp( + `<svg.*?viewBox="0 0 20 20".*?>${ circleString }</svg>` + ); const validateSvgIcon = ( iconHtml ) => { expect( iconHtml ).toMatch( svgIcon ); @@ -75,7 +81,9 @@ describe( 'Correctly Renders Block Icons on Inserter and Inspector', () => { it( 'Can insert the block', async () => { await insertBlock( blockTitle ); expect( - await getInnerHTML( `[data-type="${ blockName }"] [data-type="core/paragraph"] p` ) + await getInnerHTML( + `[data-type="${ blockName }"] [data-type="core/paragraph"] p` + ) ).toEqual( blockTitle ); } ); @@ -109,18 +117,24 @@ describe( 'Correctly Renders Block Icons on Inserter and Inspector', () => { it( 'Renders the icon in the inserter with the correct colors', async () => { await searchForBlock( blockTitle ); validateDashIcon( await getFirstInserterIcon() ); - expect( await getBackgroundColor( INSERTER_ICON_WRAPPER_SELECTOR ) ).toEqual( 'rgb(1, 0, 0)' ); - expect( await getColor( INSERTER_ICON_WRAPPER_SELECTOR ) ).toEqual( 'rgb(254, 0, 0)' ); + expect( + await getBackgroundColor( INSERTER_ICON_WRAPPER_SELECTOR ) + ).toEqual( 'rgb(1, 0, 0)' ); + expect( await getColor( INSERTER_ICON_WRAPPER_SELECTOR ) ).toEqual( + 'rgb(254, 0, 0)' + ); } ); it( 'Renders the icon in the inspector with the correct colors', async () => { await insertBlock( blockTitle ); await selectFirstBlock(); - validateDashIcon( - await getInnerHTML( INSPECTOR_ICON_SELECTOR ) + validateDashIcon( await getInnerHTML( INSPECTOR_ICON_SELECTOR ) ); + expect( + await getBackgroundColor( INSPECTOR_ICON_SELECTOR ) + ).toEqual( 'rgb(1, 0, 0)' ); + expect( await getColor( INSPECTOR_ICON_SELECTOR ) ).toEqual( + 'rgb(254, 0, 0)' ); - expect( await getBackgroundColor( INSPECTOR_ICON_SELECTOR ) ).toEqual( 'rgb(1, 0, 0)' ); - expect( await getColor( INSPECTOR_ICON_SELECTOR ) ).toEqual( 'rgb(254, 0, 0)' ); } ); } ); @@ -129,18 +143,24 @@ describe( 'Correctly Renders Block Icons on Inserter and Inspector', () => { it( 'Renders the icon in the inserter with the correct background color and an automatically compute readable foreground color', async () => { await searchForBlock( blockTitle ); validateSvgIcon( await getFirstInserterIcon() ); - expect( await getBackgroundColor( INSERTER_ICON_WRAPPER_SELECTOR ) ).toEqual( 'rgb(1, 0, 0)' ); - expect( await getColor( INSERTER_ICON_WRAPPER_SELECTOR ) ).toEqual( 'rgb(248, 249, 249)' ); + expect( + await getBackgroundColor( INSERTER_ICON_WRAPPER_SELECTOR ) + ).toEqual( 'rgb(1, 0, 0)' ); + expect( await getColor( INSERTER_ICON_WRAPPER_SELECTOR ) ).toEqual( + 'rgb(248, 249, 249)' + ); } ); it( 'Renders correctly the icon on the inspector', async () => { await insertBlock( blockTitle ); await selectFirstBlock(); - validateSvgIcon( - await getInnerHTML( INSPECTOR_ICON_SELECTOR ) + validateSvgIcon( await getInnerHTML( INSPECTOR_ICON_SELECTOR ) ); + expect( + await getBackgroundColor( INSPECTOR_ICON_SELECTOR ) + ).toEqual( 'rgb(1, 0, 0)' ); + expect( await getColor( INSPECTOR_ICON_SELECTOR ) ).toEqual( + 'rgb(248, 249, 249)' ); - expect( await getBackgroundColor( INSPECTOR_ICON_SELECTOR ) ).toEqual( 'rgb(1, 0, 0)' ); - expect( await getColor( INSPECTOR_ICON_SELECTOR ) ).toEqual( 'rgb(248, 249, 249)' ); } ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/plugins/container-blocks.test.js b/packages/e2e-tests/specs/editor/plugins/container-blocks.test.js index 4afa4273f373fa..94b8a34cc21256 100644 --- a/packages/e2e-tests/specs/editor/plugins/container-blocks.test.js +++ b/packages/e2e-tests/specs/editor/plugins/container-blocks.test.js @@ -8,6 +8,7 @@ import { getEditedPostContent, insertBlock, switchEditorModeTo, + pressKeyWithModifier, } from '@wordpress/e2e-test-utils'; describe( 'InnerBlocks Template Sync', () => { @@ -31,23 +32,39 @@ describe( 'InnerBlocks Template Sync', () => { `; await insertBlock( blockName ); await switchEditorModeTo( 'Code' ); - await page.$eval( '.editor-post-text-editor', ( element, _paragraph, _blockSlug ) => { - const blockDelimiter = `<!-- /wp:${ _blockSlug } -->`; - element.value = element.value.replace( blockDelimiter, `${ _paragraph }${ blockDelimiter }` ); - }, paragraphToAdd, blockSlug ); + await page.$eval( + '.editor-post-text-editor', + ( element, _paragraph, _blockSlug ) => { + const blockDelimiter = `<!-- /wp:${ _blockSlug } -->`; + element.value = element.value.replace( + blockDelimiter, + `${ _paragraph }${ blockDelimiter }` + ); + }, + paragraphToAdd, + blockSlug + ); // Press "Enter" inside the Code Editor to fire the `onChange` event for the new value. await page.click( '.editor-post-text-editor' ); + await pressKeyWithModifier( 'primary', 'A' ); + await page.keyboard.press( 'ArrowRight' ); await page.keyboard.press( 'Enter' ); await switchEditorModeTo( 'Visual' ); }; it( 'Ensures blocks without locking are kept intact even if they do not match the template ', async () => { - await insertBlockAndAddParagraphInside( 'Test Inner Blocks no locking', 'test/test-inner-blocks-no-locking' ); + await insertBlockAndAddParagraphInside( + 'Test Inner Blocks no locking', + 'test/test-inner-blocks-no-locking' + ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); it( 'Removes blocks that are not expected by the template if a lock all exists ', async () => { - await insertBlockAndAddParagraphInside( 'Test InnerBlocks locking all', 'test/test-inner-blocks-locking-all' ); + await insertBlockAndAddParagraphInside( + 'Test InnerBlocks locking all', + 'test/test-inner-blocks-locking-all' + ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -63,7 +80,9 @@ describe( 'InnerBlocks Template Sync', () => { describe( 'Container block without paragraph support', () => { beforeAll( async () => { - await activatePlugin( 'gutenberg-test-container-block-without-paragraph' ); + await activatePlugin( + 'gutenberg-test-container-block-without-paragraph' + ); } ); beforeEach( async () => { @@ -71,19 +90,23 @@ describe( 'Container block without paragraph support', () => { } ); afterAll( async () => { - await deactivatePlugin( 'gutenberg-test-container-block-without-paragraph' ); + await deactivatePlugin( + 'gutenberg-test-container-block-without-paragraph' + ); } ); it( 'ensures we can use the alternative block appender properly', async () => { await insertBlock( 'Container without paragraph' ); // Open the specific appender used when there's no paragraph support. - await page.click( '.block-editor-inner-blocks .block-list-appender .block-list-appender__toggle' ); + await page.click( + '.block-editor-inner-blocks .block-list-appender .block-list-appender__toggle' + ); // Insert an image block. - const insertButton = ( await page.$x( - `//button//span[contains(text(), 'Image')]` - ) )[ 0 ]; + const insertButton = ( + await page.$x( `//button//span[contains(text(), 'Image')]` ) + )[ 0 ]; await insertButton.click(); // Check the inserted content. diff --git a/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js b/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js index 92689539fb5e84..9233dd71572695 100644 --- a/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js +++ b/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js @@ -28,7 +28,10 @@ describe( 'cpt locking', () => { }; const shouldNotAllowBlocksToBeRemoved = async () => { - await page.type( '.block-editor-rich-text__editable.wp-block-paragraph', 'p1' ); + await page.type( + '.block-editor-rich-text__editable.wp-block-paragraph', + 'p1' + ); await clickBlockToolbarButton( 'More options' ); expect( await page.$x( '//button[contains(text(), "Remove Block")]' ) @@ -36,12 +39,15 @@ describe( 'cpt locking', () => { }; const shouldAllowBlocksToBeMoved = async () => { - await page.click( '.block-editor-rich-text__editable.wp-block-paragraph' ); - expect( - await page.$( 'button[aria-label="Move up"]' ) - ).not.toBeNull(); + await page.click( + '.block-editor-rich-text__editable.wp-block-paragraph' + ); + expect( await page.$( 'button[aria-label="Move up"]' ) ).not.toBeNull(); await page.click( 'button[aria-label="Move up"]' ); - await page.type( '.block-editor-rich-text__editable.wp-block-paragraph', 'p1' ); + await page.type( + '.block-editor-rich-text__editable.wp-block-paragraph', + 'p1' + ); expect( await getEditedPostContent() ).toMatchSnapshot(); }; @@ -52,17 +58,22 @@ describe( 'cpt locking', () => { it( 'should remove the inserter', shouldRemoveTheInserter ); - it( 'should not allow blocks to be removed', shouldNotAllowBlocksToBeRemoved ); + it( + 'should not allow blocks to be removed', + shouldNotAllowBlocksToBeRemoved + ); it( 'should not allow blocks to be moved', async () => { - await page.click( '.block-editor-rich-text__editable.wp-block-paragraph' ); - expect( - await page.$( 'button[aria-label="Move up"]' ) - ).toBeNull(); + await page.click( + '.block-editor-rich-text__editable.wp-block-paragraph' + ); + expect( await page.$( 'button[aria-label="Move up"]' ) ).toBeNull(); } ); it( 'should not error when deleting the cotents of a paragraph', async () => { - await page.click( '.block-editor-block-list__block[data-type="core/paragraph"] p' ); + await page.click( + '.block-editor-block-list__block[data-type="core/paragraph"] p' + ); const textToType = 'Paragraph'; await page.keyboard.type( 'Paragraph' ); await pressKeyTimes( 'Backspace', textToType.length + 1 ); @@ -71,16 +82,21 @@ describe( 'cpt locking', () => { it( 'should show invalid template notice if the blocks do not match the templte', async () => { const content = await getEditedPostContent(); - const [ , contentWithoutImage ] = content.split( '<!-- /wp:image -->' ); + const [ , contentWithoutImage ] = content.split( + '<!-- /wp:image -->' + ); await setPostContent( contentWithoutImage ); - const VALIDATION_PARAGRAPH_SELECTOR = '.editor-template-validation-notice .components-notice__content p'; + const VALIDATION_PARAGRAPH_SELECTOR = + '.editor-template-validation-notice .components-notice__content p'; await page.waitForSelector( VALIDATION_PARAGRAPH_SELECTOR ); expect( await page.evaluate( ( element ) => element.textContent, await page.$( VALIDATION_PARAGRAPH_SELECTOR ) ) - ).toEqual( 'The content of your post doesn’t match the template assigned to your post type.' ); + ).toEqual( + 'The content of your post doesn’t match the template assigned to your post type.' + ); } ); } ); @@ -91,7 +107,10 @@ describe( 'cpt locking', () => { it( 'should remove the inserter', shouldRemoveTheInserter ); - it( 'should not allow blocks to be removed', shouldNotAllowBlocksToBeRemoved ); + it( + 'should not allow blocks to be removed', + shouldNotAllowBlocksToBeRemoved + ); it( 'should allow blocks to be moved', shouldAllowBlocksToBeMoved ); } ); @@ -111,9 +130,14 @@ describe( 'cpt locking', () => { } ); it( 'should allow blocks to be removed', async () => { - await page.type( '.block-editor-rich-text__editable.wp-block-paragraph', 'p1' ); + await page.type( + '.block-editor-rich-text__editable.wp-block-paragraph', + 'p1' + ); await clickBlockToolbarButton( 'More options' ); - const [ removeBlock ] = await page.$x( '//button[contains(text(), "Remove Block")]' ); + const [ removeBlock ] = await page.$x( + '//button[contains(text(), "Remove Block")]' + ); await removeBlock.click(); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); diff --git a/packages/e2e-tests/specs/editor/plugins/custom-post-types.test.js b/packages/e2e-tests/specs/editor/plugins/custom-post-types.test.js index da3e0a3e99231c..fdfa152256a354 100644 --- a/packages/e2e-tests/specs/editor/plugins/custom-post-types.test.js +++ b/packages/e2e-tests/specs/editor/plugins/custom-post-types.test.js @@ -13,14 +13,17 @@ const openPageAttributesPanel = async () => { const openButton = await findSidebarPanelWithTitle( 'Page Attributes' ); // Get the classes from the panel - const buttonClassName = await ( await openButton.getProperty( 'className' ) ).jsonValue(); + const buttonClassName = await ( + await openButton.getProperty( 'className' ) + ).jsonValue(); // Open the panel if needed. if ( -1 === buttonClassName.indexOf( 'is-opened' ) ) { await openButton.click(); } }; -const SELECT_OPTION_SELECTOR = '.editor-page-attributes__parent option:nth-child(2)'; +const SELECT_OPTION_SELECTOR = + '.editor-page-attributes__parent option:nth-child(2)'; describe( 'Test Custom Post Types', () => { beforeAll( async () => { @@ -42,15 +45,23 @@ describe( 'Test Custom Post Types', () => { await openPageAttributesPanel(); await page.waitForSelector( SELECT_OPTION_SELECTOR ); const optionToSelect = await page.$( SELECT_OPTION_SELECTOR ); - const valueToSelect = await ( await optionToSelect.getProperty( 'value' ) ).jsonValue(); - await page.select( '.editor-page-attributes__parent select', valueToSelect ); + const valueToSelect = await ( + await optionToSelect.getProperty( 'value' ) + ).jsonValue(); + await page.select( + '.editor-page-attributes__parent select', + valueToSelect + ); await page.click( '.block-editor-writing-flow' ); await page.keyboard.type( 'Child Post' ); await publishPost(); // Reload the child post and verify it is still correctly selected as a child post. await page.reload(); await page.waitForSelector( SELECT_OPTION_SELECTOR ); - const selectedValue = await page.$eval( '.editor-page-attributes__parent select', ( el ) => el.value ); + const selectedValue = await page.$eval( + '.editor-page-attributes__parent select', + ( el ) => el.value + ); expect( selectedValue ).toEqual( valueToSelect ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/plugins/custom-taxonomies.test.js b/packages/e2e-tests/specs/editor/plugins/custom-taxonomies.test.js index fccba18eafae1d..41f15fbb616b2f 100644 --- a/packages/e2e-tests/specs/editor/plugins/custom-taxonomies.test.js +++ b/packages/e2e-tests/specs/editor/plugins/custom-taxonomies.test.js @@ -4,7 +4,9 @@ import { activatePlugin, createNewPost, - deactivatePlugin, findSidebarPanelWithTitle, openDocumentSettingsSidebar, + deactivatePlugin, + findSidebarPanelWithTitle, + openDocumentSettingsSidebar, } from '@wordpress/e2e-test-utils'; describe( 'Custom Taxonomies labels are used', () => { @@ -28,7 +30,9 @@ describe( 'Custom Taxonomies labels are used', () => { expect( openButton ).not.toBeFalsy(); // Get the classes from the panel - const buttonClassName = await ( await openButton.getProperty( 'className' ) ).jsonValue(); + const buttonClassName = await ( + await openButton.getProperty( 'className' ) + ).jsonValue(); // Open the panel if needed. if ( -1 === buttonClassName.indexOf( 'is-opened' ) ) { @@ -36,15 +40,22 @@ describe( 'Custom Taxonomies labels are used', () => { } // Check the add new button - const labelNew = await page.$x( "//label[@class='components-form-token-field__label' and contains(text(), 'Add New Model')]" ); + const labelNew = await page.$x( + "//label[@class='components-form-token-field__label' and contains(text(), 'Add New Model')]" + ); expect( labelNew ).not.toBeFalsy(); // Fill with one entry - await page.type( 'input.components-form-token-field__input', 'Model 1' ); + await page.type( + 'input.components-form-token-field__input', + 'Model 1' + ); await page.keyboard.press( 'Enter' ); // Check the "Remove Model" - const value = await page.$x( "//div[@class='components-form-token-field__input-container']//span//button[@aria-label='Remove Model']" ); + const value = await page.$x( + "//div[@class='components-form-token-field__input-container']//span//button[@aria-label='Remove Model']" + ); expect( value ).not.toBeFalsy(); } ); } ); diff --git a/packages/e2e-tests/specs/editor/plugins/hooks-api.test.js b/packages/e2e-tests/specs/editor/plugins/hooks-api.test.js index 4641c534493b3f..07f018c528cf24 100644 --- a/packages/e2e-tests/specs/editor/plugins/hooks-api.test.js +++ b/packages/e2e-tests/specs/editor/plugins/hooks-api.test.js @@ -25,13 +25,18 @@ describe( 'Using Hooks API', () => { it( 'Should contain a reset block button on the sidebar', async () => { await clickBlockAppender(); await page.keyboard.type( 'First paragraph' ); - expect( await page.$( '.edit-post-sidebar .e2e-reset-block-button' ) ).not.toBeNull(); + expect( + await page.$( '.edit-post-sidebar .e2e-reset-block-button' ) + ).not.toBeNull(); } ); it( 'Pressing reset block button resets the block', async () => { await clickBlockAppender(); await page.keyboard.type( 'First paragraph' ); - const paragraphContent = await page.$eval( 'div[data-type="core/paragraph"] p', ( element ) => element.textContent ); + const paragraphContent = await page.$eval( + 'div[data-type="core/paragraph"] p', + ( element ) => element.textContent + ); expect( paragraphContent ).toEqual( 'First paragraph' ); await page.click( '.edit-post-sidebar .e2e-reset-block-button' ); expect( await getEditedPostContent() ).toMatchSnapshot(); diff --git a/packages/e2e-tests/specs/editor/plugins/inner-blocks-allowed-blocks.test.js b/packages/e2e-tests/specs/editor/plugins/inner-blocks-allowed-blocks.test.js index c0a2055ade7623..24d01d7abaf723 100644 --- a/packages/e2e-tests/specs/editor/plugins/inner-blocks-allowed-blocks.test.js +++ b/packages/e2e-tests/specs/editor/plugins/inner-blocks-allowed-blocks.test.js @@ -12,7 +12,8 @@ import { } from '@wordpress/e2e-test-utils'; describe( 'Allowed Blocks Setting on InnerBlocks ', () => { - const paragraphSelector = '.block-editor-rich-text__editable.wp-block-paragraph'; + const paragraphSelector = + '.block-editor-rich-text__editable.wp-block-paragraph'; beforeAll( async () => { await activatePlugin( 'gutenberg-test-innerblocks-allowed-blocks' ); } ); @@ -46,9 +47,7 @@ describe( 'Allowed Blocks Setting on InnerBlocks ', () => { await page.click( childParagraphSelector ); await openGlobalBlockInserter(); await openAllBlockInserterCategories(); - expect( - await getAllBlockInserterItemTitles() - ).toEqual( [ + expect( await getAllBlockInserterItemTitles() ).toEqual( [ 'Button', 'Gallery', 'List', @@ -65,22 +64,18 @@ describe( 'Allowed Blocks Setting on InnerBlocks ', () => { await page.waitForSelector( appenderSelector ); await page.click( appenderSelector ); await openAllBlockInserterCategories(); - expect( - await getAllBlockInserterItemTitles() - ).toEqual( [ + expect( await getAllBlockInserterItemTitles() ).toEqual( [ 'Image', 'List', ] ); - const insertButton = ( await page.$x( - `//button//span[contains(text(), 'List')]` - ) )[ 0 ]; + const insertButton = ( + await page.$x( `//button//span[contains(text(), 'List')]` ) + )[ 0 ]; await insertButton.click(); await insertBlock( 'Image' ); await page.click( appenderSelector ); await openAllBlockInserterCategories(); - expect( - await getAllBlockInserterItemTitles() - ).toEqual( [ + expect( await getAllBlockInserterItemTitles() ).toEqual( [ 'Gallery', 'Video', ] ); diff --git a/packages/e2e-tests/specs/editor/plugins/innerblocks-locking-all-embed.js b/packages/e2e-tests/specs/editor/plugins/innerblocks-locking-all-embed.js index 3cba39c9f129d2..c0f081976ecfe3 100644 --- a/packages/e2e-tests/specs/editor/plugins/innerblocks-locking-all-embed.js +++ b/packages/e2e-tests/specs/editor/plugins/innerblocks-locking-all-embed.js @@ -18,12 +18,15 @@ describe( 'Embed block inside a locked all parent', () => { } ); afterAll( async () => { - await deactivatePlugin( 'gutenberg-test-innerblocks-locking-all-embed' ); + await deactivatePlugin( + 'gutenberg-test-innerblocks-locking-all-embed' + ); } ); it( 'embed block should be able to embed external content', async () => { await insertBlock( 'Test Inner Blocks Locking All Embed' ); - const embedInputSelector = '.components-placeholder__input[aria-label="Embed URL"]'; + const embedInputSelector = + '.components-placeholder__input[aria-label="Embed URL"]'; await page.waitForSelector( embedInputSelector ); await page.click( embedInputSelector ); // This URL should not have a trailing slash. diff --git a/packages/e2e-tests/specs/editor/plugins/meta-attribute-block.test.js b/packages/e2e-tests/specs/editor/plugins/meta-attribute-block.test.js index 934e2ac73d69d0..68f4de2768fbb5 100644 --- a/packages/e2e-tests/specs/editor/plugins/meta-attribute-block.test.js +++ b/packages/e2e-tests/specs/editor/plugins/meta-attribute-block.test.js @@ -40,7 +40,9 @@ describe( 'Block with a meta attribute', () => { await page.reload(); expect( await getEditedPostContent() ).toMatchSnapshot(); - const persistedValue = await page.evaluate( () => document.querySelector( '.my-meta-input' ).value ); + const persistedValue = await page.evaluate( + () => document.querySelector( '.my-meta-input' ).value + ); expect( persistedValue ).toBe( 'Meta Value' ); } ); @@ -51,15 +53,17 @@ describe( 'Block with a meta attribute', () => { await page.keyboard.type( 'Meta Value' ); const inputs = await page.$$( '.my-meta-input' ); - await Promise.all( inputs.map( async ( input ) => { - // Clicking the input selects the block, - // and selecting the block enables the sync data mode - // as otherwise the asynchronous re-rendering of unselected blocks - // may cause the input to have not yet been updated for the other blocks - await input.click(); - const inputValue = await input.getProperty( 'value' ); - expect( await inputValue.jsonValue() ).toBe( 'Meta Value' ); - } ) ); + await Promise.all( + inputs.map( async ( input ) => { + // Clicking the input selects the block, + // and selecting the block enables the sync data mode + // as otherwise the asynchronous re-rendering of unselected blocks + // may cause the input to have not yet been updated for the other blocks + await input.click(); + const inputValue = await input.getProperty( 'value' ); + expect( await inputValue.jsonValue() ).toBe( 'Meta Value' ); + } ) + ); } ); it( 'Should persist the meta attribute properly in a different post type', async () => { @@ -79,7 +83,9 @@ describe( 'Block with a meta attribute', () => { await page.reload(); expect( await getEditedPostContent() ).toMatchSnapshot(); - const persistedValue = await page.evaluate( () => document.querySelector( '.my-meta-input' ).value ); + const persistedValue = await page.evaluate( + () => document.querySelector( '.my-meta-input' ).value + ); expect( persistedValue ).toBe( 'Meta Value' ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/plugins/meta-boxes.test.js b/packages/e2e-tests/specs/editor/plugins/meta-boxes.test.js index 0a21dfe20d9d72..b7b13a4be08dad 100644 --- a/packages/e2e-tests/specs/editor/plugins/meta-boxes.test.js +++ b/packages/e2e-tests/specs/editor/plugins/meta-boxes.test.js @@ -53,7 +53,9 @@ describe( 'Meta boxes', () => { await publishPost(); // View the post. - const viewPostLinks = await page.$x( "//a[contains(text(), 'View Post')]" ); + const viewPostLinks = await page.$x( + "//a[contains(text(), 'View Post')]" + ); await viewPostLinks[ 0 ].click(); await page.waitForNavigation(); @@ -68,17 +70,17 @@ describe( 'Meta boxes', () => { await publishPost(); // View the post. - const viewPostLinks = await page.$x( "//a[contains(text(), 'View Post')]" ); + const viewPostLinks = await page.$x( + "//a[contains(text(), 'View Post')]" + ); await viewPostLinks[ 0 ].click(); await page.waitForNavigation(); // Retrieve the excerpt used as meta const metaExcerpt = await page.evaluate( () => { - return document.querySelector( - 'meta[property="gutenberg:hello"]' - ).getAttribute( - 'content' - ); + return document + .querySelector( 'meta[property="gutenberg:hello"]' ) + .getAttribute( 'content' ); } ); expect( metaExcerpt ).toEqual( 'Excerpt from content.' ); @@ -91,29 +93,34 @@ describe( 'Meta boxes', () => { // Open the excerpt panel await openDocumentSettingsSidebar(); - const excerptButton = await findSidebarPanelToggleButtonWithTitle( 'Excerpt' ); + const excerptButton = await findSidebarPanelToggleButtonWithTitle( + 'Excerpt' + ); if ( excerptButton ) { await excerptButton.click( 'button' ); } await page.waitForSelector( '.editor-post-excerpt textarea' ); - await page.type( '.editor-post-excerpt textarea', 'Explicitly set excerpt.' ); + await page.type( + '.editor-post-excerpt textarea', + 'Explicitly set excerpt.' + ); await publishPost(); // View the post. - const viewPostLinks = await page.$x( "//a[contains(text(), 'View Post')]" ); + const viewPostLinks = await page.$x( + "//a[contains(text(), 'View Post')]" + ); await viewPostLinks[ 0 ].click(); await page.waitForNavigation(); // Retrieve the excerpt used as meta const metaExcerpt = await page.evaluate( () => { - return document.querySelector( - 'meta[property="gutenberg:hello"]' - ).getAttribute( - 'content' - ); + return document + .querySelector( 'meta[property="gutenberg:hello"]' ) + .getAttribute( 'content' ); } ); expect( metaExcerpt ).toEqual( 'Explicitly set excerpt.' ); diff --git a/packages/e2e-tests/specs/editor/plugins/plugins-api.test.js b/packages/e2e-tests/specs/editor/plugins/plugins-api.test.js index fedf3f25503c4d..627d3a7493f078 100644 --- a/packages/e2e-tests/specs/editor/plugins/plugins-api.test.js +++ b/packages/e2e-tests/specs/editor/plugins/plugins-api.test.js @@ -30,7 +30,10 @@ describe( 'Using Plugins API', () => { it( 'Should render post status info inside Document Setting sidebar', async () => { await openDocumentSettingsSidebar(); - const pluginPostStatusInfoText = await page.$eval( '.edit-post-post-status .my-post-status-info-plugin', ( el ) => el.innerText ); + const pluginPostStatusInfoText = await page.$eval( + '.edit-post-post-status .my-post-status-info-plugin', + ( el ) => el.innerText + ); expect( pluginPostStatusInfoText ).toBe( 'My post status info' ); } ); } ); @@ -45,14 +48,20 @@ describe( 'Using Plugins API', () => { it( 'Should render publish panel inside Pre-publish sidebar', async () => { await openPublishPanel(); - const pluginPublishPanelText = await page.$eval( '.editor-post-publish-panel .my-publish-panel-plugin__pre', ( el ) => el.innerText ); + const pluginPublishPanelText = await page.$eval( + '.editor-post-publish-panel .my-publish-panel-plugin__pre', + ( el ) => el.innerText + ); expect( pluginPublishPanelText ).toMatch( 'My pre publish panel' ); } ); it( 'Should render publish panel inside Post-publish sidebar', async () => { await publishPost(); - const pluginPublishPanelText = await page.$eval( '.editor-post-publish-panel .my-publish-panel-plugin__post', ( el ) => el.innerText ); + const pluginPublishPanelText = await page.$eval( + '.editor-post-publish-panel .my-publish-panel-plugin__post', + ( el ) => el.innerText + ); expect( pluginPublishPanelText ).toMatch( 'My post publish panel' ); } ); } ); @@ -61,7 +70,10 @@ describe( 'Using Plugins API', () => { it( 'Should open plugins sidebar using More Menu item and render content', async () => { await clickOnMoreMenuItem( 'Sidebar title plugin' ); - const pluginSidebarContent = await page.$eval( '.edit-post-sidebar', ( el ) => el.innerHTML ); + const pluginSidebarContent = await page.$eval( + '.edit-post-sidebar', + ( el ) => el.innerHTML + ); expect( pluginSidebarContent ).toMatchSnapshot(); } ); @@ -89,7 +101,10 @@ describe( 'Using Plugins API', () => { it( 'Should open plugins sidebar using More Menu item and render content', async () => { await clickOnMoreMenuItem( 'Sidebar title plugin' ); - const pluginSidebarContent = await page.$eval( '.edit-post-sidebar', ( el ) => el.innerHTML ); + const pluginSidebarContent = await page.$eval( + '.edit-post-sidebar', + ( el ) => el.innerHTML + ); expect( pluginSidebarContent ).toMatchSnapshot(); } ); } ); @@ -98,7 +113,10 @@ describe( 'Using Plugins API', () => { describe( 'Document Setting Custom Panel', () => { it( 'Should render a custom panel inside Document Setting sidebar', async () => { await openDocumentSettingsSidebar(); - const pluginDocumentSettingsText = await page.$eval( '.edit-post-sidebar .my-document-setting-plugin', ( el ) => el.innerText ); + const pluginDocumentSettingsText = await page.$eval( + '.edit-post-sidebar .my-document-setting-plugin', + ( el ) => el.innerText + ); expect( pluginDocumentSettingsText ).toMatchSnapshot(); } ); } ); diff --git a/packages/e2e-tests/specs/editor/plugins/templates.test.js b/packages/e2e-tests/specs/editor/plugins/templates.test.js index 749842554fe664..a738ffae94da2a 100644 --- a/packages/e2e-tests/specs/editor/plugins/templates.test.js +++ b/packages/e2e-tests/specs/editor/plugins/templates.test.js @@ -74,12 +74,16 @@ describe( 'templates', () => { } beforeAll( async () => { - await activatePlugin( 'gutenberg-test-plugin-post-formats-support' ); + await activatePlugin( + 'gutenberg-test-plugin-post-formats-support' + ); await setPostFormat( 'image' ); } ); afterAll( async () => { await setPostFormat( STANDARD_FORMAT_VALUE ); - await deactivatePlugin( 'gutenberg-test-plugin-post-formats-support' ); + await deactivatePlugin( + 'gutenberg-test-plugin-post-formats-support' + ); } ); it( 'should populate new post with default block for format', async () => { diff --git a/packages/e2e-tests/specs/editor/various/a11y.test.js b/packages/e2e-tests/specs/editor/various/a11y.test.js index 525898b7c1c7b2..8f16e6a7ac6361 100644 --- a/packages/e2e-tests/specs/editor/various/a11y.test.js +++ b/packages/e2e-tests/specs/editor/various/a11y.test.js @@ -1,10 +1,7 @@ /** * WordPress dependencies */ -import { - createNewPost, - pressKeyWithModifier, -} from '@wordpress/e2e-test-utils'; +import { createNewPost, pressKeyWithModifier } from '@wordpress/e2e-test-utils'; function isCloseButtonFocused() { return page.$eval( ':focus', ( focusedElement ) => { @@ -22,9 +19,14 @@ describe( 'a11y', () => { await page.keyboard.press( 'Tab' ); - const isFocusedToggle = await page.$eval( ':focus', ( focusedElement ) => { - return focusedElement.classList.contains( 'editor-post-publish-button__button' ); - } ); + const isFocusedToggle = await page.$eval( + ':focus', + ( focusedElement ) => { + return focusedElement.classList.contains( + 'editor-post-publish-button__button' + ); + } + ); expect( isFocusedToggle ).toBe( true ); } ); diff --git a/packages/e2e-tests/specs/editor/various/adding-blocks.test.js b/packages/e2e-tests/specs/editor/various/adding-blocks.test.js index 2eb67bb812f3c2..2434e541990914 100644 --- a/packages/e2e-tests/specs/editor/various/adding-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/adding-blocks.test.js @@ -26,7 +26,7 @@ describe( 'adding blocks', () => { */ async function clickAtBottom( elementHandle ) { const box = await elementHandle.boundingBox(); - const x = box.x + ( box.width / 2 ); + const x = box.x + box.width / 2; const y = box.y + box.height - 50; return page.mouse.click( x, y ); } @@ -41,7 +41,9 @@ describe( 'adding blocks', () => { await setBrowserViewport( { width: 960, height: 1400 } ); // Click below editor to focus last field (block appender) - await clickAtBottom( await page.$( '.edit-post-editor-regions__content' ) ); + await clickAtBottom( + await page.$( '.edit-post-editor-regions__content' ) + ); expect( await page.$( '[data-type="core/paragraph"]' ) ).not.toBeNull(); await page.keyboard.type( 'Paragraph block' ); @@ -94,15 +96,24 @@ describe( 'adding blocks', () => { // Using the between inserter const insertionPoint = await page.$( '[data-type="core/quote"]' ); const rect = await insertionPoint.boundingBox(); - await page.mouse.move( rect.x + ( rect.width / 2 ), rect.y - 10, { steps: 10 } ); - await page.waitForSelector( '.block-editor-block-list__insertion-point .block-editor-inserter__toggle' ); - await page.click( '.block-editor-block-list__insertion-point .block-editor-inserter__toggle' ); + await page.mouse.move( rect.x + rect.width / 2, rect.y - 10, { + steps: 10, + } ); + await page.waitForSelector( + '.block-editor-block-list__insertion-point .block-editor-inserter__toggle' + ); + await page.click( + '.block-editor-block-list__insertion-point .block-editor-inserter__toggle' + ); // [TODO]: Search input should be focused immediately. It shouldn't be // necessary to have `waitForFunction`. - await page.waitForFunction( () => ( - document.activeElement && - document.activeElement.classList.contains( 'block-editor-inserter__search' ) - ) ); + await page.waitForFunction( + () => + document.activeElement && + document.activeElement.classList.contains( + 'block-editor-inserter__search' + ) + ); await page.keyboard.type( 'para' ); await pressKeyTimes( 'Tab', 3 ); await page.keyboard.press( 'Enter' ); @@ -117,38 +128,60 @@ describe( 'adding blocks', () => { it( 'should not allow transfer of focus outside of the block-insertion menu once open', async () => { // Enter the default block and click the inserter toggle button to the left of it. await page.keyboard.press( 'ArrowDown' ); - await page.click( '.block-editor-block-list__empty-block-inserter .block-editor-inserter__toggle' ); + await page.click( + '.block-editor-block-list__empty-block-inserter .block-editor-inserter__toggle' + ); // Expect the inserter search input to be the active element. - let activeElementClassList = await page.evaluate( () => document.activeElement.classList ); - expect( Object.values( activeElementClassList ) ).toContain( 'block-editor-inserter__search' ); + let activeElementClassList = await page.evaluate( + () => document.activeElement.classList + ); + expect( Object.values( activeElementClassList ) ).toContain( + 'block-editor-inserter__search' + ); // Try using the up arrow key (vertical navigation triggers the issue described in #9583). await page.keyboard.press( 'ArrowUp' ); // Expect the inserter search input to still be the active element. - activeElementClassList = await page.evaluate( () => document.activeElement.classList ); - expect( Object.values( activeElementClassList ) ).toContain( 'block-editor-inserter__search' ); + activeElementClassList = await page.evaluate( + () => document.activeElement.classList + ); + expect( Object.values( activeElementClassList ) ).toContain( + 'block-editor-inserter__search' + ); // Tab to the block search results await page.keyboard.press( 'Tab' ); // Expect the search results to be the active element. - activeElementClassList = await page.evaluate( () => document.activeElement.classList ); - expect( Object.values( activeElementClassList ) ).toContain( 'block-editor-inserter__results' ); + activeElementClassList = await page.evaluate( + () => document.activeElement.classList + ); + expect( Object.values( activeElementClassList ) ).toContain( + 'block-editor-inserter__results' + ); // Try using the up arrow key await page.keyboard.press( 'ArrowUp' ); // Expect the search results to still be the active element. - activeElementClassList = await page.evaluate( () => document.activeElement.classList ); - expect( Object.values( activeElementClassList ) ).toContain( 'block-editor-inserter__results' ); + activeElementClassList = await page.evaluate( + () => document.activeElement.classList + ); + expect( Object.values( activeElementClassList ) ).toContain( + 'block-editor-inserter__results' + ); // Press escape to close the block inserter. await page.keyboard.press( 'Escape' ); // Expect focus to have transferred back to the inserter toggle button. - activeElementClassList = await page.evaluate( () => document.activeElement.classList ); - expect( Object.values( activeElementClassList ) ).toContain( 'block-editor-inserter__toggle' ); + activeElementClassList = await page.evaluate( + () => document.activeElement.classList + ); + expect( Object.values( activeElementClassList ) ).toContain( + 'block-editor-inserter__toggle' + ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/adding-inline-tokens.test.js b/packages/e2e-tests/specs/editor/various/adding-inline-tokens.test.js index b11077e70fb3d3..0049bdd1d5c3f2 100644 --- a/packages/e2e-tests/specs/editor/various/adding-inline-tokens.test.js +++ b/packages/e2e-tests/specs/editor/various/adding-inline-tokens.test.js @@ -33,20 +33,31 @@ describe( 'adding inline tokens', () => { // Wait for media modal to appear and upload image. await page.waitForSelector( '.media-modal input[type=file]' ); const inputElement = await page.$( '.media-modal input[type=file]' ); - const testImagePath = path.join( __dirname, '..', '..', '..', 'assets', '10x10_e2e_test_image_z9T8jK.png' ); + const testImagePath = path.join( + __dirname, + '..', + '..', + '..', + 'assets', + '10x10_e2e_test_image_z9T8jK.png' + ); const filename = uuid(); const tmpFileName = path.join( os.tmpdir(), filename + '.png' ); fs.copyFileSync( testImagePath, tmpFileName ); await inputElement.uploadFile( tmpFileName ); // Wait for upload. - await page.waitForSelector( `.media-modal li[aria-label="${ filename }"]` ); + await page.waitForSelector( + `.media-modal li[aria-label="${ filename }"]` + ); // Insert the uploaded image. await page.click( '.media-modal button.media-button-select' ); // Check the content. - const regex = new RegExp( `<!-- wp:paragraph -->\\s*<p>a <img class="wp-image-\\d+" style="width:\\s*10px;?" src="[^"]+\\/${ filename }\\.png" alt=""\\/?><\\/p>\\s*<!-- \\/wp:paragraph -->` ); + const regex = new RegExp( + `<!-- wp:paragraph -->\\s*<p>a <img class="wp-image-\\d+" style="width:\\s*10px;?" src="[^"]+\\/${ filename }\\.png" alt=""\\/?><\\/p>\\s*<!-- \\/wp:paragraph -->` + ); expect( await getEditedPostContent() ).toMatch( regex ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/autosave.test.js b/packages/e2e-tests/specs/editor/various/autosave.test.js index 3c966f15a0748d..a4f6d83b85eb8e 100644 --- a/packages/e2e-tests/specs/editor/various/autosave.test.js +++ b/packages/e2e-tests/specs/editor/various/autosave.test.js @@ -14,8 +14,10 @@ import { // Constant to override editor preference const AUTOSAVE_INTERVAL_SECONDS = 5; -const AUTOSAVE_NOTICE_REMOTE = 'There is an autosave of this post that is more recent than the version below.'; -const AUTOSAVE_NOTICE_LOCAL = 'The backup of this post in your browser is different from the version below.'; +const AUTOSAVE_NOTICE_REMOTE = + 'There is an autosave of this post that is more recent than the version below.'; +const AUTOSAVE_NOTICE_LOCAL = + 'The backup of this post in your browser is different from the version below.'; // Save and wait for "Saved" to confirm save complete. Preserves focus in the // editing area. @@ -47,14 +49,15 @@ async function readSessionStorageAutosave( postId ) { } async function getCurrentPostId() { - return page.evaluate( - () => window.wp.data.select( 'core/editor' ).getCurrentPostId() + return page.evaluate( () => + window.wp.data.select( 'core/editor' ).getCurrentPostId() ); } async function setLocalAutosaveInterval( value ) { return page.evaluate( ( _value ) => { - window.wp.data.dispatch( 'core/edit-post' ) + window.wp.data + .dispatch( 'core/edit-post' ) .__experimentalUpdateLocalAutosaveInterval( _value ); }, value ); } @@ -95,7 +98,9 @@ describe( 'autosave', () => { await sleep( 2 ); const newAutosave = await readSessionStorageAutosave( id ); - expect( JSON.parse( newAutosave ).content ).toBe( wrapParagraph( 'before save after save 123' ) ); + expect( JSON.parse( newAutosave ).content ).toBe( + wrapParagraph( 'before save after save 123' ) + ); } ); it( 'should recover from sessionStorage', async () => { @@ -105,16 +110,27 @@ describe( 'autosave', () => { await page.keyboard.type( ' after save' ); // Trigger local autosave - await page.evaluate( () => window.wp.data.dispatch( 'core/editor' ).__experimentalLocalAutosave() ); + await page.evaluate( () => + window.wp.data + .dispatch( 'core/editor' ) + .__experimentalLocalAutosave() + ); // Reload without saving on the server await page.reload(); - const notice = await page.$eval( '.components-notice__content', ( element ) => element.innerText ); + const notice = await page.$eval( + '.components-notice__content', + ( element ) => element.innerText + ); expect( notice ).toContain( AUTOSAVE_NOTICE_LOCAL ); - expect( await getEditedPostContent() ).toEqual( wrapParagraph( 'before save' ) ); + expect( await getEditedPostContent() ).toEqual( + wrapParagraph( 'before save' ) + ); await page.click( '.components-notice__action' ); - expect( await getEditedPostContent() ).toEqual( wrapParagraph( 'before save after save' ) ); + expect( await getEditedPostContent() ).toEqual( + wrapParagraph( 'before save after save' ) + ); } ); it( 'should clear sessionStorage upon user logout', async () => { @@ -123,11 +139,21 @@ describe( 'autosave', () => { await saveDraft(); // Fake local autosave - await page.evaluate( ( postId ) => window.sessionStorage.setItem( - `wp-autosave-block-editor-post-${ postId }`, - JSON.stringify( { post_title: 'A', content: 'B', excerpt: 'C' } ) - ), await getCurrentPostId() ); - expect( await page.evaluate( () => window.sessionStorage.length ) ).toBe( 1 ); + await page.evaluate( + ( postId ) => + window.sessionStorage.setItem( + `wp-autosave-block-editor-post-${ postId }`, + JSON.stringify( { + post_title: 'A', + content: 'B', + excerpt: 'C', + } ) + ), + await getCurrentPostId() + ); + expect( + await page.evaluate( () => window.sessionStorage.length ) + ).toBe( 1 ); await Promise.all( [ page.waitForSelector( '#wp-admin-bar-logout', { visible: true } ), @@ -138,24 +164,41 @@ describe( 'autosave', () => { page.click( '#wp-admin-bar-logout' ), ] ); - expect( await page.evaluate( () => window.sessionStorage.length ) ).toBe( 0 ); + expect( + await page.evaluate( () => window.sessionStorage.length ) + ).toBe( 0 ); } ); - it( 'shouldn\'t contaminate other posts', async () => { + it( "shouldn't contaminate other posts", async () => { await clickBlockAppender(); await page.keyboard.type( 'before save' ); await saveDraft(); // Fake local autosave - await page.evaluate( ( postId ) => window.sessionStorage.setItem( - `wp-autosave-block-editor-post-${ postId }`, - JSON.stringify( { post_title: 'A', content: 'B', excerpt: 'C' } ) - ), await getCurrentPostId() ); - expect( await page.evaluate( () => window.sessionStorage.length ) ).toBe( 1 ); + await page.evaluate( + ( postId ) => + window.sessionStorage.setItem( + `wp-autosave-block-editor-post-${ postId }`, + JSON.stringify( { + post_title: 'A', + content: 'B', + excerpt: 'C', + } ) + ), + await getCurrentPostId() + ); + expect( + await page.evaluate( () => window.sessionStorage.length ) + ).toBe( 1 ); await page.reload(); - const notice = await page.$eval( '.components-notice__content', ( element ) => element.innerText ); - expect( notice ).toContain( 'The backup of this post in your browser is different from the version below.' ); + const notice = await page.$eval( + '.components-notice__content', + ( element ) => element.innerText + ); + expect( notice ).toContain( + 'The backup of this post in your browser is different from the version below.' + ); await createNewPost(); expect( await page.$( '.components-notice__content' ) ).toBe( null ); @@ -169,15 +212,25 @@ describe( 'autosave', () => { await page.keyboard.type( ' after save' ); // Trigger local autosave - await page.evaluate( () => window.wp.data.dispatch( 'core/editor' ).__experimentalLocalAutosave() ); - expect( await page.evaluate( () => window.sessionStorage.length ) ).toBe( 1 ); + await page.evaluate( () => + window.wp.data + .dispatch( 'core/editor' ) + .__experimentalLocalAutosave() + ); + expect( + await page.evaluate( () => window.sessionStorage.length ) + ).toBe( 1 ); // Trigger remote autosave - await page.evaluate( () => window.wp.data.dispatch( 'core/editor' ).autosave() ); - expect( await page.evaluate( () => window.sessionStorage.length ) ).toBe( 0 ); + await page.evaluate( () => + window.wp.data.dispatch( 'core/editor' ).autosave() + ); + expect( + await page.evaluate( () => window.sessionStorage.length ) + ).toBe( 0 ); } ); - it( 'shouldn\'t clear local autosave if remote autosave fails', async () => { + it( "shouldn't clear local autosave if remote autosave fails", async () => { // Edit, save draft, edit again await clickBlockAppender(); await page.keyboard.type( 'before save' ); @@ -185,13 +238,23 @@ describe( 'autosave', () => { await page.keyboard.type( ' after save' ); // Trigger local autosave - await page.evaluate( () => window.wp.data.dispatch( 'core/editor' ).__experimentalLocalAutosave() ); - expect( await page.evaluate( () => window.sessionStorage.length ) ).toBe( 1 ); + await page.evaluate( () => + window.wp.data + .dispatch( 'core/editor' ) + .__experimentalLocalAutosave() + ); + expect( + await page.evaluate( () => window.sessionStorage.length ) + ).toBe( 1 ); // Bring network down and attempt to autosave remotely toggleOfflineMode( true ); - await page.evaluate( () => window.wp.data.dispatch( 'core/editor' ).autosave() ); - expect( await page.evaluate( () => window.sessionStorage.length ) ).toBe( 1 ); + await page.evaluate( () => + window.wp.data.dispatch( 'core/editor' ).autosave() + ); + expect( + await page.evaluate( () => window.sessionStorage.length ) + ).toBe( 1 ); } ); it( 'should clear local autosave after successful save', async () => { @@ -202,14 +265,22 @@ describe( 'autosave', () => { await page.keyboard.type( ' after save' ); // Trigger local autosave - await page.evaluate( () => window.wp.data.dispatch( 'core/editor' ).__experimentalLocalAutosave() ); - expect( await page.evaluate( () => window.sessionStorage.length ) ).toBe( 1 ); + await page.evaluate( () => + window.wp.data + .dispatch( 'core/editor' ) + .__experimentalLocalAutosave() + ); + expect( + await page.evaluate( () => window.sessionStorage.length ) + ).toBe( 1 ); await saveDraftWithKeyboard(); - expect( await page.evaluate( () => window.sessionStorage.length ) ).toBe( 0 ); + expect( + await page.evaluate( () => window.sessionStorage.length ) + ).toBe( 0 ); } ); - it( 'shouldn\'t clear local autosave if save fails', async () => { + it( "shouldn't clear local autosave if save fails", async () => { // Edit, save draft, edit again await clickBlockAppender(); await page.keyboard.type( 'before save' ); @@ -217,16 +288,24 @@ describe( 'autosave', () => { await page.keyboard.type( ' after save' ); // Trigger local autosave - await page.evaluate( () => window.wp.data.dispatch( 'core/editor' ).__experimentalLocalAutosave() ); - expect( await page.evaluate( () => window.sessionStorage.length ) ).toBe( 1 ); + await page.evaluate( () => + window.wp.data + .dispatch( 'core/editor' ) + .__experimentalLocalAutosave() + ); + expect( + await page.evaluate( () => window.sessionStorage.length ) + ).toBe( 1 ); // Bring network down and attempt to save toggleOfflineMode( true ); saveDraftWithKeyboard(); - expect( await page.evaluate( () => window.sessionStorage.length ) ).toBe( 1 ); + expect( + await page.evaluate( () => window.sessionStorage.length ) + ).toBe( 1 ); } ); - it( 'shouldn\'t conflict with server-side autosave', async () => { + it( "shouldn't conflict with server-side autosave", async () => { await clickBlockAppender(); await page.keyboard.type( 'before publish' ); await publishPost(); @@ -235,11 +314,19 @@ describe( 'autosave', () => { await page.keyboard.type( ' after publish' ); // Trigger remote autosave - await page.evaluate( () => window.wp.data.dispatch( 'core/editor' ).autosave() ); + await page.evaluate( () => + window.wp.data.dispatch( 'core/editor' ).autosave() + ); // Force conflicting local autosave - await page.evaluate( () => window.wp.data.dispatch( 'core/editor' ).__experimentalLocalAutosave() ); - expect( await page.evaluate( () => window.sessionStorage.length ) ).toBe( 1 ); + await page.evaluate( () => + window.wp.data + .dispatch( 'core/editor' ) + .__experimentalLocalAutosave() + ); + expect( + await page.evaluate( () => window.sessionStorage.length ) + ).toBe( 1 ); await page.reload(); @@ -249,7 +336,11 @@ describe( 'autosave', () => { // unrelated to *local* autosave, until we can understand them, we'll // drop this test's expectations if we don't have an autosave object // available. - const stillHasRemoteAutosave = await page.evaluate( () => window.wp.data.select( 'core/editor' ).getEditorSettings().autosave ); + const stillHasRemoteAutosave = await page.evaluate( + () => + window.wp.data.select( 'core/editor' ).getEditorSettings() + .autosave + ); if ( ! stillHasRemoteAutosave ) { return; } @@ -257,7 +348,10 @@ describe( 'autosave', () => { // Only one autosave notice should be displayed. const notices = await page.$$( '.components-notice' ); expect( notices.length ).toBe( 1 ); - const notice = await page.$eval( '.components-notice__content', ( element ) => element.innerText ); + const notice = await page.$eval( + '.components-notice__content', + ( element ) => element.innerText + ); expect( notice ).toContain( AUTOSAVE_NOTICE_REMOTE ); } ); diff --git a/packages/e2e-tests/specs/editor/various/block-deletion.test.js b/packages/e2e-tests/specs/editor/various/block-deletion.test.js index a231977ed171a6..00027d94137cf2 100644 --- a/packages/e2e-tests/specs/editor/various/block-deletion.test.js +++ b/packages/e2e-tests/specs/editor/various/block-deletion.test.js @@ -35,7 +35,10 @@ const clickOnBlockSettingsMenuRemoveBlockButton = async () => { let isRemoveButton = false; - let numButtons = await page.$$eval( '.block-editor-block-settings-menu__content button', ( btns ) => btns.length ); + let numButtons = await page.$$eval( + '.block-editor-block-settings-menu__content button', + ( btns ) => btns.length + ); // Limit by the number of buttons available while ( --numButtons ) { @@ -112,7 +115,9 @@ describe( 'block deletion -', () => { await page.click( '.editor-post-title' ); // Click on the image block so that its wrapper is selected and backspace to delete it. - await page.click( '.wp-block[data-type="core/image"] .components-placeholder__label' ); + await page.click( + '.wp-block[data-type="core/image"] .components-placeholder__label' + ); await page.keyboard.press( 'Backspace' ); expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -159,7 +164,9 @@ describe( 'deleting all blocks', () => { await clickOnBlockSettingsMenuRemoveBlockButton(); // There is a default block: - expect( await page.$$( '.block-editor-block-list__block' ) ).toHaveLength( 1 ); + expect( + await page.$$( '.block-editor-block-list__block' ) + ).toHaveLength( 1 ); // But the effective saved content is still empty: expect( await getEditedPostContent() ).toBe( '' ); @@ -179,8 +186,12 @@ describe( 'deleting all blocks', () => { // configured to not allow the default (paragraph) block type, either // by plugin editor settings filtering or user block preferences. await page.evaluate( () => { - const defaultBlockName = wp.data.select( 'core/blocks' ).getDefaultBlockName(); - wp.data.dispatch( 'core/blocks' ).removeBlockTypes( defaultBlockName ); + const defaultBlockName = wp.data + .select( 'core/blocks' ) + .getDefaultBlockName(); + wp.data + .dispatch( 'core/blocks' ) + .removeBlockTypes( defaultBlockName ); } ); // Add and remove a block. @@ -191,7 +202,9 @@ describe( 'deleting all blocks', () => { // TODO: There should be expectations around where focus is placed in // this scenario. Currently, a focus loss occurs (not acceptable). const selectedBlocksCount = await page.evaluate( () => { - return wp.data.select( 'core/block-editor' ).getSelectedBlockClientIds().length; + return wp.data + .select( 'core/block-editor' ) + .getSelectedBlockClientIds().length; } ); expect( selectedBlocksCount ).toBe( 0 ); diff --git a/packages/e2e-tests/specs/editor/various/block-grouping.test.js b/packages/e2e-tests/specs/editor/various/block-grouping.test.js index 4822aabe706e99..01a5c38def41a0 100644 --- a/packages/e2e-tests/specs/editor/various/block-grouping.test.js +++ b/packages/e2e-tests/specs/editor/various/block-grouping.test.js @@ -78,7 +78,9 @@ describe( 'Block Grouping', () => { await clickBlockToolbarButton( 'More options' ); - const groupButton = await page.waitForXPath( '//button[text()="Group"]' ); + const groupButton = await page.waitForXPath( + '//button[text()="Group"]' + ); await groupButton.click(); expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -92,14 +94,18 @@ describe( 'Block Grouping', () => { // Group await clickBlockToolbarButton( 'More options' ); - const groupButton = await page.waitForXPath( '//button[text()="Group"]' ); + const groupButton = await page.waitForXPath( + '//button[text()="Group"]' + ); await groupButton.click(); expect( await getEditedPostContent() ).toMatchSnapshot(); // UnGroup await clickBlockToolbarButton( 'More options' ); - const unGroupButton = await page.waitForXPath( '//button[text()="Ungroup"]' ); + const unGroupButton = await page.waitForXPath( + '//button[text()="Ungroup"]' + ); await unGroupButton.click(); expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -108,7 +114,9 @@ describe( 'Block Grouping', () => { it( 'does not allow ungrouping a group block that has no children', async () => { await insertBlock( 'Group' ); await clickBlockToolbarButton( 'More options' ); - const ungroupButtons = await page.$x( '//button[text()="Ungroup"]' ); + const ungroupButtons = await page.$x( + '//button[text()="Ungroup"]' + ); expect( ungroupButtons ).toHaveLength( 0 ); } ); } ); @@ -138,15 +146,18 @@ describe( 'Block Grouping', () => { it( 'does not show group transform if Grouping block is disabled', async () => { const availableTransforms = await getAvailableBlockTransforms(); - expect( - availableTransforms - ).not.toContain( 'Group' ); + expect( availableTransforms ).not.toContain( 'Group' ); } ); it( 'does not show group option in the options toolbar if Grouping block is disabled ', async () => { await clickBlockToolbarButton( 'More options' ); - const blockOptionsDropdownHTML = await page.evaluate( () => document.querySelector( '.block-editor-block-settings-menu__popover' ).innerHTML ); + const blockOptionsDropdownHTML = await page.evaluate( + () => + document.querySelector( + '.block-editor-block-settings-menu__popover' + ).innerHTML + ); expect( blockOptionsDropdownHTML ).not.toContain( 'Group' ); } ); @@ -199,7 +210,9 @@ describe( 'Block Grouping', () => { it( 'should use registered grouping block for grouping interactions', async () => { // Set custom Block as the Block to use for Grouping await page.evaluate( () => { - window.wp.blocks.setGroupingBlockName( 'test/alternative-group-block' ); + window.wp.blocks.setGroupingBlockName( + 'test/alternative-group-block' + ); } ); // Creating test blocks @@ -213,7 +226,9 @@ describe( 'Block Grouping', () => { // as opposed to "transformTo()" which uses whatever is passed to it. To // ensure this test is meaningful we must rely on what is registered. await clickBlockToolbarButton( 'More options' ); - const groupButton = await page.waitForXPath( '//button[text()="Group"]' ); + const groupButton = await page.waitForXPath( + '//button[text()="Group"]' + ); await groupButton.click(); expect( await getEditedPostContent() ).toMatchSnapshot(); diff --git a/packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js b/packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js index 278989f17c4459..643998905e00e3 100644 --- a/packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js +++ b/packages/e2e-tests/specs/editor/various/block-hierarchy-navigation.test.js @@ -11,7 +11,9 @@ import { async function openBlockNavigator() { await pressKeyWithModifier( 'access', 'o' ); - await page.waitForSelector( '.block-editor-block-navigation__item-button.is-selected' ); + await page.waitForSelector( + '.block-editor-block-navigation__item-button.is-selected' + ); } describe( 'Navigating the block hierarchy', () => { @@ -33,11 +35,17 @@ describe( 'Navigating the block hierarchy', () => { // Navigate to the columns blocks. await page.click( '[aria-label="Block navigation"]' ); - const columnsBlockMenuItem = ( await page.$x( "//button[contains(@class,'block-editor-block-navigation__item') and contains(text(), 'Columns')]" ) )[ 0 ]; + const columnsBlockMenuItem = ( + await page.$x( + "//button[contains(@class,'block-editor-block-navigation__item') and contains(text(), 'Columns')]" + ) + )[ 0 ]; await columnsBlockMenuItem.click(); // Tweak the columns count. - await page.focus( '.block-editor-block-inspector .components-range-control__number[aria-label="Columns"]' ); + await page.focus( + '.block-editor-block-inspector .components-range-control__number[aria-label="Columns"]' + ); await page.keyboard.down( 'Shift' ); await page.keyboard.press( 'ArrowLeft' ); await page.keyboard.up( 'Shift' ); @@ -45,9 +53,11 @@ describe( 'Navigating the block hierarchy', () => { // Navigate to the last column block. await page.click( '[aria-label="Block navigation"]' ); - const lastColumnsBlockMenuItem = ( await page.$x( - "//button[contains(@class,'block-editor-block-navigation__item') and contains(text(), 'Column')]" - ) )[ 3 ]; + const lastColumnsBlockMenuItem = ( + await page.$x( + "//button[contains(@class,'block-editor-block-navigation__item') and contains(text(), 'Column')]" + ) + )[ 3 ]; await lastColumnsBlockMenuItem.click(); // Insert text in the last column block. diff --git a/packages/e2e-tests/specs/editor/various/block-switcher.test.js b/packages/e2e-tests/specs/editor/various/block-switcher.test.js index 9282e83dc855f1..12c40442195160 100644 --- a/packages/e2e-tests/specs/editor/various/block-switcher.test.js +++ b/packages/e2e-tests/specs/editor/various/block-switcher.test.js @@ -24,9 +24,7 @@ describe( 'adding blocks', () => { expect( await hasBlockSwitcher() ).toBeTruthy(); // Verify the correct block transforms appear. - expect( - await getAvailableBlockTransforms() - ).toEqual( [ + expect( await getAvailableBlockTransforms() ).toEqual( [ 'Group', 'Paragraph', 'Quote', @@ -48,9 +46,7 @@ describe( 'adding blocks', () => { expect( await hasBlockSwitcher() ).toBeTruthy(); // Verify the correct block transforms appear. - expect( - await getAvailableBlockTransforms() - ).toEqual( [ + expect( await getAvailableBlockTransforms() ).toEqual( [ 'Group', 'Paragraph', ] ); @@ -59,11 +55,9 @@ describe( 'adding blocks', () => { it( 'Should not show the block switcher if all the blocks the list block transforms into are removed', async () => { // Remove the paragraph and quote block from the list of registered blocks. await page.evaluate( () => { - ( [ - 'core/quote', - 'core/paragraph', - 'core/group', - ] ).map( ( block ) => wp.blocks.unregisterBlockType( block ) ); + [ 'core/quote', 'core/paragraph', 'core/group' ].map( ( block ) => + wp.blocks.unregisterBlockType( block ) + ); } ); // Insert a list block. @@ -74,8 +68,6 @@ describe( 'adding blocks', () => { // Verify the block switcher exists. expect( await hasBlockSwitcher() ).toBeFalsy(); // Verify the correct block transforms appear. - expect( - await getAvailableBlockTransforms() - ).toHaveLength( 0 ); + expect( await getAvailableBlockTransforms() ).toHaveLength( 0 ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/change-detection.test.js b/packages/e2e-tests/specs/editor/various/change-detection.test.js index 4f5cb5822fa7f8..514a7fdd106f10 100644 --- a/packages/e2e-tests/specs/editor/various/change-detection.test.js +++ b/packages/e2e-tests/specs/editor/various/change-detection.test.js @@ -81,7 +81,9 @@ describe( 'Change detection', () => { // Force autosave to occur immediately. await Promise.all( [ - page.evaluate( () => window.wp.data.dispatch( 'core/editor' ).autosave() ), + page.evaluate( () => + window.wp.data.dispatch( 'core/editor' ).autosave() + ), page.waitForSelector( '.editor-post-saved-state.is-autosaving' ), page.waitForSelector( '.editor-post-saved-state.is-saved' ), ] ); @@ -96,12 +98,16 @@ describe( 'Change detection', () => { // Toggle post as needing review (not persisted for autosave). await ensureSidebarOpened(); - const postPendingReviewButton = ( await page.$x( "//label[contains(text(), 'Pending review')]" ) )[ 0 ]; + const postPendingReviewButton = ( + await page.$x( "//label[contains(text(), 'Pending review')]" ) + )[ 0 ]; await postPendingReviewButton.click( 'button' ); // Force autosave to occur immediately. await Promise.all( [ - page.evaluate( () => window.wp.data.dispatch( 'core/editor' ).autosave() ), + page.evaluate( () => + window.wp.data.dispatch( 'core/editor' ).autosave() + ), page.waitForSelector( '.editor-post-saved-state.is-autosaving' ), page.waitForSelector( '.editor-post-saved-state.is-saved' ), ] ); @@ -116,7 +122,9 @@ describe( 'Change detection', () => { // Close publish panel. await Promise.all( [ - page.waitForFunction( () => ! document.querySelector( '.editor-post-publish-panel' ) ), + page.waitForFunction( + () => ! document.querySelector( '.editor-post-publish-panel' ) + ), page.click( '.editor-post-publish-panel__header button' ), ] ); @@ -125,8 +133,12 @@ describe( 'Change detection', () => { await Promise.all( [ page.waitForSelector( '.editor-post-publish-button.is-busy' ), - page.waitForSelector( '.editor-post-publish-button:not( .is-busy )' ), - page.evaluate( () => window.wp.data.dispatch( 'core/editor' ).autosave() ), + page.waitForSelector( + '.editor-post-publish-button:not( .is-busy )' + ), + page.evaluate( () => + window.wp.data.dispatch( 'core/editor' ).autosave() + ), ] ); await assertIsDirty( true ); @@ -210,7 +222,9 @@ describe( 'Change detection', () => { await assertIsDirty( true ); - expect( console ).toHaveErroredWith( 'Failed to load resource: net::ERR_INTERNET_DISCONNECTED' ); + expect( console ).toHaveErroredWith( + 'Failed to load resource: net::ERR_INTERNET_DISCONNECTED' + ); } ); it( 'Should prompt if changes and save is in-flight', async () => { @@ -304,7 +318,11 @@ describe( 'Change detection', () => { // long as the experimental reusable blocks fetching data flow exists. // // See: https://github.com/WordPress/gutenberg/issues/14766 - await page.evaluate( () => window.wp.data.dispatch( 'core/editor' ).__experimentalReceiveReusableBlocks( [] ) ); + await page.evaluate( () => + window.wp.data + .dispatch( 'core/editor' ) + .__experimentalReceiveReusableBlocks( [] ) + ); await assertIsDirty( false ); } ); @@ -334,8 +352,8 @@ describe( 'Change detection', () => { // Save await saveDraft(); - const postId = await page.evaluate( - () => window.wp.data.select( 'core/editor' ).getCurrentPostId() + const postId = await page.evaluate( () => + window.wp.data.select( 'core/editor' ).getCurrentPostId() ); // Trash post. @@ -350,7 +368,12 @@ describe( 'Change detection', () => { await page.waitForNavigation(), ] ); - expect( isCurrentURL( '/wp-admin/edit.php', `post_type=post&ids=${ postId }` ) ).toBe( true ); + expect( + isCurrentURL( + '/wp-admin/edit.php', + `post_type=post&ids=${ postId }` + ) + ).toBe( true ); } ); it( 'consecutive edits to the same attribute should mark the post as dirty after a save', async () => { @@ -371,7 +394,9 @@ describe( 'Change detection', () => { // Increase the paragraph's font size. await page.click( '[data-type="core/paragraph"]' ); await page.click( '.components-font-size-picker__select' ); - await page.click( '.components-custom-select-control__item:nth-child(3)' ); + await page.click( + '.components-custom-select-control__item:nth-child(3)' + ); await page.click( '[data-type="core/paragraph"]' ); // Check that the post is dirty. @@ -386,7 +411,9 @@ describe( 'Change detection', () => { // Increase the paragraph's font size again. await page.click( '[data-type="core/paragraph"]' ); await page.click( '.components-font-size-picker__select' ); - await page.click( '.components-custom-select-control__item:nth-child(4)' ); + await page.click( + '.components-custom-select-control__item:nth-child(4)' + ); await page.click( '[data-type="core/paragraph"]' ); // Check that the post is dirty. diff --git a/packages/e2e-tests/specs/editor/various/compatibility-classic-editor.test.js b/packages/e2e-tests/specs/editor/various/compatibility-classic-editor.test.js index 9b311275b72961..eeffbd3c5f533e 100644 --- a/packages/e2e-tests/specs/editor/various/compatibility-classic-editor.test.js +++ b/packages/e2e-tests/specs/editor/various/compatibility-classic-editor.test.js @@ -1,7 +1,11 @@ /** * WordPress dependencies */ -import { createNewPost, insertBlock, publishPost } from '@wordpress/e2e-test-utils'; +import { + createNewPost, + insertBlock, + publishPost, +} from '@wordpress/e2e-test-utils'; describe( 'Compatibility with classic editor', () => { beforeEach( async () => { @@ -18,13 +22,17 @@ describe( 'Compatibility with classic editor', () => { await publishPost(); // View the post. - const viewPostLinks = await page.$x( "//a[contains(text(), 'View Post')]" ); + const viewPostLinks = await page.$x( + "//a[contains(text(), 'View Post')]" + ); await viewPostLinks[ 0 ].click(); await page.waitForNavigation(); // Check the the content doesn't contain <p> tags await page.waitForSelector( '.entry-content' ); - const content = await page.$eval( '.entry-content', ( element ) => element.innerHTML.trim() ); + const content = await page.$eval( '.entry-content', ( element ) => + element.innerHTML.trim() + ); expect( content ).toMatchSnapshot(); } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/datepicker.test.js b/packages/e2e-tests/specs/editor/various/datepicker.test.js index e3ce2540d39f2e..dc7e8982f17f82 100644 --- a/packages/e2e-tests/specs/editor/various/datepicker.test.js +++ b/packages/e2e-tests/specs/editor/various/datepicker.test.js @@ -33,7 +33,9 @@ describe( 'Datepicker', () => { ( dateLabel ) => dateLabel.textContent ); - expect( publishingDate ).toMatch( /[A-Za-z]{3} \d{1,2}, \d{4} \d{1,2}:\d{2} [ap]m/ ); + expect( publishingDate ).toMatch( + /[A-Za-z]{3} \d{1,2}, \d{4} \d{1,2}:\d{2} [ap]m/ + ); } ); it( 'should show the publishing date if the date is in the future', async () => { @@ -54,6 +56,8 @@ describe( 'Datepicker', () => { expect( publishingDate ).not.toEqual( 'Immediately' ); // The expected date format will be "Sep 26, 2018 11:52 pm". - expect( publishingDate ).toMatch( /[A-Za-z]{3} \d{1,2}, \d{4} \d{1,2}:\d{2} [ap]m/ ); + expect( publishingDate ).toMatch( + /[A-Za-z]{3} \d{1,2}, \d{4} \d{1,2}:\d{2} [ap]m/ + ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/editor-modes.test.js b/packages/e2e-tests/specs/editor/various/editor-modes.test.js index a1a3f27249ea33..f77d25592d35d0 100644 --- a/packages/e2e-tests/specs/editor/various/editor-modes.test.js +++ b/packages/e2e-tests/specs/editor/various/editor-modes.test.js @@ -17,7 +17,9 @@ describe( 'Editing modes (visual/HTML)', () => { it( 'should switch between visual and HTML modes', async () => { // This block should be in "visual" mode by default. - let visualBlock = await page.$$( '.block-editor-block-list__layout .block-editor-block-list__block .rich-text' ); + let visualBlock = await page.$$( + '.block-editor-block-list__layout .block-editor-block-list__block .rich-text' + ); expect( visualBlock ).toHaveLength( 1 ); // Move the mouse to show the block toolbar @@ -26,11 +28,15 @@ describe( 'Editing modes (visual/HTML)', () => { // Change editing mode from "Visual" to "HTML". await clickBlockToolbarButton( 'More options' ); - let changeModeButton = await page.waitForXPath( '//button[text()="Edit as HTML"]' ); + let changeModeButton = await page.waitForXPath( + '//button[text()="Edit as HTML"]' + ); await changeModeButton.click(); // Wait for the block to be converted to HTML editing mode. - const htmlBlock = await page.$$( '.block-editor-block-list__layout .block-editor-block-list__block .block-editor-block-list__block-html-textarea' ); + const htmlBlock = await page.$$( + '.block-editor-block-list__layout .block-editor-block-list__block .block-editor-block-list__block-html-textarea' + ); expect( htmlBlock ).toHaveLength( 1 ); // Move the mouse to show the block toolbar @@ -39,11 +45,15 @@ describe( 'Editing modes (visual/HTML)', () => { // Change editing mode from "HTML" back to "Visual". await clickBlockToolbarButton( 'More options' ); - changeModeButton = await page.waitForXPath( '//button[text()="Edit visually"]' ); + changeModeButton = await page.waitForXPath( + '//button[text()="Edit visually"]' + ); await changeModeButton.click(); // This block should be in "visual" mode by default. - visualBlock = await page.$$( '.block-editor-block-list__layout .block-editor-block-list__block .rich-text' ); + visualBlock = await page.$$( + '.block-editor-block-list__layout .block-editor-block-list__block .rich-text' + ); expect( visualBlock ).toHaveLength( 1 ); } ); @@ -54,12 +64,16 @@ describe( 'Editing modes (visual/HTML)', () => { // Change editing mode from "Visual" to "HTML". await clickBlockToolbarButton( 'More options' ); - const changeModeButton = await page.waitForXPath( '//button[text()="Edit as HTML"]' ); + const changeModeButton = await page.waitForXPath( + '//button[text()="Edit as HTML"]' + ); await changeModeButton.click(); // The font size picker for the paragraph block should appear, even in // HTML editing mode. - const fontSizePicker = await page.$$( '.edit-post-sidebar .components-font-size-picker__controls' ); + const fontSizePicker = await page.$$( + '.edit-post-sidebar .components-font-size-picker__controls' + ); expect( fontSizePicker ).toHaveLength( 1 ); } ); @@ -70,20 +84,32 @@ describe( 'Editing modes (visual/HTML)', () => { // Change editing mode from "Visual" to "HTML". await clickBlockToolbarButton( 'More options' ); - const changeModeButton = await page.waitForXPath( '//button[text()="Edit as HTML"]' ); + const changeModeButton = await page.waitForXPath( + '//button[text()="Edit as HTML"]' + ); await changeModeButton.click(); // Make sure the paragraph content is rendered as expected. - let htmlBlockContent = await page.$eval( '.block-editor-block-list__layout .block-editor-block-list__block .block-editor-block-list__block-html-textarea', ( node ) => node.textContent ); + let htmlBlockContent = await page.$eval( + '.block-editor-block-list__layout .block-editor-block-list__block .block-editor-block-list__block-html-textarea', + ( node ) => node.textContent + ); expect( htmlBlockContent ).toEqual( '<p>Hello world!</p>' ); // Change the font size using the sidebar. await page.click( '.components-font-size-picker__select' ); - await page.click( '.components-custom-select-control__item:nth-child(5)' ); + await page.click( + '.components-custom-select-control__item:nth-child(5)' + ); // Make sure the HTML content updated. - htmlBlockContent = await page.$eval( '.block-editor-block-list__layout .block-editor-block-list__block .block-editor-block-list__block-html-textarea', ( node ) => node.textContent ); - expect( htmlBlockContent ).toEqual( '<p class="has-large-font-size">Hello world!</p>' ); + htmlBlockContent = await page.$eval( + '.block-editor-block-list__layout .block-editor-block-list__block .block-editor-block-list__block-html-textarea', + ( node ) => node.textContent + ); + expect( htmlBlockContent ).toEqual( + '<p class="has-large-font-size">Hello world!</p>' + ); } ); it( 'the code editor should unselect blocks and disable the inserter', async () => { @@ -95,22 +121,30 @@ describe( 'Editing modes (visual/HTML)', () => { expect( title ).toBe( 'Paragraph' ); // The Block inspector should be active - let blockInspectorTab = await page.$( '.edit-post-sidebar__panel-tab.is-active[data-label="Block"]' ); + let blockInspectorTab = await page.$( + '.edit-post-sidebar__panel-tab.is-active[data-label="Block"]' + ); expect( blockInspectorTab ).not.toBeNull(); await switchEditorModeTo( 'Code' ); // The Block inspector should not be active anymore - blockInspectorTab = await page.$( '.edit-post-sidebar__panel-tab.is-active[data-label="Block"]' ); + blockInspectorTab = await page.$( + '.edit-post-sidebar__panel-tab.is-active[data-label="Block"]' + ); expect( blockInspectorTab ).toBeNull(); // No block is selected await page.click( '.edit-post-sidebar__panel-tab[data-label="Block"]' ); - const noBlocksElement = await page.$( '.block-editor-block-inspector__no-blocks' ); + const noBlocksElement = await page.$( + '.block-editor-block-inspector__no-blocks' + ); expect( noBlocksElement ).not.toBeNull(); // The inserter is disabled - const disabledInserter = await page.$( '.block-editor-inserter > button:disabled, .block-editor-inserter > button[aria-disabled="true"]' ); + const disabledInserter = await page.$( + '.block-editor-inserter > button:disabled, .block-editor-inserter > button[aria-disabled="true"]' + ); expect( disabledInserter ).not.toBeNull(); } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/embedding.test.js b/packages/e2e-tests/specs/editor/various/embedding.test.js index d753a89863e787..24fc469fca9d70 100644 --- a/packages/e2e-tests/specs/editor/various/embedding.test.js +++ b/packages/e2e-tests/specs/editor/various/embedding.test.js @@ -16,7 +16,8 @@ import { const MOCK_EMBED_WORDPRESS_SUCCESS_RESPONSE = { url: 'https://wordpress.org/gutenberg/handbook/block-api/attributes/', - html: '<div class="wp-embedded-content" data-secret="shhhh it is a secret">WordPress embed</div>', + html: + '<div class="wp-embedded-content" data-secret="shhhh it is a secret">WordPress embed</div>', type: 'rich', provider_name: 'WordPress', provider_url: 'https://wordpress.org', @@ -68,7 +69,8 @@ const MOCK_BAD_EMBED_PROVIDER_RESPONSE = { const MOCK_CANT_EMBED_RESPONSE = { provider_name: 'Embed Handler', - html: '<a href="https://twitter.com/wooyaygutenberg123454312">https://twitter.com/wooyaygutenberg123454312</a>', + html: + '<a href="https://twitter.com/wooyaygutenberg123454312">https://twitter.com/wooyaygutenberg123454312</a>', }; const MOCK_BAD_WORDPRESS_RESPONSE = { @@ -82,27 +84,41 @@ const MOCK_BAD_WORDPRESS_RESPONSE = { const MOCK_RESPONSES = [ { - match: createEmbeddingMatcher( 'https://wordpress.org/gutenberg/handbook' ), + match: createEmbeddingMatcher( + 'https://wordpress.org/gutenberg/handbook' + ), onRequestMatch: createJSONResponse( MOCK_BAD_WORDPRESS_RESPONSE ), }, { - match: createEmbeddingMatcher( 'https://wordpress.org/gutenberg/handbook/' ), + match: createEmbeddingMatcher( + 'https://wordpress.org/gutenberg/handbook/' + ), onRequestMatch: createJSONResponse( MOCK_BAD_WORDPRESS_RESPONSE ), }, { - match: createEmbeddingMatcher( 'https://wordpress.org/gutenberg/handbook/block-api/attributes/' ), - onRequestMatch: createJSONResponse( MOCK_EMBED_WORDPRESS_SUCCESS_RESPONSE ), + match: createEmbeddingMatcher( + 'https://wordpress.org/gutenberg/handbook/block-api/attributes/' + ), + onRequestMatch: createJSONResponse( + MOCK_EMBED_WORDPRESS_SUCCESS_RESPONSE + ), }, { - match: createEmbeddingMatcher( 'https://www.youtube.com/watch?v=lXMskKTw3Bc' ), + match: createEmbeddingMatcher( + 'https://www.youtube.com/watch?v=lXMskKTw3Bc' + ), onRequestMatch: createJSONResponse( MOCK_EMBED_VIDEO_SUCCESS_RESPONSE ), }, { - match: createEmbeddingMatcher( 'https://soundcloud.com/a-boogie-wit-da-hoodie/swervin' ), + match: createEmbeddingMatcher( + 'https://soundcloud.com/a-boogie-wit-da-hoodie/swervin' + ), onRequestMatch: createJSONResponse( MOCK_EMBED_AUDIO_SUCCESS_RESPONSE ), }, { - match: createEmbeddingMatcher( 'https://www.instagram.com/p/Bvl97o2AK6x/' ), + match: createEmbeddingMatcher( + 'https://www.instagram.com/p/Bvl97o2AK6x/' + ), onRequestMatch: createJSONResponse( MOCK_EMBED_IMAGE_SUCCESS_RESPONSE ), }, { @@ -122,7 +138,9 @@ const MOCK_RESPONSES = [ onRequestMatch: createJSONResponse( MOCK_BAD_EMBED_PROVIDER_RESPONSE ), }, { - match: createEmbeddingMatcher( 'https://twitter.com/wooyaygutenberg123454312' ), + match: createEmbeddingMatcher( + 'https://twitter.com/wooyaygutenberg123454312' + ), onRequestMatch: createJSONResponse( MOCK_CANT_EMBED_RESPONSE ), }, // Respond to the instagram URL with a non-image response, doesn't matter what it is, @@ -152,9 +170,13 @@ describe( 'Embedding content', () => { await clickBlockAppender(); await page.keyboard.type( '/embed' ); await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'https://twitter.com/wooyaygutenberg123454312' ); + await page.keyboard.type( + 'https://twitter.com/wooyaygutenberg123454312' + ); await page.keyboard.press( 'Enter' ); - await page.waitForSelector( 'input[value="https://twitter.com/wooyaygutenberg123454312"]' ); + await page.waitForSelector( + 'input[value="https://twitter.com/wooyaygutenberg123454312"]' + ); // WordPress invalid content. Should render failed, edit state. await clickBlockAppender(); @@ -162,7 +184,9 @@ describe( 'Embedding content', () => { await page.keyboard.press( 'Enter' ); await page.keyboard.type( 'https://wordpress.org/gutenberg/handbook/' ); await page.keyboard.press( 'Enter' ); - await page.waitForSelector( 'input[value="https://wordpress.org/gutenberg/handbook/"]' ); + await page.waitForSelector( + 'input[value="https://wordpress.org/gutenberg/handbook/"]' + ); // Provider whose oembed API has gone wrong. Should render failed, edit // state. @@ -171,14 +195,18 @@ describe( 'Embedding content', () => { await page.keyboard.press( 'Enter' ); await page.keyboard.type( 'https://twitter.com/thatbunty' ); await page.keyboard.press( 'Enter' ); - await page.waitForSelector( 'input[value="https://twitter.com/thatbunty"]' ); + await page.waitForSelector( + 'input[value="https://twitter.com/thatbunty"]' + ); // WordPress content that can be embedded. Should render valid figure // element. await clickBlockAppender(); await page.keyboard.type( '/embed' ); await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'https://wordpress.org/gutenberg/handbook/block-api/attributes/' ); + await page.keyboard.type( + 'https://wordpress.org/gutenberg/handbook/block-api/attributes/' + ); await page.keyboard.press( 'Enter' ); await page.waitForSelector( 'figure.wp-block-embed-wordpress' ); @@ -187,9 +215,13 @@ describe( 'Embedding content', () => { await clickBlockAppender(); await page.keyboard.type( '/embed' ); await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'https://www.youtube.com/watch?v=lXMskKTw3Bc' ); + await page.keyboard.type( + 'https://www.youtube.com/watch?v=lXMskKTw3Bc' + ); await page.keyboard.press( 'Enter' ); - await page.waitForSelector( 'figure.wp-block-embed-youtube.wp-embed-aspect-16-9' ); + await page.waitForSelector( + 'figure.wp-block-embed-youtube.wp-embed-aspect-16-9' + ); // Photo content. Should render valid figure element. await clickBlockAppender(); @@ -204,9 +236,14 @@ describe( 'Embedding content', () => { await clickBlockAppender(); await page.keyboard.type( '/embed' ); await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'https://twitter.com/wooyaygutenberg123454312' ); + await page.keyboard.type( + 'https://twitter.com/wooyaygutenberg123454312' + ); await page.keyboard.press( 'Enter' ); + // Wait for the request to fail and present an error. + await page.waitForSelector( '.components-placeholder__error' ); + await clickButton( 'Convert to link' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -227,17 +264,25 @@ describe( 'Embedding content', () => { await clickBlockAppender(); await page.keyboard.type( '/embed' ); await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'https://twitter.com/wooyaygutenberg123454312' ); + await page.keyboard.type( + 'https://twitter.com/wooyaygutenberg123454312' + ); await page.keyboard.press( 'Enter' ); + + // Wait for the request to fail and present an error. + await page.waitForSelector( '.components-placeholder__error' ); + // Set up a different mock to make sure that try again actually does make the request again. - await setUpResponseMocking( - [ - { - match: createEmbeddingMatcher( 'https://twitter.com/wooyaygutenberg123454312' ), - onRequestMatch: createJSONResponse( MOCK_EMBED_RICH_SUCCESS_RESPONSE ), - }, - ] - ); + await setUpResponseMocking( [ + { + match: createEmbeddingMatcher( + 'https://twitter.com/wooyaygutenberg123454312' + ), + onRequestMatch: createJSONResponse( + MOCK_EMBED_RICH_SUCCESS_RESPONSE + ), + }, + ] ); await clickButton( 'Try again' ); await page.waitForSelector( 'figure.wp-block-embed-twitter' ); } ); @@ -251,7 +296,10 @@ describe( 'Embedding content', () => { await insertBlock( 'Paragraph' ); await page.keyboard.type( 'Hello there!' ); await publishPost(); - const postUrl = await page.$eval( '[id^=inspector-text-control-]', ( el ) => el.value ); + const postUrl = await page.$eval( + '[id^=inspector-text-control-]', + ( el ) => el.value + ); // Start a new post, embed the previous post. await createNewPost(); diff --git a/packages/e2e-tests/specs/editor/various/font-size-picker.test.js b/packages/e2e-tests/specs/editor/various/font-size-picker.test.js index ca1f603ab382ce..d3ab21d68d0acf 100644 --- a/packages/e2e-tests/specs/editor/various/font-size-picker.test.js +++ b/packages/e2e-tests/specs/editor/various/font-size-picker.test.js @@ -18,7 +18,9 @@ describe( 'Font Size Picker', () => { await clickBlockAppender(); await page.keyboard.type( 'Paragraph to be made "large"' ); await page.click( '.components-font-size-picker__select' ); - await page.click( '.components-custom-select-control__item:nth-child(5)' ); + await page.click( + '.components-custom-select-control__item:nth-child(5)' + ); // Ensure content matches snapshot. const content = await getEditedPostContent(); @@ -30,7 +32,9 @@ describe( 'Font Size Picker', () => { await clickBlockAppender(); await page.keyboard.type( 'Paragraph to be made "small"' ); - await page.click( '.components-font-size-picker__controls .components-range-control__number' ); + await page.click( + '.components-font-size-picker__controls .components-range-control__number' + ); // This should be the "small" font-size of the editor defaults. await page.keyboard.type( '13' ); @@ -44,7 +48,9 @@ describe( 'Font Size Picker', () => { await clickBlockAppender(); await page.keyboard.type( 'Paragraph to be made "small"' ); - await page.click( '.components-font-size-picker__controls .components-range-control__number' ); + await page.click( + '.components-font-size-picker__controls .components-range-control__number' + ); await page.keyboard.type( '23' ); // Ensure content matches snapshot. @@ -55,12 +61,20 @@ describe( 'Font Size Picker', () => { it( 'should reset a named font size using the reset button', async () => { // Create a paragraph block with some content. await clickBlockAppender(); - await page.keyboard.type( 'Paragraph with font size reset using button' ); + await page.keyboard.type( + 'Paragraph with font size reset using button' + ); await page.click( '.components-font-size-picker__select' ); - await page.click( '.components-custom-select-control__item:nth-child(2)' ); - - const resetButton = ( await page.$x( '//*[contains(concat(" ", @class, " "), " components-font-size-picker__controls ")]//*[text()=\'Reset\']' ) )[ 0 ]; + await page.click( + '.components-custom-select-control__item:nth-child(2)' + ); + + const resetButton = ( + await page.$x( + '//*[contains(concat(" ", @class, " "), " components-font-size-picker__controls ")]//*[text()=\'Reset\']' + ) + )[ 0 ]; await resetButton.click(); // Ensure content matches snapshot. @@ -71,13 +85,19 @@ describe( 'Font Size Picker', () => { it( 'should reset a named font size using input field', async () => { // Create a paragraph block with some content. await clickBlockAppender(); - await page.keyboard.type( 'Paragraph with font size reset using input field' ); + await page.keyboard.type( + 'Paragraph with font size reset using input field' + ); await page.click( '.components-font-size-picker__select' ); - await page.click( '.components-custom-select-control__item:nth-child(3)' ); + await page.click( + '.components-custom-select-control__item:nth-child(3)' + ); // Clear the custom font size input. - await page.click( '.components-font-size-picker__controls .components-range-control__number' ); + await page.click( + '.components-font-size-picker__controls .components-range-control__number' + ); await pressKeyTimes( 'ArrowRight', 5 ); await pressKeyTimes( 'Backspace', 5 ); @@ -91,12 +111,16 @@ describe( 'Font Size Picker', () => { await clickBlockAppender(); await page.keyboard.type( 'Paragraph to be made "small"' ); - await page.click( '.components-font-size-picker__controls .components-range-control__number' ); + await page.click( + '.components-font-size-picker__controls .components-range-control__number' + ); await page.keyboard.type( '23' ); await page.keyboard.press( 'Backspace' ); - await page.click( '.components-font-size-picker__controls .components-range-control__number' ); + await page.click( + '.components-font-size-picker__controls .components-range-control__number' + ); await page.keyboard.press( 'Backspace' ); await page.keyboard.press( 'Backspace' ); diff --git a/packages/e2e-tests/specs/editor/various/fullscreen-mode.test.js b/packages/e2e-tests/specs/editor/various/fullscreen-mode.test.js index 42007b1c03acb9..5b6bd90ca4c10e 100644 --- a/packages/e2e-tests/specs/editor/various/fullscreen-mode.test.js +++ b/packages/e2e-tests/specs/editor/various/fullscreen-mode.test.js @@ -22,7 +22,9 @@ describe( 'Fullscreen Mode', () => { expect( isFullscreenEnabled ).toBe( true ); - const fullscreenToolbar = await page.$( '.edit-post-fullscreen-mode-close__toolbar' ); + const fullscreenToolbar = await page.$( + '.edit-post-fullscreen-mode-close__toolbar' + ); expect( fullscreenToolbar ).not.toBeNull(); } ); diff --git a/packages/e2e-tests/specs/editor/various/invalid-block.test.js b/packages/e2e-tests/specs/editor/various/invalid-block.test.js index d0d8dda5a805d6..7185017ddfd3c2 100644 --- a/packages/e2e-tests/specs/editor/various/invalid-block.test.js +++ b/packages/e2e-tests/specs/editor/various/invalid-block.test.js @@ -20,11 +20,15 @@ describe( 'invalid blocks', () => { await clickBlockToolbarButton( 'More options' ); // Change to HTML mode and close the options - const changeModeButton = await page.waitForXPath( '//button[text()="Edit as HTML"]' ); + const changeModeButton = await page.waitForXPath( + '//button[text()="Edit as HTML"]' + ); await changeModeButton.click(); // Focus on the textarea and enter an invalid paragraph - await page.click( '.block-editor-block-list__layout .block-editor-block-list__block .block-editor-block-list__block-html-textarea' ); + await page.click( + '.block-editor-block-list__layout .block-editor-block-list__block .block-editor-block-list__block-html-textarea' + ); await page.keyboard.type( '<p>invalid paragraph' ); // Takes the focus away from the block so the invalid warning is triggered @@ -36,7 +40,12 @@ describe( 'invalid blocks', () => { await page.click( '.block-editor-warning__actions button' ); // Check we get the resolve modal with the appropriate contents - const htmlBlockContent = await page.$eval( '.block-editor-block-compare__html', ( node ) => node.textContent ); - expect( htmlBlockContent ).toEqual( '<p>hello</p><p>invalid paragraph' ); + const htmlBlockContent = await page.$eval( + '.block-editor-block-compare__html', + ( node ) => node.textContent + ); + expect( htmlBlockContent ).toEqual( + '<p>hello</p><p>invalid paragraph' + ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js b/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js index aeb9b47fc35c37..a32474aad3b5ab 100644 --- a/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js @@ -36,9 +36,9 @@ const tabThroughParagraphBlock = async ( paragraphText ) => { await page.keyboard.press( 'Tab' ); await expect( await getActiveLabel() ).toBe( 'Paragraph block' ); - await expect( await page.evaluate( () => - document.activeElement.innerHTML - ) ).toBe( paragraphText ); + await expect( + await page.evaluate( () => document.activeElement.innerHTML ) + ).toBe( paragraphText ); await page.keyboard.press( 'Tab' ); await expect( await getActiveLabel() ).toBe( 'Document' ); @@ -119,15 +119,21 @@ describe( 'Order of block keyboard navigation', () => { } ); await page.keyboard.press( 'Tab' ); - await expect( await page.evaluate( () => { - return document.activeElement.placeholder; - } ) ).toBe( 'Add title' ); + await expect( + await page.evaluate( () => { + return document.activeElement.placeholder; + } ) + ).toBe( 'Add title' ); await page.keyboard.press( 'Tab' ); - await expect( await getActiveLabel() ).toBe( 'Paragraph Block. Row 1. 0' ); + await expect( await getActiveLabel() ).toBe( + 'Paragraph Block. Row 1. 0' + ); await page.keyboard.press( 'Tab' ); - await expect( await getActiveLabel() ).toBe( 'Paragraph Block. Row 2. 1' ); + await expect( await getActiveLabel() ).toBe( + 'Paragraph Block. Row 2. 1' + ); await page.keyboard.press( 'Tab' ); await expect( await getActiveLabel() ).toBe( 'Document (selected)' ); @@ -145,19 +151,27 @@ describe( 'Order of block keyboard navigation', () => { // Clear the selected block and put focus behind the block list. await page.evaluate( () => { document.querySelector( '.edit-post-visual-editor' ).focus(); - document.querySelector( '.edit-post-editor-regions__sidebar' ).focus(); + document + .querySelector( '.edit-post-editor-regions__sidebar' ) + .focus(); } ); await pressKeyWithModifier( 'shift', 'Tab' ); - await expect( await getActiveLabel() ).toBe( 'Paragraph Block. Row 2. 1' ); + await expect( await getActiveLabel() ).toBe( + 'Paragraph Block. Row 2. 1' + ); await pressKeyWithModifier( 'shift', 'Tab' ); - await expect( await getActiveLabel() ).toBe( 'Paragraph Block. Row 1. 0' ); + await expect( await getActiveLabel() ).toBe( + 'Paragraph Block. Row 1. 0' + ); await pressKeyWithModifier( 'shift', 'Tab' ); - await expect( await page.evaluate( () => { - return document.activeElement.placeholder; - } ) ).toBe( 'Add title' ); + await expect( + await page.evaluate( () => { + return document.activeElement.placeholder; + } ) + ).toBe( 'Add title' ); } ); it( 'should navigate correctly with multi selection', async () => { @@ -180,7 +194,9 @@ describe( 'Order of block keyboard navigation', () => { await expect( await getActiveLabel() ).toBe( 'Document' ); await pressKeyWithModifier( 'shift', 'Tab' ); - await expect( await getActiveLabel() ).toBe( 'Multiple selected blocks' ); + await expect( await getActiveLabel() ).toBe( + 'Multiple selected blocks' + ); await pressKeyWithModifier( 'shift', 'Tab' ); await expect( await getActiveLabel() ).toBe( 'More options' ); diff --git a/packages/e2e-tests/specs/editor/various/links.test.js b/packages/e2e-tests/specs/editor/various/links.test.js index 7589f449d1d00b..c6d8d12263e75d 100644 --- a/packages/e2e-tests/specs/editor/various/links.test.js +++ b/packages/e2e-tests/specs/editor/various/links.test.js @@ -22,7 +22,9 @@ describe( 'Links', () => { } ); const waitForAutoFocus = async () => { - await page.waitForFunction( () => !! document.activeElement.closest( '.block-editor-url-input' ) ); + await page.waitForFunction( + () => !! document.activeElement.closest( '.block-editor-url-input' ) + ); }; it( 'can be created by selecting text and clicking Link', async () => { @@ -100,7 +102,9 @@ describe( 'Links', () => { it( 'can be created instantly when a URL is selected', async () => { // Create a block with some text await clickBlockAppender(); - await page.keyboard.type( 'This is Gutenberg: https://wordpress.org/gutenberg' ); + await page.keyboard.type( + 'This is Gutenberg: https://wordpress.org/gutenberg' + ); // Select the URL await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); @@ -189,7 +193,9 @@ describe( 'Links', () => { const toggleFixedToolbar = async ( isFixed ) => { await page.evaluate( ( _isFixed ) => { const { select, dispatch } = wp.data; - const isCurrentlyFixed = select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ); + const isCurrentlyFixed = select( 'core/edit-post' ).isFeatureActive( + 'fixedToolbar' + ); if ( isCurrentlyFixed !== _isFixed ) { dispatch( 'core/edit-post' ).toggleFeature( 'fixedToolbar' ); } @@ -262,8 +268,15 @@ describe( 'Links', () => { await page.click( '.editor-post-publish-button' ); // Return the URL of the new post - await page.waitForSelector( '.post-publish-panel__postpublish-post-address input' ); - return page.evaluate( () => document.querySelector( '.post-publish-panel__postpublish-post-address input' ).value ); + await page.waitForSelector( + '.post-publish-panel__postpublish-post-address input' + ); + return page.evaluate( + () => + document.querySelector( + '.post-publish-panel__postpublish-post-address input' + ).value + ); }; it( 'allows use of escape key to dismiss the url popover', async () => { @@ -346,9 +359,17 @@ describe( 'Links', () => { // focused with the value previously inserted. await pressKeyWithModifier( 'primary', 'K' ); await waitForAutoFocus(); - const activeElementParentClasses = await page.evaluate( () => Object.values( document.activeElement.parentElement.parentElement.classList ) ); - expect( activeElementParentClasses ).toContain( 'block-editor-url-input' ); - const activeElementValue = await page.evaluate( () => document.activeElement.value ); + const activeElementParentClasses = await page.evaluate( () => + Object.values( + document.activeElement.parentElement.parentElement.classList + ) + ); + expect( activeElementParentClasses ).toContain( + 'block-editor-url-input' + ); + const activeElementValue = await page.evaluate( + () => document.activeElement.value + ); expect( activeElementValue ).toBe( URL ); } ); @@ -360,8 +381,12 @@ describe( 'Links', () => { await waitForAutoFocus(); await page.keyboard.type( 'http://#test.com' ); await page.keyboard.press( 'Enter' ); - const assertiveContent = await page.evaluate( () => document.querySelector( '#a11y-speak-assertive' ).textContent ); - expect( assertiveContent.trim() ).toBe( 'Warning: the link has been inserted but may have errors. Please test it.' ); + const assertiveContent = await page.evaluate( + () => document.querySelector( '#a11y-speak-assertive' ).textContent + ); + expect( assertiveContent.trim() ).toBe( + 'Warning: the link has been inserted but may have errors. Please test it.' + ); } ); it( 'link popover remains visible after a mouse drag event', async () => { @@ -392,7 +417,9 @@ describe( 'Links', () => { await page.mouse.move( bounds.x, bounds.y ); await page.mouse.down(); - await page.mouse.move( bounds.x + ( bounds.width / 2 ), bounds.y, { steps: 10 } ); + await page.mouse.move( bounds.x + bounds.width / 2, bounds.y, { + steps: 10, + } ); await page.mouse.up(); // The link popover should still be visible @@ -462,7 +489,9 @@ describe( 'Links', () => { await page.keyboard.press( 'Tab' ); await page.keyboard.press( 'Space' ); await page.keyboard.press( 'Tab' ); - const isChecked = await page.evaluate( () => document.activeElement.checked ); + const isChecked = await page.evaluate( + () => document.activeElement.checked + ); expect( isChecked ).toBe( false ); expect( await getEditedPostContent() ).toMatchSnapshot(); diff --git a/packages/e2e-tests/specs/editor/various/manage-reusable-blocks.test.js b/packages/e2e-tests/specs/editor/various/manage-reusable-blocks.test.js index 9d17922f519f39..be20ca9e6d52d6 100644 --- a/packages/e2e-tests/specs/editor/various/manage-reusable-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/manage-reusable-blocks.test.js @@ -16,7 +16,9 @@ describe( 'Managing reusable blocks', () => { * @return {Promise} Promise resolving to number of post list entries. */ async function getNumberOfEntries() { - return page.evaluate( () => document.querySelectorAll( '.hentry' ).length ); + return page.evaluate( + () => document.querySelectorAll( '.hentry' ).length + ); } beforeAll( async () => { @@ -28,22 +30,38 @@ describe( 'Managing reusable blocks', () => { // Import Reusable block await page.waitForSelector( '.list-reusable-blocks__container' ); - const importButton = await page.$( '.list-reusable-blocks__container button' ); + const importButton = await page.$( + '.list-reusable-blocks__container button' + ); await importButton.click(); // Select the file to upload - const testReusableBlockFile = path.join( __dirname, '..', '..', '..', 'assets', 'greeting-reusable-block.json' ); + const testReusableBlockFile = path.join( + __dirname, + '..', + '..', + '..', + 'assets', + 'greeting-reusable-block.json' + ); const input = await page.$( '.list-reusable-blocks-import-form input' ); await input.uploadFile( testReusableBlockFile ); // Submit the form - const button = await page.$( '.list-reusable-blocks-import-form__button' ); + const button = await page.$( + '.list-reusable-blocks-import-form__button' + ); await button.click(); // Wait for the success notice await page.waitForSelector( '.notice-success' ); - const noticeContent = await page.$eval( '.notice-success', ( element ) => element.textContent ); - expect( noticeContent ).toEqual( 'Reusable block imported successfully!' ); + const noticeContent = await page.$eval( + '.notice-success', + ( element ) => element.textContent + ); + expect( noticeContent ).toEqual( + 'Reusable block imported successfully!' + ); // Refresh the page await visitAdminPage( 'edit.php', 'post_type=wp_block' ); diff --git a/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js b/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js index 7adbdacd7f1261..832cf25d7d4fa6 100644 --- a/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js +++ b/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js @@ -15,17 +15,17 @@ async function getSelectedFlatIndices() { const indices = []; let single; - Array.from( - document.querySelectorAll( '.wp-block' ) - ).forEach( ( node, index ) => { - if ( node.classList.contains( 'is-selected' ) ) { - single = index; + Array.from( document.querySelectorAll( '.wp-block' ) ).forEach( + ( node, index ) => { + if ( node.classList.contains( 'is-selected' ) ) { + single = index; + } + + if ( node.classList.contains( 'is-multi-selected' ) ) { + indices.push( index ); + } } - - if ( node.classList.contains( 'is-multi-selected' ) ) { - indices.push( index ); - } - } ); + ); return single !== undefined ? single : indices; } ); @@ -110,7 +110,10 @@ describe( 'Multi-block selection', () => { // DOM-dependant side-effect setup code and doesn't seem straightforward // to mock. Instead, we check for the DOM node that `wp.a11y.speak()` // inserts text into. - const speakTextContent = await page.$eval( '#a11y-speak-assertive', ( element ) => element.textContent ); + const speakTextContent = await page.$eval( + '#a11y-speak-assertive', + ( element ) => element.textContent + ); expect( speakTextContent.trim() ).toEqual( '3 blocks selected.' ); } ); @@ -271,17 +274,19 @@ describe( 'Multi-block selection', () => { await page.keyboard.press( 'ArrowUp' ); const [ coord1, coord2 ] = await page.evaluate( () => { - const elements = Array.from( document.querySelectorAll( '.wp-block-paragraph' ) ); + const elements = Array.from( + document.querySelectorAll( '.wp-block-paragraph' ) + ); const rect1 = elements[ 0 ].getBoundingClientRect(); const rect2 = elements[ 1 ].getBoundingClientRect(); return [ { - x: rect1.x + ( rect1.width / 2 ), - y: rect1.y + ( rect1.height / 2 ), + x: rect1.x + rect1.width / 2, + y: rect1.y + rect1.height / 2, }, { - x: rect2.x + ( rect2.width / 2 ), - y: rect2.y + ( rect2.height / 2 ), + x: rect2.x + rect2.width / 2, + y: rect2.y + rect2.height / 2, }, ]; } ); @@ -305,17 +310,19 @@ describe( 'Multi-block selection', () => { await page.keyboard.type( '2' ); const [ coord1, coord2 ] = await page.evaluate( () => { - const elements = Array.from( document.querySelectorAll( '.wp-block-paragraph' ) ); + const elements = Array.from( + document.querySelectorAll( '.wp-block-paragraph' ) + ); const rect1 = elements[ 0 ].getBoundingClientRect(); const rect2 = elements[ 1 ].getBoundingClientRect(); return [ { - x: rect1.x + ( rect1.width / 2 ), - y: rect1.y + ( rect1.height / 2 ), + x: rect1.x + rect1.width / 2, + y: rect1.y + rect1.height / 2, }, { - x: rect2.x + ( rect2.width / 2 ), - y: rect2.y + ( rect2.height / 2 ), + x: rect2.x + rect2.width / 2, + y: rect2.y + rect2.height / 2, }, ]; } ); @@ -386,12 +393,12 @@ describe( 'Multi-block selection', () => { return [ { x: rect1.x, - y: rect1.y + ( rect1.height / 2 ), + y: rect1.y + rect1.height / 2, }, { // Move a bit outside the paragraph. x: rect2.x - 10, - y: rect2.y + ( rect2.height / 2 ), + y: rect2.y + rect2.height / 2, }, ]; } ); @@ -402,7 +409,9 @@ describe( 'Multi-block selection', () => { await page.mouse.up(); // Wait for the selection to update. - await page.evaluate( () => new Promise( window.requestAnimationFrame ) ); + await page.evaluate( + () => new Promise( window.requestAnimationFrame ) + ); // Only "1" should be deleted. await page.keyboard.press( 'Backspace' ); @@ -419,17 +428,19 @@ describe( 'Multi-block selection', () => { await page.keyboard.type( '3' ); const [ coord1, coord2 ] = await page.evaluate( () => { - const elements = Array.from( document.querySelectorAll( '.wp-block-paragraph' ) ); + const elements = Array.from( + document.querySelectorAll( '.wp-block-paragraph' ) + ); const rect1 = elements[ 2 ].getBoundingClientRect(); const rect2 = elements[ 1 ].getBoundingClientRect(); return [ { - x: rect1.x + ( rect1.width / 2 ), - y: rect1.y + ( rect1.height / 2 ), + x: rect1.x + rect1.width / 2, + y: rect1.y + rect1.height / 2, }, { - x: rect2.x + ( rect2.width / 2 ), - y: rect2.y + ( rect2.height / 2 ), + x: rect2.x + rect2.width / 2, + y: rect2.y + rect2.height / 2, }, ]; } ); @@ -465,7 +476,7 @@ describe( 'Multi-block selection', () => { const rect = element.getBoundingClientRect(); return { x: rect.x - 1, - y: rect.y + ( rect.height / 2 ), + y: rect.y + rect.height / 2, }; } ); diff --git a/packages/e2e-tests/specs/editor/various/navigable-toolbar.test.js b/packages/e2e-tests/specs/editor/various/navigable-toolbar.test.js index 62403ee6da796f..f10ad5f3190023 100644 --- a/packages/e2e-tests/specs/editor/various/navigable-toolbar.test.js +++ b/packages/e2e-tests/specs/editor/various/navigable-toolbar.test.js @@ -3,67 +3,80 @@ */ import { createNewPost, pressKeyWithModifier } from '@wordpress/e2e-test-utils'; -describe.each( [ [ 'unified', true ], [ 'contextual', false ] ] )( - 'block toolbar (%s: %p)', - ( label, isUnifiedToolbar ) => { - beforeEach( async () => { - await createNewPost(); +describe.each( [ + [ 'unified', true ], + [ 'contextual', false ], +] )( 'block toolbar (%s: %p)', ( label, isUnifiedToolbar ) => { + beforeEach( async () => { + await createNewPost(); - await page.evaluate( ( _isUnifiedToolbar ) => { - const { select, dispatch } = wp.data; - const isCurrentlyUnified = select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ); - if ( isCurrentlyUnified !== _isUnifiedToolbar ) { - dispatch( 'core/edit-post' ).toggleFeature( 'fixedToolbar' ); - } - }, isUnifiedToolbar ); - } ); + await page.evaluate( ( _isUnifiedToolbar ) => { + const { select, dispatch } = wp.data; + const isCurrentlyUnified = select( + 'core/edit-post' + ).isFeatureActive( 'fixedToolbar' ); + if ( isCurrentlyUnified !== _isUnifiedToolbar ) { + dispatch( 'core/edit-post' ).toggleFeature( 'fixedToolbar' ); + } + }, isUnifiedToolbar ); + } ); - const isInBlockToolbar = () => page.evaluate( ( _isUnifiedToolbar ) => { + const isInBlockToolbar = () => + page.evaluate( ( _isUnifiedToolbar ) => { if ( _isUnifiedToolbar ) { return !! document.activeElement .closest( '.edit-post-header-toolbar' ) .querySelector( '.block-editor-block-toolbar' ); } - return !! document.activeElement.closest( '.block-editor-block-toolbar' ); + return !! document.activeElement.closest( + '.block-editor-block-toolbar' + ); }, isUnifiedToolbar ); - it( 'navigates in and out of toolbar by keyboard (Alt+F10, Escape)', async () => { - // Assumes new post focus starts in title. Create first new - // block by ArrowDown. - await page.keyboard.press( 'ArrowDown' ); + it( 'navigates in and out of toolbar by keyboard (Alt+F10, Escape)', async () => { + // Assumes new post focus starts in title. Create first new + // block by ArrowDown. + await page.keyboard.press( 'ArrowDown' ); - // [TEMPORARY]: A new paragraph is not technically a block yet - // until starting to type within it. - await page.keyboard.type( 'Example' ); + // [TEMPORARY]: A new paragraph is not technically a block yet + // until starting to type within it. + await page.keyboard.type( 'Example' ); - // Upward - await pressKeyWithModifier( 'alt', 'F10' ); - expect( await isInBlockToolbar() ).toBe( true ); - } ); + // Upward + await pressKeyWithModifier( 'alt', 'F10' ); + expect( await isInBlockToolbar() ).toBe( true ); + } ); - if ( ! isUnifiedToolbar ) { - it( 'should not scroll page', async () => { - while ( await page.evaluate( () => - wp.dom.getScrollContainer( document.activeElement ).scrollTop === 0 - ) ) { - await page.keyboard.press( 'Enter' ); - } + if ( ! isUnifiedToolbar ) { + it( 'should not scroll page', async () => { + while ( + await page.evaluate( + () => + wp.dom.getScrollContainer( document.activeElement ) + .scrollTop === 0 + ) + ) { + await page.keyboard.press( 'Enter' ); + } - await page.keyboard.type( 'a' ); + await page.keyboard.type( 'a' ); - const scrollTopBefore = await page.evaluate( () => - wp.dom.getScrollContainer( document.activeElement ).scrollTop - ); + const scrollTopBefore = await page.evaluate( + () => + wp.dom.getScrollContainer( document.activeElement ) + .scrollTop + ); - await pressKeyWithModifier( 'alt', 'F10' ); - expect( await isInBlockToolbar() ).toBe( true ); + await pressKeyWithModifier( 'alt', 'F10' ); + expect( await isInBlockToolbar() ).toBe( true ); - const scrollTopAfter = await page.evaluate( () => - wp.dom.getScrollContainer( document.activeElement ).scrollTop - ); + const scrollTopAfter = await page.evaluate( + () => + wp.dom.getScrollContainer( document.activeElement ) + .scrollTop + ); - expect( scrollTopBefore ).toBe( scrollTopAfter ); - } ); - } + expect( scrollTopBefore ).toBe( scrollTopAfter ); + } ); } -); +} ); diff --git a/packages/e2e-tests/specs/editor/various/new-post-default-content.test.js b/packages/e2e-tests/specs/editor/various/new-post-default-content.test.js index 3fdad97d46f64e..4c917f265eaf30 100644 --- a/packages/e2e-tests/specs/editor/various/new-post-default-content.test.js +++ b/packages/e2e-tests/specs/editor/various/new-post-default-content.test.js @@ -33,7 +33,9 @@ describe( 'new editor filtered state', () => { // open the sidebar, we want to see the excerpt. await openDocumentSettingsSidebar(); - const excerptButton = await findSidebarPanelToggleButtonWithTitle( 'Excerpt' ); + const excerptButton = await findSidebarPanelToggleButtonWithTitle( + 'Excerpt' + ); if ( excerptButton ) { await excerptButton.click( 'button' ); } diff --git a/packages/e2e-tests/specs/editor/various/new-post.test.js b/packages/e2e-tests/specs/editor/various/new-post.test.js index c4cb3fa082dcad..41db6ce7148b2b 100644 --- a/packages/e2e-tests/specs/editor/various/new-post.test.js +++ b/packages/e2e-tests/specs/editor/various/new-post.test.js @@ -21,13 +21,17 @@ describe( 'new editor state', () => { } ); it( 'should show the New Post page in Gutenberg', async () => { - expect( page.url() ).toEqual( expect.stringContaining( 'post-new.php' ) ); + expect( page.url() ).toEqual( + expect.stringContaining( 'post-new.php' ) + ); // Should display the blank title. const title = await page.$( '[placeholder="Add title"]' ); expect( title ).not.toBeNull(); expect( title.innerHTML ).toBeFalsy(); // Should display the Preview button. - const postPreviewButton = await page.$( '.editor-post-preview.components-button' ); + const postPreviewButton = await page.$( + '.editor-post-preview.components-button' + ); expect( postPreviewButton ).not.toBeNull(); // Should display the Post Formats UI. const postFormatsUi = await page.$( '.editor-post-format' ); @@ -35,8 +39,12 @@ describe( 'new editor state', () => { } ); it( 'should have no history', async () => { - const undoButton = await page.$( '.editor-history__undo[aria-disabled="false"]' ); - const redoButton = await page.$( '.editor-history__redo[aria-disabled="false"]' ); + const undoButton = await page.$( + '.editor-history__undo[aria-disabled="false"]' + ); + const redoButton = await page.$( + '.editor-history__redo[aria-disabled="false"]' + ); expect( undoButton ).toBeNull(); expect( redoButton ).toBeNull(); @@ -70,7 +78,9 @@ describe( 'new editor state', () => { return document.activeElement.tagName.toLowerCase(); } ); - expect( activeElementClasses ).not.toContain( 'editor-post-title__input' ); + expect( activeElementClasses ).not.toContain( + 'editor-post-title__input' + ); // The document `body` should be the `activeElement`, because nothing is // focused by default when a post already has a title. expect( activeElementTagName ).toEqual( 'body' ); diff --git a/packages/e2e-tests/specs/editor/various/nux.test.js b/packages/e2e-tests/specs/editor/various/nux.test.js index e597272d2feb39..03a795b6c66c75 100644 --- a/packages/e2e-tests/specs/editor/various/nux.test.js +++ b/packages/e2e-tests/specs/editor/various/nux.test.js @@ -11,46 +11,70 @@ describe( 'New User Experience (NUX)', () => { await createNewPost( { showWelcomeGuide: true } ); // Guide should be on page 1 of 4 - welcomeGuideText = await page.$eval( '.edit-post-welcome-guide', ( element ) => element.innerText ); + welcomeGuideText = await page.$eval( + '.edit-post-welcome-guide', + ( element ) => element.innerText + ); expect( welcomeGuideText ).toContain( 'Welcome to the Block Editor' ); // Click on the 'Next' button - const [ nextButton ] = await page.$x( '//button[contains(text(), "Next")]' ); + const [ nextButton ] = await page.$x( + '//button[contains(text(), "Next")]' + ); await nextButton.click(); // Guide should be on page 2 of 4 - welcomeGuideText = await page.$eval( '.edit-post-welcome-guide', ( element ) => element.innerText ); + welcomeGuideText = await page.$eval( + '.edit-post-welcome-guide', + ( element ) => element.innerText + ); expect( welcomeGuideText ).toContain( 'Make each block your own' ); // Click on the 'Previous' button - const [ previousButton ] = await page.$x( '//button[contains(text(), "Previous")]' ); + const [ previousButton ] = await page.$x( + '//button[contains(text(), "Previous")]' + ); await previousButton.click(); // Guide should be on page 1 of 4 - welcomeGuideText = await page.$eval( '.edit-post-welcome-guide', ( element ) => element.innerText ); + welcomeGuideText = await page.$eval( + '.edit-post-welcome-guide', + ( element ) => element.innerText + ); expect( welcomeGuideText ).toContain( 'Welcome to the Block Editor' ); // Press the button for Page 2 await page.click( 'button[aria-label="Page 2 of 4"]' ); - await page.waitForXPath( '//h1[contains(text(), "Make each block your own")]' ); + await page.waitForXPath( + '//h1[contains(text(), "Make each block your own")]' + ); // This shouldn't be necessary // eslint-disable-next-line no-restricted-syntax await page.waitFor( 500 ); // Press the right arrow key for Page 3 await page.keyboard.press( 'ArrowRight' ); - await page.waitForXPath( '//h1[contains(text(), "Get to know the Block Library")]' ); + await page.waitForXPath( + '//h1[contains(text(), "Get to know the Block Library")]' + ); // Press the right arrow key for Page 4 await page.keyboard.press( 'ArrowRight' ); - await page.waitForXPath( '//h1[contains(text(), "Learn how to use the Block Editor")]' ); + await page.waitForXPath( + '//h1[contains(text(), "Learn how to use the Block Editor")]' + ); // Click on the *visible* 'Get started' button. There are two in the DOM // but only one is shown depending on viewport size let getStartedButton; - for ( const buttonHandle of await page.$x( '//button[contains(text(), "Get started")]' ) ) { + for ( const buttonHandle of await page.$x( + '//button[contains(text(), "Get started")]' + ) ) { if ( - await page.evaluate( ( button ) => button.style.display !== 'none', buttonHandle ) + await page.evaluate( + ( button ) => button.style.display !== 'none', + buttonHandle + ) ) { getStartedButton = buttonHandle; } diff --git a/packages/e2e-tests/specs/editor/various/popovers.test.js b/packages/e2e-tests/specs/editor/various/popovers.test.js index 5a1e17817dabff..2b0399f4569f29 100644 --- a/packages/e2e-tests/specs/editor/various/popovers.test.js +++ b/packages/e2e-tests/specs/editor/various/popovers.test.js @@ -10,7 +10,8 @@ describe( 'popovers', () => { describe( 'dropdown', () => { it( 'toggles via click', async () => { - const isMoreMenuOpen = async () => !! await page.$( '.edit-post-more-menu__content' ); + const isMoreMenuOpen = async () => + !! ( await page.$( '.edit-post-more-menu__content' ) ); expect( await isMoreMenuOpen() ).toBe( false ); diff --git a/packages/e2e-tests/specs/editor/various/post-visibility.test.js b/packages/e2e-tests/specs/editor/various/post-visibility.test.js index 2aa49ef22c96df..dc04a8e2a8ee21 100644 --- a/packages/e2e-tests/specs/editor/various/post-visibility.test.js +++ b/packages/e2e-tests/specs/editor/various/post-visibility.test.js @@ -21,11 +21,15 @@ describe( 'Post visibility', () => { await page.click( '.edit-post-post-visibility__toggle' ); - const [ privateLabel ] = await page.$x( '//label[text()="Private"]' ); + const [ privateLabel ] = await page.$x( + '//label[text()="Private"]' + ); await privateLabel.click(); const currentStatus = await page.evaluate( () => { - return wp.data.select( 'core/editor' ).getEditedPostAttribute( 'status' ); + return wp.data + .select( 'core/editor' ) + .getEditedPostAttribute( 'status' ); } ); expect( currentStatus ).toBe( 'private' ); @@ -42,10 +46,14 @@ describe( 'Post visibility', () => { // Set a publish date for the next month. await page.click( '.edit-post-post-schedule__toggle' ); - await page.click( 'div[aria-label="Move forward to switch to the next month."]' ); + await page.click( + 'div[aria-label="Move forward to switch to the next month."]' + ); await ( - await page.$x( '//td[contains(concat(" ", @class, " "), " CalendarDay ")][text() = "15"]' ) - )[ 0 ].click(); + await page.$x( + '//td[contains(concat(" ", @class, " "), " CalendarDay ")][text() = "15"]' + ) + )[ 0 ].click(); await page.click( '.edit-post-post-visibility__toggle' ); @@ -58,7 +66,9 @@ describe( 'Post visibility', () => { await page.click( '.editor-post-publish-button' ); const currentStatus = await page.evaluate( () => { - return wp.data.select( 'core/editor' ).getEditedPostAttribute( 'status' ); + return wp.data + .select( 'core/editor' ) + .getEditedPostAttribute( 'status' ); } ); expect( currentStatus ).toBe( 'private' ); diff --git a/packages/e2e-tests/specs/editor/various/preferences.test.js b/packages/e2e-tests/specs/editor/various/preferences.test.js index 24cf347d96501e..b21c850b416517 100644 --- a/packages/e2e-tests/specs/editor/various/preferences.test.js +++ b/packages/e2e-tests/specs/editor/various/preferences.test.js @@ -44,7 +44,9 @@ describe( 'preferences', () => { expect( await getActiveSidebarTabText() ).toBe( 'Document' ); // Dismiss - await page.click( '.edit-post-sidebar__panel-tabs [aria-label="Close settings"]' ); + await page.click( + '.edit-post-sidebar__panel-tabs [aria-label="Close settings"]' + ); expect( await getActiveSidebarTabText() ).toBe( null ); // Remember after reload. diff --git a/packages/e2e-tests/specs/editor/various/preview.test.js b/packages/e2e-tests/specs/editor/various/preview.test.js index 88ab8192b90a53..324be3f9f2efb9 100644 --- a/packages/e2e-tests/specs/editor/various/preview.test.js +++ b/packages/e2e-tests/specs/editor/various/preview.test.js @@ -61,7 +61,8 @@ async function waitForPreviewNavigation( previewPage ) { * @param {boolean} shouldBeChecked If true, turns the option on. If false, off. */ async function toggleCustomFieldsOption( shouldBeChecked ) { - const checkboxXPath = '//*[contains(@class, "edit-post-options-modal")]//label[contains(text(), "Custom fields")]'; + const checkboxXPath = + '//*[contains(@class, "edit-post-options-modal")]//label[contains(text(), "Custom fields")]'; await clickOnMoreMenuItem( 'Options' ); await page.waitForXPath( checkboxXPath ); const [ checkboxHandle ] = await page.$x( checkboxXPath ); @@ -73,7 +74,11 @@ async function toggleCustomFieldsOption( shouldBeChecked ) { if ( isChecked !== shouldBeChecked ) { await checkboxHandle.click(); - const [ saveButton ] = await page.$x( shouldBeChecked ? '//button[text()="Enable & Reload"]' : '//button[text()="Disable & Reload"]' ); + const [ saveButton ] = await page.$x( + shouldBeChecked + ? '//button[text()="Enable & Reload"]' + : '//button[text()="Disable & Reload"]' + ); const navigationCompleted = page.waitForNavigation(); saveButton.click(); await navigationCompleted; @@ -94,7 +99,7 @@ describe( 'Preview', () => { // Disabled until content present. const isPreviewDisabled = await editorPage.$$eval( '.editor-post-preview:not( :disabled ):not( [aria-disabled="true"] )', - ( enabledButtons ) => ! enabledButtons.length, + ( enabledButtons ) => ! enabledButtons.length ); expect( isPreviewDisabled ).toBe( true ); @@ -104,15 +109,23 @@ describe( 'Preview', () => { // When autosave completes for a new post, the URL of the editor should // update to include the ID. Use this to assert on preview URL. - const [ , postId ] = await ( await editorPage.waitForFunction( () => { - return window.location.search.match( /[\?&]post=(\d+)/ ); - } ) ).jsonValue(); - - const expectedPreviewURL = createURL( '', `?p=${ postId }&preview=true` ); + const [ , postId ] = await ( + await editorPage.waitForFunction( () => { + return window.location.search.match( /[\?&]post=(\d+)/ ); + } ) + ).jsonValue(); + + const expectedPreviewURL = createURL( + '', + `?p=${ postId }&preview=true` + ); expect( previewPage.url() ).toBe( expectedPreviewURL ); // Title in preview should match input. - let previewTitle = await previewPage.$eval( '.entry-title', ( node ) => node.textContent ); + let previewTitle = await previewPage.$eval( + '.entry-title', + ( node ) => node.textContent + ); expect( previewTitle ).toBe( 'Hello World' ); // Return to editor to change title. @@ -121,14 +134,20 @@ describe( 'Preview', () => { await waitForPreviewNavigation( previewPage ); // Title in preview should match updated input. - previewTitle = await previewPage.$eval( '.entry-title', ( node ) => node.textContent ); + previewTitle = await previewPage.$eval( + '.entry-title', + ( node ) => node.textContent + ); expect( previewTitle ).toBe( 'Hello World!' ); // Pressing preview without changes should bring same preview window to // front and reload, but should not show interstitial. await editorPage.bringToFront(); await waitForPreviewNavigation( previewPage ); - previewTitle = await previewPage.$eval( '.entry-title', ( node ) => node.textContent ); + previewTitle = await previewPage.$eval( + '.entry-title', + ( node ) => node.textContent + ); expect( previewTitle ).toBe( 'Hello World!' ); // Preview for published post (no unsaved changes) directs to canonical URL for post. @@ -141,7 +160,10 @@ describe( 'Preview', () => { await waitForPreviewNavigation( previewPage ); // Title in preview should match updated input. - previewTitle = await previewPage.$eval( '.entry-title', ( node ) => node.textContent ); + previewTitle = await previewPage.$eval( + '.entry-title', + ( node ) => node.textContent + ); expect( previewTitle ).toBe( 'Hello World! And more.' ); // Published preview URL should include ID and nonce parameters. @@ -159,7 +181,10 @@ describe( 'Preview', () => { await waitForPreviewNavigation( previewPage ); // Title in preview should match updated input. - previewTitle = await previewPage.$eval( '.entry-title', ( node ) => node.textContent ); + previewTitle = await previewPage.$eval( + '.entry-title', + ( node ) => node.textContent + ); expect( previewTitle ).toBe( 'Hello World! And more.' ); await previewPage.close(); @@ -180,7 +205,10 @@ describe( 'Preview', () => { const previewPage = await openPreviewPage( editorPage ); // Title in preview should match input. - let previewTitle = await previewPage.$eval( '.entry-title', ( node ) => node.textContent ); + let previewTitle = await previewPage.$eval( + '.entry-title', + ( node ) => node.textContent + ); expect( previewTitle ).toBe( 'aaaaa' ); // Return to editor. @@ -196,7 +224,10 @@ describe( 'Preview', () => { await waitForPreviewNavigation( previewPage ); // Title in preview should match updated input. - previewTitle = await previewPage.$eval( '.entry-title', ( node ) => node.textContent ); + previewTitle = await previewPage.$eval( + '.entry-title', + ( node ) => node.textContent + ); expect( previewTitle ).toBe( 'aaaaabbbbb' ); await previewPage.close(); @@ -231,9 +262,15 @@ describe( 'Preview with Custom Fields enabled', () => { const previewPage = await openPreviewPage( editorPage ); // Check the title and preview match. - let previewTitle = await previewPage.$eval( '.entry-title', ( node ) => node.textContent ); + let previewTitle = await previewPage.$eval( + '.entry-title', + ( node ) => node.textContent + ); expect( previewTitle ).toBe( 'title 1' ); - let previewContent = await previewPage.$eval( '.entry-content p', ( node ) => node.textContent ); + let previewContent = await previewPage.$eval( + '.entry-content p', + ( node ) => node.textContent + ); expect( previewContent ).toBe( 'content 1' ); // Return to editor and modify the title and content. @@ -251,9 +288,15 @@ describe( 'Preview with Custom Fields enabled', () => { await waitForPreviewNavigation( previewPage ); // Title in preview should match input. - previewTitle = await previewPage.$eval( '.entry-title', ( node ) => node.textContent ); + previewTitle = await previewPage.$eval( + '.entry-title', + ( node ) => node.textContent + ); expect( previewTitle ).toBe( 'title 2' ); - previewContent = await previewPage.$eval( '.entry-content p', ( node ) => node.textContent ); + previewContent = await previewPage.$eval( + '.entry-content p', + ( node ) => node.textContent + ); expect( previewContent ).toBe( 'content 2' ); // Make sure the editor is active for the afterEach function. diff --git a/packages/e2e-tests/specs/editor/various/publish-button.test.js b/packages/e2e-tests/specs/editor/various/publish-button.test.js index ecdd6bf76b0005..680ca0082c2eda 100644 --- a/packages/e2e-tests/specs/editor/various/publish-button.test.js +++ b/packages/e2e-tests/specs/editor/various/publish-button.test.js @@ -11,7 +11,7 @@ import { describe( 'PostPublishButton', () => { let werePrePublishChecksEnabled; beforeEach( async () => { - await createNewPost( ); + await createNewPost(); werePrePublishChecksEnabled = await arePrePublishChecksEnabled(); if ( werePrePublishChecksEnabled ) { await disablePrePublishChecks(); @@ -25,23 +25,35 @@ describe( 'PostPublishButton', () => { } ); it( 'should be disabled when post is not saveable', async () => { - const publishButton = await page.$( '.editor-post-publish-button[aria-disabled="true"]' ); + const publishButton = await page.$( + '.editor-post-publish-button[aria-disabled="true"]' + ); expect( publishButton ).not.toBeNull(); } ); it( 'should be disabled when post is being saved', async () => { await page.type( '.editor-post-title__input', 'E2E Test Post' ); // Make it saveable - expect( await page.$( '.editor-post-publish-button[aria-disabled="true"]' ) ).toBeNull(); + expect( + await page.$( '.editor-post-publish-button[aria-disabled="true"]' ) + ).toBeNull(); await page.click( '.editor-post-save-draft' ); - expect( await page.$( '.editor-post-publish-button[aria-disabled="true"]' ) ).not.toBeNull(); + expect( + await page.$( '.editor-post-publish-button[aria-disabled="true"]' ) + ).not.toBeNull(); } ); it( 'should be disabled when metabox is being saved', async () => { await page.type( '.editor-post-title__input', 'E2E Test Post' ); // Make it saveable - expect( await page.$( '.editor-post-publish-button[aria-disabled="true"]' ) ).toBeNull(); + expect( + await page.$( '.editor-post-publish-button[aria-disabled="true"]' ) + ).toBeNull(); - await page.evaluate( () => window.wp.data.dispatch( 'core/edit-post' ).requestMetaBoxUpdates() ); - expect( await page.$( '.editor-post-publish-button[aria-disabled="true"]' ) ).not.toBeNull(); + await page.evaluate( () => + window.wp.data.dispatch( 'core/edit-post' ).requestMetaBoxUpdates() + ); + expect( + await page.$( '.editor-post-publish-button[aria-disabled="true"]' ) + ).not.toBeNull(); } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/publish-panel.test.js b/packages/e2e-tests/specs/editor/various/publish-panel.test.js index c28a3837d74438..3a6aefd8f66870 100644 --- a/packages/e2e-tests/specs/editor/various/publish-panel.test.js +++ b/packages/e2e-tests/specs/editor/various/publish-panel.test.js @@ -14,7 +14,7 @@ import { describe( 'PostPublishPanel', () => { let werePrePublishChecksEnabled; beforeEach( async () => { - await createNewPost( ); + await createNewPost(); werePrePublishChecksEnabled = await arePrePublishChecksEnabled(); if ( ! werePrePublishChecksEnabled ) { await enablePrePublishChecks(); @@ -31,10 +31,15 @@ describe( 'PostPublishPanel', () => { await page.type( '.editor-post-title__input', 'E2E Test Post' ); await openPublishPanel(); - const focusedElementClassList = await page.$eval( ':focus', ( focusedElement ) => { - return Object.values( focusedElement.classList ); - } ); - expect( focusedElementClassList ).toContain( 'editor-post-publish-button' ); + const focusedElementClassList = await page.$eval( + ':focus', + ( focusedElement ) => { + return Object.values( focusedElement.classList ); + } + ); + expect( focusedElementClassList ).toContain( + 'editor-post-publish-button' + ); } ); it( 'PostPublish: post link should have the focus', async () => { @@ -42,12 +47,18 @@ describe( 'PostPublishPanel', () => { await page.type( '.editor-post-title__input', postTitle ); await publishPost(); - const focusedElementTag = await page.$eval( ':focus', ( focusedElement ) => { - return focusedElement.tagName.toLowerCase(); - } ); - const focusedElementText = await page.$eval( ':focus', ( focusedElement ) => { - return focusedElement.text; - } ); + const focusedElementTag = await page.$eval( + ':focus', + ( focusedElement ) => { + return focusedElement.tagName.toLowerCase(); + } + ); + const focusedElementText = await page.$eval( + ':focus', + ( focusedElement ) => { + return focusedElement.text; + } + ); expect( focusedElementTag ).toBe( 'a' ); expect( focusedElementText ).toBe( postTitle ); } ); @@ -57,9 +68,14 @@ describe( 'PostPublishPanel', () => { await openPublishPanel(); await pressKeyWithModifier( 'shift', 'Tab' ); - const focusedElementClassList = await page.$eval( ':focus', ( focusedElement ) => { - return Object.values( focusedElement.classList ); - } ); - expect( focusedElementClassList ).toContain( 'components-checkbox-control__input' ); + const focusedElementClassList = await page.$eval( + ':focus', + ( focusedElement ) => { + return Object.values( focusedElement.classList ); + } + ); + expect( focusedElementClassList ).toContain( + 'components-checkbox-control__input' + ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/publishing.test.js b/packages/e2e-tests/specs/editor/various/publishing.test.js index 64b8705ef8bab0..745008a0a15f1f 100644 --- a/packages/e2e-tests/specs/editor/various/publishing.test.js +++ b/packages/e2e-tests/specs/editor/various/publishing.test.js @@ -12,41 +12,40 @@ import { } from '@wordpress/e2e-test-utils'; describe( 'Publishing', () => { - describe.each( [ 'post', 'page' ] )( - 'a %s', - ( postType ) => { - let werePrePublishChecksEnabled; + describe.each( [ 'post', 'page' ] )( 'a %s', ( postType ) => { + let werePrePublishChecksEnabled; - beforeEach( async () => { - await createNewPost( postType ); - werePrePublishChecksEnabled = await arePrePublishChecksEnabled(); - if ( ! werePrePublishChecksEnabled ) { - await enablePrePublishChecks(); - } - } ); + beforeEach( async () => { + await createNewPost( postType ); + werePrePublishChecksEnabled = await arePrePublishChecksEnabled(); + if ( ! werePrePublishChecksEnabled ) { + await enablePrePublishChecks(); + } + } ); - afterEach( async () => { - if ( ! werePrePublishChecksEnabled ) { - await disablePrePublishChecks(); - } - } ); + afterEach( async () => { + if ( ! werePrePublishChecksEnabled ) { + await disablePrePublishChecks(); + } + } ); - it( `should publish the ${ postType } and close the panel once we start editing again.`, async () => { - await page.type( '.editor-post-title__input', 'E2E Test Post' ); + it( `should publish the ${ postType } and close the panel once we start editing again.`, async () => { + await page.type( '.editor-post-title__input', 'E2E Test Post' ); - await publishPost(); + await publishPost(); - // The post-publishing panel is visible. - expect( await page.$( '.editor-post-publish-panel' ) ).not.toBeNull(); + // The post-publishing panel is visible. + expect( + await page.$( '.editor-post-publish-panel' ) + ).not.toBeNull(); - // Start editing again. - await page.type( '.editor-post-title__input', ' (Updated)' ); + // Start editing again. + await page.type( '.editor-post-title__input', ' (Updated)' ); - // The post-publishing panel is not visible anymore. - expect( await page.$( '.editor-post-publish-panel' ) ).toBeNull(); - } ); - } - ); + // The post-publishing panel is not visible anymore. + expect( await page.$( '.editor-post-publish-panel' ) ).toBeNull(); + } ); + } ); describe.each( [ 'post', 'page' ] )( 'a %s with pre-publish checks disabled', @@ -71,13 +70,19 @@ describe( 'Publishing', () => { await page.type( '.editor-post-title__input', 'E2E Test Post' ); // The "Publish" button should be shown instead of the "Publish..." toggle - expect( await page.$( '.editor-post-publish-panel__toggle' ) ).toBeNull(); - expect( await page.$( '.editor-post-publish-button' ) ).not.toBeNull(); + expect( + await page.$( '.editor-post-publish-panel__toggle' ) + ).toBeNull(); + expect( + await page.$( '.editor-post-publish-button' ) + ).not.toBeNull(); await publishPostWithPrePublishChecksDisabled(); // The post-publishing panel should have been not shown. - expect( await page.$( '.editor-post-publish-panel' ) ).toBeNull(); + expect( + await page.$( '.editor-post-publish-panel' ) + ).toBeNull(); } ); } ); @@ -104,8 +109,12 @@ describe( 'Publishing', () => { } ); it( `should ignore the pre-publish checks and show the Publish... toggle instead of the Publish button`, async () => { - expect( await page.$( '.editor-post-publish-panel__toggle' ) ).not.toBeNull(); - expect( await page.$( '.editor-post-publish-button' ) ).toBeNull(); + expect( + await page.$( '.editor-post-publish-panel__toggle' ) + ).not.toBeNull(); + expect( + await page.$( '.editor-post-publish-button' ) + ).toBeNull(); } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js b/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js index 7f2e8a4b6ea03b..b453bbc0aeeeed 100644 --- a/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js @@ -37,7 +37,9 @@ describe( 'Reusable blocks', () => { await clickBlockToolbarButton( 'More options' ); - const convertButton = await page.waitForXPath( '//button[text()="Add to Reusable blocks"]' ); + const convertButton = await page.waitForXPath( + '//button[text()="Add to Reusable blocks"]' + ); await convertButton.click(); // Wait for creation to finish @@ -59,7 +61,9 @@ describe( 'Reusable blocks', () => { await page.waitForXPath( '//button[text()="Edit"]' ); // Check that we have a reusable block on the page - const block = await page.$( '.block-editor-block-list__block[data-type="core/block"]' ); + const block = await page.$( + '.block-editor-block-list__block[data-type="core/block"]' + ); expect( block ).not.toBeNull(); // Check that its title is displayed @@ -77,7 +81,9 @@ describe( 'Reusable blocks', () => { await clickBlockToolbarButton( 'More options' ); - const convertButton = await page.waitForXPath( '//button[text()="Add to Reusable blocks"]' ); + const convertButton = await page.waitForXPath( + '//button[text()="Add to Reusable blocks"]' + ); await convertButton.click(); // Wait for creation to finish @@ -93,7 +99,9 @@ describe( 'Reusable blocks', () => { await page.waitForXPath( '//button[text()="Edit"]' ); // Check that we have a reusable block on the page - const block = await page.$( '.block-editor-block-list__block[data-type="core/block"]' ); + const block = await page.$( + '.block-editor-block-list__block[data-type="core/block"]' + ); expect( block ).not.toBeNull(); // Check that it is untitled @@ -134,7 +142,9 @@ describe( 'Reusable blocks', () => { await page.waitForXPath( '//button[text()="Edit"]' ); // Check that we have a reusable block on the page - const block = await page.$( '.block-editor-block-list__block[data-type="core/block"]' ); + const block = await page.$( + '.block-editor-block-list__block[data-type="core/block"]' + ); expect( block ).not.toBeNull(); // Check that its title is displayed @@ -164,7 +174,9 @@ describe( 'Reusable blocks', () => { await convertButton.click(); // Check that we have a paragraph block on the page - const block = await page.$( '.block-editor-block-list__block[data-type="core/paragraph"]' ); + const block = await page.$( + '.block-editor-block-list__block[data-type="core/paragraph"]' + ); expect( block ).not.toBeNull(); // Check that its content is up to date @@ -181,7 +193,9 @@ describe( 'Reusable blocks', () => { // Delete the block and accept the confirmation dialog await clickBlockToolbarButton( 'More options' ); - const deleteButton = await page.waitForXPath( '//button[text()="Remove from Reusable blocks"]' ); + const deleteButton = await page.waitForXPath( + '//button[text()="Remove from Reusable blocks"]' + ); await Promise.all( [ waitForAndAcceptDialog(), deleteButton.click() ] ); // Wait for deletion to finish @@ -217,7 +231,9 @@ describe( 'Reusable blocks', () => { // Convert block to a reusable block await clickBlockToolbarButton( 'More options' ); - const convertButton = await page.waitForXPath( '//button[text()="Add to Reusable blocks"]' ); + const convertButton = await page.waitForXPath( + '//button[text()="Add to Reusable blocks"]' + ); await convertButton.click(); // Wait for creation to finish @@ -239,7 +255,9 @@ describe( 'Reusable blocks', () => { await page.waitForXPath( '//button[text()="Edit"]' ); // Check that we have a reusable block on the page - const block = await page.$( '.block-editor-block-list__block[data-type="core/block"]' ); + const block = await page.$( + '.block-editor-block-list__block[data-type="core/block"]' + ); expect( block ).not.toBeNull(); // Check that its title is displayed diff --git a/packages/e2e-tests/specs/editor/various/rich-text.test.js b/packages/e2e-tests/specs/editor/various/rich-text.test.js index 262f5c53cbb79f..cd3fa85e62fdcf 100644 --- a/packages/e2e-tests/specs/editor/various/rich-text.test.js +++ b/packages/e2e-tests/specs/editor/various/rich-text.test.js @@ -70,9 +70,11 @@ describe( 'RichText', () => { await pressKeyWithModifier( 'shift', 'ArrowLeft' ); await pressKeyWithModifier( 'primary', 'b' ); - const count = await page.evaluate( () => document.querySelectorAll( - '*[data-rich-text-format-boundary]' - ).length ); + const count = await page.evaluate( + () => + document.querySelectorAll( '*[data-rich-text-format-boundary]' ) + .length + ); expect( count ).toBe( 1 ); } ); @@ -182,21 +184,32 @@ describe( 'RichText', () => { window.unsubscribes = [ () => mutationObserver.disconnect() ]; - document.addEventListener( 'selectionchange', () => { - function throwMultipleSelectionChange() { - throw new Error( 'Typing should only emit one selection change event.' ); - } - - document.addEventListener( - 'selectionchange', - throwMultipleSelectionChange, - { once: true } - ); + document.addEventListener( + 'selectionchange', + () => { + function throwMultipleSelectionChange() { + throw new Error( + 'Typing should only emit one selection change event.' + ); + } - window.unsubscribes.push( () => { - document.removeEventListener( 'selectionchange', throwMultipleSelectionChange ); - } ); - }, { once: true } ); + document.addEventListener( + 'selectionchange', + throwMultipleSelectionChange, + { + once: true, + } + ); + + window.unsubscribes.push( () => { + document.removeEventListener( + 'selectionchange', + throwMultipleSelectionChange + ); + } ); + }, + { once: true } + ); } ); await page.keyboard.type( '4' ); @@ -206,7 +219,9 @@ describe( 'RichText', () => { // one item in `window.unsubscribes`, it means that only one // function is present to disconnect the `mutationObserver`. if ( window.unsubscribes.length === 1 ) { - throw new Error( 'The selection change event listener was never called.' ); + throw new Error( + 'The selection change event listener was never called.' + ); } window.unsubscribes.forEach( ( unsubscribe ) => unsubscribe() ); @@ -275,7 +290,9 @@ describe( 'RichText', () => { } ); // Wait for the next animation frame, see the focus event listener in // RichText. - await page.evaluate( () => new Promise( window.requestAnimationFrame ) ); + await page.evaluate( + () => new Promise( window.requestAnimationFrame ) + ); await pressKeyWithModifier( 'primary', 'b' ); await page.keyboard.type( '2' ); await pressKeyWithModifier( 'primary', 'b' ); diff --git a/packages/e2e-tests/specs/editor/various/rtl.test.js b/packages/e2e-tests/specs/editor/various/rtl.test.js index 76e6d15ff9aa30..c4fd3797cc2926 100644 --- a/packages/e2e-tests/specs/editor/various/rtl.test.js +++ b/packages/e2e-tests/specs/editor/various/rtl.test.js @@ -18,7 +18,7 @@ describe( 'RTL', () => { } ); it( 'should arrow navigate', async () => { - await page.evaluate( () => document.dir = 'rtl' ); + await page.evaluate( () => ( document.dir = 'rtl' ) ); await page.keyboard.press( 'Enter' ); // We need at least three characters as arrow navigation *from* the @@ -36,7 +36,7 @@ describe( 'RTL', () => { } ); it( 'should split', async () => { - await page.evaluate( () => document.dir = 'rtl' ); + await page.evaluate( () => ( document.dir = 'rtl' ) ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( ARABIC_ZERO ); @@ -48,7 +48,7 @@ describe( 'RTL', () => { } ); it( 'should merge backward', async () => { - await page.evaluate( () => document.dir = 'rtl' ); + await page.evaluate( () => ( document.dir = 'rtl' ) ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( ARABIC_ZERO ); @@ -61,7 +61,7 @@ describe( 'RTL', () => { } ); it( 'should merge forward', async () => { - await page.evaluate( () => document.dir = 'rtl' ); + await page.evaluate( () => ( document.dir = 'rtl' ) ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( ARABIC_ZERO ); @@ -75,7 +75,7 @@ describe( 'RTL', () => { } ); it( 'should arrow navigate between blocks', async () => { - await page.evaluate( () => document.dir = 'rtl' ); + await page.evaluate( () => ( document.dir = 'rtl' ) ); await page.keyboard.press( 'Enter' ); await page.keyboard.type( ARABIC_ZERO ); @@ -101,7 +101,7 @@ describe( 'RTL', () => { } ); it( 'should navigate inline boundaries', async () => { - await page.evaluate( () => document.dir = 'rtl' ); + await page.evaluate( () => ( document.dir = 'rtl' ) ); await page.keyboard.press( 'Enter' ); await pressKeyWithModifier( 'primary', 'b' ); diff --git a/packages/e2e-tests/specs/editor/various/scheduling.test.js b/packages/e2e-tests/specs/editor/various/scheduling.test.js index 3cd75b21d8e29b..7732f8d8a0e7ce 100644 --- a/packages/e2e-tests/specs/editor/various/scheduling.test.js +++ b/packages/e2e-tests/specs/editor/various/scheduling.test.js @@ -1,16 +1,16 @@ /** * WordPress dependencies */ -import { - createNewPost, -} from '@wordpress/e2e-test-utils'; +import { createNewPost } from '@wordpress/e2e-test-utils'; describe( 'Scheduling', () => { beforeEach( createNewPost ); const isDateTimeComponentFocused = () => { return page.evaluate( () => { - const dateTimeElement = document.querySelector( '.components-datetime__date' ); + const dateTimeElement = document.querySelector( + '.components-datetime__date' + ); if ( ! dateTimeElement || ! document.activeElement ) { return false; } @@ -20,9 +20,13 @@ describe( 'Scheduling', () => { it( 'Should keep date time UI focused when the previous and next month buttons are clicked', async () => { await page.click( '.edit-post-post-schedule__toggle' ); - await page.click( 'div[aria-label="Move backward to switch to the previous month."]' ); + await page.click( + 'div[aria-label="Move backward to switch to the previous month."]' + ); expect( await isDateTimeComponentFocused() ).toBe( true ); - await page.click( 'div[aria-label="Move forward to switch to the next month."]' ); + await page.click( + 'div[aria-label="Move forward to switch to the next month."]' + ); expect( await isDateTimeComponentFocused() ).toBe( true ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/shortcut-help.test.js b/packages/e2e-tests/specs/editor/various/shortcut-help.test.js index 23d51ef2c2d708..838a3edac2a2a4 100644 --- a/packages/e2e-tests/specs/editor/various/shortcut-help.test.js +++ b/packages/e2e-tests/specs/editor/various/shortcut-help.test.js @@ -15,25 +15,33 @@ describe( 'keyboard shortcut help modal', () => { it( 'displays the shortcut help modal when opened using the menu item in the more menu', async () => { await clickOnMoreMenuItem( 'Keyboard shortcuts' ); - const shortcutHelpModalElements = await page.$$( '.edit-post-keyboard-shortcut-help-modal' ); + const shortcutHelpModalElements = await page.$$( + '.edit-post-keyboard-shortcut-help-modal' + ); expect( shortcutHelpModalElements ).toHaveLength( 1 ); } ); it( 'closes the shortcut help modal when the close icon is clicked', async () => { await clickOnCloseModalButton(); - const shortcutHelpModalElements = await page.$$( '.edit-post-keyboard-shortcut-help-modal' ); + const shortcutHelpModalElements = await page.$$( + '.edit-post-keyboard-shortcut-help-modal' + ); expect( shortcutHelpModalElements ).toHaveLength( 0 ); } ); it( 'displays the shortcut help modal when opened using the shortcut key (access+h)', async () => { await pressKeyWithModifier( 'access', 'h' ); - const shortcutHelpModalElements = await page.$$( '.edit-post-keyboard-shortcut-help-modal' ); + const shortcutHelpModalElements = await page.$$( + '.edit-post-keyboard-shortcut-help-modal' + ); expect( shortcutHelpModalElements ).toHaveLength( 1 ); } ); it( 'closes the shortcut help modal when the shortcut key (access+h) is pressed again', async () => { await pressKeyWithModifier( 'access', 'h' ); - const shortcutHelpModalElements = await page.$$( '.edit-post-keyboard-shortcut-help-modal' ); + const shortcutHelpModalElements = await page.$$( + '.edit-post-keyboard-shortcut-help-modal' + ); expect( shortcutHelpModalElements ).toHaveLength( 0 ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/sidebar-permalink-panel.test.js b/packages/e2e-tests/specs/editor/various/sidebar-permalink-panel.test.js index 16de106f570e67..33a58782fa64b6 100644 --- a/packages/e2e-tests/specs/editor/various/sidebar-permalink-panel.test.js +++ b/packages/e2e-tests/specs/editor/various/sidebar-permalink-panel.test.js @@ -26,7 +26,9 @@ describe( 'Sidebar Permalink Panel', () => { it( 'should not render permalink sidebar panel while the post is new', async () => { await createNewPost(); await openDocumentSettingsSidebar(); - expect( await findSidebarPanelWithTitle( 'Permalink' ) ).toBeUndefined(); + expect( + await findSidebarPanelWithTitle( 'Permalink' ) + ).toBeUndefined(); } ); it( 'should render permalink sidebar panel after the post is published and allow its removal', async () => { @@ -40,7 +42,9 @@ describe( 'Sidebar Permalink Panel', () => { const { removeEditorPanel } = wp.data.dispatch( 'core/edit-post' ); removeEditorPanel( 'post-link' ); } ); - expect( await findSidebarPanelWithTitle( 'Permalink' ) ).toBeUndefined(); + expect( + await findSidebarPanelWithTitle( 'Permalink' ) + ).toBeUndefined(); } ); it( 'should not render link panel when post is publicly queryable but not public', async () => { @@ -49,7 +53,9 @@ describe( 'Sidebar Permalink Panel', () => { await publishPost(); // Start editing again. await page.type( '.editor-post-title__input', ' (Updated)' ); - expect( await findSidebarPanelWithTitle( 'Permalink' ) ).toBeUndefined(); + expect( + await findSidebarPanelWithTitle( 'Permalink' ) + ).toBeUndefined(); } ); it( 'should not render link panel when post is public but not publicly queryable', async () => { @@ -58,7 +64,9 @@ describe( 'Sidebar Permalink Panel', () => { await publishPost(); // Start editing again. await page.type( '.editor-post-title__input', ' (Updated)' ); - expect( await findSidebarPanelWithTitle( 'Permalink' ) ).toBeUndefined(); + expect( + await findSidebarPanelWithTitle( 'Permalink' ) + ).toBeUndefined(); } ); it( 'should render link panel when post is public and publicly queryable', async () => { diff --git a/packages/e2e-tests/specs/editor/various/sidebar.test.js b/packages/e2e-tests/specs/editor/various/sidebar.test.js index 6c61f822ef1890..ab2043f17b887a 100644 --- a/packages/e2e-tests/specs/editor/various/sidebar.test.js +++ b/packages/e2e-tests/specs/editor/various/sidebar.test.js @@ -26,15 +26,18 @@ describe( 'Sidebar', () => { await clearLocalStorage(); await createNewPost(); await enableFocusLossObservation(); - const { nodesCount, content, height, width } = await page.$$eval( ACTIVE_SIDEBAR_TAB_SELECTOR, ( nodes ) => { - const firstNode = nodes[ 0 ]; - return { - nodesCount: nodes.length, - content: firstNode.innerText, - height: firstNode.offsetHeight, - width: firstNode.offsetWidth, - }; - } ); + const { nodesCount, content, height, width } = await page.$$eval( + ACTIVE_SIDEBAR_TAB_SELECTOR, + ( nodes ) => { + const firstNode = nodes[ 0 ]; + return { + nodesCount: nodes.length, + content: firstNode.innerText, + height: firstNode.offsetHeight, + width: firstNode.offsetWidth, + }; + } + ); // should have only one active sidebar tab. expect( nodesCount ).toBe( 1 ); @@ -99,19 +102,21 @@ describe( 'Sidebar', () => { // Tab lands at first (presumed selected) option "Document". await page.keyboard.press( 'Tab' ); - const isActiveDocumentTab = await page.evaluate( () => ( - document.activeElement.textContent === 'Document' && - document.activeElement.classList.contains( 'is-active' ) - ) ); + const isActiveDocumentTab = await page.evaluate( + () => + document.activeElement.textContent === 'Document' && + document.activeElement.classList.contains( 'is-active' ) + ); expect( isActiveDocumentTab ).toBe( true ); // Tab into and activate "Block". await page.keyboard.press( 'Tab' ); await page.keyboard.press( 'Space' ); - const isActiveBlockTab = await page.evaluate( () => ( - document.activeElement.textContent === 'Block' && - document.activeElement.classList.contains( 'is-active' ) - ) ); + const isActiveBlockTab = await page.evaluate( + () => + document.activeElement.textContent === 'Block' && + document.activeElement.classList.contains( 'is-active' ) + ); expect( isActiveBlockTab ).toBe( true ); } ); @@ -123,10 +128,14 @@ describe( 'Sidebar', () => { expect( await findSidebarPanelWithTitle( 'Categories' ) ).toBeDefined(); expect( await findSidebarPanelWithTitle( 'Tags' ) ).toBeDefined(); - expect( await findSidebarPanelWithTitle( 'Featured image' ) ).toBeDefined(); + expect( + await findSidebarPanelWithTitle( 'Featured image' ) + ).toBeDefined(); expect( await findSidebarPanelWithTitle( 'Excerpt' ) ).toBeDefined(); expect( await findSidebarPanelWithTitle( 'Discussion' ) ).toBeDefined(); - expect( await findSidebarPanelWithTitle( 'Status & visibility' ) ).toBeDefined(); + expect( + await findSidebarPanelWithTitle( 'Status & visibility' ) + ).toBeDefined(); await page.evaluate( () => { const { removeEditorPanel } = wp.data.dispatch( 'core/edit-post' ); @@ -139,11 +148,19 @@ describe( 'Sidebar', () => { removeEditorPanel( 'post-status' ); } ); - expect( await findSidebarPanelWithTitle( 'Categories' ) ).toBeUndefined(); + expect( + await findSidebarPanelWithTitle( 'Categories' ) + ).toBeUndefined(); expect( await findSidebarPanelWithTitle( 'Tags' ) ).toBeUndefined(); - expect( await findSidebarPanelWithTitle( 'Featured image' ) ).toBeUndefined(); + expect( + await findSidebarPanelWithTitle( 'Featured image' ) + ).toBeUndefined(); expect( await findSidebarPanelWithTitle( 'Excerpt' ) ).toBeUndefined(); - expect( await findSidebarPanelWithTitle( 'Discussion' ) ).toBeUndefined(); - expect( await findSidebarPanelWithTitle( 'Status & visibility' ) ).toBeUndefined(); + expect( + await findSidebarPanelWithTitle( 'Discussion' ) + ).toBeUndefined(); + expect( + await findSidebarPanelWithTitle( 'Status & visibility' ) + ).toBeUndefined(); } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/splitting-merging.test.js b/packages/e2e-tests/specs/editor/various/splitting-merging.test.js index a394317c5dd752..05e08db82afce6 100644 --- a/packages/e2e-tests/specs/editor/various/splitting-merging.test.js +++ b/packages/e2e-tests/specs/editor/various/splitting-merging.test.js @@ -194,7 +194,9 @@ describe( 'splitting and merging blocks', () => { await page.keyboard.press( 'Backspace' ); // There is a default block: - expect( await page.$$( '.block-editor-block-list__block' ) ).toHaveLength( 1 ); + expect( + await page.$$( '.block-editor-block-list__block' ) + ).toHaveLength( 1 ); // But the effective saved content is still empty: expect( await getEditedPostContent() ).toBe( '' ); diff --git a/packages/e2e-tests/specs/editor/various/style-variation.test.js b/packages/e2e-tests/specs/editor/various/style-variation.test.js index 7b073fe7b3f26d..9d070c0d95b43c 100644 --- a/packages/e2e-tests/specs/editor/various/style-variation.test.js +++ b/packages/e2e-tests/specs/editor/various/style-variation.test.js @@ -20,7 +20,9 @@ describe( 'adding blocks', () => { await clickBlockToolbarButton( 'Change block type or style' ); - const styleVariations = await page.$$( '.block-editor-block-styles__item' ); + const styleVariations = await page.$$( + '.block-editor-block-styles__item' + ); await styleVariations[ 1 ].click(); // Check the content diff --git a/packages/e2e-tests/specs/editor/various/taxonomies.test.js b/packages/e2e-tests/specs/editor/various/taxonomies.test.js index f68343dcb2866f..45ac6cbc1e3203 100644 --- a/packages/e2e-tests/specs/editor/various/taxonomies.test.js +++ b/packages/e2e-tests/specs/editor/various/taxonomies.test.js @@ -16,43 +16,45 @@ import { /** * Module constants */ -const TAG_TOKEN_SELECTOR = '.components-form-token-field__token-text span:not(.components-visually-hidden)'; +const TAG_TOKEN_SELECTOR = + '.components-form-token-field__token-text span:not(.components-visually-hidden)'; describe( 'Taxonomies', () => { const canCreatTermInTaxonomy = ( taxonomy ) => { - return page.evaluate( - ( _taxonomy ) => { - const post = wp.data.select( 'core/editor' ).getCurrentPost(); - if ( ! post._links ) { - return false; - } - return !! post._links[ `wp:action-create-${ _taxonomy }` ]; - }, - taxonomy - ); + return page.evaluate( ( _taxonomy ) => { + const post = wp.data.select( 'core/editor' ).getCurrentPost(); + if ( ! post._links ) { + return false; + } + return !! post._links[ `wp:action-create-${ _taxonomy }` ]; + }, taxonomy ); }; const getSelectCategories = () => { - return page.evaluate( - () => { - return Array.from( document.querySelectorAll( + return page.evaluate( () => { + return Array.from( + document.querySelectorAll( '.editor-post-taxonomies__hierarchical-terms-choice .components-checkbox-control__input:checked' - ) ).map( ( node ) => { - return node.parentElement.nextSibling.innerText; - } ); - } - ); + ) + ).map( ( node ) => { + return node.parentElement.nextSibling.innerText; + } ); + } ); }; const getCurrentTags = async () => { const tagsPanel = await findSidebarPanelWithTitle( 'Tags' ); - return page.evaluate( ( node, selector ) => { - return Array.from( node.querySelectorAll( - selector - ) ).map( ( field ) => { - return field.innerText; - } ); - }, tagsPanel, TAG_TOKEN_SELECTOR ); + return page.evaluate( + ( node, selector ) => { + return Array.from( node.querySelectorAll( selector ) ).map( + ( field ) => { + return field.innerText; + } + ); + }, + tagsPanel, + TAG_TOKEN_SELECTOR + ); }; const openSidebarPanelWithTitle = async ( title ) => { @@ -74,10 +76,14 @@ describe( 'Taxonomies', () => { return; } - await page.waitForSelector( 'button.editor-post-taxonomies__hierarchical-terms-add' ); + await page.waitForSelector( + 'button.editor-post-taxonomies__hierarchical-terms-add' + ); // Click add new category button. - await page.click( 'button.editor-post-taxonomies__hierarchical-terms-add' ); + await page.click( + 'button.editor-post-taxonomies__hierarchical-terms-add' + ); // Type the category name in the field. await page.type( @@ -86,10 +92,14 @@ describe( 'Taxonomies', () => { ); // Click the submit button. - await page.click( '.editor-post-taxonomies__hierarchical-terms-submit' ); + await page.click( + '.editor-post-taxonomies__hierarchical-terms-submit' + ); // Wait for the categories to load. - await page.waitForSelector( '.editor-post-taxonomies__hierarchical-terms-choice .components-checkbox-control__input:checked' ); + await page.waitForSelector( + '.editor-post-taxonomies__hierarchical-terms-choice .components-checkbox-control__input:checked' + ); let selectedCategories = await getSelectCategories(); @@ -107,7 +117,9 @@ describe( 'Taxonomies', () => { await page.reload(); // Wait for the categories to load. - await page.waitForSelector( '.editor-post-taxonomies__hierarchical-terms-choice .components-checkbox-control__input:checked' ); + await page.waitForSelector( + '.editor-post-taxonomies__hierarchical-terms-choice .components-checkbox-control__input:checked' + ); selectedCategories = await getSelectCategories(); @@ -129,14 +141,12 @@ describe( 'Taxonomies', () => { } // At the start there are no tag tokens - expect( - await page.$$( - TAG_TOKEN_SELECTOR - ) - ).toHaveLength( 0 ); + expect( await page.$$( TAG_TOKEN_SELECTOR ) ).toHaveLength( 0 ); const tagsPanel = await findSidebarPanelWithTitle( 'Tags' ); - const tagInput = await tagsPanel.$( '.components-form-token-field__input' ); + const tagInput = await tagsPanel.$( + '.components-form-token-field__input' + ); // Click the tag input field. await tagInput.click(); diff --git a/packages/e2e-tests/specs/editor/various/typewriter.test.js b/packages/e2e-tests/specs/editor/various/typewriter.test.js index 98d54e17da1b7a..2661d69acd9055 100644 --- a/packages/e2e-tests/specs/editor/various/typewriter.test.js +++ b/packages/e2e-tests/specs/editor/various/typewriter.test.js @@ -15,7 +15,7 @@ describe( 'TypeWriter', () => { const BUFFER = 1; const getDiff = async ( caretPosition ) => - Math.abs( await getCaretPosition() - caretPosition ); + Math.abs( ( await getCaretPosition() ) - caretPosition ); it( 'should maintain caret position', async () => { // Create first block. @@ -29,9 +29,13 @@ describe( 'TypeWriter', () => { expect( await getCaretPosition() ).toBeGreaterThan( initialPosition ); // Create blocks until the the typewriter effect kicks in. - while ( await page.evaluate( () => - wp.dom.getScrollContainer( document.activeElement ).scrollTop === 0 - ) ) { + while ( + await page.evaluate( + () => + wp.dom.getScrollContainer( document.activeElement ) + .scrollTop === 0 + ) + ) { await page.keyboard.press( 'Enter' ); } @@ -43,10 +47,16 @@ describe( 'TypeWriter', () => { expect( await getDiff( newPosition ) ).toBeLessThanOrEqual( BUFFER ); // Type until the text wraps. - while ( await page.evaluate( () => - document.activeElement.clientHeight <= - parseInt( getComputedStyle( document.activeElement ).lineHeight, 10 ) - ) ) { + while ( + await page.evaluate( + () => + document.activeElement.clientHeight <= + parseInt( + getComputedStyle( document.activeElement ).lineHeight, + 10 + ) + ) + ) { await page.keyboard.type( 'a' ); } @@ -68,7 +78,9 @@ describe( 'TypeWriter', () => { // Should be scrolled to new position. await page.keyboard.press( 'Enter' ); - expect( await getDiff( positionAfterArrowUp ) ).toBeLessThanOrEqual( BUFFER ); + expect( await getDiff( positionAfterArrowUp ) ).toBeLessThanOrEqual( + BUFFER + ); } ); it( 'should maintain caret position after scroll', async () => { @@ -76,14 +88,19 @@ describe( 'TypeWriter', () => { await page.keyboard.press( 'Enter' ); // Create blocks until there is a scrollable container. - while ( await page.evaluate( () => - ! wp.dom.getScrollContainer( document.activeElement ) - ) ) { + while ( + await page.evaluate( + () => ! wp.dom.getScrollContainer( document.activeElement ) + ) + ) { await page.keyboard.press( 'Enter' ); } - await page.evaluate( () => - wp.dom.getScrollContainer( document.activeElement ).scrollTop = 1 + await page.evaluate( + () => + ( wp.dom.getScrollContainer( + document.activeElement + ).scrollTop = 1 ) ); const initialPosition = await getCaretPosition(); @@ -91,7 +108,9 @@ describe( 'TypeWriter', () => { // Should maintain scroll position. await page.keyboard.press( 'Enter' ); - expect( await getDiff( initialPosition ) ).toBeLessThanOrEqual( BUFFER ); + expect( await getDiff( initialPosition ) ).toBeLessThanOrEqual( + BUFFER + ); } ); it( 'should maintain caret position after leaving last editable', async () => { @@ -107,7 +126,9 @@ describe( 'TypeWriter', () => { // Should maintain scroll position. await page.keyboard.press( 'Enter' ); - expect( await getDiff( initialPosition ) ).toBeLessThanOrEqual( BUFFER ); + expect( await getDiff( initialPosition ) ).toBeLessThanOrEqual( + BUFFER + ); } ); it( 'should scroll caret into view from the top', async () => { @@ -115,18 +136,24 @@ describe( 'TypeWriter', () => { await page.keyboard.press( 'Enter' ); // Create blocks until there is a scrollable container. - while ( await page.evaluate( () => - ! wp.dom.getScrollContainer( document.activeElement ) - ) ) { + while ( + await page.evaluate( + () => ! wp.dom.getScrollContainer( document.activeElement ) + ) + ) { await page.keyboard.press( 'Enter' ); } let count = 0; // Create blocks until the the typewriter effect kicks in. - while ( await page.evaluate( () => - wp.dom.getScrollContainer( document.activeElement ).scrollTop === 0 - ) ) { + while ( + await page.evaluate( + () => + wp.dom.getScrollContainer( document.activeElement ) + .scrollTop === 0 + ) + ) { await page.keyboard.press( 'Enter' ); count++; } @@ -150,7 +177,9 @@ describe( 'TypeWriter', () => { // Should maintain new caret position. await page.keyboard.press( 'Enter' ); - expect( await getDiff( newBottomPosition ) ).toBeLessThanOrEqual( BUFFER ); + expect( await getDiff( newBottomPosition ) ).toBeLessThanOrEqual( + BUFFER + ); await page.keyboard.press( 'Backspace' ); diff --git a/packages/e2e-tests/specs/editor/various/undo.test.js b/packages/e2e-tests/specs/editor/various/undo.test.js index 7c1a2602d7bfe3..ed70af98bdaa2a 100644 --- a/packages/e2e-tests/specs/editor/various/undo.test.js +++ b/packages/e2e-tests/specs/editor/various/undo.test.js @@ -22,7 +22,9 @@ const getSelection = async () => { return {}; } - const editables = Array.from( selectedBlock.querySelectorAll( '[contenteditable]' ) ); + const editables = Array.from( + selectedBlock.querySelectorAll( '[contenteditable]' ) + ); const editableIndex = editables.indexOf( document.activeElement ); const selection = window.getSelection(); @@ -169,7 +171,9 @@ describe( 'undo', () => { await pressKeyWithModifier( 'primary', 'b' ); await pressKeyWithModifier( 'primary', 'z' ); - const visibleResult = await page.evaluate( () => document.activeElement.innerHTML ); + const visibleResult = await page.evaluate( + () => document.activeElement.innerHTML + ); expect( visibleResult ).toBe( 'test' ); } ); @@ -253,7 +257,9 @@ describe( 'undo', () => { expect( await getEditedPostContent() ).toBe( '' ); expect( await getSelection() ).toEqual( {} ); // After undoing every action, there should be no more undo history. - expect( await page.$( '.editor-history__undo[aria-disabled="true"]' ) ).not.toBeNull(); + expect( + await page.$( '.editor-history__undo[aria-disabled="true"]' ) + ).not.toBeNull(); await pressKeyWithModifier( 'primaryShift', 'z' ); // Redo 1st block. @@ -265,7 +271,9 @@ describe( 'undo', () => { endOffset: 0, } ); // After redoing one change, the undo button should be enabled again. - expect( await page.$( '.editor-history__undo[aria-disabled="true"]' ) ).toBeNull(); + expect( + await page.$( '.editor-history__undo[aria-disabled="true"]' ) + ).toBeNull(); await pressKeyWithModifier( 'primaryShift', 'z' ); // Redo 1st paragraph text. @@ -346,7 +354,9 @@ describe( 'undo', () => { // regression present was accurate, it would produce the correct // content. The issue had manifested in the form of what was shown to // the user since the blocks state failed to sync to block editor. - const visibleContent = await page.evaluate( () => document.activeElement.textContent ); + const visibleContent = await page.evaluate( + () => document.activeElement.textContent + ); expect( visibleContent ).toBe( 'original' ); } ); @@ -376,14 +386,18 @@ describe( 'undo', () => { await page.reload(); // Expect undo button to be disabled. - expect( await page.$( '.editor-history__undo[aria-disabled="true"]' ) ).not.toBeNull(); + expect( + await page.$( '.editor-history__undo[aria-disabled="true"]' ) + ).not.toBeNull(); await page.click( '.wp-block-paragraph' ); await page.keyboard.type( '2' ); // Expect undo button to be enabled. - expect( await page.$( '.editor-history__undo[aria-disabled="true"]' ) ).toBeNull(); + expect( + await page.$( '.editor-history__undo[aria-disabled="true"]' ) + ).toBeNull(); await pressKeyWithModifier( 'primary', 'z' ); diff --git a/packages/e2e-tests/specs/editor/various/writing-flow.test.js b/packages/e2e-tests/specs/editor/various/writing-flow.test.js index e08e4a9d7ce871..d06176cc7de460 100644 --- a/packages/e2e-tests/specs/editor/various/writing-flow.test.js +++ b/packages/e2e-tests/specs/editor/various/writing-flow.test.js @@ -10,7 +10,10 @@ import { insertBlock, } from '@wordpress/e2e-test-utils'; -const getActiveBlockName = async () => page.evaluate( () => wp.data.select( 'core/block-editor' ).getSelectedBlock().name ); +const getActiveBlockName = async () => + page.evaluate( + () => wp.data.select( 'core/block-editor' ).getSelectedBlock().name + ); describe( 'Writing Flow', () => { beforeEach( async () => { @@ -62,7 +65,9 @@ describe( 'Writing Flow', () => { await page.keyboard.press( 'ArrowUp' ); activeBlockName = await getActiveBlockName(); expect( activeBlockName ).toBe( 'core/paragraph' ); - activeElementText = await page.evaluate( () => document.activeElement.textContent ); + activeElementText = await page.evaluate( + () => document.activeElement.textContent + ); expect( activeElementText ).toBe( '2nd col' ); // Arrow up in inner blocks should navigate through (1) column wrapper, @@ -73,23 +78,25 @@ describe( 'Writing Flow', () => { await page.keyboard.press( 'ArrowUp' ); activeBlockName = await getActiveBlockName(); expect( activeBlockName ).toBe( 'core/paragraph' ); - activeElementText = await page.evaluate( () => document.activeElement.textContent ); + activeElementText = await page.evaluate( + () => document.activeElement.textContent + ); expect( activeElementText ).toBe( '1st col' ); // Arrow up from first text field in nested context focuses column and // columns wrappers before escaping out. let activeElementBlockType; await page.keyboard.press( 'ArrowUp' ); - activeElementBlockType = await page.evaluate( () => ( + activeElementBlockType = await page.evaluate( () => document.activeElement.getAttribute( 'data-type' ) - ) ); + ); expect( activeElementBlockType ).toBe( 'core/column' ); activeBlockName = await getActiveBlockName(); expect( activeBlockName ).toBe( 'core/column' ); await page.keyboard.press( 'ArrowUp' ); - activeElementBlockType = await page.evaluate( () => ( + activeElementBlockType = await page.evaluate( () => document.activeElement.getAttribute( 'data-type' ) - ) ); + ); expect( activeElementBlockType ).toBe( 'core/columns' ); activeBlockName = await getActiveBlockName(); expect( activeBlockName ).toBe( 'core/columns' ); @@ -99,7 +106,9 @@ describe( 'Writing Flow', () => { await page.keyboard.press( 'ArrowUp' ); activeBlockName = await getActiveBlockName(); expect( activeBlockName ).toBe( 'core/paragraph' ); - activeElementText = await page.evaluate( () => document.activeElement.textContent ); + activeElementText = await page.evaluate( + () => document.activeElement.textContent + ); expect( activeElementText ).toBe( 'First paragraph' ); expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -256,23 +265,23 @@ describe( 'Writing Flow', () => { // Should remain in title upon ArrowRight: await page.keyboard.press( 'ArrowRight' ); - let isInTitle = await page.evaluate( () => ( - !! document.activeElement.closest( '.editor-post-title' ) - ) ); + let isInTitle = await page.evaluate( + () => !! document.activeElement.closest( '.editor-post-title' ) + ); expect( isInTitle ).toBe( true ); // Should remain in title upon modifier + ArrowDown: await pressKeyWithModifier( 'primary', 'ArrowDown' ); - isInTitle = await page.evaluate( () => ( - !! document.activeElement.closest( '.editor-post-title' ) - ) ); + isInTitle = await page.evaluate( + () => !! document.activeElement.closest( '.editor-post-title' ) + ); expect( isInTitle ).toBe( true ); // Should navigate into blocks list upon ArrowDown: await page.keyboard.press( 'ArrowDown' ); - const isInBlock = await page.evaluate( () => ( - !! document.activeElement.closest( '[data-type]' ) - ) ); + const isInBlock = await page.evaluate( + () => !! document.activeElement.closest( '[data-type]' ) + ); expect( isInBlock ).toBe( true ); } ); @@ -469,15 +478,19 @@ describe( 'Writing Flow', () => { // expected that the sibling inserter would be placed. const paragraph = await page.$( '[data-type="core/paragraph"]' ); const paragraphRect = await paragraph.boundingBox(); - const x = paragraphRect.x + ( 2 * paragraphRect.width / 3 ); + const x = paragraphRect.x + ( 2 * paragraphRect.width ) / 3; const y = paragraphRect.y + paragraphRect.height + 1; await page.mouse.move( x, y ); - await page.waitForSelector( '.block-editor-block-list__insertion-point-inserter' ); + await page.waitForSelector( + '.block-editor-block-list__insertion-point-inserter' + ); - const inserter = await page.$( '.block-editor-block-list__insertion-point-inserter' ); + const inserter = await page.$( + '.block-editor-block-list__insertion-point-inserter' + ); const inserterRect = await inserter.boundingBox(); - const lowerInserterY = inserterRect.y + ( 2 * inserterRect.height / 3 ); + const lowerInserterY = inserterRect.y + ( 2 * inserterRect.height ) / 3; await page.mouse.click( x, lowerInserterY ); await page.keyboard.type( '3' ); @@ -495,15 +508,19 @@ describe( 'Writing Flow', () => { // expected that the sibling inserter would be placed. const paragraph = await page.$( '[data-type="core/paragraph"]' ); const paragraphRect = await paragraph.boundingBox(); - const x = paragraphRect.x + ( 2 * paragraphRect.width / 3 ); + const x = paragraphRect.x + ( 2 * paragraphRect.width ) / 3; const y = paragraphRect.y + paragraphRect.height + 1; await page.mouse.move( x, y ); - await page.waitForSelector( '.block-editor-block-list__insertion-point-inserter' ); + await page.waitForSelector( + '.block-editor-block-list__insertion-point-inserter' + ); - const inserter = await page.$( '.block-editor-block-list__insertion-point-inserter' ); + const inserter = await page.$( + '.block-editor-block-list__insertion-point-inserter' + ); const inserterRect = await inserter.boundingBox(); - const upperInserterY = inserterRect.y + ( inserterRect.height / 3 ); + const upperInserterY = inserterRect.y + inserterRect.height / 3; await page.mouse.click( x, upperInserterY ); await page.keyboard.type( '3' ); diff --git a/packages/e2e-tests/specs/performance/performance.test.js b/packages/e2e-tests/specs/performance/performance.test.js index 7f402dad605ca6..0bdc172682fafc 100644 --- a/packages/e2e-tests/specs/performance/performance.test.js +++ b/packages/e2e-tests/specs/performance/performance.test.js @@ -14,12 +14,16 @@ import { } from '@wordpress/e2e-test-utils'; function readFile( filePath ) { - return existsSync( filePath ) ? readFileSync( filePath, 'utf8' ).trim() : ''; + return existsSync( filePath ) + ? readFileSync( filePath, 'utf8' ).trim() + : ''; } describe( 'Performance', () => { it( '1000 paragraphs', async () => { - const html = readFile( join( __dirname, '../../assets/large-post.html' ) ); + const html = readFile( + join( __dirname, '../../assets/large-post.html' ) + ); await createNewPost(); await page.evaluate( ( _html ) => { @@ -47,8 +51,12 @@ describe( 'Performance', () => { let i = 1; let startTime; - await page.on( 'load', () => results.load.push( new Date() - startTime ) ); - await page.on( 'domcontentloaded', () => results.domcontentloaded.push( new Date() - startTime ) ); + await page.on( 'load', () => + results.load.push( new Date() - startTime ) + ); + await page.on( 'domcontentloaded', () => + results.domcontentloaded.push( new Date() - startTime ) + ); while ( i-- ) { startTime = new Date(); @@ -65,7 +73,10 @@ describe( 'Performance', () => { results.type.push( new Date() - startTime ); } - writeFileSync( __dirname + '/results.json', JSON.stringify( results, null, 2 ) ); + writeFileSync( + __dirname + '/results.json', + JSON.stringify( results, null, 2 ) + ); expect( true ).toBe( true ); } ); diff --git a/packages/e2e-tests/specs/plugins/inner-blocks-render-appender.test.js b/packages/e2e-tests/specs/plugins/inner-blocks-render-appender.test.js index fd5bec2955b116..ad27838a4a8ed1 100644 --- a/packages/e2e-tests/specs/plugins/inner-blocks-render-appender.test.js +++ b/packages/e2e-tests/specs/plugins/inner-blocks-render-appender.test.js @@ -37,26 +37,28 @@ describe( 'RenderAppender prop of InnerBlocks ', () => { // Verify if the custom block appender text is the expected one. expect( await page.evaluate( - ( el ) => ( el.innerText ), + ( el ) => el.innerText, await page.$( `${ APPENDER_SELECTOR } > span` ) ) ).toEqual( 'My custom awesome appender' ); // Open the inserter of our custom block appender and expand all the categories. - await page.click( `${ APPENDER_SELECTOR } .block-editor-button-block-appender` ); + await page.click( + `${ APPENDER_SELECTOR } .block-editor-button-block-appender` + ); await openAllBlockInserterCategories(); // Verify if the blocks the custom inserter is rendering are the expected ones. - expect( - await getAllBlockInserterItemTitles() - ).toEqual( [ + expect( await getAllBlockInserterItemTitles() ).toEqual( [ 'Quote', 'Video', ] ); // Find the quote block insert button option within the inserter popover. const inserterPopover = await page.$( INSERTER_RESULTS_SELECTOR ); - const quoteButton = ( await inserterPopover.$x( QUOTE_INSERT_BUTTON_SELECTOR ) )[ 0 ]; + const quoteButton = ( + await inserterPopover.$x( QUOTE_INSERT_BUTTON_SELECTOR ) + )[ 0 ]; // Insert a quote block. await quoteButton.click(); @@ -82,16 +84,16 @@ describe( 'RenderAppender prop of InnerBlocks ', () => { await openAllBlockInserterCategories(); // Verify if the blocks the custom inserter is rendering are the expected ones. - expect( - await getAllBlockInserterItemTitles() - ).toEqual( [ + expect( await getAllBlockInserterItemTitles() ).toEqual( [ 'Quote', 'Video', ] ); // Find the quote block insert button option within the inserter popover. const inserterPopover = await page.$( INSERTER_RESULTS_SELECTOR ); - const quoteButton = ( await inserterPopover.$x( QUOTE_INSERT_BUTTON_SELECTOR ) )[ 0 ]; + const quoteButton = ( + await inserterPopover.$x( QUOTE_INSERT_BUTTON_SELECTOR ) + )[ 0 ]; // Insert a quote block. await quoteButton.click(); @@ -102,9 +104,7 @@ describe( 'RenderAppender prop of InnerBlocks ', () => { ); // Verify that the custom appender button is still being rendered. - expect( - await page.$( blockAppenderButtonSelector ) - ).toBeTruthy(); + expect( await page.$( blockAppenderButtonSelector ) ).toBeTruthy(); // Insert a video block. await insertBlock( 'Video' ); @@ -115,9 +115,7 @@ describe( 'RenderAppender prop of InnerBlocks ', () => { ); // Verify that the custom appender button is now not being rendered. - expect( - await page.$( blockAppenderButtonSelector ) - ).toBeFalsy(); + expect( await page.$( blockAppenderButtonSelector ) ).toBeFalsy(); // Verify that final block markup is the expected one. expect( await getEditedPostContent() ).toMatchSnapshot(); diff --git a/packages/edit-post/src/components/admin-notices/index.js b/packages/edit-post/src/components/admin-notices/index.js index d39c74c34a0620..a84d1e2e4388de 100644 --- a/packages/edit-post/src/components/admin-notices/index.js +++ b/packages/edit-post/src/components/admin-notices/index.js @@ -27,7 +27,9 @@ const NOTICE_CLASS_STATUSES = { function getAdminNotices() { // The order is reversed to match expectations of rendered order, since a // NoticesList is itself rendered in reverse order (newest to oldest). - return Array.from( document.querySelectorAll( '#wpbody-content > .notice' ) ).reverse(); + return Array.from( + document.querySelectorAll( '#wpbody-content > .notice' ) + ).reverse(); } /** @@ -81,7 +83,9 @@ export class AdminNotices extends Component { // Convert and create. const status = getNoticeStatus( element ); const content = getNoticeHTML( element ); - const isDismissible = element.classList.contains( 'is-dismissible' ); + const isDismissible = element.classList.contains( + 'is-dismissible' + ); createNotice( status, content, { speak: false, __unstableHTML: true, diff --git a/packages/edit-post/src/components/admin-notices/test/index.js b/packages/edit-post/src/components/admin-notices/test/index.js index 1dcda9a00a2755..792fe785b78ae0 100644 --- a/packages/edit-post/src/components/admin-notices/test/index.js +++ b/packages/edit-post/src/components/admin-notices/test/index.js @@ -56,6 +56,8 @@ describe( 'AdminNotices', () => { ] ); // Verify all but `<aside>` are removed. - expect( document.getElementById( 'wpbody-content' ).childElementCount ).toBe( 1 ); + expect( + document.getElementById( 'wpbody-content' ).childElementCount + ).toBe( 1 ); } ); } ); diff --git a/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-group.js b/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-group.js index 435f912ce03a88..02865868864a39 100644 --- a/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-group.js +++ b/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-group.js @@ -9,24 +9,32 @@ import { isEmpty, map } from 'lodash'; import { createSlotFill } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; -const { Fill: PluginBlockSettingsMenuGroup, Slot } = createSlotFill( 'PluginBlockSettingsMenuGroup' ); +const { Fill: PluginBlockSettingsMenuGroup, Slot } = createSlotFill( + 'PluginBlockSettingsMenuGroup' +); const PluginBlockSettingsMenuGroupSlot = ( { fillProps, selectedBlocks } ) => { selectedBlocks = map( selectedBlocks, ( block ) => block.name ); return ( - <Slot fillProps={ { ...fillProps, selectedBlocks } } > - { ( fills ) => ! isEmpty( fills ) && ( - <> - <div className="block-editor-block-settings-menu__separator" /> - { fills } - </> - ) } + <Slot fillProps={ { ...fillProps, selectedBlocks } }> + { ( fills ) => + ! isEmpty( fills ) && ( + <> + <div className="block-editor-block-settings-menu__separator" /> + { fills } + </> + ) + } </Slot> ); }; -PluginBlockSettingsMenuGroup.Slot = withSelect( ( select, { fillProps: { clientIds } } ) => ( { - selectedBlocks: select( 'core/block-editor' ).getBlocksByClientId( clientIds ), -} ) )( PluginBlockSettingsMenuGroupSlot ); +PluginBlockSettingsMenuGroup.Slot = withSelect( + ( select, { fillProps: { clientIds } } ) => ( { + selectedBlocks: select( 'core/block-editor' ).getBlocksByClientId( + clientIds + ), + } ) +)( PluginBlockSettingsMenuGroupSlot ); export default PluginBlockSettingsMenuGroup; diff --git a/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js b/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js index 29c281af1b5db0..026f61d5bd3dbd 100644 --- a/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js +++ b/packages/edit-post/src/components/block-settings-menu/plugin-block-settings-menu-item.js @@ -14,7 +14,8 @@ import { compose } from '@wordpress/compose'; */ import PluginBlockSettingsMenuGroup from './plugin-block-settings-menu-group'; -const isEverySelectedBlockAllowed = ( selected, allowed ) => difference( selected, allowed ).length === 0; +const isEverySelectedBlockAllowed = ( selected, allowed ) => + difference( selected, allowed ).length === 0; /** * Plugins may want to add an item to the menu either for every block @@ -27,7 +28,8 @@ const isEverySelectedBlockAllowed = ( selected, allowed ) => difference( selecte * @param {string[]} allowedBlocks Array containing the names of the blocks allowed * @return {boolean} Whether the item will be rendered or not. */ -const shouldRenderItem = ( selectedBlocks, allowedBlocks ) => ! Array.isArray( allowedBlocks ) || +const shouldRenderItem = ( selectedBlocks, allowedBlocks ) => + ! Array.isArray( allowedBlocks ) || isEverySelectedBlockAllowed( selectedBlocks, allowedBlocks ); /** @@ -83,20 +85,29 @@ const shouldRenderItem = ( selectedBlocks, allowedBlocks ) => ! Array.isArray( a * * @return {WPComponent} The component to be rendered. */ -const PluginBlockSettingsMenuItem = ( { allowedBlocks, icon, label, onClick, small, role } ) => ( +const PluginBlockSettingsMenuItem = ( { + allowedBlocks, + icon, + label, + onClick, + small, + role, +} ) => ( <PluginBlockSettingsMenuGroup> { ( { selectedBlocks, onClose } ) => { if ( ! shouldRenderItem( selectedBlocks, allowedBlocks ) ) { return null; } - return ( <MenuItem - onClick={ compose( onClick, onClose ) } - icon={ icon || 'admin-plugins' } - label={ small ? label : undefined } - role={ role } - > - { ! small && label } - </MenuItem> ); + return ( + <MenuItem + onClick={ compose( onClick, onClose ) } + icon={ icon || 'admin-plugins' } + label={ small ? label : undefined } + role={ role } + > + { ! small && label } + </MenuItem> + ); } } </PluginBlockSettingsMenuGroup> ); diff --git a/packages/edit-post/src/components/browser-url/index.js b/packages/edit-post/src/components/browser-url/index.js index 34a8e889cefd42..d4cbd7f8677068 100644 --- a/packages/edit-post/src/components/browser-url/index.js +++ b/packages/edit-post/src/components/browser-url/index.js @@ -52,7 +52,10 @@ export class BrowserURL extends Component { return; } - if ( ( postId !== prevProps.postId || postId !== historyId ) && postStatus !== 'auto-draft' ) { + if ( + ( postId !== prevProps.postId || postId !== historyId ) && + postStatus !== 'auto-draft' + ) { this.setBrowserURL( postId ); } } diff --git a/packages/edit-post/src/components/editor-initialization/listener-hooks.js b/packages/edit-post/src/components/editor-initialization/listener-hooks.js index edefe8972fddcd..ff168dc7413fc9 100644 --- a/packages/edit-post/src/components/editor-initialization/listener-hooks.js +++ b/packages/edit-post/src/components/editor-initialization/listener-hooks.js @@ -20,10 +20,7 @@ import { * @param {number} postId The current post id. */ export const useBlockSelectionListener = ( postId ) => { - const { - hasBlockSelection, - isEditorSidebarOpened, - } = useSelect( + const { hasBlockSelection, isEditorSidebarOpened } = useSelect( ( select ) => ( { hasBlockSelection: !! select( 'core/block-editor' @@ -57,12 +54,16 @@ export const useAdjustSidebarListener = ( postId ) => { const { isSmall, activeGeneralSidebarName } = useSelect( ( select ) => ( { isSmall: select( 'core/viewport' ).isViewportMatch( '< medium' ), - activeGeneralSidebarName: select( STORE_KEY ).getActiveGeneralSidebarName(), + activeGeneralSidebarName: select( + STORE_KEY + ).getActiveGeneralSidebarName(), } ), [ postId ] ); - const { openGeneralSidebar, closeGeneralSidebar } = useDispatch( STORE_KEY ); + const { openGeneralSidebar, closeGeneralSidebar } = useDispatch( + STORE_KEY + ); const previousIsSmall = useRef( null ); const sidebarToReOpenOnExpand = useRef( null ); @@ -78,7 +79,10 @@ export const useAdjustSidebarListener = ( postId ) => { if ( activeGeneralSidebarName ) { closeGeneralSidebar(); } - } else if ( sidebarToReOpenOnExpand.current && ! activeGeneralSidebarName ) { + } else if ( + sidebarToReOpenOnExpand.current && + ! activeGeneralSidebarName + ) { openGeneralSidebar( sidebarToReOpenOnExpand.current ); sidebarToReOpenOnExpand.current = null; } @@ -101,7 +105,8 @@ export const useUpdatePostLinkListener = ( postId ) => { const nodeToUpdate = useRef(); useEffect( () => { - nodeToUpdate.current = document.querySelector( VIEW_AS_PREVIEW_LINK_SELECTOR ) || + nodeToUpdate.current = + document.querySelector( VIEW_AS_PREVIEW_LINK_SELECTOR ) || document.querySelector( VIEW_AS_LINK_SELECTOR ); }, [ postId ] ); diff --git a/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js b/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js index 0c8742bbe02fcc..e25967fb0f87fa 100644 --- a/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js +++ b/packages/edit-post/src/components/editor-initialization/test/listener-hooks.js @@ -38,34 +38,36 @@ describe( 'listener hook tests', () => { }; let subscribeTrigger; const registry = { - select: jest.fn().mockImplementation( - ( storeName ) => mockStores[ storeName ] - ), - dispatch: jest.fn().mockImplementation( - ( storeName ) => mockStores[ storeName ] - ), + select: jest + .fn() + .mockImplementation( ( storeName ) => mockStores[ storeName ] ), + dispatch: jest + .fn() + .mockImplementation( ( storeName ) => mockStores[ storeName ] ), subscribe: ( subscription ) => { subscribeTrigger = subscription; }, }; const setMockReturnValue = ( store, functionName, value ) => { - mockStores[ store ][ functionName ] = jest.fn().mockReturnValue( value ); + mockStores[ store ][ functionName ] = jest + .fn() + .mockReturnValue( value ); }; - const getSpyedFunction = ( - store, - functionName - ) => mockStores[ store ][ functionName ]; + const getSpyedFunction = ( store, functionName ) => + mockStores[ store ][ functionName ]; const renderComponent = ( testedHook, id, renderer = null ) => { const TestComponent = ( { postId } ) => { testedHook( postId ); return null; }; - const TestedOutput = <RegistryProvider value={ registry }> - <TestComponent postId={ id } /> - </RegistryProvider>; - return renderer === null ? - TestRenderer.create( TestedOutput ) : - renderer.update( TestedOutput ); + const TestedOutput = ( + <RegistryProvider value={ registry }> + <TestComponent postId={ id } /> + </RegistryProvider> + ); + return renderer === null + ? TestRenderer.create( TestedOutput ) + : renderer.update( TestedOutput ); }; afterEach( () => { Object.values( mockStores ).forEach( ( storeMocks ) => { @@ -90,7 +92,11 @@ describe( 'listener hook tests', () => { } ); it( 'opens block sidebar if block is selected', () => { setMockReturnValue( STORE_KEY, 'isEditorSidebarOpened', true ); - setMockReturnValue( 'core/block-editor', 'getBlockSelectionStart', true ); + setMockReturnValue( + 'core/block-editor', + 'getBlockSelectionStart', + true + ); act( () => { renderComponent( useBlockSelectionListener, 10 ); } ); @@ -100,7 +106,11 @@ describe( 'listener hook tests', () => { } ); it( 'opens document sidebar if block is not selected', () => { setMockReturnValue( STORE_KEY, 'isEditorSidebarOpened', true ); - setMockReturnValue( 'core/block-editor', 'getBlockSelectionStart', false ); + setMockReturnValue( + 'core/block-editor', + 'getBlockSelectionStart', + false + ); act( () => { renderComponent( useBlockSelectionListener, 10 ); } ); @@ -112,21 +122,11 @@ describe( 'listener hook tests', () => { describe( 'useAdjustSidebarListener', () => { it( 'initializes and does nothing when viewport is not small', () => { setMockReturnValue( 'core/viewport', 'isViewPortMatch', false ); - setMockReturnValue( STORE_KEY, 'getActiveGeneralSidebarName', 'edit-post/document' ); - act( () => { - renderComponent( useAdjustSidebarListener, 10 ); - } ); - expect( - getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) - ).not.toHaveBeenCalled(); - expect( - getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) - ).not.toHaveBeenCalled(); - } ); - it( 'does not close sidebar if viewport is small and there is no ' + - 'active sidebar name available', () => { - setMockReturnValue( 'core/viewport', 'isViewPortMatch', true ); - setMockReturnValue( STORE_KEY, 'getActiveGeneralSidebarName', null ); + setMockReturnValue( + STORE_KEY, + 'getActiveGeneralSidebarName', + 'edit-post/document' + ); act( () => { renderComponent( useAdjustSidebarListener, 10 ); } ); @@ -137,78 +137,130 @@ describe( 'listener hook tests', () => { getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) ).not.toHaveBeenCalled(); } ); - it( 'closes sidebar if viewport is small and there is an active ' + - 'sidebar name available on initial render', () => { - setMockReturnValue( 'core/viewport', 'isViewportMatch', true ); - setMockReturnValue( STORE_KEY, 'getActiveGeneralSidebarName', 'foo' ); - act( () => { - renderComponent( useAdjustSidebarListener, 10 ); - } ); - expect( - getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) - ).not.toHaveBeenCalled(); - expect( - getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) - ).toHaveBeenCalled(); - } ); - it( 'closes sidebar if viewport is small and there is an active ' + - 'sidebar name available when viewport size changes', () => { - setMockReturnValue( 'core/viewport', 'isViewportMatch', false ); - setMockReturnValue( STORE_KEY, 'getActiveGeneralSidebarName', 'foo' ); - // initial render does nothing and sidebar will be open already. - act( () => { - renderComponent( useAdjustSidebarListener, 10 ); - } ); - setMockReturnValue( 'core/viewport', 'isViewportMatch', true ); - // This render should result in the sidebar closing because viewport is - // now small triggering a change. - act( () => { - subscribeTrigger(); - } ); - expect( - getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) - ).not.toHaveBeenCalled(); - expect( - getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) - ).toHaveBeenCalledTimes( 1 ); - } ); - it( 'opens sidebar if viewport is not small, and there is a cached sidebar ' + - 'to reopen on expand', () => { - setMockReturnValue( 'core/viewport', 'isViewportMatch', false ); - setMockReturnValue( STORE_KEY, 'getActiveGeneralSidebarName', 'foo' ); - // initial render does nothing and sidebar should be open. - act( () => { - renderComponent( useAdjustSidebarListener, 10 ); - } ); - setMockReturnValue( 'core/viewport', 'isViewportMatch', true ); - setMockReturnValue( STORE_KEY, 'getActiveGeneralSidebarName', 'bar' ); - // next render should close the sidebar and active sidebar at time of - // closing is cached. - act( () => { - subscribeTrigger(); - } ); - setMockReturnValue( 'core/viewport', 'isViewportMatch', false ); - setMockReturnValue( STORE_KEY, 'getActiveGeneralSidebarName', '' ); - // next render should open the sidebar to the cached general sidebar name. - act( () => { - subscribeTrigger(); - } ); - expect( - getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) - ).toHaveBeenCalledWith( 'bar' ); - expect( - getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) - ).toHaveBeenCalledTimes( 1 ); - expect( - getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) - ).toHaveBeenCalledTimes( 1 ); - } ); + it( + 'does not close sidebar if viewport is small and there is no ' + + 'active sidebar name available', + () => { + setMockReturnValue( 'core/viewport', 'isViewPortMatch', true ); + setMockReturnValue( + STORE_KEY, + 'getActiveGeneralSidebarName', + null + ); + act( () => { + renderComponent( useAdjustSidebarListener, 10 ); + } ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).not.toHaveBeenCalled(); + expect( + getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) + ).not.toHaveBeenCalled(); + } + ); + it( + 'closes sidebar if viewport is small and there is an active ' + + 'sidebar name available on initial render', + () => { + setMockReturnValue( 'core/viewport', 'isViewportMatch', true ); + setMockReturnValue( + STORE_KEY, + 'getActiveGeneralSidebarName', + 'foo' + ); + act( () => { + renderComponent( useAdjustSidebarListener, 10 ); + } ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).not.toHaveBeenCalled(); + expect( + getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) + ).toHaveBeenCalled(); + } + ); + it( + 'closes sidebar if viewport is small and there is an active ' + + 'sidebar name available when viewport size changes', + () => { + setMockReturnValue( 'core/viewport', 'isViewportMatch', false ); + setMockReturnValue( + STORE_KEY, + 'getActiveGeneralSidebarName', + 'foo' + ); + // initial render does nothing and sidebar will be open already. + act( () => { + renderComponent( useAdjustSidebarListener, 10 ); + } ); + setMockReturnValue( 'core/viewport', 'isViewportMatch', true ); + // This render should result in the sidebar closing because viewport is + // now small triggering a change. + act( () => { + subscribeTrigger(); + } ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).not.toHaveBeenCalled(); + expect( + getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) + ).toHaveBeenCalledTimes( 1 ); + } + ); + it( + 'opens sidebar if viewport is not small, and there is a cached sidebar ' + + 'to reopen on expand', + () => { + setMockReturnValue( 'core/viewport', 'isViewportMatch', false ); + setMockReturnValue( + STORE_KEY, + 'getActiveGeneralSidebarName', + 'foo' + ); + // initial render does nothing and sidebar should be open. + act( () => { + renderComponent( useAdjustSidebarListener, 10 ); + } ); + setMockReturnValue( 'core/viewport', 'isViewportMatch', true ); + setMockReturnValue( + STORE_KEY, + 'getActiveGeneralSidebarName', + 'bar' + ); + // next render should close the sidebar and active sidebar at time of + // closing is cached. + act( () => { + subscribeTrigger(); + } ); + setMockReturnValue( 'core/viewport', 'isViewportMatch', false ); + setMockReturnValue( + STORE_KEY, + 'getActiveGeneralSidebarName', + '' + ); + // next render should open the sidebar to the cached general sidebar name. + act( () => { + subscribeTrigger(); + } ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).toHaveBeenCalledWith( 'bar' ); + expect( + getSpyedFunction( STORE_KEY, 'openGeneralSidebar' ) + ).toHaveBeenCalledTimes( 1 ); + expect( + getSpyedFunction( STORE_KEY, 'closeGeneralSidebar' ) + ).toHaveBeenCalledTimes( 1 ); + } + ); } ); describe( 'useUpdatePostLinkListener', () => { const setAttribute = jest.fn(); const mockSelector = jest.fn(); beforeEach( () => { - document.querySelector = mockSelector.mockReturnValue( { setAttribute } ); + document.querySelector = mockSelector.mockReturnValue( { + setAttribute, + } ); } ); afterEach( () => { setAttribute.mockClear(); @@ -216,22 +268,16 @@ describe( 'listener hook tests', () => { } ); it( 'updates nothing if there is no view link available', () => { mockSelector.mockImplementation( () => null ); - setMockReturnValue( - 'core/editor', - 'getCurrentPost', - { link: 'foo' } - ); + setMockReturnValue( 'core/editor', 'getCurrentPost', { + link: 'foo', + } ); act( () => { renderComponent( useUpdatePostLinkListener, 10 ); } ); expect( setAttribute ).not.toHaveBeenCalled(); } ); it( 'updates nothing if there is no permalink', () => { - setMockReturnValue( - 'core/editor', - 'getCurrentPost', - { link: '' } - ); + setMockReturnValue( 'core/editor', 'getCurrentPost', { link: '' } ); act( () => { renderComponent( useUpdatePostLinkListener, 10 ); } ); @@ -239,7 +285,10 @@ describe( 'listener hook tests', () => { } ); it( 'only calls document query selector once across renders', () => { act( () => { - const renderer = renderComponent( useUpdatePostLinkListener, 10 ); + const renderer = renderComponent( + useUpdatePostLinkListener, + 10 + ); renderComponent( useUpdatePostLinkListener, 20, renderer ); } ); expect( mockSelector ).toHaveBeenCalledTimes( 1 ); @@ -249,11 +298,9 @@ describe( 'listener hook tests', () => { expect( mockSelector ).toHaveBeenCalledTimes( 1 ); } ); it( 'only updates the permalink when it changes', () => { - setMockReturnValue( - 'core/editor', - 'getCurrentPost', - { link: 'foo' } - ); + setMockReturnValue( 'core/editor', 'getCurrentPost', { + link: 'foo', + } ); act( () => { renderComponent( useUpdatePostLinkListener, 10 ); } ); @@ -263,19 +310,15 @@ describe( 'listener hook tests', () => { expect( setAttribute ).toHaveBeenCalledTimes( 1 ); } ); it( 'updates the permalink when it changes', () => { - setMockReturnValue( - 'core/editor', - 'getCurrentPost', - { link: 'foo' } - ); + setMockReturnValue( 'core/editor', 'getCurrentPost', { + link: 'foo', + } ); act( () => { renderComponent( useUpdatePostLinkListener, 10 ); } ); - setMockReturnValue( - 'core/editor', - 'getCurrentPost', - { link: 'bar' } - ); + setMockReturnValue( 'core/editor', 'getCurrentPost', { + link: 'bar', + } ); act( () => { subscribeTrigger(); } ); diff --git a/packages/edit-post/src/components/editor-regions/index.js b/packages/edit-post/src/components/editor-regions/index.js index 2e43e2f95f3124..eee9dfcfdae9d6 100644 --- a/packages/edit-post/src/components/editor-regions/index.js +++ b/packages/edit-post/src/components/editor-regions/index.js @@ -9,7 +9,14 @@ import classnames from 'classnames'; import { navigateRegions } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -function EditorRegions( { footer, header, sidebar, content, publish, className } ) { +function EditorRegions( { + footer, + header, + sidebar, + content, + publish, + className, +} ) { return ( <div className={ classnames( className, 'edit-post-editor-regions' ) }> { !! header && ( diff --git a/packages/edit-post/src/components/fullscreen-mode/test/index.js b/packages/edit-post/src/components/fullscreen-mode/test/index.js index 928722b340da24..199930777479f6 100644 --- a/packages/edit-post/src/components/fullscreen-mode/test/index.js +++ b/packages/edit-post/src/components/fullscreen-mode/test/index.js @@ -12,13 +12,17 @@ describe( 'FullscreenMode', () => { it( 'fullscreen mode to be added to document body when active', () => { shallow( <FullscreenMode isActive={ true } /> ); - expect( document.body.classList.contains( 'is-fullscreen-mode' ) ).toBe( true ); + expect( document.body.classList.contains( 'is-fullscreen-mode' ) ).toBe( + true + ); } ); it( 'fullscreen mode not to be added to document body when active', () => { shallow( <FullscreenMode isActive={ false } /> ); - expect( document.body.classList.contains( 'is-fullscreen-mode' ) ).toBe( false ); + expect( document.body.classList.contains( 'is-fullscreen-mode' ) ).toBe( + false + ); } ); it( 'sticky-menu to be removed from the body class if present', () => { @@ -26,7 +30,9 @@ describe( 'FullscreenMode', () => { shallow( <FullscreenMode isActive={ false } /> ); - expect( document.body.classList.contains( 'sticky-menu' ) ).toBe( false ); + expect( document.body.classList.contains( 'sticky-menu' ) ).toBe( + false + ); } ); it( 'sticky-menu to be restored when component unmounted and originally present', () => { @@ -35,6 +41,8 @@ describe( 'FullscreenMode', () => { const mode = shallow( <FullscreenMode isActive={ false } /> ); mode.unmount(); - expect( document.body.classList.contains( 'sticky-menu' ) ).toBe( true ); + expect( document.body.classList.contains( 'sticky-menu' ) ).toBe( + true + ); } ); } ); diff --git a/packages/edit-post/src/components/header/feature-toggle/index.js b/packages/edit-post/src/components/header/feature-toggle/index.js index 89a2a90d1a4179..7d19d5d5611985 100644 --- a/packages/edit-post/src/components/header/feature-toggle/index.js +++ b/packages/edit-post/src/components/header/feature-toggle/index.js @@ -12,7 +12,15 @@ import { MenuItem, withSpokenMessages } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { check } from '@wordpress/icons'; -function FeatureToggle( { onToggle, isActive, label, info, messageActivated, messageDeactivated, speak } ) { +function FeatureToggle( { + onToggle, + isActive, + label, + info, + messageActivated, + messageDeactivated, + speak, +} ) { const speakMessage = () => { if ( isActive ) { speak( messageDeactivated || __( 'Feature deactivated' ) ); diff --git a/packages/edit-post/src/components/header/fullscreen-mode-close/index.js b/packages/edit-post/src/components/header/fullscreen-mode-close/index.js index 1240dd29311c37..cd91c8308000c0 100644 --- a/packages/edit-post/src/components/header/fullscreen-mode-close/index.js +++ b/packages/edit-post/src/components/header/fullscreen-mode-close/index.js @@ -20,7 +20,9 @@ function FullscreenModeClose( { isActive, postType } ) { <Toolbar className="edit-post-fullscreen-mode-close__toolbar"> <Button icon="arrow-left-alt2" - href={ addQueryArgs( 'edit.php', { post_type: postType.slug } ) } + href={ addQueryArgs( 'edit.php', { + post_type: postType.slug, + } ) } label={ get( postType, [ 'labels', 'view_items' ], diff --git a/packages/edit-post/src/components/header/header-toolbar/index.js b/packages/edit-post/src/components/header/header-toolbar/index.js index 2fe10fbcf7b948..46873f2ee9aa0f 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.js @@ -18,26 +18,38 @@ import { } from '@wordpress/editor'; function HeaderToolbar() { - const { hasFixedToolbar, showInserter, isTextModeEnabled } = useSelect( ( select ) => ( { - hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ), - // This setting (richEditingEnabled) should not live in the block editor's setting. - showInserter: select( 'core/edit-post' ).getEditorMode() === 'visual' && select( 'core/editor' ).getEditorSettings().richEditingEnabled, - isTextModeEnabled: select( 'core/edit-post' ).getEditorMode() === 'text', - } ), [] ); + const { hasFixedToolbar, showInserter, isTextModeEnabled } = useSelect( + ( select ) => ( { + hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( + 'fixedToolbar' + ), + // This setting (richEditingEnabled) should not live in the block editor's setting. + showInserter: + select( 'core/edit-post' ).getEditorMode() === 'visual' && + select( 'core/editor' ).getEditorSettings().richEditingEnabled, + isTextModeEnabled: + select( 'core/edit-post' ).getEditorMode() === 'text', + } ), + [] + ); const isLargeViewport = useViewportMatch( 'medium' ); - const toolbarAriaLabel = hasFixedToolbar ? - /* translators: accessibility text for the editor toolbar when Top Toolbar is on */ - __( 'Document and block tools' ) : - /* translators: accessibility text for the editor toolbar when Top Toolbar is off */ - __( 'Document tools' ); + const toolbarAriaLabel = hasFixedToolbar + ? /* translators: accessibility text for the editor toolbar when Top Toolbar is on */ + __( 'Document and block tools' ) + : /* translators: accessibility text for the editor toolbar when Top Toolbar is off */ + __( 'Document tools' ); return ( <NavigableToolbar className="edit-post-header-toolbar" aria-label={ toolbarAriaLabel } > - <Inserter disabled={ ! showInserter } position="bottom right" showInserterHelpPanel /> + <Inserter + disabled={ ! showInserter } + position="bottom right" + showInserterHelpPanel + /> <EditorHistoryUndo /> <EditorHistoryRedo /> <TableOfContents hasOutlineItemsDisabled={ isTextModeEnabled } /> diff --git a/packages/edit-post/src/components/header/header-toolbar/index.native.js b/packages/edit-post/src/components/header/header-toolbar/index.native.js index 6f87cc7bfc7df5..56bb9a4a2dbcf2 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.native.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.native.js @@ -11,10 +11,7 @@ import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; import { withViewportMatch } from '@wordpress/viewport'; import { __ } from '@wordpress/i18n'; -import { - Inserter, - BlockToolbar, -} from '@wordpress/block-editor'; +import { Inserter, BlockToolbar } from '@wordpress/block-editor'; import { Toolbar, ToolbarButton } from '@wordpress/components'; /** @@ -39,7 +36,12 @@ function HeaderToolbar( { }; return ( - <View style={ getStylesFromColorScheme( styles.container, styles.containerDark ) }> + <View + style={ getStylesFromColorScheme( + styles.container, + styles.containerDark + ) } + > <ScrollView ref={ scrollViewRef } onContentSizeChange={ scrollToStart } @@ -57,30 +59,34 @@ function HeaderToolbar( { icon="undo" isDisabled={ ! hasUndo } onClick={ undo } - extraProps={ { hint: __( 'Double tap to undo last change' ) } } + extraProps={ { + hint: __( 'Double tap to undo last change' ), + } } /> <ToolbarButton title={ __( 'Redo' ) } icon="redo" isDisabled={ ! hasRedo } onClick={ redo } - extraProps={ { hint: __( 'Double tap to redo last change' ) } } + extraProps={ { + hint: __( 'Double tap to redo last change' ), + } } /> </Toolbar> - { hasFixedToolbar && - <BlockToolbar /> - } + { hasFixedToolbar && <BlockToolbar /> } </ScrollView> - { showKeyboardHideButton && + { showKeyboardHideButton && ( <Toolbar passedStyle={ styles.keyboardHideContainer }> <ToolbarButton title={ __( 'Hide keyboard' ) } icon="keyboard-hide" onClick={ onHideKeyboard } - extraProps={ { hint: __( 'Tap to hide the keyboard' ) } } + extraProps={ { + hint: __( 'Tap to hide the keyboard' ), + } } /> </Toolbar> - } + ) } </View> ); } @@ -89,10 +95,15 @@ export default compose( [ withSelect( ( select ) => ( { hasRedo: select( 'core/editor' ).hasEditorRedo(), hasUndo: select( 'core/editor' ).hasEditorUndo(), - hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ), + hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( + 'fixedToolbar' + ), // This setting (richEditingEnabled) should not live in the block editor's setting. - showInserter: select( 'core/edit-post' ).getEditorMode() === 'visual' && select( 'core/editor' ).getEditorSettings().richEditingEnabled, - isTextModeEnabled: select( 'core/edit-post' ).getEditorMode() === 'text', + showInserter: + select( 'core/edit-post' ).getEditorMode() === 'visual' && + select( 'core/editor' ).getEditorSettings().richEditingEnabled, + isTextModeEnabled: + select( 'core/edit-post' ).getEditorMode() === 'text', } ) ), withDispatch( ( dispatch ) => { const { clearSelectedBlock } = dispatch( 'core/block-editor' ); diff --git a/packages/edit-post/src/components/header/index.js b/packages/edit-post/src/components/header/index.js index 3c992a958277d9..730bd4d542000a 100644 --- a/packages/edit-post/src/components/header/index.js +++ b/packages/edit-post/src/components/header/index.js @@ -3,10 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { Button } from '@wordpress/components'; -import { - PostPreviewButton, - PostSavedState, -} from '@wordpress/editor'; +import { PostPreviewButton, PostSavedState } from '@wordpress/editor'; import { useSelect, useDispatch } from '@wordpress/data'; /** @@ -26,19 +23,36 @@ function Header() { isPublishSidebarOpened, isSaving, getBlockSelectionStart, - } = useSelect( ( select ) => ( { - shortcut: select( 'core/keyboard-shortcuts' ).getShortcutRepresentation( 'core/edit-post/toggle-sidebar' ), - hasActiveMetaboxes: select( 'core/edit-post' ).hasMetaBoxes(), - isEditorSidebarOpened: select( 'core/edit-post' ).isEditorSidebarOpened(), - isPublishSidebarOpened: select( 'core/edit-post' ).isPublishSidebarOpened(), - isSaving: select( 'core/edit-post' ).isSavingMetaBoxes(), - getBlockSelectionStart: select( 'core/block-editor' ).getBlockSelectionStart, - } ), [] ); - const { openGeneralSidebar, closeGeneralSidebar } = useDispatch( 'core/edit-post' ); + } = useSelect( + ( select ) => ( { + shortcut: select( + 'core/keyboard-shortcuts' + ).getShortcutRepresentation( 'core/edit-post/toggle-sidebar' ), + hasActiveMetaboxes: select( 'core/edit-post' ).hasMetaBoxes(), + isEditorSidebarOpened: select( + 'core/edit-post' + ).isEditorSidebarOpened(), + isPublishSidebarOpened: select( + 'core/edit-post' + ).isPublishSidebarOpened(), + isSaving: select( 'core/edit-post' ).isSavingMetaBoxes(), + getBlockSelectionStart: select( 'core/block-editor' ) + .getBlockSelectionStart, + } ), + [] + ); + const { openGeneralSidebar, closeGeneralSidebar } = useDispatch( + 'core/edit-post' + ); - const toggleGeneralSidebar = isEditorSidebarOpened ? - closeGeneralSidebar : - () => openGeneralSidebar( getBlockSelectionStart() ? 'edit-post/block' : 'edit-post/document' ); + const toggleGeneralSidebar = isEditorSidebarOpened + ? closeGeneralSidebar + : () => + openGeneralSidebar( + getBlockSelectionStart() + ? 'edit-post/block' + : 'edit-post/document' + ); return ( <div className="edit-post-header"> diff --git a/packages/edit-post/src/components/header/index.native.js b/packages/edit-post/src/components/header/index.native.js index 93fbf88ce79674..727061f24aa465 100644 --- a/packages/edit-post/src/components/header/index.native.js +++ b/packages/edit-post/src/components/header/index.native.js @@ -45,7 +45,9 @@ export default class Header extends Component { render() { return ( - <HeaderToolbar showKeyboardHideButton={ this.state.isKeyboardVisible } /> + <HeaderToolbar + showKeyboardHideButton={ this.state.isKeyboardVisible } + /> ); } } diff --git a/packages/edit-post/src/components/header/mode-switcher/index.js b/packages/edit-post/src/components/header/mode-switcher/index.js index 49569b034b9155..78a522470f729a 100644 --- a/packages/edit-post/src/components/header/mode-switcher/index.js +++ b/packages/edit-post/src/components/header/mode-switcher/index.js @@ -27,12 +27,19 @@ function ModeSwitcher() { isRichEditingEnabled, isCodeEditingEnabled, mode, - } = useSelect( ( select ) => ( { - shortcut: select( 'core/keyboard-shortcuts' ).getShortcutRepresentation( 'core/edit-post/toggle-mode' ), - isRichEditingEnabled: select( 'core/editor' ).getEditorSettings().richEditingEnabled, - isCodeEditingEnabled: select( 'core/editor' ).getEditorSettings().codeEditingEnabled, - mode: select( 'core/edit-post' ).getEditorMode(), - } ), [] ); + } = useSelect( + ( select ) => ( { + shortcut: select( + 'core/keyboard-shortcuts' + ).getShortcutRepresentation( 'core/edit-post/toggle-mode' ), + isRichEditingEnabled: select( 'core/editor' ).getEditorSettings() + .richEditingEnabled, + isCodeEditingEnabled: select( 'core/editor' ).getEditorSettings() + .codeEditingEnabled, + mode: select( 'core/edit-post' ).getEditorMode(), + } ), + [] + ); const { switchEditorMode } = useDispatch( 'core/edit-post' ); const choices = MODES.map( ( choice ) => { @@ -47,9 +54,7 @@ function ModeSwitcher() { } return ( - <MenuGroup - label={ __( 'Editor' ) } - > + <MenuGroup label={ __( 'Editor' ) }> <MenuItemsChoice choices={ choices } value={ mode } diff --git a/packages/edit-post/src/components/header/more-menu/test/index.js b/packages/edit-post/src/components/header/more-menu/test/index.js index 83f9de19ba7068..2da96abe04ea4e 100644 --- a/packages/edit-post/src/components/header/more-menu/test/index.js +++ b/packages/edit-post/src/components/header/more-menu/test/index.js @@ -10,9 +10,7 @@ import MoreMenu from '../index'; describe( 'MoreMenu', () => { it( 'should match snapshot', () => { - const wrapper = mount( - <MoreMenu /> - ); + const wrapper = mount( <MoreMenu /> ); expect( wrapper ).toMatchSnapshot(); } ); diff --git a/packages/edit-post/src/components/header/pinned-plugins/index.js b/packages/edit-post/src/components/header/pinned-plugins/index.js index 0d6c50f1b78a46..35f07ab0678d8b 100644 --- a/packages/edit-post/src/components/header/pinned-plugins/index.js +++ b/packages/edit-post/src/components/header/pinned-plugins/index.js @@ -12,11 +12,11 @@ const { Fill: PinnedPlugins, Slot } = createSlotFill( 'PinnedPlugins' ); PinnedPlugins.Slot = ( props ) => ( <Slot { ...props }> - { ( fills ) => ! isEmpty( fills ) && ( - <div className="edit-post-pinned-plugins"> - { fills } - </div> - ) } + { ( fills ) => + ! isEmpty( fills ) && ( + <div className="edit-post-pinned-plugins">{ fills }</div> + ) + } </Slot> ); diff --git a/packages/edit-post/src/components/header/plugin-more-menu-item/index.js b/packages/edit-post/src/components/header/plugin-more-menu-item/index.js index 08a5bef83b35eb..66940b9579baaa 100644 --- a/packages/edit-post/src/components/header/plugin-more-menu-item/index.js +++ b/packages/edit-post/src/components/header/plugin-more-menu-item/index.js @@ -85,5 +85,5 @@ export default compose( return { icon: ownProps.icon || context.icon, }; - } ), + } ) )( PluginMoreMenuItem ); diff --git a/packages/edit-post/src/components/header/plugin-more-menu-item/test/index.js b/packages/edit-post/src/components/header/plugin-more-menu-item/test/index.js index 9ab1b35920bcc8..1bf7651db97a5e 100644 --- a/packages/edit-post/src/components/header/plugin-more-menu-item/test/index.js +++ b/packages/edit-post/src/components/header/plugin-more-menu-item/test/index.js @@ -23,9 +23,7 @@ describe( 'PluginMoreMenuItem', () => { test( 'renders menu item as button properly', () => { const component = ReactTestRenderer.create( <SlotFillProvider> - <PluginMoreMenuItem - icon="smiley" - > + <PluginMoreMenuItem icon="smiley"> My plugin button menu item </PluginMoreMenuItem> <PluginsMoreMenuGroup.Slot fillProps={ fillProps } /> @@ -39,10 +37,7 @@ describe( 'PluginMoreMenuItem', () => { const url = 'https://make.wordpress.org'; const component = ReactTestRenderer.create( <SlotFillProvider> - <PluginMoreMenuItem - icon="smiley" - href={ url } - > + <PluginMoreMenuItem icon="smiley" href={ url }> My plugin link menu item </PluginMoreMenuItem> <PluginsMoreMenuGroup.Slot fillProps={ fillProps } /> diff --git a/packages/edit-post/src/components/header/plugin-sidebar-more-menu-item/index.js b/packages/edit-post/src/components/header/plugin-sidebar-more-menu-item/index.js index 530a645f1dd755..fef6449f130a8d 100644 --- a/packages/edit-post/src/components/header/plugin-sidebar-more-menu-item/index.js +++ b/packages/edit-post/src/components/header/plugin-sidebar-more-menu-item/index.js @@ -11,7 +11,12 @@ import { check } from '@wordpress/icons'; */ import PluginMoreMenuItem from '../plugin-more-menu-item'; -const PluginSidebarMoreMenuItem = ( { children, icon, isSelected, onClick } ) => ( +const PluginSidebarMoreMenuItem = ( { + children, + icon, + isSelected, + onClick, +} ) => ( <PluginMoreMenuItem icon={ isSelected ? check : icon } isSelected={ isSelected } @@ -75,23 +80,20 @@ export default compose( }; } ), withSelect( ( select, { sidebarName } ) => { - const { - getActiveGeneralSidebarName, - } = select( 'core/edit-post' ); + const { getActiveGeneralSidebarName } = select( 'core/edit-post' ); return { isSelected: getActiveGeneralSidebarName() === sidebarName, }; } ), withDispatch( ( dispatch, { isSelected, sidebarName } ) => { - const { - closeGeneralSidebar, - openGeneralSidebar, - } = dispatch( 'core/edit-post' ); - const onClick = isSelected ? - closeGeneralSidebar : - () => openGeneralSidebar( sidebarName ); + const { closeGeneralSidebar, openGeneralSidebar } = dispatch( + 'core/edit-post' + ); + const onClick = isSelected + ? closeGeneralSidebar + : () => openGeneralSidebar( sidebarName ); return { onClick }; - } ), + } ) )( PluginSidebarMoreMenuItem ); diff --git a/packages/edit-post/src/components/header/plugins-more-menu-group/index.js b/packages/edit-post/src/components/header/plugins-more-menu-group/index.js index 82fe50c4d53ec5..50b7aa5efd69d7 100644 --- a/packages/edit-post/src/components/header/plugins-more-menu-group/index.js +++ b/packages/edit-post/src/components/header/plugins-more-menu-group/index.js @@ -9,15 +9,17 @@ import { isEmpty } from 'lodash'; import { createSlotFill, MenuGroup } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -const { Fill: PluginsMoreMenuGroup, Slot } = createSlotFill( 'PluginsMoreMenuGroup' ); +const { Fill: PluginsMoreMenuGroup, Slot } = createSlotFill( + 'PluginsMoreMenuGroup' +); PluginsMoreMenuGroup.Slot = ( { fillProps } ) => ( <Slot fillProps={ fillProps }> - { ( fills ) => ! isEmpty( fills ) && ( - <MenuGroup label={ __( 'Plugins' ) }> - { fills } - </MenuGroup> - ) } + { ( fills ) => + ! isEmpty( fills ) && ( + <MenuGroup label={ __( 'Plugins' ) }>{ fills }</MenuGroup> + ) + } </Slot> ); diff --git a/packages/edit-post/src/components/header/post-publish-button-or-toggle.js b/packages/edit-post/src/components/header/post-publish-button-or-toggle.js index e483e3ee270bbd..6c31425c2e192f 100644 --- a/packages/edit-post/src/components/header/post-publish-button-or-toggle.js +++ b/packages/edit-post/src/components/header/post-publish-button-or-toggle.js @@ -84,8 +84,12 @@ export default compose( isBeingScheduled: select( 'core/editor' ).isEditedPostBeingScheduled(), isPending: select( 'core/editor' ).isCurrentPostPending(), isPublished: select( 'core/editor' ).isCurrentPostPublished(), - isPublishSidebarEnabled: select( 'core/editor' ).isPublishSidebarEnabled(), - isPublishSidebarOpened: select( 'core/edit-post' ).isPublishSidebarOpened(), + isPublishSidebarEnabled: select( + 'core/editor' + ).isPublishSidebarEnabled(), + isPublishSidebarOpened: select( + 'core/edit-post' + ).isPublishSidebarOpened(), isScheduled: select( 'core/editor' ).isCurrentPostScheduled(), } ) ), withDispatch( ( dispatch ) => { @@ -93,5 +97,5 @@ export default compose( return { togglePublishSidebar, }; - } ), + } ) )( PostPublishButtonOrToggle ); diff --git a/packages/edit-post/src/components/header/test/index.js b/packages/edit-post/src/components/header/test/index.js index 95ce0b5fc61361..b004b784304f2e 100644 --- a/packages/edit-post/src/components/header/test/index.js +++ b/packages/edit-post/src/components/header/test/index.js @@ -10,34 +10,44 @@ import { PostPublishButtonOrToggle } from '../post-publish-button-or-toggle'; describe( 'PostPublishButtonOrToggle should render a', () => { it( 'button when the post is published (1)', () => { - const wrapper = shallow( <PostPublishButtonOrToggle isPublished={ true } /> ); + const wrapper = shallow( + <PostPublishButtonOrToggle isPublished={ true } /> + ); expect( wrapper ).toMatchSnapshot(); } ); it( 'button when the post is scheduled (2)', () => { - const wrapper = shallow( <PostPublishButtonOrToggle - isScheduled={ true } - isBeingScheduled={ true } - /> ); + const wrapper = shallow( + <PostPublishButtonOrToggle + isScheduled={ true } + isBeingScheduled={ true } + /> + ); expect( wrapper ).toMatchSnapshot(); } ); it( 'button when the post is pending and cannot be published but the viewport is >= medium (3)', () => { - const wrapper = shallow( <PostPublishButtonOrToggle - isPending={ true } - hasPublishAction={ false } - /> ); + const wrapper = shallow( + <PostPublishButtonOrToggle + isPending={ true } + hasPublishAction={ false } + /> + ); expect( wrapper ).toMatchSnapshot(); } ); it( 'toggle when post is not (1), (2), (3), the viewport is >= medium, and the publish sidebar is enabled', () => { - const wrapper = shallow( <PostPublishButtonOrToggle isPublishSidebarEnabled={ true } /> ); + const wrapper = shallow( + <PostPublishButtonOrToggle isPublishSidebarEnabled={ true } /> + ); expect( wrapper ).toMatchSnapshot(); } ); it( 'button when post is not (1), (2), (3), the viewport is >= medium, and the publish sidebar is disabled', () => { - const wrapper = shallow( <PostPublishButtonOrToggle isPublishSidebarEnabled={ false } /> ); + const wrapper = shallow( + <PostPublishButtonOrToggle isPublishSidebarEnabled={ false } /> + ); expect( wrapper ).toMatchSnapshot(); } ); } ); diff --git a/packages/edit-post/src/components/header/tools-more-menu-group/index.js b/packages/edit-post/src/components/header/tools-more-menu-group/index.js index 01571604d4cb92..d49db11a0b4afb 100644 --- a/packages/edit-post/src/components/header/tools-more-menu-group/index.js +++ b/packages/edit-post/src/components/header/tools-more-menu-group/index.js @@ -9,15 +9,17 @@ import { isEmpty } from 'lodash'; import { createSlotFill, MenuGroup } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -const { Fill: ToolsMoreMenuGroup, Slot } = createSlotFill( 'ToolsMoreMenuGroup' ); +const { Fill: ToolsMoreMenuGroup, Slot } = createSlotFill( + 'ToolsMoreMenuGroup' +); ToolsMoreMenuGroup.Slot = ( { fillProps } ) => ( <Slot fillProps={ fillProps }> - { ( fills ) => ! isEmpty( fills ) && ( - <MenuGroup label={ __( 'Tools' ) }> - { fills } - </MenuGroup> - ) } + { ( fills ) => + ! isEmpty( fills ) && ( + <MenuGroup label={ __( 'Tools' ) }>{ fills }</MenuGroup> + ) + } </Slot> ); diff --git a/packages/edit-post/src/components/header/writing-menu/index.js b/packages/edit-post/src/components/header/writing-menu/index.js index ee9769818b1cae..b7c125b0e9a4e4 100644 --- a/packages/edit-post/src/components/header/writing-menu/index.js +++ b/packages/edit-post/src/components/header/writing-menu/index.js @@ -17,13 +17,13 @@ function WritingMenu() { } return ( - <MenuGroup - label={ _x( 'View', 'noun' ) } - > + <MenuGroup label={ _x( 'View', 'noun' ) }> <FeatureToggle feature="fixedToolbar" label={ __( 'Top toolbar' ) } - info={ __( 'Access all block and document tools in a single place' ) } + info={ __( + 'Access all block and document tools in a single place' + ) } messageActivated={ __( 'Top toolbar activated' ) } messageDeactivated={ __( 'Top toolbar deactivated' ) } /> diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js b/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js index 16181ac4c47761..04e3ebf7e75fc0 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/index.js @@ -28,16 +28,20 @@ const ShortcutList = ( { shortcuts } ) => ( * Safari+VoiceOver won't announce the list otherwise. */ /* eslint-disable jsx-a11y/no-redundant-roles */ - <ul className="edit-post-keyboard-shortcut-help-modal__shortcut-list" role="list"> + <ul + className="edit-post-keyboard-shortcut-help-modal__shortcut-list" + role="list" + > { shortcuts.map( ( shortcut, index ) => ( <li className="edit-post-keyboard-shortcut-help-modal__shortcut" key={ index } > - { isString( shortcut ) ? - <DynamicShortcut name={ shortcut } /> : + { isString( shortcut ) ? ( + <DynamicShortcut name={ shortcut } /> + ) : ( <Shortcut { ...shortcut } /> - } + ) } </li> ) ) } </ul> @@ -45,7 +49,12 @@ const ShortcutList = ( { shortcuts } ) => ( ); const ShortcutSection = ( { title, shortcuts, className } ) => ( - <section className={ classnames( 'edit-post-keyboard-shortcut-help-modal__section', className ) }> + <section + className={ classnames( + 'edit-post-keyboard-shortcut-help-modal__section', + className + ) } + > { !! title && ( <h2 className="edit-post-keyboard-shortcut-help-modal__section-title"> { title } @@ -56,7 +65,9 @@ const ShortcutSection = ( { title, shortcuts, className } ) => ( ); export function KeyboardShortcutHelpModal( { isModalActive, toggleModal } ) { - useShortcut( 'core/edit-post/keyboard-shortcuts', toggleModal, { bindGlobal: true } ); + useShortcut( 'core/edit-post/keyboard-shortcuts', toggleModal, { + bindGlobal: true, + } ); if ( ! isModalActive ) { return null; @@ -103,7 +114,9 @@ export function KeyboardShortcutHelpModal( { isModalActive, toggleModal } ) { 'core/block-editor/insert-after', { keyCombination: { character: '/' }, - description: __( 'Change the block type after adding a new paragraph.' ), + description: __( + 'Change the block type after adding a new paragraph.' + ), /* translators: The forward-slash character. e.g. '/'. */ ariaLabel: __( 'Forward-slash' ), }, @@ -122,13 +135,11 @@ export default compose( [ isModalActive: select( 'core/edit-post' ).isModalActive( MODAL_NAME ), } ) ), withDispatch( ( dispatch, { isModalActive } ) => { - const { - openModal, - closeModal, - } = dispatch( 'core/edit-post' ); + const { openModal, closeModal } = dispatch( 'core/edit-post' ); return { - toggleModal: () => isModalActive ? closeModal() : openModal( MODAL_NAME ), + toggleModal: () => + isModalActive ? closeModal() : openModal( MODAL_NAME ), }; } ), ] )( KeyboardShortcutHelpModal ); diff --git a/packages/edit-post/src/components/keyboard-shortcut-help-modal/shortcut.js b/packages/edit-post/src/components/keyboard-shortcut-help-modal/shortcut.js index a0c810528908a4..aceed29d2f189f 100644 --- a/packages/edit-post/src/components/keyboard-shortcut-help-modal/shortcut.js +++ b/packages/edit-post/src/components/keyboard-shortcut-help-modal/shortcut.js @@ -10,12 +10,16 @@ import { Fragment } from '@wordpress/element'; import { displayShortcutList, shortcutAriaLabel } from '@wordpress/keycodes'; function KeyCombination( { keyCombination, forceAriaLabel } ) { - const shortcut = keyCombination.modifier ? - displayShortcutList[ keyCombination.modifier ]( keyCombination.character ) : - keyCombination.character; - const ariaLabel = keyCombination.modifier ? - shortcutAriaLabel[ keyCombination.modifier ]( keyCombination.character ) : - keyCombination.character; + const shortcut = keyCombination.modifier + ? displayShortcutList[ keyCombination.modifier ]( + keyCombination.character + ) + : keyCombination.character; + const ariaLabel = keyCombination.modifier + ? shortcutAriaLabel[ keyCombination.modifier ]( + keyCombination.character + ) + : keyCombination.character; return ( <kbd @@ -24,11 +28,7 @@ function KeyCombination( { keyCombination, forceAriaLabel } ) { > { castArray( shortcut ).map( ( character, index ) => { if ( character === '+' ) { - return ( - <Fragment key={ index }> - { character } - </Fragment> - ); + return <Fragment key={ index }>{ character }</Fragment>; } return ( @@ -51,9 +51,16 @@ function Shortcut( { description, keyCombination, aliases = [], ariaLabel } ) { { description } </div> <div className="edit-post-keyboard-shortcut-help-modal__shortcut-term"> - <KeyCombination keyCombination={ keyCombination } forceAriaLabel={ ariaLabel } /> + <KeyCombination + keyCombination={ keyCombination } + forceAriaLabel={ ariaLabel } + /> { aliases.map( ( alias, index ) => ( - <KeyCombination keyCombination={ alias } forceAriaLabel={ ariaLabel } key={ index } /> + <KeyCombination + keyCombination={ alias } + forceAriaLabel={ ariaLabel } + key={ index } + /> ) ) } </div> </> diff --git a/packages/edit-post/src/components/keyboard-shortcuts/index.js b/packages/edit-post/src/components/keyboard-shortcuts/index.js index 8655247797a25d..d261551e43113b 100644 --- a/packages/edit-post/src/components/keyboard-shortcuts/index.js +++ b/packages/edit-post/src/components/keyboard-shortcuts/index.js @@ -16,9 +16,11 @@ function KeyboardShortcuts() { } = useSelect( ( select ) => { const settings = select( 'core/editor' ).getEditorSettings(); return { - getBlockSelectionStart: select( 'core/block-editor' ).getBlockSelectionStart, + getBlockSelectionStart: select( 'core/block-editor' ) + .getBlockSelectionStart, getEditorMode: select( 'core/edit-post' ).getEditorMode, - isEditorSidebarOpened: select( 'core/edit-post' ).isEditorSidebarOpened, + isEditorSidebarOpened: select( 'core/edit-post' ) + .isEditorSidebarOpened, richEditingEnabled: settings.richEditingEnabled, codeEditingEnabled: settings.codeEditingEnabled, }; @@ -70,10 +72,12 @@ function KeyboardShortcuts() { modifier: 'ctrl', character: '`', }, - aliases: [ { - modifier: 'access', - character: 'n', - } ], + aliases: [ + { + modifier: 'access', + character: 'n', + }, + ], } ); registerShortcut( { @@ -84,10 +88,12 @@ function KeyboardShortcuts() { modifier: 'ctrlShift', character: '`', }, - aliases: [ { - modifier: 'access', - character: 'p', - } ], + aliases: [ + { + modifier: 'access', + character: 'p', + }, + ], } ); registerShortcut( { @@ -101,22 +107,37 @@ function KeyboardShortcuts() { } ); }, [] ); - useShortcut( 'core/edit-post/toggle-mode', () => { - switchEditorMode( getEditorMode() === 'visual' ? 'text' : 'visual' ); - }, { bindGlobal: true, isDisabled: ! richEditingEnabled || ! codeEditingEnabled } ); + useShortcut( + 'core/edit-post/toggle-mode', + () => { + switchEditorMode( + getEditorMode() === 'visual' ? 'text' : 'visual' + ); + }, + { + bindGlobal: true, + isDisabled: ! richEditingEnabled || ! codeEditingEnabled, + } + ); - useShortcut( 'core/edit-post/toggle-sidebar', ( event ) => { - // This shortcut has no known clashes, but use preventDefault to prevent any - // obscure shortcuts from triggering. - event.preventDefault(); + useShortcut( + 'core/edit-post/toggle-sidebar', + ( event ) => { + // This shortcut has no known clashes, but use preventDefault to prevent any + // obscure shortcuts from triggering. + event.preventDefault(); - if ( isEditorSidebarOpened() ) { - closeGeneralSidebar(); - } else { - const sidebarToOpen = getBlockSelectionStart() ? 'edit-post/block' : 'edit-post/document'; - openGeneralSidebar( sidebarToOpen ); - } - }, { bindGlobal: true } ); + if ( isEditorSidebarOpened() ) { + closeGeneralSidebar(); + } else { + const sidebarToOpen = getBlockSelectionStart() + ? 'edit-post/block' + : 'edit-post/document'; + openGeneralSidebar( sidebarToOpen ); + } + }, + { bindGlobal: true } + ); return null; } diff --git a/packages/edit-post/src/components/layout/index.js b/packages/edit-post/src/components/layout/index.js index 09a3f485d13e0c..adefc6eb48d83d 100644 --- a/packages/edit-post/src/components/layout/index.js +++ b/packages/edit-post/src/components/layout/index.js @@ -52,7 +52,9 @@ import WelcomeGuide from '../welcome-guide'; function Layout() { const isMobileViewport = useViewportMatch( 'small', '<' ); - const { closePublishSidebar, togglePublishSidebar } = useDispatch( 'core/edit-post' ); + const { closePublishSidebar, togglePublishSidebar } = useDispatch( + 'core/edit-post' + ); const { mode, isRichEditingEnabled, @@ -65,21 +67,37 @@ function Layout() { previousShortcut, nextShortcut, } = useSelect( ( select ) => { - return ( { - hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ), - editorSidebarOpened: select( 'core/edit-post' ).isEditorSidebarOpened(), - pluginSidebarOpened: select( 'core/edit-post' ).isPluginSidebarOpened(), - publishSidebarOpened: select( 'core/edit-post' ).isPublishSidebarOpened(), + return { + hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( + 'fixedToolbar' + ), + editorSidebarOpened: select( + 'core/edit-post' + ).isEditorSidebarOpened(), + pluginSidebarOpened: select( + 'core/edit-post' + ).isPluginSidebarOpened(), + publishSidebarOpened: select( + 'core/edit-post' + ).isPublishSidebarOpened(), mode: select( 'core/edit-post' ).getEditorMode(), - isRichEditingEnabled: select( 'core/editor' ).getEditorSettings().richEditingEnabled, + isRichEditingEnabled: select( 'core/editor' ).getEditorSettings() + .richEditingEnabled, hasActiveMetaboxes: select( 'core/edit-post' ).hasMetaBoxes(), isSaving: select( 'core/edit-post' ).isSavingMetaBoxes(), - previousShortcut: select( 'core/keyboard-shortcuts' ).getAllShortcutRawKeyCombinations( 'core/edit-post/previous-region' ), - nextShortcut: select( 'core/keyboard-shortcuts' ).getAllShortcutRawKeyCombinations( 'core/edit-post/next-region' ), - } ); + previousShortcut: select( + 'core/keyboard-shortcuts' + ).getAllShortcutRawKeyCombinations( + 'core/edit-post/previous-region' + ), + nextShortcut: select( + 'core/keyboard-shortcuts' + ).getAllShortcutRawKeyCombinations( 'core/edit-post/next-region' ), + }; }, [] ); const showPageTemplatePicker = __experimentalUsePageTemplatePickerVisible(); - const sidebarIsOpened = editorSidebarOpened || pluginSidebarOpened || publishSidebarOpened; + const sidebarIsOpened = + editorSidebarOpened || pluginSidebarOpened || publishSidebarOpened; const className = classnames( 'edit-post-layout', 'is-mode-' + mode, { 'is-sidebar-opened': sidebarIsOpened, 'has-fixed-toolbar': hasFixedToolbar, @@ -99,49 +117,66 @@ function Layout() { <EditorRegions className={ className } header={ <Header /> } - sidebar={ ! publishSidebarOpened && ( - <> - <SettingsSidebar /> - <Sidebar.Slot /> - </> - ) } + sidebar={ + ! publishSidebarOpened && ( + <> + <SettingsSidebar /> + <Sidebar.Slot /> + </> + ) + } content={ <> <EditorNotices /> - { ( mode === 'text' || ! isRichEditingEnabled ) && <TextEditor /> } - { isRichEditingEnabled && mode === 'visual' && <VisualEditor /> } + { ( mode === 'text' || ! isRichEditingEnabled ) && ( + <TextEditor /> + ) } + { isRichEditingEnabled && mode === 'visual' && ( + <VisualEditor /> + ) } <div className="edit-post-layout__metaboxes"> <MetaBoxes location="normal" /> <MetaBoxes location="advanced" /> </div> - { isMobileViewport && sidebarIsOpened && <ScrollLock /> } + { isMobileViewport && sidebarIsOpened && ( + <ScrollLock /> + ) } </> } - footer={ isRichEditingEnabled && mode === 'visual' && ( - <div className="edit-post-layout__footer"> - <BlockBreadcrumb /> - </div> - ) } - publish={ publishSidebarOpened ? ( - <PostPublishPanel - onClose={ closePublishSidebar } - forceIsDirty={ hasActiveMetaboxes } - forceIsSaving={ isSaving } - PrePublishExtension={ PluginPrePublishPanel.Slot } - PostPublishExtension={ PluginPostPublishPanel.Slot } - /> - ) : ( - <div className="edit-post-toggle-publish-panel"> - <Button - isSecondary - className="edit-post-toggle-publish-panel__button" - onClick={ togglePublishSidebar } - aria-expanded={ false } - > - { __( 'Open publish panel' ) } - </Button> - </div> - ) } + footer={ + isRichEditingEnabled && + mode === 'visual' && ( + <div className="edit-post-layout__footer"> + <BlockBreadcrumb /> + </div> + ) + } + publish={ + publishSidebarOpened ? ( + <PostPublishPanel + onClose={ closePublishSidebar } + forceIsDirty={ hasActiveMetaboxes } + forceIsSaving={ isSaving } + PrePublishExtension={ + PluginPrePublishPanel.Slot + } + PostPublishExtension={ + PluginPostPublishPanel.Slot + } + /> + ) : ( + <div className="edit-post-toggle-publish-panel"> + <Button + isSecondary + className="edit-post-toggle-publish-panel__button" + onClick={ togglePublishSidebar } + aria-expanded={ false } + > + { __( 'Open publish panel' ) } + </Button> + </div> + ) + } shortcuts={ { previous: previousShortcut, next: nextShortcut, @@ -153,9 +188,10 @@ function Layout() { <WelcomeGuide /> <Popover.Slot /> <PluginArea /> - { showPageTemplatePicker && <__experimentalPageTemplatePicker /> } + { showPageTemplatePicker && ( + <__experimentalPageTemplatePicker /> + ) } </FocusReturnProvider> - </> ); } diff --git a/packages/edit-post/src/components/layout/index.native.js b/packages/edit-post/src/components/layout/index.native.js index f4925702eb2a51..54ec6aa801e60a 100644 --- a/packages/edit-post/src/components/layout/index.native.js +++ b/packages/edit-post/src/components/layout/index.native.js @@ -10,9 +10,17 @@ import { sendNativeEditorDidLayout } from 'react-native-gutenberg-bridge'; */ import { Component } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; -import { BottomSheetSettings, __experimentalPageTemplatePicker, __experimentalWithPageTemplatePickerVisible } from '@wordpress/block-editor'; +import { + BottomSheetSettings, + __experimentalPageTemplatePicker, + __experimentalWithPageTemplatePickerVisible, +} from '@wordpress/block-editor'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; -import { HTMLTextInput, KeyboardAvoidingView, ReadableContentView } from '@wordpress/components'; +import { + HTMLTextInput, + KeyboardAvoidingView, + ReadableContentView, +} from '@wordpress/components'; import { AutosaveMonitor } from '@wordpress/editor'; /** @@ -36,16 +44,24 @@ class Layout extends Component { isFullyBordered: true, }; - SafeArea.getSafeAreaInsetsForRootView().then( this.onSafeAreaInsetsUpdate ); + SafeArea.getSafeAreaInsetsForRootView().then( + this.onSafeAreaInsetsUpdate + ); } componentDidMount() { this._isMounted = true; - SafeArea.addEventListener( 'safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsUpdate ); + SafeArea.addEventListener( + 'safeAreaInsetsForRootViewDidChange', + this.onSafeAreaInsetsUpdate + ); } componentWillUnmount() { - SafeArea.removeEventListener( 'safeAreaInsetsForRootViewDidChange', this.onSafeAreaInsetsUpdate ); + SafeArea.removeEventListener( + 'safeAreaInsetsForRootViewDidChange', + this.onSafeAreaInsetsUpdate + ); this._isMounted = false; } @@ -76,15 +92,11 @@ class Layout extends Component { } renderHTML() { - return ( - <HTMLTextInput parentHeight={ this.state.rootViewHeight } /> - ); + return <HTMLTextInput parentHeight={ this.state.rootViewHeight } />; } renderVisual() { - const { - isReady, - } = this.props; + const { isReady } = this.props; if ( ! isReady ) { return null; @@ -108,7 +120,10 @@ class Layout extends Component { const isHtmlView = mode === 'text'; // add a margin view at the bottom for the header - const marginBottom = Platform.OS === 'android' && ! isHtmlView ? headerToolbarStyles.container.height : 0; + const marginBottom = + Platform.OS === 'android' && ! isHtmlView + ? headerToolbarStyles.container.height + : 0; const toolbarKeyboardAvoidingViewStyle = { ...styles.toolbarKeyboardAvoidingView, @@ -118,21 +133,41 @@ class Layout extends Component { }; return ( - <SafeAreaView style={ getStylesFromColorScheme( styles.container, styles.containerDark ) } onLayout={ this.onRootViewLayout }> + <SafeAreaView + style={ getStylesFromColorScheme( + styles.container, + styles.containerDark + ) } + onLayout={ this.onRootViewLayout } + > <AutosaveMonitor /> - <View style={ getStylesFromColorScheme( styles.background, styles.backgroundDark ) }> + <View + style={ getStylesFromColorScheme( + styles.background, + styles.backgroundDark + ) } + > { isHtmlView ? this.renderHTML() : this.renderVisual() } </View> - <View style={ { flex: 0, flexBasis: marginBottom, height: marginBottom } } /> + <View + style={ { + flex: 0, + flexBasis: marginBottom, + height: marginBottom, + } } + /> { ! isHtmlView && ( <KeyboardAvoidingView parentHeight={ this.state.rootViewHeight } style={ toolbarKeyboardAvoidingViewStyle } > - { showPageTemplatePicker && <__experimentalPageTemplatePicker /> } + { showPageTemplatePicker && ( + <__experimentalPageTemplatePicker /> + ) } <Header /> <BottomSheetSettings /> - </KeyboardAvoidingView> ) } + </KeyboardAvoidingView> + ) } </SafeAreaView> ); } @@ -140,12 +175,10 @@ class Layout extends Component { export default compose( [ withSelect( ( select ) => { - const { - __unstableIsEditorReady: isEditorReady, - } = select( 'core/editor' ); - const { - getEditorMode, - } = select( 'core/edit-post' ); + const { __unstableIsEditorReady: isEditorReady } = select( + 'core/editor' + ); + const { getEditorMode } = select( 'core/edit-post' ); return { isReady: isEditorReady(), diff --git a/packages/edit-post/src/components/manage-blocks-modal/category.js b/packages/edit-post/src/components/manage-blocks-modal/category.js index c4beeebb642ea8..5df158fe254333 100644 --- a/packages/edit-post/src/components/manage-blocks-modal/category.js +++ b/packages/edit-post/src/components/manage-blocks-modal/category.js @@ -27,17 +27,14 @@ function BlockManagerCategory( { } ) { const settings = useContext( EditPostSettings ); const { allowedBlockTypes } = settings; - const filteredBlockTypes = useMemo( - () => { - if ( allowedBlockTypes === true ) { - return blockTypes; - } - return blockTypes.filter( ( { name } ) => { - return includes( allowedBlockTypes || [], name ); - } ); - }, - [ allowedBlockTypes, blockTypes ] - ); + const filteredBlockTypes = useMemo( () => { + if ( allowedBlockTypes === true ) { + return blockTypes; + } + return blockTypes.filter( ( { name } ) => { + return includes( allowedBlockTypes || [], name ); + } ); + }, [ allowedBlockTypes, blockTypes ] ); if ( ! filteredBlockTypes.length ) { return null; @@ -48,7 +45,8 @@ function BlockManagerCategory( { ...hiddenBlockTypes ); - const titleId = 'edit-post-manage-blocks-modal__category-title-' + instanceId; + const titleId = + 'edit-post-manage-blocks-modal__category-title-' + instanceId; const isAllChecked = checkedBlockNames.length === filteredBlockTypes.length; @@ -93,10 +91,7 @@ export default compose( [ }; } ), withDispatch( ( dispatch, ownProps ) => { - const { - showBlockTypes, - hideBlockTypes, - } = dispatch( 'core/edit-post' ); + const { showBlockTypes, hideBlockTypes } = dispatch( 'core/edit-post' ); return { toggleVisible( blockName, nextIsChecked ) { diff --git a/packages/edit-post/src/components/manage-blocks-modal/checklist.js b/packages/edit-post/src/components/manage-blocks-modal/checklist.js index 22ebb8139934a4..ab561b071fbb4c 100644 --- a/packages/edit-post/src/components/manage-blocks-modal/checklist.js +++ b/packages/edit-post/src/components/manage-blocks-modal/checklist.js @@ -18,12 +18,12 @@ function BlockTypesChecklist( { blockTypes, value, onItemChange } ) { className="edit-post-manage-blocks-modal__checklist-item" > <CheckboxControl - label={ ( + label={ <> { blockType.title } <BlockIcon icon={ blockType.icon } /> </> - ) } + } checked={ value.includes( blockType.name ) } onChange={ partial( onItemChange, blockType.name ) } /> diff --git a/packages/edit-post/src/components/manage-blocks-modal/manager.js b/packages/edit-post/src/components/manage-blocks-modal/manager.js index 727ddc4c75e815..bdb8349be888f7 100644 --- a/packages/edit-post/src/components/manage-blocks-modal/manager.js +++ b/packages/edit-post/src/components/manage-blocks-modal/manager.js @@ -28,11 +28,13 @@ function BlockManager( { // Filtering occurs here (as opposed to `withSelect`) to avoid wasted // wasted renders by consequence of `Array#filter` producing a new // value reference on each call. - blockTypes = blockTypes.filter( ( blockType ) => ( - hasBlockSupport( blockType, 'inserter', true ) && - ( ! search || isMatchingSearchTerm( blockType, search ) ) && - ( ! blockType.parent || includes( blockType.parent, 'core/post-content' ) ) - ) ); + blockTypes = blockTypes.filter( + ( blockType ) => + hasBlockSupport( blockType, 'inserter', true ) && + ( ! search || isMatchingSearchTerm( blockType, search ) ) && + ( ! blockType.parent || + includes( blockType.parent, 'core/post-content' ) ) + ); return ( <div className="edit-post-manage-blocks-modal__content"> @@ -40,23 +42,23 @@ function BlockManager( { type="search" label={ __( 'Search for a block' ) } value={ search } - onChange={ ( nextSearch ) => setState( { - search: nextSearch, - } ) } + onChange={ ( nextSearch ) => + setState( { + search: nextSearch, + } ) + } className="edit-post-manage-blocks-modal__search" /> { !! numberOfHiddenBlocks && ( <div className="edit-post-manage-blocks-modal__disabled-blocks-count"> - { - sprintf( - _n( - '%1$d block is disabled.', - '%1$d blocks are disabled.', - numberOfHiddenBlocks - ), + { sprintf( + _n( + '%1$d block is disabled.', + '%1$d blocks are disabled.', numberOfHiddenBlocks - ) - } + ), + numberOfHiddenBlocks + ) } </div> ) } <div @@ -95,7 +97,8 @@ export default compose( [ } = select( 'core/blocks' ); const { getPreference } = select( 'core/edit-post' ); const hiddenBlockTypes = getPreference( 'hiddenBlockTypes' ); - const numberOfHiddenBlocks = isArray( hiddenBlockTypes ) && hiddenBlockTypes.length; + const numberOfHiddenBlocks = + isArray( hiddenBlockTypes ) && hiddenBlockTypes.length; return { blockTypes: getBlockTypes(), diff --git a/packages/edit-post/src/components/manage-blocks-modal/show-all.js b/packages/edit-post/src/components/manage-blocks-modal/show-all.js index d024dfecca208f..62f2716089c002 100644 --- a/packages/edit-post/src/components/manage-blocks-modal/show-all.js +++ b/packages/edit-post/src/components/manage-blocks-modal/show-all.js @@ -15,10 +15,8 @@ export default function BlockManagerShowAll( { checked, onChange } ) { htmlFor={ id } className="edit-post-manage-blocks-modal__show-all-label" > - { - /* translators: Checkbox toggle label */ - __( 'Show section' ) - } + { /* translators: Checkbox toggle label */ + __( 'Show section' ) } </label> <FormToggle id={ id } diff --git a/packages/edit-post/src/components/meta-boxes/index.js b/packages/edit-post/src/components/meta-boxes/index.js index 20f10bd32a1ac6..35ef20f04a2df3 100644 --- a/packages/edit-post/src/components/meta-boxes/index.js +++ b/packages/edit-post/src/components/meta-boxes/index.js @@ -26,7 +26,9 @@ function MetaBoxes( { location, isVisible, metaBoxes } ) { } export default withSelect( ( select, { location } ) => { - const { isMetaBoxLocationVisible, getMetaBoxesPerLocation } = select( 'core/edit-post' ); + const { isMetaBoxLocationVisible, getMetaBoxesPerLocation } = select( + 'core/edit-post' + ); return { metaBoxes: getMetaBoxesPerLocation( location ), diff --git a/packages/edit-post/src/components/meta-boxes/meta-box-visibility.js b/packages/edit-post/src/components/meta-boxes/meta-box-visibility.js index 970cf26355e6ca..c6af87bebb2607 100644 --- a/packages/edit-post/src/components/meta-boxes/meta-box-visibility.js +++ b/packages/edit-post/src/components/meta-boxes/meta-box-visibility.js @@ -36,5 +36,7 @@ class MetaBoxVisibility extends Component { } export default withSelect( ( select, { id } ) => ( { - isVisible: select( 'core/edit-post' ).isEditorPanelEnabled( `meta-box-${ id }` ), + isVisible: select( 'core/edit-post' ).isEditorPanelEnabled( + `meta-box-${ id }` + ), } ) )( MetaBoxVisibility ); diff --git a/packages/edit-post/src/components/meta-boxes/meta-boxes-area/index.js b/packages/edit-post/src/components/meta-boxes/meta-boxes-area/index.js index 9a32498c2c2757..239d013be69b98 100644 --- a/packages/edit-post/src/components/meta-boxes/meta-boxes-area/index.js +++ b/packages/edit-post/src/components/meta-boxes/meta-boxes-area/index.js @@ -23,7 +23,9 @@ class MetaBoxesArea extends Component { * @inheritdoc */ componentDidMount() { - this.form = document.querySelector( '.metabox-location-' + this.props.location ); + this.form = document.querySelector( + '.metabox-location-' + this.props.location + ); if ( this.form ) { this.container.appendChild( this.form ); } @@ -64,7 +66,10 @@ class MetaBoxesArea extends Component { return ( <div className={ classes }> { isSaving && <Spinner /> } - <div className="edit-post-meta-boxes-area__container" ref={ this.bindContainerNode } /> + <div + className="edit-post-meta-boxes-area__container" + ref={ this.bindContainerNode } + /> <div className="edit-post-meta-boxes-area__clear" /> </div> ); diff --git a/packages/edit-post/src/components/options-modal/index.js b/packages/edit-post/src/components/options-modal/index.js index bd5b83fb3f5290..c03648bf2c0bfa 100644 --- a/packages/edit-post/src/components/options-modal/index.js +++ b/packages/edit-post/src/components/options-modal/index.js @@ -45,13 +45,21 @@ export function OptionsModal( { isModalActive, isViewable, closeModal } ) { onRequestClose={ closeModal } > <Section title={ __( 'General' ) }> - <EnablePublishSidebarOption label={ __( 'Pre-publish checks' ) } /> - <EnableFeature feature="showInserterHelpPanel" label={ __( 'Inserter help panel' ) } /> + <EnablePublishSidebarOption + label={ __( 'Pre-publish checks' ) } + /> + <EnableFeature + feature="showInserterHelpPanel" + label={ __( 'Inserter help panel' ) } + /> </Section> <Section title={ __( 'Document panels' ) }> <EnablePluginDocumentSettingPanelOption.Slot /> { isViewable && ( - <EnablePanelOption label={ __( 'Permalink' ) } panelName="post-link" /> + <EnablePanelOption + label={ __( 'Permalink' ) } + panelName="post-link" + /> ) } <PostTaxonomies taxonomyWrapper={ ( content, taxonomy ) => ( @@ -62,16 +70,30 @@ export function OptionsModal( { isModalActive, isViewable, closeModal } ) { ) } /> <PostFeaturedImageCheck> - <EnablePanelOption label={ __( 'Featured image' ) } panelName="featured-image" /> + <EnablePanelOption + label={ __( 'Featured image' ) } + panelName="featured-image" + /> </PostFeaturedImageCheck> <PostExcerptCheck> - <EnablePanelOption label={ __( 'Excerpt' ) } panelName="post-excerpt" /> + <EnablePanelOption + label={ __( 'Excerpt' ) } + panelName="post-excerpt" + /> </PostExcerptCheck> - <PostTypeSupportCheck supportKeys={ [ 'comments', 'trackbacks' ] }> - <EnablePanelOption label={ __( 'Discussion' ) } panelName="discussion-panel" /> + <PostTypeSupportCheck + supportKeys={ [ 'comments', 'trackbacks' ] } + > + <EnablePanelOption + label={ __( 'Discussion' ) } + panelName="discussion-panel" + /> </PostTypeSupportCheck> <PageAttributesCheck> - <EnablePanelOption label={ __( 'Page attributes' ) } panelName="page-attributes" /> + <EnablePanelOption + label={ __( 'Page attributes' ) } + panelName="page-attributes" + /> </PageAttributesCheck> </Section> <MetaBoxesSection title={ __( 'Advanced panels' ) } /> @@ -86,7 +108,9 @@ export default compose( const postType = getPostType( getEditedPostAttribute( 'type' ) ); return { - isModalActive: select( 'core/edit-post' ).isModalActive( MODAL_NAME ), + isModalActive: select( 'core/edit-post' ).isModalActive( + MODAL_NAME + ), isViewable: get( postType, [ 'viewable' ], false ), }; } ), diff --git a/packages/edit-post/src/components/options-modal/meta-boxes-section.js b/packages/edit-post/src/components/options-modal/meta-boxes-section.js index 0443c47ee55603..802f8a88955a26 100644 --- a/packages/edit-post/src/components/options-modal/meta-boxes-section.js +++ b/packages/edit-post/src/components/options-modal/meta-boxes-section.js @@ -15,9 +15,16 @@ import { withSelect } from '@wordpress/data'; import Section from './section'; import { EnableCustomFieldsOption, EnablePanelOption } from './options'; -export function MetaBoxesSection( { areCustomFieldsRegistered, metaBoxes, ...sectionProps } ) { +export function MetaBoxesSection( { + areCustomFieldsRegistered, + metaBoxes, + ...sectionProps +} ) { // The 'Custom Fields' meta box is a special case that we handle separately. - const thirdPartyMetaBoxes = filter( metaBoxes, ( { id } ) => id !== 'postcustom' ); + const thirdPartyMetaBoxes = filter( + metaBoxes, + ( { id } ) => id !== 'postcustom' + ); if ( ! areCustomFieldsRegistered && thirdPartyMetaBoxes.length === 0 ) { return null; @@ -25,9 +32,15 @@ export function MetaBoxesSection( { areCustomFieldsRegistered, metaBoxes, ...sec return ( <Section { ...sectionProps }> - { areCustomFieldsRegistered && <EnableCustomFieldsOption label={ __( 'Custom fields' ) } /> } + { areCustomFieldsRegistered && ( + <EnableCustomFieldsOption label={ __( 'Custom fields' ) } /> + ) } { map( thirdPartyMetaBoxes, ( { id, title } ) => ( - <EnablePanelOption key={ id } label={ title } panelName={ `meta-box-${ id }` } /> + <EnablePanelOption + key={ id } + label={ title } + panelName={ `meta-box-${ id }` } + /> ) ) } </Section> ); @@ -39,7 +52,8 @@ export default withSelect( ( select ) => { return { // This setting should not live in the block editor's store. - areCustomFieldsRegistered: getEditorSettings().enableCustomFields !== undefined, + areCustomFieldsRegistered: + getEditorSettings().enableCustomFields !== undefined, metaBoxes: getAllMetaBoxes(), }; } )( MetaBoxesSection ); diff --git a/packages/edit-post/src/components/options-modal/options/enable-custom-fields.js b/packages/edit-post/src/components/options-modal/options/enable-custom-fields.js index bb74c77bf62c87..d93392635a3f91 100644 --- a/packages/edit-post/src/components/options-modal/options/enable-custom-fields.js +++ b/packages/edit-post/src/components/options-modal/options/enable-custom-fields.js @@ -17,7 +17,9 @@ export function CustomFieldsConfirmation( { willEnable } ) { return ( <> <p className="edit-post-options-modal__custom-fields-confirmation-message"> - { __( 'A page reload is required for this change. Make sure your content is saved before reloading.' ) } + { __( + 'A page reload is required for this change. Make sure your content is saved before reloading.' + ) } </p> <Button className="edit-post-options-modal__custom-fields-confirmation-button" @@ -26,10 +28,14 @@ export function CustomFieldsConfirmation( { willEnable } ) { disabled={ isReloading } onClick={ () => { setIsReloading( true ); - document.getElementById( 'toggle-custom-fields-form' ).submit(); + document + .getElementById( 'toggle-custom-fields-form' ) + .submit(); } } > - { willEnable ? __( 'Enable & Reload' ) : __( 'Disable & Reload' ) } + { willEnable + ? __( 'Enable & Reload' ) + : __( 'Disable & Reload' ) } </Button> </> ); @@ -44,11 +50,14 @@ export function EnableCustomFieldsOption( { label, areCustomFieldsEnabled } ) { isChecked={ isChecked } onChange={ setIsChecked } > - { isChecked !== areCustomFieldsEnabled && <CustomFieldsConfirmation willEnable={ isChecked } /> } + { isChecked !== areCustomFieldsEnabled && ( + <CustomFieldsConfirmation willEnable={ isChecked } /> + ) } </BaseOption> ); } export default withSelect( ( select ) => ( { - areCustomFieldsEnabled: !! select( 'core/editor' ).getEditorSettings().enableCustomFields, + areCustomFieldsEnabled: !! select( 'core/editor' ).getEditorSettings() + .enableCustomFields, } ) )( EnableCustomFieldsOption ); diff --git a/packages/edit-post/src/components/options-modal/options/enable-panel.js b/packages/edit-post/src/components/options-modal/options/enable-panel.js index d47ab67e7e6d92..57d134c0e6ef68 100644 --- a/packages/edit-post/src/components/options-modal/options/enable-panel.js +++ b/packages/edit-post/src/components/options-modal/options/enable-panel.js @@ -11,7 +11,9 @@ import BaseOption from './base'; export default compose( withSelect( ( select, { panelName } ) => { - const { isEditorPanelEnabled, isEditorPanelRemoved } = select( 'core/edit-post' ); + const { isEditorPanelEnabled, isEditorPanelRemoved } = select( + 'core/edit-post' + ); return { isRemoved: isEditorPanelRemoved( panelName ), isChecked: isEditorPanelEnabled( panelName ), @@ -19,6 +21,7 @@ export default compose( } ), ifCondition( ( { isRemoved } ) => ! isRemoved ), withDispatch( ( dispatch, { panelName } ) => ( { - onChange: () => dispatch( 'core/edit-post' ).toggleEditorPanelEnabled( panelName ), + onChange: () => + dispatch( 'core/edit-post' ).toggleEditorPanelEnabled( panelName ), } ) ) )( BaseOption ); diff --git a/packages/edit-post/src/components/options-modal/options/enable-plugin-document-setting-panel.js b/packages/edit-post/src/components/options-modal/options/enable-plugin-document-setting-panel.js index ac979c357ad782..b18b0436579bcb 100644 --- a/packages/edit-post/src/components/options-modal/options/enable-plugin-document-setting-panel.js +++ b/packages/edit-post/src/components/options-modal/options/enable-plugin-document-setting-panel.js @@ -8,14 +8,13 @@ import { createSlotFill } from '@wordpress/components'; */ import { EnablePanelOption } from './index'; -const { Fill, Slot } = createSlotFill( 'EnablePluginDocumentSettingPanelOption' ); +const { Fill, Slot } = createSlotFill( + 'EnablePluginDocumentSettingPanelOption' +); const EnablePluginDocumentSettingPanelOption = ( { label, panelName } ) => ( <Fill> - <EnablePanelOption - label={ label } - panelName={ panelName } - /> + <EnablePanelOption label={ label } panelName={ panelName } /> </Fill> ); diff --git a/packages/edit-post/src/components/options-modal/options/enable-publish-sidebar.js b/packages/edit-post/src/components/options-modal/options/enable-publish-sidebar.js index e080fae5781274..ce2bc2f7528dba 100644 --- a/packages/edit-post/src/components/options-modal/options/enable-publish-sidebar.js +++ b/packages/edit-post/src/components/options-modal/options/enable-publish-sidebar.js @@ -15,12 +15,15 @@ export default compose( isChecked: select( 'core/editor' ).isPublishSidebarEnabled(), } ) ), withDispatch( ( dispatch ) => { - const { enablePublishSidebar, disablePublishSidebar } = dispatch( 'core/editor' ); + const { enablePublishSidebar, disablePublishSidebar } = dispatch( + 'core/editor' + ); return { - onChange: ( isEnabled ) => ( isEnabled ? enablePublishSidebar() : disablePublishSidebar() ), + onChange: ( isEnabled ) => + isEnabled ? enablePublishSidebar() : disablePublishSidebar(), }; } ), // In < medium viewports we override this option and always show the publish sidebar. // See the edit-post's header component for the specific logic. - ifViewportMatches( 'medium' ), + ifViewportMatches( 'medium' ) )( BaseOption ); diff --git a/packages/edit-post/src/components/options-modal/options/test/enable-custom-fields.js b/packages/edit-post/src/components/options-modal/options/test/enable-custom-fields.js index 5ac60c92753af3..2ec4bc7af80e40 100644 --- a/packages/edit-post/src/components/options-modal/options/test/enable-custom-fields.js +++ b/packages/edit-post/src/components/options-modal/options/test/enable-custom-fields.js @@ -11,12 +11,17 @@ import { Button } from '@wordpress/components'; /** * Internal dependencies */ -import { EnableCustomFieldsOption, CustomFieldsConfirmation } from '../enable-custom-fields'; +import { + EnableCustomFieldsOption, + CustomFieldsConfirmation, +} from '../enable-custom-fields'; import BaseOption from '../base'; describe( 'EnableCustomFieldsOption', () => { it( 'renders a checked checkbox when custom fields are enabled', () => { - const renderer = TestRenderer.create( <EnableCustomFieldsOption areCustomFieldsEnabled /> ); + const renderer = TestRenderer.create( + <EnableCustomFieldsOption areCustomFieldsEnabled /> + ); expect( renderer ).toMatchSnapshot(); } ); @@ -28,7 +33,9 @@ describe( 'EnableCustomFieldsOption', () => { } ); it( 'renders an unchecked checkbox and a confirmation message when toggled off', () => { - const renderer = new TestRenderer.create( <EnableCustomFieldsOption areCustomFieldsEnabled /> ); + const renderer = new TestRenderer.create( + ( <EnableCustomFieldsOption areCustomFieldsEnabled /> ) + ); act( () => { renderer.root.findByType( BaseOption ).props.onChange( false ); } ); @@ -37,7 +44,7 @@ describe( 'EnableCustomFieldsOption', () => { it( 'renders a checked checkbox and a confirmation message when toggled on', () => { const renderer = new TestRenderer.create( - <EnableCustomFieldsOption areCustomFieldsEnabled={ false } /> + ( <EnableCustomFieldsOption areCustomFieldsEnabled={ false } /> ) ); act( () => { renderer.root.findByType( BaseOption ).props.onChange( true ); @@ -49,16 +56,22 @@ describe( 'EnableCustomFieldsOption', () => { describe( 'CustomFieldsConfirmation', () => { it( 'submits the toggle-custom-fields-form', () => { const submit = jest.fn(); - const getElementById = jest.spyOn( document, 'getElementById' ).mockImplementation( () => ( { - submit, - } ) ); + const getElementById = jest + .spyOn( document, 'getElementById' ) + .mockImplementation( () => ( { + submit, + } ) ); - const renderer = new TestRenderer.create( <CustomFieldsConfirmation /> ); + const renderer = new TestRenderer.create( + ( <CustomFieldsConfirmation /> ) + ); act( () => { renderer.root.findByType( Button ).props.onClick(); } ); - expect( getElementById ).toHaveBeenCalledWith( 'toggle-custom-fields-form' ); + expect( getElementById ).toHaveBeenCalledWith( + 'toggle-custom-fields-form' + ); expect( submit ).toHaveBeenCalled(); getElementById.mockRestore(); diff --git a/packages/edit-post/src/components/options-modal/test/meta-boxes-section.js b/packages/edit-post/src/components/options-modal/test/meta-boxes-section.js index 5cd95105ee34ea..b00d2fdffb512c 100644 --- a/packages/edit-post/src/components/options-modal/test/meta-boxes-section.js +++ b/packages/edit-post/src/components/options-modal/test/meta-boxes-section.js @@ -13,7 +13,9 @@ describe( 'MetaBoxesSection', () => { const wrapper = shallow( <MetaBoxesSection areCustomFieldsRegistered={ false } - metaBoxes={ [ { id: 'postcustom', title: 'This should not render' } ] } + metaBoxes={ [ + { id: 'postcustom', title: 'This should not render' }, + ] } /> ); expect( wrapper.isEmptyRender() ).toBe( true ); @@ -24,7 +26,9 @@ describe( 'MetaBoxesSection', () => { <MetaBoxesSection title="Advanced panels" areCustomFieldsRegistered - metaBoxes={ [ { id: 'postcustom', title: 'This should not render' } ] } + metaBoxes={ [ + { id: 'postcustom', title: 'This should not render' }, + ] } /> ); expect( wrapper ).toMatchSnapshot(); diff --git a/packages/edit-post/src/components/sidebar/discussion-panel/index.js b/packages/edit-post/src/components/sidebar/discussion-panel/index.js index 9bb36801995927..47ea5ae4e500b5 100644 --- a/packages/edit-post/src/components/sidebar/discussion-panel/index.js +++ b/packages/edit-post/src/components/sidebar/discussion-panel/index.js @@ -3,7 +3,11 @@ */ import { __ } from '@wordpress/i18n'; import { PanelBody, PanelRow } from '@wordpress/components'; -import { PostComments, PostPingbacks, PostTypeSupportCheck } from '@wordpress/editor'; +import { + PostComments, + PostPingbacks, + PostTypeSupportCheck, +} from '@wordpress/editor'; import { compose } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; @@ -19,7 +23,11 @@ function DiscussionPanel( { isEnabled, isOpened, onTogglePanel } ) { return ( <PostTypeSupportCheck supportKeys={ [ 'comments', 'trackbacks' ] }> - <PanelBody title={ __( 'Discussion' ) } opened={ isOpened } onToggle={ onTogglePanel }> + <PanelBody + title={ __( 'Discussion' ) } + opened={ isOpened } + onToggle={ onTogglePanel } + > <PostTypeSupportCheck supportKeys="comments"> <PanelRow> <PostComments /> @@ -39,14 +47,19 @@ function DiscussionPanel( { isEnabled, isOpened, onTogglePanel } ) { export default compose( [ withSelect( ( select ) => { return { - isEnabled: select( 'core/edit-post' ).isEditorPanelEnabled( PANEL_NAME ), - isOpened: select( 'core/edit-post' ).isEditorPanelOpened( PANEL_NAME ), + isEnabled: select( 'core/edit-post' ).isEditorPanelEnabled( + PANEL_NAME + ), + isOpened: select( 'core/edit-post' ).isEditorPanelOpened( + PANEL_NAME + ), }; } ), withDispatch( ( dispatch ) => ( { onTogglePanel() { - return dispatch( 'core/edit-post' ).toggleEditorPanelOpened( PANEL_NAME ); + return dispatch( 'core/edit-post' ).toggleEditorPanelOpened( + PANEL_NAME + ); }, } ) ), ] )( DiscussionPanel ); - diff --git a/packages/edit-post/src/components/sidebar/featured-image/index.js b/packages/edit-post/src/components/sidebar/featured-image/index.js index e6ff45262c1610..63a95fb76178b5 100644 --- a/packages/edit-post/src/components/sidebar/featured-image/index.js +++ b/packages/edit-post/src/components/sidebar/featured-image/index.js @@ -42,7 +42,9 @@ function FeaturedImage( { isEnabled, isOpened, postType, onTogglePanel } ) { const applyWithSelect = withSelect( ( select ) => { const { getEditedPostAttribute } = select( 'core/editor' ); const { getPostType } = select( 'core' ); - const { isEditorPanelEnabled, isEditorPanelOpened } = select( 'core/edit-post' ); + const { isEditorPanelEnabled, isEditorPanelOpened } = select( + 'core/edit-post' + ); return { postType: getPostType( getEditedPostAttribute( 'type' ) ), @@ -59,7 +61,4 @@ const applyWithDispatch = withDispatch( ( dispatch ) => { }; } ); -export default compose( - applyWithSelect, - applyWithDispatch, -)( FeaturedImage ); +export default compose( applyWithSelect, applyWithDispatch )( FeaturedImage ); diff --git a/packages/edit-post/src/components/sidebar/index.js b/packages/edit-post/src/components/sidebar/index.js index 790ce933fa9722..e6175b72b2685a 100644 --- a/packages/edit-post/src/components/sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/index.js @@ -6,7 +6,11 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { createSlotFill, withFocusReturn, Animate } from '@wordpress/components'; +import { + createSlotFill, + withFocusReturn, + Animate, +} from '@wordpress/components'; import { withSelect } from '@wordpress/data'; import { ifCondition, compose } from '@wordpress/compose'; @@ -27,7 +31,9 @@ function Sidebar( { children, className } ) { Sidebar = withFocusReturn( { onFocusReturn() { - const button = document.querySelector( '.edit-post-header__settings [aria-label="Settings"]' ); + const button = document.querySelector( + '.edit-post-header__settings [aria-label="Settings"]' + ); if ( button ) { button.focus(); return false; @@ -47,9 +53,10 @@ function AnimatedSidebarFill( props ) { const WrappedSidebar = compose( withSelect( ( select, { name } ) => ( { - isActive: select( 'core/edit-post' ).getActiveGeneralSidebarName() === name, + isActive: + select( 'core/edit-post' ).getActiveGeneralSidebarName() === name, } ) ), - ifCondition( ( { isActive } ) => isActive ), + ifCondition( ( { isActive } ) => isActive ) )( AnimatedSidebarFill ); WrappedSidebar.Slot = Slot; diff --git a/packages/edit-post/src/components/sidebar/page-attributes/index.js b/packages/edit-post/src/components/sidebar/page-attributes/index.js index 5095edf98b34bd..97e7199ef41871 100644 --- a/packages/edit-post/src/components/sidebar/page-attributes/index.js +++ b/packages/edit-post/src/components/sidebar/page-attributes/index.js @@ -9,7 +9,12 @@ import { get, partial } from 'lodash'; import { __ } from '@wordpress/i18n'; import { PanelBody, PanelRow } from '@wordpress/components'; import { compose } from '@wordpress/compose'; -import { PageAttributesCheck, PageAttributesOrder, PageAttributesParent, PageTemplate } from '@wordpress/editor'; +import { + PageAttributesCheck, + PageAttributesOrder, + PageAttributesParent, + PageTemplate, +} from '@wordpress/editor'; import { withSelect, withDispatch } from '@wordpress/data'; /** @@ -17,14 +22,23 @@ import { withSelect, withDispatch } from '@wordpress/data'; */ const PANEL_NAME = 'page-attributes'; -export function PageAttributes( { isEnabled, isOpened, onTogglePanel, postType } ) { +export function PageAttributes( { + isEnabled, + isOpened, + onTogglePanel, + postType, +} ) { if ( ! isEnabled || ! postType ) { return null; } return ( <PageAttributesCheck> <PanelBody - title={ get( postType, [ 'labels', 'attributes' ], __( 'Page attributes' ) ) } + title={ get( + postType, + [ 'labels', 'attributes' ], + __( 'Page attributes' ) + ) } opened={ isOpened } onToggle={ onTogglePanel } > @@ -40,7 +54,9 @@ export function PageAttributes( { isEnabled, isOpened, onTogglePanel, postType } const applyWithSelect = withSelect( ( select ) => { const { getEditedPostAttribute } = select( 'core/editor' ); - const { isEditorPanelEnabled, isEditorPanelOpened } = select( 'core/edit-post' ); + const { isEditorPanelEnabled, isEditorPanelOpened } = select( + 'core/edit-post' + ); const { getPostType } = select( 'core' ); return { isEnabled: isEditorPanelEnabled( PANEL_NAME ), @@ -57,7 +73,4 @@ const applyWithDispatch = withDispatch( ( dispatch ) => { }; } ); -export default compose( - applyWithSelect, - applyWithDispatch, -)( PageAttributes ); +export default compose( applyWithSelect, applyWithDispatch )( PageAttributes ); diff --git a/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js b/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js index 62a651c7f0c24e..757e4965e5d991 100644 --- a/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-document-setting-panel/index.js @@ -17,7 +17,16 @@ import { EnablePluginDocumentSettingPanelOption } from '../../options-modal/opti export const { Fill, Slot } = createSlotFill( 'PluginDocumentSettingPanel' ); -const PluginDocumentSettingFill = ( { isEnabled, panelName, opened, onToggle, className, title, icon, children } ) => { +const PluginDocumentSettingFill = ( { + isEnabled, + panelName, + opened, + onToggle, + className, + title, + icon, + children, +} ) => { return ( <> <EnablePluginDocumentSettingPanelOption @@ -99,18 +108,20 @@ const PluginDocumentSettingPanel = compose( }; } ), withSelect( ( select, { panelName } ) => { - return ( - { - opened: select( 'core/edit-post' ).isEditorPanelOpened( panelName ), - isEnabled: select( 'core/edit-post' ).isEditorPanelEnabled( panelName ), - } - ); + return { + opened: select( 'core/edit-post' ).isEditorPanelOpened( panelName ), + isEnabled: select( 'core/edit-post' ).isEditorPanelEnabled( + panelName + ), + }; } ), withDispatch( ( dispatch, { panelName } ) => ( { onToggle() { - return dispatch( 'core/edit-post' ).toggleEditorPanelOpened( panelName ); + return dispatch( 'core/edit-post' ).toggleEditorPanelOpened( + panelName + ); }, - } ) ), + } ) ) )( PluginDocumentSettingFill ); PluginDocumentSettingPanel.Slot = Slot; diff --git a/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/index.js b/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/index.js index b1c53372857023..43d1fc2d1eaac4 100644 --- a/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/index.js @@ -7,7 +7,13 @@ import { createSlotFill, PanelBody } from '@wordpress/components'; const { Fill, Slot } = createSlotFill( 'PluginPostPublishPanel' ); -const PluginPostPublishPanelFill = ( { children, className, title, initialOpen = false, icon } ) => ( +const PluginPostPublishPanelFill = ( { + children, + className, + title, + initialOpen = false, + icon, +} ) => ( <Fill> <PanelBody className={ className } @@ -73,7 +79,7 @@ const PluginPostPublishPanel = compose( return { icon: ownProps.icon || context.icon, }; - } ), + } ) )( PluginPostPublishPanelFill ); PluginPostPublishPanel.Slot = Slot; diff --git a/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/test/index.js b/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/test/index.js index f4b5ca77eca163..d140251917559f 100644 --- a/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/test/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-post-publish-panel/test/index.js @@ -19,7 +19,7 @@ describe( 'PluginPostPublishPanel', () => { title="My panel title" initialOpen={ true } > - My panel content + My panel content </PluginPostPublishPanel> <PluginPostPublishPanel.Slot /> </SlotFillProvider>, diff --git a/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js b/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js index efff9ba774d9ed..10259358e764f9 100644 --- a/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-post-status-info/index.js @@ -53,9 +53,7 @@ export const { Fill, Slot } = createSlotFill( 'PluginPostStatusInfo' ); */ const PluginPostStatusInfo = ( { children, className } ) => ( <Fill> - <PanelRow className={ className }> - { children } - </PanelRow> + <PanelRow className={ className }>{ children }</PanelRow> </Fill> ); diff --git a/packages/edit-post/src/components/sidebar/plugin-post-status-info/test/index.js b/packages/edit-post/src/components/sidebar/plugin-post-status-info/test/index.js index ea7b173a42028f..033f9fad709bf9 100644 --- a/packages/edit-post/src/components/sidebar/plugin-post-status-info/test/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-post-status-info/test/index.js @@ -17,9 +17,7 @@ describe( 'PluginPostStatusInfo', () => { test( 'renders fill properly', () => { const tree = ReactTestRenderer.create( <SlotFillProvider> - <PluginPostStatusInfo - className="my-plugin-post-status-info" - > + <PluginPostStatusInfo className="my-plugin-post-status-info"> My plugin post status info </PluginPostStatusInfo> <PluginPostStatusInfo.Slot /> diff --git a/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/index.js b/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/index.js index 13863b24f16d50..db6900995db37d 100644 --- a/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-pre-publish-panel/index.js @@ -6,7 +6,13 @@ import { compose } from '@wordpress/compose'; import { withPluginContext } from '@wordpress/plugins'; const { Fill, Slot } = createSlotFill( 'PluginPrePublishPanel' ); -const PluginPrePublishPanelFill = ( { children, className, title, initialOpen = false, icon } ) => ( +const PluginPrePublishPanelFill = ( { + children, + className, + title, + initialOpen = false, + icon, +} ) => ( <Fill> <PanelBody className={ className } @@ -75,7 +81,7 @@ const PluginPrePublishPanel = compose( return { icon: ownProps.icon || context.icon, }; - } ), + } ) )( PluginPrePublishPanelFill ); PluginPrePublishPanel.Slot = Slot; diff --git a/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js b/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js index e0977effb87a92..179463f8aeeac2 100644 --- a/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/plugin-sidebar/index.js @@ -32,33 +32,35 @@ function PluginSidebar( props ) { <> { isPinnable && ( <PinnedPlugins> - { isPinned && <Button - icon={ icon } - label={ title } - onClick={ toggleSidebar } - isPressed={ isActive } - aria-expanded={ isActive } - /> } + { isPinned && ( + <Button + icon={ icon } + label={ title } + onClick={ toggleSidebar } + isPressed={ isActive } + aria-expanded={ isActive } + /> + ) } </PinnedPlugins> ) } <Sidebar name={ sidebarName }> - <SidebarHeader - closeLabel={ __( 'Close plugin' ) } - > + <SidebarHeader closeLabel={ __( 'Close plugin' ) }> <strong>{ title }</strong> { isPinnable && ( <Button icon={ isPinned ? 'star-filled' : 'star-empty' } - label={ isPinned ? __( 'Unpin from toolbar' ) : __( 'Pin to toolbar' ) } + label={ + isPinned + ? __( 'Unpin from toolbar' ) + : __( 'Pin to toolbar' ) + } onClick={ togglePin } isPressed={ isPinned } aria-expanded={ isPinned } /> ) } </SidebarHeader> - <Panel className={ className }> - { children } - </Panel> + <Panel className={ className }>{ children }</Panel> </Sidebar> </> ); @@ -136,10 +138,9 @@ export default compose( }; } ), withSelect( ( select, { sidebarName } ) => { - const { - getActiveGeneralSidebarName, - isPluginItemPinned, - } = select( 'core/edit-post' ); + const { getActiveGeneralSidebarName, isPluginItemPinned } = select( + 'core/edit-post' + ); return { isActive: getActiveGeneralSidebarName() === sidebarName, @@ -165,5 +166,5 @@ export default compose( } }, }; - } ), + } ) )( PluginSidebar ); diff --git a/packages/edit-post/src/components/sidebar/post-author/index.js b/packages/edit-post/src/components/sidebar/post-author/index.js index 3a876320d70ad6..5febcfc14b5a2a 100644 --- a/packages/edit-post/src/components/sidebar/post-author/index.js +++ b/packages/edit-post/src/components/sidebar/post-author/index.js @@ -2,7 +2,10 @@ * WordPress dependencies */ import { PanelRow } from '@wordpress/components'; -import { PostAuthor as PostAuthorForm, PostAuthorCheck } from '@wordpress/editor'; +import { + PostAuthor as PostAuthorForm, + PostAuthorCheck, +} from '@wordpress/editor'; export function PostAuthor() { return ( diff --git a/packages/edit-post/src/components/sidebar/post-excerpt/index.js b/packages/edit-post/src/components/sidebar/post-excerpt/index.js index 5b9a96cd174d24..1502bc7e7a3de2 100644 --- a/packages/edit-post/src/components/sidebar/post-excerpt/index.js +++ b/packages/edit-post/src/components/sidebar/post-excerpt/index.js @@ -3,7 +3,10 @@ */ import { __ } from '@wordpress/i18n'; import { PanelBody } from '@wordpress/components'; -import { PostExcerpt as PostExcerptForm, PostExcerptCheck } from '@wordpress/editor'; +import { + PostExcerpt as PostExcerptForm, + PostExcerptCheck, +} from '@wordpress/editor'; import { compose } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; @@ -19,7 +22,11 @@ function PostExcerpt( { isEnabled, isOpened, onTogglePanel } ) { return ( <PostExcerptCheck> - <PanelBody title={ __( 'Excerpt' ) } opened={ isOpened } onToggle={ onTogglePanel }> + <PanelBody + title={ __( 'Excerpt' ) } + opened={ isOpened } + onToggle={ onTogglePanel } + > <PostExcerptForm /> </PanelBody> </PostExcerptCheck> @@ -29,14 +36,19 @@ function PostExcerpt( { isEnabled, isOpened, onTogglePanel } ) { export default compose( [ withSelect( ( select ) => { return { - isEnabled: select( 'core/edit-post' ).isEditorPanelEnabled( PANEL_NAME ), - isOpened: select( 'core/edit-post' ).isEditorPanelOpened( PANEL_NAME ), + isEnabled: select( 'core/edit-post' ).isEditorPanelEnabled( + PANEL_NAME + ), + isOpened: select( 'core/edit-post' ).isEditorPanelOpened( + PANEL_NAME + ), }; } ), withDispatch( ( dispatch ) => ( { onTogglePanel() { - return dispatch( 'core/edit-post' ).toggleEditorPanelOpened( PANEL_NAME ); + return dispatch( 'core/edit-post' ).toggleEditorPanelOpened( + PANEL_NAME + ); }, } ) ), ] )( PostExcerpt ); - diff --git a/packages/edit-post/src/components/sidebar/post-format/index.js b/packages/edit-post/src/components/sidebar/post-format/index.js index c70dbf70017fa9..67f8f690f74037 100644 --- a/packages/edit-post/src/components/sidebar/post-format/index.js +++ b/packages/edit-post/src/components/sidebar/post-format/index.js @@ -2,7 +2,10 @@ * WordPress dependencies */ import { PanelRow } from '@wordpress/components'; -import { PostFormat as PostFormatForm, PostFormatCheck } from '@wordpress/editor'; +import { + PostFormat as PostFormatForm, + PostFormatCheck, +} from '@wordpress/editor'; export function PostFormat() { return ( diff --git a/packages/edit-post/src/components/sidebar/post-link/index.js b/packages/edit-post/src/components/sidebar/post-link/index.js index 4fdeb992241381..b1f295a62080c1 100644 --- a/packages/edit-post/src/components/sidebar/post-link/index.js +++ b/packages/edit-post/src/components/sidebar/post-link/index.js @@ -34,13 +34,18 @@ function PostLink( { } ) { const { prefix, suffix } = permalinkParts; let prefixElement, postNameElement, suffixElement; - const currentSlug = safeDecodeURIComponent( postSlug ) || cleanForSlug( postTitle ) || postID; + const currentSlug = + safeDecodeURIComponent( postSlug ) || + cleanForSlug( postTitle ) || + postID; if ( isEditable ) { prefixElement = prefix && ( <span className="edit-post-post-link__link-prefix">{ prefix }</span> ); postNameElement = currentSlug && ( - <span className="edit-post-post-link__link-post-name">{ currentSlug }</span> + <span className="edit-post-post-link__link-post-name"> + { currentSlug } + </span> ); suffixElement = suffix && ( <span className="edit-post-post-link__link-suffix">{ suffix }</span> @@ -88,8 +93,7 @@ function PostLink( { } } /> <p> - { __( 'The last part of the URL.' ) } - { ' ' } + { __( 'The last part of the URL.' ) }{ ' ' } <ExternalLink href="https://wordpress.org/support/article/writing-posts/#post-field-descriptions"> { __( 'Read about permalinks' ) } </ExternalLink> @@ -105,12 +109,15 @@ function PostLink( { href={ postLink } target="_blank" > - { isEditable ? - ( <> - { prefixElement }{ postNameElement }{ suffixElement } - </> ) : + { isEditable ? ( + <> + { prefixElement } + { postNameElement } + { suffixElement } + </> + ) : ( postLink - } + ) } </ExternalLink> </div> </PanelBody> @@ -127,13 +134,10 @@ export default compose( [ getPermalinkParts, getEditedPostAttribute, } = select( 'core/editor' ); - const { - isEditorPanelEnabled, - isEditorPanelOpened, - } = select( 'core/edit-post' ); - const { - getPostType, - } = select( 'core' ); + const { isEditorPanelEnabled, isEditorPanelOpened } = select( + 'core/edit-post' + ); + const { getPostType } = select( 'core' ); const { link, id } = getCurrentPost(); @@ -155,9 +159,13 @@ export default compose( [ postTypeLabel: get( postType, [ 'labels', 'view_item' ] ), }; } ), - ifCondition( ( { isEnabled, isNew, postLink, isViewable, permalinkParts } ) => { - return isEnabled && ! isNew && postLink && isViewable && permalinkParts; - } ), + ifCondition( + ( { isEnabled, isNew, postLink, isViewable, permalinkParts } ) => { + return ( + isEnabled && ! isNew && postLink && isViewable && permalinkParts + ); + } + ), withDispatch( ( dispatch ) => { const { toggleEditorPanelOpened } = dispatch( 'core/edit-post' ); const { editPost } = dispatch( 'core/editor' ); diff --git a/packages/edit-post/src/components/sidebar/post-pending-status/index.js b/packages/edit-post/src/components/sidebar/post-pending-status/index.js index f961c0880b91c2..739aff6034b509 100644 --- a/packages/edit-post/src/components/sidebar/post-pending-status/index.js +++ b/packages/edit-post/src/components/sidebar/post-pending-status/index.js @@ -2,7 +2,10 @@ * WordPress dependencies */ import { PanelRow } from '@wordpress/components'; -import { PostPendingStatus as PostPendingStatusForm, PostPendingStatusCheck } from '@wordpress/editor'; +import { + PostPendingStatus as PostPendingStatusForm, + PostPendingStatusCheck, +} from '@wordpress/editor'; export function PostPendingStatus() { return ( diff --git a/packages/edit-post/src/components/sidebar/post-schedule/index.js b/packages/edit-post/src/components/sidebar/post-schedule/index.js index e483e741490d8a..ab5fb2ba668d00 100644 --- a/packages/edit-post/src/components/sidebar/post-schedule/index.js +++ b/packages/edit-post/src/components/sidebar/post-schedule/index.js @@ -3,15 +3,17 @@ */ import { __ } from '@wordpress/i18n'; import { PanelRow, Dropdown, Button } from '@wordpress/components'; -import { PostSchedule as PostScheduleForm, PostScheduleLabel, PostScheduleCheck } from '@wordpress/editor'; +import { + PostSchedule as PostScheduleForm, + PostScheduleLabel, + PostScheduleCheck, +} from '@wordpress/editor'; export function PostSchedule() { return ( <PostScheduleCheck> <PanelRow className="edit-post-post-schedule"> - <span> - { __( 'Publish' ) } - </span> + <span>{ __( 'Publish' ) }</span> <Dropdown position="bottom left" contentClassName="edit-post-post-schedule__dialog" diff --git a/packages/edit-post/src/components/sidebar/post-status/index.js b/packages/edit-post/src/components/sidebar/post-status/index.js index 7e6509096b5412..11311e91849c2b 100644 --- a/packages/edit-post/src/components/sidebar/post-status/index.js +++ b/packages/edit-post/src/components/sidebar/post-status/index.js @@ -26,7 +26,12 @@ const PANEL_NAME = 'post-status'; function PostStatus( { isOpened, onTogglePanel } ) { return ( - <PanelBody className="edit-post-post-status" title={ __( 'Status & visibility' ) } opened={ isOpened } onToggle={ onTogglePanel }> + <PanelBody + className="edit-post-post-status" + title={ __( 'Status & visibility' ) } + opened={ isOpened } + onToggle={ onTogglePanel } + > <PluginPostStatusInfo.Slot> { ( fills ) => ( <> @@ -50,7 +55,9 @@ export default compose( [ withSelect( ( select ) => { // We use isEditorPanelRemoved to hide the panel if it was programatically removed. We do // not use isEditorPanelEnabled since this panel should not be disabled through the UI. - const { isEditorPanelRemoved, isEditorPanelOpened } = select( 'core/edit-post' ); + const { isEditorPanelRemoved, isEditorPanelOpened } = select( + 'core/edit-post' + ); return { isRemoved: isEditorPanelRemoved( PANEL_NAME ), isOpened: isEditorPanelOpened( PANEL_NAME ), @@ -59,8 +66,9 @@ export default compose( [ ifCondition( ( { isRemoved } ) => ! isRemoved ), withDispatch( ( dispatch ) => ( { onTogglePanel() { - return dispatch( 'core/edit-post' ).toggleEditorPanelOpened( PANEL_NAME ); + return dispatch( 'core/edit-post' ).toggleEditorPanelOpened( + PANEL_NAME + ); }, } ) ), ] )( PostStatus ); - diff --git a/packages/edit-post/src/components/sidebar/post-sticky/index.js b/packages/edit-post/src/components/sidebar/post-sticky/index.js index a8dc776b7b0c1f..971428fcd23181 100644 --- a/packages/edit-post/src/components/sidebar/post-sticky/index.js +++ b/packages/edit-post/src/components/sidebar/post-sticky/index.js @@ -2,7 +2,10 @@ * WordPress dependencies */ import { PanelRow } from '@wordpress/components'; -import { PostSticky as PostStickyForm, PostStickyCheck } from '@wordpress/editor'; +import { + PostSticky as PostStickyForm, + PostStickyCheck, +} from '@wordpress/editor'; export function PostSticky() { return ( diff --git a/packages/edit-post/src/components/sidebar/post-taxonomies/index.js b/packages/edit-post/src/components/sidebar/post-taxonomies/index.js index be04dbcf6b6f9e..d44815ee98b71b 100644 --- a/packages/edit-post/src/components/sidebar/post-taxonomies/index.js +++ b/packages/edit-post/src/components/sidebar/post-taxonomies/index.js @@ -1,7 +1,10 @@ /** * WordPress dependencies */ -import { PostTaxonomies as PostTaxonomiesForm, PostTaxonomiesCheck } from '@wordpress/editor'; +import { + PostTaxonomies as PostTaxonomiesForm, + PostTaxonomiesCheck, +} from '@wordpress/editor'; /** * Internal dependencies diff --git a/packages/edit-post/src/components/sidebar/post-taxonomies/taxonomy-panel.js b/packages/edit-post/src/components/sidebar/post-taxonomies/taxonomy-panel.js index 5c85446e0ac141..59febc55554a39 100644 --- a/packages/edit-post/src/components/sidebar/post-taxonomies/taxonomy-panel.js +++ b/packages/edit-post/src/components/sidebar/post-taxonomies/taxonomy-panel.js @@ -10,7 +10,13 @@ import { compose } from '@wordpress/compose'; import { PanelBody } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; -function TaxonomyPanel( { isEnabled, taxonomy, isOpened, onTogglePanel, children } ) { +function TaxonomyPanel( { + isEnabled, + taxonomy, + isOpened, + onTogglePanel, + children, +} ) { if ( ! isEnabled ) { return null; } @@ -37,17 +43,19 @@ export default compose( const panelName = slug ? `taxonomy-panel-${ slug }` : ''; return { panelName, - isEnabled: slug ? - select( 'core/edit-post' ).isEditorPanelEnabled( panelName ) : - false, - isOpened: slug ? - select( 'core/edit-post' ).isEditorPanelOpened( panelName ) : - false, + isEnabled: slug + ? select( 'core/edit-post' ).isEditorPanelEnabled( panelName ) + : false, + isOpened: slug + ? select( 'core/edit-post' ).isEditorPanelOpened( panelName ) + : false, }; } ), withDispatch( ( dispatch, ownProps ) => ( { onTogglePanel: () => { - dispatch( 'core/edit-post' ).toggleEditorPanelOpened( ownProps.panelName ); + dispatch( 'core/edit-post' ).toggleEditorPanelOpened( + ownProps.panelName + ); }, - } ) ), + } ) ) )( TaxonomyPanel ); diff --git a/packages/edit-post/src/components/sidebar/post-visibility/index.js b/packages/edit-post/src/components/sidebar/post-visibility/index.js index 5bc1e4c563e662..3840c77b1c335b 100644 --- a/packages/edit-post/src/components/sidebar/post-visibility/index.js +++ b/packages/edit-post/src/components/sidebar/post-visibility/index.js @@ -3,33 +3,43 @@ */ import { __ } from '@wordpress/i18n'; import { PanelRow, Dropdown, Button } from '@wordpress/components'; -import { PostVisibility as PostVisibilityForm, PostVisibilityLabel, PostVisibilityCheck } from '@wordpress/editor'; +import { + PostVisibility as PostVisibilityForm, + PostVisibilityLabel, + PostVisibilityCheck, +} from '@wordpress/editor'; export function PostVisibility() { return ( - <PostVisibilityCheck render={ ( { canEdit } ) => ( - <PanelRow className="edit-post-post-visibility"> - <span>{ __( 'Visibility' ) }</span> - { ! canEdit && <span><PostVisibilityLabel /></span> } - { canEdit && ( - <Dropdown - position="bottom left" - contentClassName="edit-post-post-visibility__dialog" - renderToggle={ ( { isOpen, onToggle } ) => ( - <Button - aria-expanded={ isOpen } - className="edit-post-post-visibility__toggle" - onClick={ onToggle } - isLink - > - <PostVisibilityLabel /> - </Button> - ) } - renderContent={ () => <PostVisibilityForm /> } - /> - ) } - </PanelRow> - ) } /> + <PostVisibilityCheck + render={ ( { canEdit } ) => ( + <PanelRow className="edit-post-post-visibility"> + <span>{ __( 'Visibility' ) }</span> + { ! canEdit && ( + <span> + <PostVisibilityLabel /> + </span> + ) } + { canEdit && ( + <Dropdown + position="bottom left" + contentClassName="edit-post-post-visibility__dialog" + renderToggle={ ( { isOpen, onToggle } ) => ( + <Button + aria-expanded={ isOpen } + className="edit-post-post-visibility__toggle" + onClick={ onToggle } + isLink + > + <PostVisibilityLabel /> + </Button> + ) } + renderContent={ () => <PostVisibilityForm /> } + /> + ) } + </PanelRow> + ) } + /> ); } diff --git a/packages/edit-post/src/components/sidebar/settings-header/index.js b/packages/edit-post/src/components/sidebar/settings-header/index.js index 4bb0ddc0c2235f..4f2a2d4ebe9ff2 100644 --- a/packages/edit-post/src/components/sidebar/settings-header/index.js +++ b/packages/edit-post/src/components/sidebar/settings-header/index.js @@ -10,19 +10,25 @@ import { withDispatch } from '@wordpress/data'; */ import SidebarHeader from '../sidebar-header'; -const SettingsHeader = ( { openDocumentSettings, openBlockSettings, sidebarName } ) => { +const SettingsHeader = ( { + openDocumentSettings, + openBlockSettings, + sidebarName, +} ) => { const blockLabel = __( 'Block' ); - const [ documentAriaLabel, documentActiveClass ] = sidebarName === 'edit-post/document' ? - // translators: ARIA label for the Document sidebar tab, selected. - [ __( 'Document (selected)' ), 'is-active' ] : - // translators: ARIA label for the Document sidebar tab, not selected. - [ __( 'Document' ), '' ]; + const [ documentAriaLabel, documentActiveClass ] = + sidebarName === 'edit-post/document' + ? // translators: ARIA label for the Document sidebar tab, selected. + [ __( 'Document (selected)' ), 'is-active' ] + : // translators: ARIA label for the Document sidebar tab, not selected. + [ __( 'Document' ), '' ]; - const [ blockAriaLabel, blockActiveClass ] = sidebarName === 'edit-post/block' ? - // translators: ARIA label for the Settings Sidebar tab, selected. - [ __( 'Block (selected)' ), 'is-active' ] : - // translators: ARIA label for the Settings Sidebar tab, not selected. - [ __( 'Block' ), '' ]; + const [ blockAriaLabel, blockActiveClass ] = + sidebarName === 'edit-post/block' + ? // translators: ARIA label for the Settings Sidebar tab, selected. + [ __( 'Block (selected)' ), 'is-active' ] + : // translators: ARIA label for the Settings Sidebar tab, not selected. + [ __( 'Block' ), '' ]; return ( <SidebarHeader diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js index b1d3b8d417ac41..df162808e438d5 100644 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js @@ -40,24 +40,21 @@ const SettingsSidebar = ( { sidebarName } ) => ( <MetaBoxes location="side" /> </> ) } - { sidebarName === 'edit-post/block' && ( - <BlockInspector /> - ) } + { sidebarName === 'edit-post/block' && <BlockInspector /> } </Panel> </Sidebar> ); export default compose( withSelect( ( select ) => { - const { - getActiveGeneralSidebarName, - isEditorSidebarOpened, - } = select( 'core/edit-post' ); + const { getActiveGeneralSidebarName, isEditorSidebarOpened } = select( + 'core/edit-post' + ); return { isEditorSidebarOpened: isEditorSidebarOpened(), sidebarName: getActiveGeneralSidebarName(), }; } ), - ifCondition( ( { isEditorSidebarOpened } ) => isEditorSidebarOpened ), + ifCondition( ( { isEditorSidebarOpened } ) => isEditorSidebarOpened ) )( SettingsSidebar ); diff --git a/packages/edit-post/src/components/sidebar/sidebar-header/index.js b/packages/edit-post/src/components/sidebar/sidebar-header/index.js index 9592341c0326f2..e0133401a58e82 100644 --- a/packages/edit-post/src/components/sidebar/sidebar-header/index.js +++ b/packages/edit-post/src/components/sidebar/sidebar-header/index.js @@ -11,10 +11,15 @@ import { Button } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; const SidebarHeader = ( { children, className, closeLabel } ) => { - const { shortcut, title } = useSelect( ( select ) => ( { - shortcut: select( 'core/keyboard-shortcuts' ).getShortcutRepresentation( 'core/edit-post/toggle-sidebar' ), - title: select( 'core/editor' ).getEditedPostAttribute( 'title' ), - } ), [] ); + const { shortcut, title } = useSelect( + ( select ) => ( { + shortcut: select( + 'core/keyboard-shortcuts' + ).getShortcutRepresentation( 'core/edit-post/toggle-sidebar' ), + title: select( 'core/editor' ).getEditedPostAttribute( 'title' ), + } ), + [] + ); const { closeGeneralSidebar } = useDispatch( 'core/edit-post' ); return ( @@ -29,7 +34,12 @@ const SidebarHeader = ( { children, className, closeLabel } ) => { label={ closeLabel } /> </div> - <div className={ classnames( 'components-panel__header edit-post-sidebar-header', className ) }> + <div + className={ classnames( + 'components-panel__header edit-post-sidebar-header', + className + ) } + > { children } <Button onClick={ closeGeneralSidebar } diff --git a/packages/edit-post/src/components/text-editor/index.js b/packages/edit-post/src/components/text-editor/index.js index 3430fc994875f3..80ac911636e0aa 100644 --- a/packages/edit-post/src/components/text-editor/index.js +++ b/packages/edit-post/src/components/text-editor/index.js @@ -27,7 +27,7 @@ function TextEditor( { onExit, isRichEditingEnabled } ) { </Button> <TextEditorGlobalKeyboardShortcuts /> </div> - ) } + ) } <div className="edit-post-text-editor__body"> <PostTitle /> <PostTextEditor /> @@ -38,7 +38,8 @@ function TextEditor( { onExit, isRichEditingEnabled } ) { export default compose( withSelect( ( select ) => ( { - isRichEditingEnabled: select( 'core/editor' ).getEditorSettings().richEditingEnabled, + isRichEditingEnabled: select( 'core/editor' ).getEditorSettings() + .richEditingEnabled, } ) ), withDispatch( ( dispatch ) => { return { diff --git a/packages/edit-post/src/components/visual-editor/block-inspector-button.js b/packages/edit-post/src/components/visual-editor/block-inspector-button.js index ab9dd93527459e..977e3f55d2de9a 100644 --- a/packages/edit-post/src/components/visual-editor/block-inspector-button.js +++ b/packages/edit-post/src/components/visual-editor/block-inspector-button.js @@ -15,21 +15,36 @@ export function BlockInspectorButton( { small = false, speak, } ) { - const { shortcut, areAdvancedSettingsOpened } = useSelect( ( select ) => ( { - shortcut: select( 'core/keyboard-shortcuts' ).getShortcutRepresentation( 'core/edit-post/toggle-sidebar' ), - areAdvancedSettingsOpened: select( 'core/edit-post' ).getActiveGeneralSidebarName() === 'edit-post/block', - } ), [] ); - const { openGeneralSidebar, closeGeneralSidebar } = useDispatch( 'core/edit-post' ); + const { shortcut, areAdvancedSettingsOpened } = useSelect( + ( select ) => ( { + shortcut: select( + 'core/keyboard-shortcuts' + ).getShortcutRepresentation( 'core/edit-post/toggle-sidebar' ), + areAdvancedSettingsOpened: + select( 'core/edit-post' ).getActiveGeneralSidebarName() === + 'edit-post/block', + } ), + [] + ); + const { openGeneralSidebar, closeGeneralSidebar } = useDispatch( + 'core/edit-post' + ); const speakMessage = () => { if ( areAdvancedSettingsOpened ) { speak( __( 'Block settings closed' ) ); } else { - speak( __( 'Additional settings are now available in the Editor block settings sidebar' ) ); + speak( + __( + 'Additional settings are now available in the Editor block settings sidebar' + ) + ); } }; - const label = areAdvancedSettingsOpened ? __( 'Hide Block Settings' ) : __( 'Show Block Settings' ); + const label = areAdvancedSettingsOpened + ? __( 'Hide Block Settings' ) + : __( 'Show Block Settings' ); return ( <MenuItem diff --git a/packages/edit-post/src/components/visual-editor/index.js b/packages/edit-post/src/components/visual-editor/index.js index b27655e629d0df..94ae067d7296ed 100644 --- a/packages/edit-post/src/components/visual-editor/index.js +++ b/packages/edit-post/src/components/visual-editor/index.js @@ -43,10 +43,16 @@ function VisualEditor() { </CopyHandler> </Typewriter> <__experimentalBlockSettingsMenuFirstItem> - { ( { onClose } ) => <BlockInspectorButton onClick={ onClose } /> } + { ( { onClose } ) => ( + <BlockInspectorButton onClick={ onClose } /> + ) } </__experimentalBlockSettingsMenuFirstItem> <__experimentalBlockSettingsMenuPluginsExtension> - { ( { clientIds, onClose } ) => <PluginBlockSettingsMenuGroup.Slot fillProps={ { clientIds, onClose } } /> } + { ( { clientIds, onClose } ) => ( + <PluginBlockSettingsMenuGroup.Slot + fillProps={ { clientIds, onClose } } + /> + ) } </__experimentalBlockSettingsMenuPluginsExtension> </BlockSelectionClearer> ); diff --git a/packages/edit-post/src/components/visual-editor/index.native.js b/packages/edit-post/src/components/visual-editor/index.native.js index 44d7c2902cef4a..ed52ef5e6eef14 100644 --- a/packages/edit-post/src/components/visual-editor/index.native.js +++ b/packages/edit-post/src/components/visual-editor/index.native.js @@ -22,7 +22,10 @@ class VisualEditor extends Component { title, getStylesFromColorScheme, } = this.props; - const blockHolderFocusedStyle = getStylesFromColorScheme( styles.blockHolderFocused, styles.blockHolderFocusedDark ); + const blockHolderFocusedStyle = getStylesFromColorScheme( + styles.blockHolderFocused, + styles.blockHolderFocusedDark + ); return ( <ReadableContentView> <PostTitle @@ -31,9 +34,9 @@ class VisualEditor extends Component { onUpdate={ editTitle } placeholder={ __( 'Add title' ) } borderStyle={ - this.props.isFullyBordered ? - styles.blockHolderFullBordered : - styles.blockHolderSemiBordered + this.props.isFullyBordered + ? styles.blockHolderFullBordered + : styles.blockHolderSemiBordered } focusedBorderColor={ blockHolderFocusedStyle.borderColor } accessibilityLabel="post-title" @@ -43,10 +46,7 @@ class VisualEditor extends Component { } render() { - const { - isFullyBordered, - safeAreaBottomInset, - } = this.props; + const { isFullyBordered, safeAreaBottomInset } = this.props; return ( <BlockList @@ -61,18 +61,14 @@ class VisualEditor extends Component { export default compose( [ withSelect( ( select ) => { - const { - getEditedPostAttribute, - } = select( 'core/editor' ); + const { getEditedPostAttribute } = select( 'core/editor' ); return { title: getEditedPostAttribute( 'title' ), }; } ), withDispatch( ( dispatch ) => { - const { - editPost, - } = dispatch( 'core/editor' ); + const { editPost } = dispatch( 'core/editor' ); const { clearSelectedBlock } = dispatch( 'core/block-editor' ); diff --git a/packages/edit-post/src/components/welcome-guide/index.js b/packages/edit-post/src/components/welcome-guide/index.js index bdc65c73036599..b71b99638d41c3 100644 --- a/packages/edit-post/src/components/welcome-guide/index.js +++ b/packages/edit-post/src/components/welcome-guide/index.js @@ -9,10 +9,20 @@ import { __experimentalCreateInterpolateElement } from '@wordpress/element'; /** * Internal dependencies */ -import { CanvasImage, EditorImage, BlockLibraryImage, DocumentationImage, InserterIconImage } from './images'; +import { + CanvasImage, + EditorImage, + BlockLibraryImage, + DocumentationImage, + InserterIconImage, +} from './images'; export default function WelcomeGuide() { - const isActive = useSelect( ( select ) => select( 'core/edit-post' ).isFeatureActive( 'welcomeGuide' ), [] ); + const isActive = useSelect( + ( select ) => + select( 'core/edit-post' ).isFeatureActive( 'welcomeGuide' ), + [] + ); const { toggleFeature } = useDispatch( 'core/edit-post' ); @@ -27,14 +37,15 @@ export default function WelcomeGuide() { finishButtonText={ __( 'Get started' ) } onFinish={ () => toggleFeature( 'welcomeGuide' ) } > - <GuidePage className="edit-post-welcome-guide__page"> <h1 className="edit-post-welcome-guide__heading"> { __( 'Welcome to the Block Editor' ) } </h1> <CanvasImage className="edit-post-welcome-guide__image" /> <p className="edit-post-welcome-guide__text"> - { __( 'In the WordPress editor, each paragraph, image, or video is presented as a distinct “block” of content.' ) } + { __( + 'In the WordPress editor, each paragraph, image, or video is presented as a distinct “block” of content.' + ) } </p> </GuidePage> @@ -44,7 +55,9 @@ export default function WelcomeGuide() { </h1> <EditorImage className="edit-post-welcome-guide__image" /> <p className="edit-post-welcome-guide__text"> - { __( 'Each block comes with its own set of controls for changing things like color, width, and alignment. These will show and hide automatically when you have a block selected.' ) } + { __( + 'Each block comes with its own set of controls for changing things like color, width, and alignment. These will show and hide automatically when you have a block selected.' + ) } </p> </GuidePage> @@ -55,12 +68,12 @@ export default function WelcomeGuide() { <BlockLibraryImage className="edit-post-welcome-guide__image" /> <p className="edit-post-welcome-guide__text"> { __experimentalCreateInterpolateElement( - __( 'All of the blocks available to you live in the Block Library. You’ll find it wherever you see the <InserterIconImage /> icon.' ), + __( + 'All of the blocks available to you live in the Block Library. You’ll find it wherever you see the <InserterIconImage /> icon.' + ), { InserterIconImage: ( - <InserterIconImage - className="edit-post-welcome-guide__inserter-icon" - /> + <InserterIconImage className="edit-post-welcome-guide__inserter-icon" /> ), } ) } @@ -73,15 +86,18 @@ export default function WelcomeGuide() { </h1> <DocumentationImage className="edit-post-welcome-guide__image" /> <p className="edit-post-welcome-guide__text"> - { __( 'New to the Block Editor? Want to learn more about using it? ' ) } + { __( + 'New to the Block Editor? Want to learn more about using it? ' + ) } <ExternalLink - href={ __( 'https://wordpress.org/support/article/wordpress-editor/' ) } + href={ __( + 'https://wordpress.org/support/article/wordpress-editor/' + ) } > - { __( 'Here\'s a detailed guide.' ) } + { __( "Here's a detailed guide." ) } </ExternalLink> </p> </GuidePage> - </Guide> ); } diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index eb6754e7cf665c..8d5f8a2fd5d7db 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -8,7 +8,11 @@ import { size, map, without } from 'lodash'; * WordPress dependencies */ import { withSelect, withDispatch } from '@wordpress/data'; -import { EditorProvider, ErrorBoundary, PostLockedModal } from '@wordpress/editor'; +import { + EditorProvider, + ErrorBoundary, + PostLockedModal, +} from '@wordpress/editor'; import { StrictMode, Component } from '@wordpress/element'; import { KeyboardShortcuts, @@ -43,7 +47,7 @@ class Editor extends Component { blockTypes, preferredStyleVariations, __experimentalLocalAutosaveInterval, - updatePreferredStyleVariations, + updatePreferredStyleVariations ) { settings = { ...settings, @@ -62,15 +66,14 @@ class Editor extends Component { // Defer to passed setting for `allowedBlockTypes` if provided as // anything other than `true` (where `true` is equivalent to allow // all block types). - const defaultAllowedBlockTypes = ( - true === settings.allowedBlockTypes ? - map( blockTypes, 'name' ) : - ( settings.allowedBlockTypes || [] ) - ); + const defaultAllowedBlockTypes = + true === settings.allowedBlockTypes + ? map( blockTypes, 'name' ) + : settings.allowedBlockTypes || []; settings.allowedBlockTypes = without( defaultAllowedBlockTypes, - ...hiddenBlockTypes, + ...hiddenBlockTypes ); } @@ -108,7 +111,7 @@ class Editor extends Component { blockTypes, preferredStyleVariations, __experimentalLocalAutosaveInterval, - updatePreferredStyleVariations, + updatePreferredStyleVariations ); return ( @@ -126,7 +129,9 @@ class Editor extends Component { <ErrorBoundary onError={ onError }> <EditorInitialization postId={ postId } /> <Layout /> - <KeyboardShortcuts shortcuts={ preventEventDiscovery } /> + <KeyboardShortcuts + shortcuts={ preventEventDiscovery } + /> </ErrorBoundary> <PostLockedModal /> </EditorProvider> @@ -149,10 +154,14 @@ export default compose( [ hasFixedToolbar: isFeatureActive( 'fixedToolbar' ), focusMode: isFeatureActive( 'focusMode' ), post: getEntityRecord( 'postType', postType, postId ), - preferredStyleVariations: getPreference( 'preferredStyleVariations' ), + preferredStyleVariations: getPreference( + 'preferredStyleVariations' + ), hiddenBlockTypes: getPreference( 'hiddenBlockTypes' ), blockTypes: getBlockTypes(), - __experimentalLocalAutosaveInterval: getPreference( 'localAutosaveInterval' ), + __experimentalLocalAutosaveInterval: getPreference( + 'localAutosaveInterval' + ), }; } ), withDispatch( ( dispatch ) => { diff --git a/packages/edit-post/src/editor.native.js b/packages/edit-post/src/editor.native.js index 097bf4b8c00410..3087d7b0b78542 100644 --- a/packages/edit-post/src/editor.native.js +++ b/packages/edit-post/src/editor.native.js @@ -41,7 +41,7 @@ class Editor extends Component { hasFixedToolbar, focusMode, hiddenBlockTypes, - blockTypes, + blockTypes ) { settings = { ...settings, @@ -54,15 +54,14 @@ class Editor extends Component { // Defer to passed setting for `allowedBlockTypes` if provided as // anything other than `true` (where `true` is equivalent to allow // all block types). - const defaultAllowedBlockTypes = ( - true === settings.allowedBlockTypes ? - map( blockTypes, 'name' ) : - ( settings.allowedBlockTypes || [] ) - ); + const defaultAllowedBlockTypes = + true === settings.allowedBlockTypes + ? map( blockTypes, 'name' ) + : settings.allowedBlockTypes || []; settings.allowedBlockTypes = without( defaultAllowedBlockTypes, - ...hiddenBlockTypes, + ...hiddenBlockTypes ); } @@ -70,11 +69,13 @@ class Editor extends Component { } componentDidMount() { - this.subscriptionParentSetFocusOnTitle = subscribeSetFocusOnTitle( () => { - if ( this.postTitleRef ) { - this.postTitleRef.focus(); + this.subscriptionParentSetFocusOnTitle = subscribeSetFocusOnTitle( + () => { + if ( this.postTitleRef ) { + this.postTitleRef.focus(); + } } - } ); + ); } componentWillUnmount() { @@ -105,7 +106,7 @@ class Editor extends Component { hasFixedToolbar, focusMode, hiddenBlockTypes, - blockTypes, + blockTypes ); const normalizedPost = post || { @@ -142,7 +143,9 @@ class Editor extends Component { export default compose( [ withSelect( ( select ) => { - const { isFeatureActive, getEditorMode, getPreference } = select( 'core/edit-post' ); + const { isFeatureActive, getEditorMode, getPreference } = select( + 'core/edit-post' + ); const { getBlockTypes } = select( 'core/blocks' ); return { @@ -154,9 +157,7 @@ export default compose( [ }; } ), withDispatch( ( dispatch ) => { - const { - switchEditorMode, - } = dispatch( 'core/edit-post' ); + const { switchEditorMode } = dispatch( 'core/edit-post' ); return { switchEditorMode, diff --git a/packages/edit-post/src/hooks/validate-multiple-use/index.js b/packages/edit-post/src/hooks/validate-multiple-use/index.js index 32e50d7bb4c5dc..b772241685bd40 100644 --- a/packages/edit-post/src/hooks/validate-multiple-use/index.js +++ b/packages/edit-post/src/hooks/validate-multiple-use/index.js @@ -44,23 +44,26 @@ const enhance = compose( // Otherwise, only pass `originalBlockClientId` if it refers to a different // block from the current one. const blocks = select( 'core/block-editor' ).getBlocks(); - const firstOfSameType = find( blocks, ( { name } ) => block.name === name ); - const isInvalid = firstOfSameType && firstOfSameType.clientId !== block.clientId; + const firstOfSameType = find( + blocks, + ( { name } ) => block.name === name + ); + const isInvalid = + firstOfSameType && firstOfSameType.clientId !== block.clientId; return { originalBlockClientId: isInvalid && firstOfSameType.clientId, }; } ), withDispatch( ( dispatch, { originalBlockClientId } ) => ( { - selectFirst: () => dispatch( 'core/block-editor' ).selectBlock( originalBlockClientId ), - } ) ), + selectFirst: () => + dispatch( 'core/block-editor' ).selectBlock( + originalBlockClientId + ), + } ) ) ); const withMultipleValidation = createHigherOrderComponent( ( BlockEdit ) => { - return enhance( ( { - originalBlockClientId, - selectFirst, - ...props - } ) => { + return enhance( ( { originalBlockClientId, selectFirst, ...props } ) => { if ( ! originalBlockClientId ) { return <BlockEdit { ...props } />; } @@ -75,22 +78,34 @@ const withMultipleValidation = createHigherOrderComponent( ( BlockEdit ) => { <Warning key="multiple-use-warning" actions={ [ - <Button key="find-original" isSecondary onClick={ selectFirst }> + <Button + key="find-original" + isSecondary + onClick={ selectFirst } + > { __( 'Find original' ) } </Button>, - <Button key="remove" isSecondary onClick={ () => props.onReplace( [] ) }> + <Button + key="remove" + isSecondary + onClick={ () => props.onReplace( [] ) } + > { __( 'Remove' ) } </Button>, outboundType && ( <Button key="transform" isSecondary - onClick={ () => props.onReplace( - createBlock( outboundType.name, props.attributes ) - ) } + onClick={ () => + props.onReplace( + createBlock( + outboundType.name, + props.attributes + ) + ) + } > - { __( 'Transform into:' ) }{ ' ' } - { outboundType.title } + { __( 'Transform into:' ) } { outboundType.title } </Button> ), ] } diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index fa3f9baec5d557..1f10cb7a36ba6d 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -7,7 +7,10 @@ import '@wordpress/editor'; import '@wordpress/keyboard-shortcuts'; import '@wordpress/viewport'; import '@wordpress/notices'; -import { registerCoreBlocks, __experimentalRegisterExperimentalCoreBlocks } from '@wordpress/block-library'; +import { + registerCoreBlocks, + __experimentalRegisterExperimentalCoreBlocks, +} from '@wordpress/block-library'; import { render, unmountComponentAtNode } from '@wordpress/element'; /** @@ -31,9 +34,22 @@ import Editor from './editor'; * considered as non-user-initiated (bypass for * unsaved changes prompt). */ -export function reinitializeEditor( postType, postId, target, settings, initialEdits ) { +export function reinitializeEditor( + postType, + postId, + target, + settings, + initialEdits +) { unmountComponentAtNode( target ); - const reboot = reinitializeEditor.bind( null, postType, postId, target, settings, initialEdits ); + const reboot = reinitializeEditor.bind( + null, + postType, + postId, + target, + settings, + initialEdits + ); render( <Editor @@ -62,19 +78,35 @@ export function reinitializeEditor( postType, postId, target, settings, initialE * considered as non-user-initiated (bypass for * unsaved changes prompt). */ -export function initializeEditor( id, postType, postId, settings, initialEdits ) { +export function initializeEditor( + id, + postType, + postId, + settings, + initialEdits +) { const target = document.getElementById( id ); - const reboot = reinitializeEditor.bind( null, postType, postId, target, settings, initialEdits ); + const reboot = reinitializeEditor.bind( + null, + postType, + postId, + target, + settings, + initialEdits + ); registerCoreBlocks(); if ( process.env.GUTENBERG_PHASE === 2 ) { __experimentalRegisterExperimentalCoreBlocks( settings ); } // Show a console log warning if the browser is not in Standards rendering mode. - const documentMode = document.compatMode === 'CSS1Compat' ? 'Standards' : 'Quirks'; + const documentMode = + document.compatMode === 'CSS1Compat' ? 'Standards' : 'Quirks'; if ( documentMode !== 'Standards' ) { // eslint-disable-next-line no-console - console.warn( "Your browser is using Quirks Mode. \nThis can cause rendering issues such as blocks overlaying meta boxes in the editor. Quirks Mode can be triggered by PHP errors or HTML code appearing before the opening <!DOCTYPE html>. Try checking the raw page source or your site's PHP error log and resolving errors there, removing any HTML before the doctype, or disabling plugins." ); + console.warn( + "Your browser is using Quirks Mode. \nThis can cause rendering issues such as blocks overlaying meta boxes in the editor. Quirks Mode can be triggered by PHP errors or HTML code appearing before the opening <!DOCTYPE html>. Try checking the raw page source or your site's PHP error log and resolving errors there, removing any HTML before the doctype, or disabling plugins." + ); } // This is a temporary fix for a couple of issues specific to Webkit on iOS. @@ -87,12 +119,15 @@ export function initializeEditor( id, postType, postId, settings, initialEdits ) const isIphone = window.navigator.userAgent.indexOf( 'iPhone' ) !== -1; if ( isIphone ) { window.addEventListener( 'scroll', function( event ) { - const editorScrollContainer = document.getElementsByClassName( 'edit-post-editor-regions__body' )[ 0 ]; + const editorScrollContainer = document.getElementsByClassName( + 'edit-post-editor-regions__body' + )[ 0 ]; if ( event.target === document ) { // Scroll element into view by scrolling the editor container by the same amount // that Mobile Safari tried to scroll the html element upwards. if ( window.scrollY > 100 ) { - editorScrollContainer.scrollTop = editorScrollContainer.scrollTop + window.scrollY; + editorScrollContainer.scrollTop = + editorScrollContainer.scrollTop + window.scrollY; } //Undo unwanted scroll on html element window.scrollTo( 0, 0 ); diff --git a/packages/edit-post/src/plugins/copy-content-menu-item/index.js b/packages/edit-post/src/plugins/copy-content-menu-item/index.js index 65e97a05bcfd05..7d3a9c8f1de8b0 100644 --- a/packages/edit-post/src/plugins/copy-content-menu-item/index.js +++ b/packages/edit-post/src/plugins/copy-content-menu-item/index.js @@ -6,7 +6,12 @@ import { withDispatch, withSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { withState, compose } from '@wordpress/compose'; -function CopyContentMenuItem( { createNotice, editedPostContent, hasCopied, setState } ) { +function CopyContentMenuItem( { + createNotice, + editedPostContent, + hasCopied, + setState, +} ) { return ( editedPostContent.length > 0 && ( <ClipboardButton @@ -15,20 +20,14 @@ function CopyContentMenuItem( { createNotice, editedPostContent, hasCopied, setS className="components-menu-item__button" onCopy={ () => { setState( { hasCopied: true } ); - createNotice( - 'info', - __( 'All content copied.' ), - { - isDismissible: true, - type: 'snackbar', - } - ); + createNotice( 'info', __( 'All content copied.' ), { + isDismissible: true, + type: 'snackbar', + } ); } } onFinishCopy={ () => setState( { hasCopied: false } ) } > - { hasCopied ? - __( 'Copied!' ) : - __( 'Copy all content' ) } + { hasCopied ? __( 'Copied!' ) : __( 'Copy all content' ) } </ClipboardButton> ) ); @@ -36,12 +35,12 @@ function CopyContentMenuItem( { createNotice, editedPostContent, hasCopied, setS export default compose( withSelect( ( select ) => ( { - editedPostContent: select( 'core/editor' ).getEditedPostAttribute( 'content' ), + editedPostContent: select( 'core/editor' ).getEditedPostAttribute( + 'content' + ), } ) ), withDispatch( ( dispatch ) => { - const { - createNotice, - } = dispatch( 'core/notices' ); + const { createNotice } = dispatch( 'core/notices' ); return { createNotice, diff --git a/packages/edit-post/src/plugins/index.js b/packages/edit-post/src/plugins/index.js index 0cbd97f681c24a..f84fb5abb7f422 100644 --- a/packages/edit-post/src/plugins/index.js +++ b/packages/edit-post/src/plugins/index.js @@ -25,16 +25,22 @@ registerPlugin( 'edit-post', { <ManageBlocksMenuItem onSelect={ onClose } /> <MenuItem role="menuitem" - href={ addQueryArgs( 'edit.php', { post_type: 'wp_block' } ) } + href={ addQueryArgs( 'edit.php', { + post_type: 'wp_block', + } ) } > { __( 'Manage all reusable blocks' ) } </MenuItem> - <KeyboardShortcutsHelpMenuItem onSelect={ onClose } /> + <KeyboardShortcutsHelpMenuItem + onSelect={ onClose } + /> <WelcomeGuideMenuItem /> <CopyContentMenuItem /> <MenuItem role="menuitem" - href={ __( 'https://wordpress.org/support/article/wordpress-editor/' ) } + href={ __( + 'https://wordpress.org/support/article/wordpress-editor/' + ) } target="_new" > { __( 'Help' ) } diff --git a/packages/edit-post/src/plugins/keyboard-shortcuts-help-menu-item/index.js b/packages/edit-post/src/plugins/keyboard-shortcuts-help-menu-item/index.js index 49e947b9c7ea49..73d3b09683f501 100644 --- a/packages/edit-post/src/plugins/keyboard-shortcuts-help-menu-item/index.js +++ b/packages/edit-post/src/plugins/keyboard-shortcuts-help-menu-item/index.js @@ -20,9 +20,7 @@ export function KeyboardShortcutsHelpMenuItem( { openModal } ) { } export default withDispatch( ( dispatch ) => { - const { - openModal, - } = dispatch( 'core/edit-post' ); + const { openModal } = dispatch( 'core/edit-post' ); return { openModal, diff --git a/packages/edit-post/src/plugins/manage-blocks-menu-item/index.js b/packages/edit-post/src/plugins/manage-blocks-menu-item/index.js index fff70dd41a8dac..4275f0a36988e3 100644 --- a/packages/edit-post/src/plugins/manage-blocks-menu-item/index.js +++ b/packages/edit-post/src/plugins/manage-blocks-menu-item/index.js @@ -18,9 +18,7 @@ export function ManageBlocksMenuItem( { openModal } ) { } export default withDispatch( ( dispatch ) => { - const { - openModal, - } = dispatch( 'core/edit-post' ); + const { openModal } = dispatch( 'core/edit-post' ); return { openModal, diff --git a/packages/edit-post/src/prevent-event-discovery.js b/packages/edit-post/src/prevent-event-discovery.js index 36e58f798752cc..daf65b20591d38 100644 --- a/packages/edit-post/src/prevent-event-discovery.js +++ b/packages/edit-post/src/prevent-event-discovery.js @@ -1,7 +1,9 @@ export default { 't a l e s o f g u t e n b e r g': ( event ) => { if ( - ! document.activeElement.classList.contains( 'edit-post-visual-editor' ) && + ! document.activeElement.classList.contains( + 'edit-post-visual-editor' + ) && document.activeElement !== document.body ) { return; @@ -10,7 +12,8 @@ export default { event.preventDefault(); window.wp.data.dispatch( 'core/block-editor' ).insertBlock( window.wp.blocks.createBlock( 'core/paragraph', { - content: '🐡🐢🦀🐤🦋🐘🐧🐹🦁🦄🦍🐼🐿🎃🐴🐝🐆🦕🦔🌱🍇π🍌🐉💧🥨🌌🍂🍠🥦🥚🥝🎟🥥🥒🛵🥖🍒🍯🎾🎲🐺🐚🐮⌛️', + content: + '🐡🐢🦀🐤🦋🐘🐧🐹🦁🦄🦍🐼🐿🎃🐴🐝🐆🦕🦔🌱🍇π🍌🐉💧🥨🌌🍂🍠🥦🥚🥝🎟🥥🥒🛵🥖🍒🍯🎾🎲🐺🐚🐮⌛️', } ) ); }, diff --git a/packages/edit-post/src/store/effects.js b/packages/edit-post/src/store/effects.js index 118ee40551a316..0e69bd005dd6f9 100644 --- a/packages/edit-post/src/store/effects.js +++ b/packages/edit-post/src/store/effects.js @@ -54,11 +54,11 @@ const effects = { const isAutosavingPost = select( 'core/editor' ).isAutosavingPost(); // Save metaboxes on save completion, except for autosaves that are not a post preview. - const shouldTriggerMetaboxesSave = ( - hasActiveMetaBoxes && ( - ( wasSavingPost && ! isSavingPost && ! wasAutosavingPost ) - ) - ); + const shouldTriggerMetaboxesSave = + hasActiveMetaBoxes && + wasSavingPost && + ! isSavingPost && + ! wasAutosavingPost; // Save current state for next inspection. wasSavingPost = isSavingPost; @@ -81,29 +81,40 @@ const effects = { // If we do not provide this data, the post will be overridden with the default values. const post = select( 'core/editor' ).getCurrentPost( state ); const additionalData = [ - post.comment_status ? [ 'comment_status', post.comment_status ] : false, + post.comment_status + ? [ 'comment_status', post.comment_status ] + : false, post.ping_status ? [ 'ping_status', post.ping_status ] : false, post.sticky ? [ 'sticky', post.sticky ] : false, post.author ? [ 'post_author', post.author ] : false, ].filter( Boolean ); // We gather all the metaboxes locations data and the base form data - const baseFormData = new window.FormData( document.querySelector( '.metabox-base-form' ) ); + const baseFormData = new window.FormData( + document.querySelector( '.metabox-base-form' ) + ); const formDataToMerge = [ baseFormData, - ...getActiveMetaBoxLocations( state ).map( ( location ) => ( - new window.FormData( getMetaBoxContainer( location ) ) - ) ), + ...getActiveMetaBoxLocations( state ).map( + ( location ) => + new window.FormData( getMetaBoxContainer( location ) ) + ), ]; // Merge all form data objects into a single one. - const formData = reduce( formDataToMerge, ( memo, currentFormData ) => { - for ( const [ key, value ] of currentFormData ) { - memo.append( key, value ); - } - return memo; - }, new window.FormData() ); - additionalData.forEach( ( [ key, value ] ) => formData.append( key, value ) ); + const formData = reduce( + formDataToMerge, + ( memo, currentFormData ) => { + for ( const [ key, value ] of currentFormData ) { + memo.append( key, value ); + } + return memo; + }, + new window.FormData() + ); + additionalData.forEach( ( [ key, value ] ) => + formData.append( key, value ) + ); // Save the metaboxes apiFetch( { @@ -111,8 +122,7 @@ const effects = { method: 'POST', body: formData, parse: false, - } ) - .then( () => store.dispatch( metaBoxUpdatesSuccess() ) ); + } ).then( () => store.dispatch( metaBoxUpdatesSuccess() ) ); }, SWITCH_MODE( action ) { // Unselect blocks when we switch to the code editor. @@ -120,7 +130,10 @@ const effects = { dispatch( 'core/block-editor' ).clearSelectedBlock(); } - const message = action.mode === 'visual' ? __( 'Visual editor selected' ) : __( 'Code editor selected' ); + const message = + action.mode === 'visual' + ? __( 'Visual editor selected' ) + : __( 'Code editor selected' ); speak( message, 'assertive' ); }, }; diff --git a/packages/edit-post/src/store/middlewares.js b/packages/edit-post/src/store/middlewares.js index ee42a43e589b04..06cacf026d58c2 100644 --- a/packages/edit-post/src/store/middlewares.js +++ b/packages/edit-post/src/store/middlewares.js @@ -17,14 +17,12 @@ import effects from './effects'; * @return {Object} Update Store Object. */ function applyMiddlewares( store ) { - const middlewares = [ - refx( effects ), - ]; + const middlewares = [ refx( effects ) ]; let enhancedDispatch = () => { throw new Error( 'Dispatching while constructing your middleware is not allowed. ' + - 'Other middleware would not be applied to this dispatch.' + 'Other middleware would not be applied to this dispatch.' ); }; let chain = []; diff --git a/packages/edit-post/src/store/reducer.js b/packages/edit-post/src/store/reducer.js index b57c6e174480c2..66991a84150f68 100644 --- a/packages/edit-post/src/store/reducer.js +++ b/packages/edit-post/src/store/reducer.js @@ -1,14 +1,7 @@ /** * External dependencies */ -import { - flow, - get, - includes, - omit, - union, - without, -} from 'lodash'; +import { flow, get, includes, omit, union, without } from 'lodash'; /** * WordPress dependencies @@ -86,7 +79,9 @@ export const preferences = flow( [ case 'TOGGLE_PANEL_OPENED': { const { panelName } = action; - const isOpen = state[ panelName ] === true || get( state, [ panelName, 'opened' ], false ); + const isOpen = + state[ panelName ] === true || + get( state, [ panelName, 'opened' ], false ); return { ...state, [ panelName ]: { @@ -120,7 +115,11 @@ export const preferences = flow( [ if ( action.type === 'TOGGLE_PINNED_PLUGIN_ITEM' ) { return { ...state, - [ action.pluginName ]: ! get( state, [ action.pluginName ], true ), + [ action.pluginName ]: ! get( + state, + [ action.pluginName ], + true + ), }; } return state; @@ -175,10 +174,7 @@ export function removedPanels( state = [], action ) { switch ( action.type ) { case 'REMOVE_PANEL': if ( ! includes( state, action.panelName ) ) { - return [ - ...state, - action.panelName, - ]; + return [ ...state, action.panelName ]; } } @@ -194,7 +190,10 @@ export function removedPanels( state = [], action ) { * * @return {?string} Updated state. */ -export function activeGeneralSidebar( state = DEFAULT_ACTIVE_GENERAL_SIDEBAR, action ) { +export function activeGeneralSidebar( + state = DEFAULT_ACTIVE_GENERAL_SIDEBAR, + action +) { switch ( action.type ) { case 'OPEN_GENERAL_SIDEBAR': return action.name; diff --git a/packages/edit-post/src/store/selectors.js b/packages/edit-post/src/store/selectors.js index fff04ac721fe37..d237ee1e9dac18 100644 --- a/packages/edit-post/src/store/selectors.js +++ b/packages/edit-post/src/store/selectors.js @@ -25,7 +25,10 @@ export function getEditorMode( state ) { export function isEditorSidebarOpened( state ) { const activeGeneralSidebar = getActiveGeneralSidebarName( state ); - return includes( [ 'edit-post/document', 'edit-post/block' ], activeGeneralSidebar ); + return includes( + [ 'edit-post/document', 'edit-post/block' ], + activeGeneralSidebar + ); } /** @@ -55,7 +58,11 @@ export function isPluginSidebarOpened( state ) { */ export function getActiveGeneralSidebarName( state ) { // Dismissal takes precedent. - const isDismissed = getPreference( state, 'isGeneralSidebarDismissed', false ); + const isDismissed = getPreference( + state, + 'isGeneralSidebarDismissed', + false + ); if ( isDismissed ) { return null; } @@ -124,8 +131,10 @@ export function isEditorPanelRemoved( state, panelName ) { export function isEditorPanelEnabled( state, panelName ) { const panels = getPreference( state, 'panels' ); - return ! isEditorPanelRemoved( state, panelName ) && - get( panels, [ panelName, 'enabled' ], true ); + return ( + ! isEditorPanelRemoved( state, panelName ) && + get( panels, [ panelName, 'enabled' ], true ) + ); } /** @@ -193,12 +202,11 @@ export function isPluginItemPinned( state, pluginName ) { */ export const getActiveMetaBoxLocations = createSelector( ( state ) => { - return Object.keys( state.metaBoxes.locations ) - .filter( ( location ) => isMetaBoxLocationActive( state, location ) ); + return Object.keys( state.metaBoxes.locations ).filter( ( location ) => + isMetaBoxLocationActive( state, location ) + ); }, - ( state ) => [ - state.metaBoxes.locations, - ] + ( state ) => [ state.metaBoxes.locations ] ); /** @@ -255,9 +263,7 @@ export const getAllMetaBoxes = createSelector( ( state ) => { return flatten( values( state.metaBoxes.locations ) ); }, - ( state ) => [ - state.metaBoxes.locations, - ] + ( state ) => [ state.metaBoxes.locations ] ); /** diff --git a/packages/edit-post/src/store/test/reducer.js b/packages/edit-post/src/store/test/reducer.js index 4241de25d5b951..b21be5ff26d9ba 100644 --- a/packages/edit-post/src/store/test/reducer.js +++ b/packages/edit-post/src/store/test/reducer.js @@ -36,10 +36,12 @@ describe( 'state', () => { } ); it( 'should set the general sidebar undismissed', () => { - const original = deepFreeze( preferences( undefined, { - type: 'OPEN_GENERAL_SIDEBAR', - name: 'edit-post/document', - } ) ); + const original = deepFreeze( + preferences( undefined, { + type: 'OPEN_GENERAL_SIDEBAR', + name: 'edit-post/document', + } ) + ); const state = preferences( original, { type: 'CLOSE_GENERAL_SIDEBAR', } ); @@ -173,10 +175,13 @@ describe( 'state', () => { } ); it( 'should toggle a feature flag', () => { - const state = preferences( deepFreeze( { features: { chicken: true } } ), { - type: 'TOGGLE_FEATURE', - feature: 'chicken', - } ); + const state = preferences( + deepFreeze( { features: { chicken: true } } ), + { + type: 'TOGGLE_FEATURE', + feature: 'chicken', + } + ); expect( state.features ).toEqual( { chicken: false } ); } ); @@ -195,7 +200,9 @@ describe( 'state', () => { pluginName: 'foo/does-not-exist', } ); - expect( state.pinnedPluginItems[ 'foo/does-not-exist' ] ).toBe( false ); + expect( state.pinnedPluginItems[ 'foo/does-not-exist' ] ).toBe( + false + ); } ); it( 'should disable a pinned plugin flag when it is enabled', () => { @@ -204,7 +211,9 @@ describe( 'state', () => { pluginName: 'foo/enabled', } ); - expect( state.pinnedPluginItems[ 'foo/enabled' ] ).toBe( false ); + expect( state.pinnedPluginItems[ 'foo/enabled' ] ).toBe( + false + ); } ); it( 'should enable a pinned plugin flag when it is disabled', () => { @@ -213,7 +222,9 @@ describe( 'state', () => { pluginName: 'foo/disabled', } ); - expect( state.pinnedPluginItems[ 'foo/disabled' ] ).toBe( true ); + expect( state.pinnedPluginItems[ 'foo/disabled' ] ).toBe( + true + ); } ); } ); @@ -228,11 +239,7 @@ describe( 'state', () => { blockNames: [ 'b', 'c' ], } ); - expect( state.hiddenBlockTypes ).toEqual( [ - 'a', - 'b', - 'c', - ] ); + expect( state.hiddenBlockTypes ).toEqual( [ 'a', 'b', 'c' ] ); } ); it( 'omits present names by enable', () => { @@ -245,9 +252,7 @@ describe( 'state', () => { blockNames: [ 'b', 'c' ], } ); - expect( state.hiddenBlockTypes ).toEqual( [ - 'a', - ] ); + expect( state.hiddenBlockTypes ).toEqual( [ 'a' ] ); } ); } ); } ); diff --git a/packages/edit-post/src/store/test/selectors.js b/packages/edit-post/src/store/test/selectors.js index 16131bf8eaf8d9..4da4686fc0379e 100644 --- a/packages/edit-post/src/store/test/selectors.js +++ b/packages/edit-post/src/store/test/selectors.js @@ -65,7 +65,9 @@ describe( 'selectors', () => { preferences: {}, }; - expect( getPreference( state, 'ribs', 'chicken' ) ).toEqual( 'chicken' ); + expect( getPreference( state, 'ribs', 'chicken' ) ).toEqual( + 'chicken' + ); } ); } ); @@ -171,7 +173,9 @@ describe( 'selectors', () => { activeGeneralSidebar: 'edit-post/block', }; - expect( getActiveGeneralSidebarName( state ) ).toBe( 'edit-post/block' ); + expect( getActiveGeneralSidebarName( state ) ).toBe( + 'edit-post/block' + ); } ); } ); @@ -207,14 +211,14 @@ describe( 'selectors', () => { removedPanels: [], } ); - expect( isEditorPanelRemoved( state, 'post-status' ) ).toBe( false ); + expect( isEditorPanelRemoved( state, 'post-status' ) ).toBe( + false + ); } ); it( 'should return true when panel was removed', () => { const state = deepFreeze( { - removedPanels: [ - 'post-status', - ], + removedPanels: [ 'post-status' ], } ); expect( isEditorPanelRemoved( state, 'post-status' ) ).toBe( true ); @@ -253,7 +257,9 @@ describe( 'selectors', () => { }, }; - expect( isEditorPanelEnabled( state, 'post-status' ) ).toBe( false ); + expect( isEditorPanelEnabled( state, 'post-status' ) ).toBe( + false + ); } ); it( 'should return false when a panel is enabled but removed', () => { @@ -268,7 +274,9 @@ describe( 'selectors', () => { removedPanels: [ 'post-status' ], } ); - expect( isEditorPanelEnabled( state, 'post-status' ) ).toBe( false ); + expect( isEditorPanelEnabled( state, 'post-status' ) ).toBe( + false + ); } ); } ); @@ -378,8 +386,7 @@ describe( 'selectors', () => { it( 'should return false if feature is not referred', () => { const state = { preferences: { - features: { - }, + features: {}, }, }; diff --git a/packages/edit-post/src/test/editor.native.js b/packages/edit-post/src/test/editor.native.js index 89cfc4a9bcc96c..08866099cb3d5a 100644 --- a/packages/edit-post/src/test/editor.native.js +++ b/packages/edit-post/src/test/editor.native.js @@ -40,8 +40,12 @@ describe( 'Editor', () => { } ); appContainer.unmount(); - expect( RNReactNativeGutenbergBridge.editorDidMount ).toHaveBeenCalledTimes( 1 ); - expect( RNReactNativeGutenbergBridge.editorDidMount ).toHaveBeenCalledWith( [ 'core/notablock' ] ); + expect( + RNReactNativeGutenbergBridge.editorDidMount + ).toHaveBeenCalledTimes( 1 ); + expect( + RNReactNativeGutenbergBridge.editorDidMount + ).toHaveBeenCalledWith( [ 'core/notablock' ] ); } ); } ); diff --git a/packages/edit-post/src/utils/meta-boxes.js b/packages/edit-post/src/utils/meta-boxes.js index 055a239f1c1d92..a781b963184024 100644 --- a/packages/edit-post/src/utils/meta-boxes.js +++ b/packages/edit-post/src/utils/meta-boxes.js @@ -7,7 +7,9 @@ * @return {string} HTML content. */ export const getMetaBoxContainer = ( location ) => { - const area = document.querySelector( `.edit-post-meta-boxes-area.is-${ location } .metabox-location-${ location }` ); + const area = document.querySelector( + `.edit-post-meta-boxes-area.is-${ location } .metabox-location-${ location }` + ); if ( area ) { return area; } diff --git a/packages/edit-site/src/components/block-editor/index.js b/packages/edit-site/src/components/block-editor/index.js index 360f7823d89f0a..ef886a68f53b59 100644 --- a/packages/edit-site/src/components/block-editor/index.js +++ b/packages/edit-site/src/components/block-editor/index.js @@ -23,7 +23,10 @@ import Sidebar from '../sidebar'; export default function BlockEditor( { settings: _settings } ) { const canUserCreateMedia = useSelect( ( select ) => { - const _canUserCreateMedia = select( 'core' ).canUser( 'create', 'media' ); + const _canUserCreateMedia = select( 'core' ).canUser( + 'create', + 'media' + ); return _canUserCreateMedia || _canUserCreateMedia !== false; }, [] ); const settings = useMemo( () => { diff --git a/packages/edit-site/src/components/save-button/index.js b/packages/edit-site/src/components/save-button/index.js index ad1ab25b832258..a7d4acc7dab8ee 100644 --- a/packages/edit-site/src/components/save-button/index.js +++ b/packages/edit-site/src/components/save-button/index.js @@ -9,7 +9,11 @@ import { __ } from '@wordpress/i18n'; import { EntitiesSavedStates } from '@wordpress/editor'; export default function SaveButton() { - const [ , setStatus ] = useEntityProp( 'postType', 'wp_template', 'status' ); + const [ , setStatus ] = useEntityProp( + 'postType', + 'wp_template', + 'status' + ); // Publish template if not done yet. useEffect( () => setStatus( 'publish' ), [] ); @@ -26,9 +30,15 @@ export default function SaveButton() { entityRecordChangesByRecord[ changedKind ] ).some( ( changedName ) => Object.keys( - entityRecordChangesByRecord[ changedKind ][ changedName ] + entityRecordChangesByRecord[ changedKind ][ + changedName + ] ).some( ( changedKey ) => - isSavingEntityRecord( changedKind, changedName, changedKey ) + isSavingEntityRecord( + changedKind, + changedName, + changedKey + ) ) ) ), 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 index ebc1099a35f7a6..4752d0305c0bad 100644 --- 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 @@ -52,14 +52,17 @@ const getWidgetAreasObject = () => { 'core' ); - return getEntityRecords( 'root', 'widgetArea' ).reduce( ( widgetAreasObject, { id } ) => { - widgetAreasObject[ id ] = getEditedEntityRecord( - 'root', - 'widgetArea', - id - ).blocks; - return widgetAreasObject; - }, {} ); + 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. @@ -67,7 +70,10 @@ const previewBlocksInWidgetArea = throttle( ( id, blocks ) => { const customizePreviewIframe = document.querySelector( '#customize-preview > iframe' ); - if ( ! customizePreviewIframe || ! customizePreviewIframe.contentDocument ) { + if ( + ! customizePreviewIframe || + ! customizePreviewIframe.contentDocument + ) { return; } @@ -115,13 +121,17 @@ if ( window.wp && window.wp.customize && window.wp.data ) { let widgetAreas; try { widgetAreas = JSON.parse( - document.getElementById( '_customize-input-gutenberg_widget_blocks' ) - .value + document.getElementById( + '_customize-input-gutenberg_widget_blocks' + ).value + ); + widgetAreas = Object.keys( widgetAreas ).reduce( + ( value, id ) => { + value[ id ] = parse( widgetAreas[ id ] ); + return value; + }, + {} ); - widgetAreas = Object.keys( widgetAreas ).reduce( ( value, id ) => { - value[ id ] = parse( widgetAreas[ id ] ); - return value; - }, {} ); } catch ( err ) { widgetAreas = {}; } @@ -130,20 +140,26 @@ if ( window.wp && window.wp.customize && window.wp.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' ] ), + () => + window.wp.data + .select( 'core' ) + .hasFinishedResolution( 'getEntityRecords', [ + 'root', + 'widgetArea', + ] ), true, - () => window.wp.data.select( 'core' ).getEntityRecords( 'root', 'widgetArea' ) + () => + window.wp.data + .select( 'core' ) + .getEntityRecords( 'root', 'widgetArea' ) ).then( () => { Object.keys( widgetAreas ).forEach( ( id ) => { - window.wp.data.dispatch( 'core' ).editEntityRecord( - 'root', - 'widgetArea', - id, - { + window.wp.data + .dispatch( 'core' ) + .editEntityRecord( 'root', 'widgetArea', id, { content: serialize( widgetAreas[ id ] ), blocks: widgetAreas[ id ], - } - ); + } ); } ); widgetAreas = getWidgetAreasObject(); window.wp.data.subscribe( () => { @@ -152,7 +168,10 @@ if ( window.wp && window.wp.customize && window.wp.data ) { let didUpdate = false; for ( const id of Object.keys( nextWidgetAreas ) ) { if ( widgetAreas[ id ] !== nextWidgetAreas[ id ] ) { - previewBlocksInWidgetArea( id, nextWidgetAreas[ id ] ); + previewBlocksInWidgetArea( + id, + nextWidgetAreas[ id ] + ); didUpdate = true; } } diff --git a/packages/edit-widgets/src/components/edit-widgets-initializer/index.js b/packages/edit-widgets/src/components/edit-widgets-initializer/index.js index 02e34e13b4cd9a..f93745fed15ae3 100644 --- a/packages/edit-widgets/src/components/edit-widgets-initializer/index.js +++ b/packages/edit-widgets/src/components/edit-widgets-initializer/index.js @@ -4,11 +4,7 @@ import Layout from '../layout'; function EditWidgetsInitializer( { settings } ) { - return ( - <Layout - blockEditorSettings={ settings } - /> - ); + return <Layout blockEditorSettings={ settings } />; } export default EditWidgetsInitializer; diff --git a/packages/edit-widgets/src/components/inserter/index.js b/packages/edit-widgets/src/components/inserter/index.js index 12139805745e17..84184b541c918b 100644 --- a/packages/edit-widgets/src/components/inserter/index.js +++ b/packages/edit-widgets/src/components/inserter/index.js @@ -3,14 +3,14 @@ */ import { createSlotFill } from '@wordpress/components'; -const { Fill: BlockInserterFill, Slot: BlockInserterSlot } = createSlotFill( 'EditWidgetsInserter' ); +const { Fill: BlockInserterFill, Slot: BlockInserterSlot } = createSlotFill( + 'EditWidgetsInserter' +); const Inserter = BlockInserterFill; Inserter.Slot = function() { - return ( - <BlockInserterSlot bubblesVirtually /> - ); + return <BlockInserterSlot bubblesVirtually />; }; export default Inserter; diff --git a/packages/edit-widgets/src/components/save-button/index.js b/packages/edit-widgets/src/components/save-button/index.js index fc1a2a1cc06029..637aac055e6357 100644 --- a/packages/edit-widgets/src/components/save-button/index.js +++ b/packages/edit-widgets/src/components/save-button/index.js @@ -12,28 +12,23 @@ import { useCallback } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; function SaveButton() { - const { editedWidgetAreaIds, isSaving } = useSelect( - ( select ) => { - const { - hasEditsForEntityRecord, - isSavingEntityRecord, - getEntityRecords, - } = select( 'core' ); - const widgetAreas = getEntityRecords( 'root', 'widgetArea' ); - const widgetAreaIds = map( widgetAreas, ( { id } ) => id ); - return { - editedWidgetAreaIds: filter( - widgetAreaIds, - ( id ) => hasEditsForEntityRecord( 'root', 'widgetArea', id ) - ), - isSaving: some( - widgetAreaIds, - ( id ) => isSavingEntityRecord( 'root', 'widgetArea', id ) - ), - }; - }, - [] - ); + const { editedWidgetAreaIds, isSaving } = useSelect( ( select ) => { + const { + hasEditsForEntityRecord, + isSavingEntityRecord, + getEntityRecords, + } = select( 'core' ); + const widgetAreas = getEntityRecords( 'root', 'widgetArea' ); + const widgetAreaIds = map( widgetAreas, ( { id } ) => id ); + return { + editedWidgetAreaIds: filter( widgetAreaIds, ( id ) => + hasEditsForEntityRecord( 'root', 'widgetArea', id ) + ), + isSaving: some( widgetAreaIds, ( id ) => + isSavingEntityRecord( 'root', 'widgetArea', id ) + ), + }; + }, [] ); const { saveEditedEntityRecord } = useDispatch( 'core' ); const onClick = useCallback( () => { diff --git a/packages/edit-widgets/src/components/sidebar/index.js b/packages/edit-widgets/src/components/sidebar/index.js index 60cb68b22fb0a1..ebd70ac9294cbe 100644 --- a/packages/edit-widgets/src/components/sidebar/index.js +++ b/packages/edit-widgets/src/components/sidebar/index.js @@ -4,7 +4,10 @@ import { createSlotFill, Panel } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -export const { Fill: BlockSidebarFill, Slot: BlockSidebarSlot } = createSlotFill( 'EditWidgetsBlockSidebar' ); +export const { + Fill: BlockSidebarFill, + Slot: BlockSidebarSlot, +} = createSlotFill( 'EditWidgetsBlockSidebar' ); function Sidebar() { return ( diff --git a/packages/edit-widgets/src/components/widget-area/index.js b/packages/edit-widgets/src/components/widget-area/index.js index c30271526926b4..b2b4e801263162 100644 --- a/packages/edit-widgets/src/components/widget-area/index.js +++ b/packages/edit-widgets/src/components/widget-area/index.js @@ -53,19 +53,30 @@ function WidgetArea( { isSelectedArea, onBlockSelected, } ) { - const { blocks, widgetAreaName, hasUploadPermissions, rawContent } = useSelect( + const { + blocks, + widgetAreaName, + hasUploadPermissions, + rawContent, + } = useSelect( ( select ) => { - const { - canUser, - getEditedEntityRecord, - } = select( 'core' ); - const widgetArea = getEditedEntityRecord( 'root', 'widgetArea', id ); + const { canUser, getEditedEntityRecord } = select( 'core' ); + const widgetArea = getEditedEntityRecord( + 'root', + 'widgetArea', + id + ); const widgetAreaContent = get( widgetArea, [ 'content' ], '' ); return { blocks: widgetArea && widgetArea.blocks, - rawContent: widgetAreaContent.raw ? widgetAreaContent.raw : widgetAreaContent, + rawContent: widgetAreaContent.raw + ? widgetAreaContent.raw + : widgetAreaContent, widgetAreaName: widgetArea && widgetArea.name, - hasUploadPermissions: defaultTo( canUser( 'create', 'media' ), true ), + hasUploadPermissions: defaultTo( + canUser( 'create', 'media' ), + true + ), }; }, [ id ] @@ -87,24 +98,19 @@ function WidgetArea( { [ editEntityRecord, id ] ); const settings = useMemo( - () => getBlockEditorSettings( blockEditorSettings, hasUploadPermissions ), + () => + getBlockEditorSettings( blockEditorSettings, hasUploadPermissions ), [ blockEditorSettings, hasUploadPermissions ] ); - useEffect( - () => { - if ( blocks ) { - return; - } - onChange( parse( rawContent ) ); - }, - [ blocks, onChange, rawContent ] - ); + useEffect( () => { + if ( blocks ) { + return; + } + onChange( parse( rawContent ) ); + }, [ blocks, onChange, rawContent ] ); return ( <Panel className="edit-widgets-widget-area"> - <PanelBody - title={ widgetAreaName } - initialOpen={ initialOpen } - > + <PanelBody title={ widgetAreaName } initialOpen={ initialOpen }> <div onFocus={ ( event ) => { // Stop propagation of the focus event to avoid the parent @@ -132,7 +138,9 @@ function WidgetArea( { onBlockSelected={ onBlockSelected } /> <Sidebar.Inspector> - <BlockInspector showNoBlockSelectedMessage={ false } /> + <BlockInspector + showNoBlockSelectedMessage={ false } + /> </Sidebar.Inspector> <div className="editor-styles-wrapper"> <WritingFlow> diff --git a/packages/edit-widgets/src/components/widget-areas/index.js b/packages/edit-widgets/src/components/widget-areas/index.js index 89d19501618b9b..b1171e10ac3d86 100644 --- a/packages/edit-widgets/src/components/widget-areas/index.js +++ b/packages/edit-widgets/src/components/widget-areas/index.js @@ -11,7 +11,12 @@ import WidgetArea from '../widget-area'; const EMPTY_ARRAY = []; -function WidgetAreas( { areas, blockEditorSettings, selectedArea, setSelectedArea } ) { +function WidgetAreas( { + areas, + blockEditorSettings, + selectedArea, + setSelectedArea, +} ) { return areas.map( ( { id }, index ) => ( <WidgetArea isSelectedArea={ index === selectedArea } diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index b93a1ab7198500..ef10dd0f50960c 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -3,7 +3,10 @@ */ import '@wordpress/notices'; import { render } from '@wordpress/element'; -import { registerCoreBlocks, __experimentalRegisterExperimentalCoreBlocks } from '@wordpress/block-library'; +import { + registerCoreBlocks, + __experimentalRegisterExperimentalCoreBlocks, +} from '@wordpress/block-library'; /** * Internal dependencies @@ -24,9 +27,7 @@ export function initialize( id, settings ) { __experimentalRegisterExperimentalCoreBlocks( settings ); } render( - <EditWidgetsInitializer - settings={ settings } - />, + <EditWidgetsInitializer settings={ settings } />, document.getElementById( id ) ); } @@ -43,9 +44,7 @@ export function customizerInitialize( id, settings ) { __experimentalRegisterExperimentalCoreBlocks( settings ); } render( - <CustomizerEditWidgetsInitializer - settings={ settings } - />, + <CustomizerEditWidgetsInitializer settings={ settings } />, document.getElementById( id ) ); } diff --git a/packages/editor/src/components/autocompleters/block.js b/packages/editor/src/components/autocompleters/block.js index 73ff5583873fae..d4fb7507477d05 100644 --- a/packages/editor/src/components/autocompleters/block.js +++ b/packages/editor/src/components/autocompleters/block.js @@ -45,7 +45,9 @@ function defaultGetInserterItems( rootClientId ) { * block is selected. */ function defaultGetSelectedBlockName() { - const { getSelectedBlockClientId, getBlockName } = select( 'core/block-editor' ); + const { getSelectedBlockClientId, getBlockName } = select( + 'core/block-editor' + ); const selectedBlockClientId = getSelectedBlockClientId(); return selectedBlockClientId ? getBlockName( selectedBlockClientId ) : null; } @@ -99,10 +101,7 @@ export function createBlockCompleter( { }, getOptionLabel( inserterItem ) { const { icon, title } = inserterItem; - return [ - <BlockIcon key="icon" icon={ icon } showColors />, - title, - ]; + return [ <BlockIcon key="icon" icon={ icon } showColors />, title ]; }, allowContext( before, after ) { return ! ( /\S/.test( before ) || /\S/.test( after ) ); diff --git a/packages/editor/src/components/autocompleters/test/block.js b/packages/editor/src/components/autocompleters/test/block.js index 71582b3e33ba80..87534d69d159d2 100644 --- a/packages/editor/src/components/autocompleters/test/block.js +++ b/packages/editor/src/components/autocompleters/test/block.js @@ -31,7 +31,9 @@ describe( 'block', () => { it( 'should retrieve block options for current insertion point', async () => { const expectedOptions = [ {}, {}, {} ]; - const mockGetBlockInsertionParentClientId = jest.fn( () => 'expected-insertion-point' ); + const mockGetBlockInsertionParentClientId = jest.fn( + () => 'expected-insertion-point' + ); const mockGetInserterItems = jest.fn( () => expectedOptions ); const completer = createBlockCompleter( { @@ -42,7 +44,9 @@ describe( 'block', () => { const actualOptions = completer.options(); expect( mockGetBlockInsertionParentClientId ).toHaveBeenCalled(); - expect( mockGetInserterItems ).toHaveBeenCalledWith( 'expected-insertion-point' ); + expect( mockGetInserterItems ).toHaveBeenCalledWith( + 'expected-insertion-point' + ); expect( actualOptions ).toEqual( expectedOptions ); } ); @@ -53,7 +57,11 @@ describe( 'block', () => { const completer = createBlockCompleter( { getBlockInsertionParentClientId: () => 'ignored', - getInserterItems: () => [ option1, option2CurrentlySelected, option3 ], + getInserterItems: () => [ + option1, + option2CurrentlySelected, + option3, + ], getSelectedBlockName: () => 'block-2-currently-selected', } ); @@ -81,25 +89,36 @@ describe( 'block', () => { // Intentionally omitted keyword list }; - expect( blockCompleter.getOptionKeywords( inserterItemWithTitleAndKeywords ) ) - .toEqual( [ 'formatting', 'foo-keyword-1', 'foo-keyword-2', 'foo' ] ); - expect( blockCompleter.getOptionKeywords( inserterItemWithTitleAndEmptyKeywords ) ) - .toEqual( [ 'common', 'bar' ] ); - expect( blockCompleter.getOptionKeywords( inserterItemWithTitleAndUndefinedKeywords ) ) - .toEqual( [ 'widgets', 'baz' ] ); + expect( + blockCompleter.getOptionKeywords( inserterItemWithTitleAndKeywords ) + ).toEqual( [ 'formatting', 'foo-keyword-1', 'foo-keyword-2', 'foo' ] ); + expect( + blockCompleter.getOptionKeywords( + inserterItemWithTitleAndEmptyKeywords + ) + ).toEqual( [ 'common', 'bar' ] ); + expect( + blockCompleter.getOptionKeywords( + inserterItemWithTitleAndUndefinedKeywords + ) + ).toEqual( [ 'widgets', 'baz' ] ); } ); it( 'should render a block option label', () => { - const labelComponents = shallow( <div> - { blockCompleter.getOptionLabel( { - icon: 'expected-icon', - title: 'expected-text', - } ) } - </div> ).children(); + const labelComponents = shallow( + <div> + { blockCompleter.getOptionLabel( { + icon: 'expected-icon', + title: 'expected-text', + } ) } + </div> + ).children(); expect( labelComponents ).toHaveLength( 2 ); expect( labelComponents.at( 0 ).name() ).toBe( 'BlockIcon' ); - expect( labelComponents.at( 0 ).prop( 'icon' ) ).toEqual( 'expected-icon' ); + expect( labelComponents.at( 0 ).prop( 'icon' ) ).toEqual( + 'expected-icon' + ); expect( labelComponents.at( 1 ).text() ).toBe( 'expected-text' ); } ); @@ -117,7 +136,11 @@ describe( 'block', () => { isDisabled: false, }; - expect( blockCompleter.isOptionDisabled( disabledInserterItem ) ).toBe( true ); - expect( blockCompleter.isOptionDisabled( enabledInserterItem ) ).toBe( false ); + expect( blockCompleter.isOptionDisabled( disabledInserterItem ) ).toBe( + true + ); + expect( blockCompleter.isOptionDisabled( enabledInserterItem ) ).toBe( + false + ); } ); } ); diff --git a/packages/editor/src/components/autocompleters/test/user.js b/packages/editor/src/components/autocompleters/test/user.js index c6e8bbce4b5dcc..f699ebfbfd2022 100644 --- a/packages/editor/src/components/autocompleters/test/user.js +++ b/packages/editor/src/components/autocompleters/test/user.js @@ -12,9 +12,24 @@ describe( 'user', () => { avatar_urls: { 24: 'http://my.avatar' }, }; const userLabel = userCompleter.getOptionLabel( user ); - expect( userLabel[ 0 ] ).toEqual( <img key="avatar" className="editor-autocompleters__user-avatar" alt="" src="http://my.avatar" /> ); - expect( userLabel[ 1 ] ).toEqual( <span key="name" className="editor-autocompleters__user-name">Smithers Jones</span> ); - expect( userLabel[ 2 ] ).toEqual( <span key="slug" className="editor-autocompleters__user-slug">userSlug</span> ); + expect( userLabel[ 0 ] ).toEqual( + <img + key="avatar" + className="editor-autocompleters__user-avatar" + alt="" + src="http://my.avatar" + /> + ); + expect( userLabel[ 1 ] ).toEqual( + <span key="name" className="editor-autocompleters__user-name"> + Smithers Jones + </span> + ); + expect( userLabel[ 2 ] ).toEqual( + <span key="slug" className="editor-autocompleters__user-slug"> + userSlug + </span> + ); } ); it( 'should return user details fragment without default avatar dashicon if avatar_urls array not set', () => { const user = { @@ -22,9 +37,19 @@ describe( 'user', () => { slug: 'userSlug', }; const userLabel = userCompleter.getOptionLabel( user ); - expect( userLabel[ 0 ] ).toEqual( <span className="editor-autocompleters__no-avatar"></span> ); - expect( userLabel[ 1 ] ).toEqual( <span key="name" className="editor-autocompleters__user-name">Smithers Jones</span> ); - expect( userLabel[ 2 ] ).toEqual( <span key="slug" className="editor-autocompleters__user-slug">userSlug</span> ); + expect( userLabel[ 0 ] ).toEqual( + <span className="editor-autocompleters__no-avatar"></span> + ); + expect( userLabel[ 1 ] ).toEqual( + <span key="name" className="editor-autocompleters__user-name"> + Smithers Jones + </span> + ); + expect( userLabel[ 2 ] ).toEqual( + <span key="slug" className="editor-autocompleters__user-slug"> + userSlug + </span> + ); } ); } ); } ); diff --git a/packages/editor/src/components/autocompleters/user.js b/packages/editor/src/components/autocompleters/user.js index be8107c39e6458..4b8c1f2dbcf374 100644 --- a/packages/editor/src/components/autocompleters/user.js +++ b/packages/editor/src/components/autocompleters/user.js @@ -26,14 +26,26 @@ export default { return [ user.slug, user.name ]; }, getOptionLabel( user ) { - const avatar = user.avatar_urls && user.avatar_urls[ 24 ] ? - <img key="avatar" className="editor-autocompleters__user-avatar" alt="" src={ user.avatar_urls[ 24 ] } /> : - <span className="editor-autocompleters__no-avatar"></span>; + const avatar = + user.avatar_urls && user.avatar_urls[ 24 ] ? ( + <img + key="avatar" + className="editor-autocompleters__user-avatar" + alt="" + src={ user.avatar_urls[ 24 ] } + /> + ) : ( + <span className="editor-autocompleters__no-avatar"></span> + ); return [ avatar, - <span key="name" className="editor-autocompleters__user-name">{ user.name }</span>, - <span key="slug" className="editor-autocompleters__user-slug">{ user.slug }</span>, + <span key="name" className="editor-autocompleters__user-name"> + { user.name } + </span>, + <span key="slug" className="editor-autocompleters__user-slug"> + { user.slug } + </span>, ]; }, getOptionCompletion( user ) { diff --git a/packages/editor/src/components/autosave-monitor/index.js b/packages/editor/src/components/autosave-monitor/index.js index e46ee8e3c20b76..1cc03a6e39ecb9 100644 --- a/packages/editor/src/components/autosave-monitor/index.js +++ b/packages/editor/src/components/autosave-monitor/index.js @@ -7,7 +7,12 @@ import { withSelect, withDispatch } from '@wordpress/data'; export class AutosaveMonitor extends Component { componentDidUpdate( prevProps ) { - const { isDirty, editsReference, isAutosaveable, isAutosaving } = this.props; + const { + isDirty, + editsReference, + isAutosaveable, + isAutosaving, + } = this.props; // The edits reference is held for comparison to avoid scheduling an // autosave if an edit has not been made since the last autosave @@ -30,9 +35,7 @@ export class AutosaveMonitor extends Component { prevProps.editsReference !== editsReference ) { this.toggleTimer( - isDirty && - isAutosaveable && - ! this.didAutosaveForEditsReference + isDirty && isAutosaveable && ! this.didAutosaveForEditsReference ); } } @@ -55,13 +58,10 @@ export class AutosaveMonitor extends Component { } if ( isPendingSave && ! ( shouldThrottle && this.pendingSave ) ) { - this.pendingSave = setTimeout( - () => { - this.props.autosave(); - delete this.pendingSave; - }, - interval * 1000 - ); + this.pendingSave = setTimeout( () => { + this.props.autosave(); + delete this.pendingSave; + }, interval * 1000 ); } } @@ -72,9 +72,7 @@ export class AutosaveMonitor extends Component { export default compose( [ withSelect( ( select, ownProps ) => { - const { - getReferenceByDistinctEdits, - } = select( 'core' ); + const { getReferenceByDistinctEdits } = select( 'core' ); const { isEditedPostDirty, diff --git a/packages/editor/src/components/autosave-monitor/test/index.js b/packages/editor/src/components/autosave-monitor/test/index.js index efba08ed31d14b..68c177171f1612 100644 --- a/packages/editor/src/components/autosave-monitor/test/index.js +++ b/packages/editor/src/components/autosave-monitor/test/index.js @@ -13,10 +13,9 @@ describe( 'AutosaveMonitor', () => { let wrapper; beforeEach( () => { toggleTimer.mockClear(); - wrapper = shallow( - <AutosaveMonitor />, - { lifecycleExperimental: true } - ); + wrapper = shallow( <AutosaveMonitor />, { + lifecycleExperimental: true, + } ); wrapper.instance().toggleTimer = toggleTimer; } ); @@ -93,14 +92,29 @@ describe( 'AutosaveMonitor', () => { const afterReference = []; // A post is non-dirty while autosave is in-flight. - wrapper.setProps( { isDirty: false, isAutosaving: true, isAutosaveable: true, editsReference: beforeReference } ); + wrapper.setProps( { + isDirty: false, + isAutosaving: true, + isAutosaveable: true, + editsReference: beforeReference, + } ); toggleTimer.mockClear(); - wrapper.setProps( { isDirty: true, isAutosaving: false, isAutosaveable: true, editsReference: beforeReference } ); + wrapper.setProps( { + isDirty: true, + isAutosaving: false, + isAutosaveable: true, + editsReference: beforeReference, + } ); expect( toggleTimer ).toHaveBeenCalledWith( false ); // Once edit occurs after autosave, resume scheduling. - wrapper.setProps( { isDirty: true, isAutosaving: false, isAutosaveable: true, editsReference: afterReference } ); + wrapper.setProps( { + isDirty: true, + isAutosaving: false, + isAutosaveable: true, + editsReference: afterReference, + } ); expect( toggleTimer.mock.calls[ 1 ][ 0 ] ).toBe( true ); } ); diff --git a/packages/editor/src/components/convert-to-group-buttons/convert-button.js b/packages/editor/src/components/convert-to-group-buttons/convert-button.js index 9f47a9d34f34ec..8c4734b40f1f9e 100644 --- a/packages/editor/src/components/convert-to-group-buttons/convert-button.js +++ b/packages/editor/src/components/convert-to-group-buttons/convert-button.js @@ -27,19 +27,16 @@ export function ConvertToGroupButton( { return ( <Fragment> { isGroupable && ( - <MenuItem - icon={ Group } - onClick={ onConvertToGroup } - > + <MenuItem icon={ Group } onClick={ onConvertToGroup }> { _x( 'Group', 'verb' ) } </MenuItem> ) } { isUngroupable && ( - <MenuItem - icon={ Ungroup } - onClick={ onConvertFromGroup } - > - { _x( 'Ungroup', 'Ungrouping blocks from within a Group block back into individual blocks within the Editor ' ) } + <MenuItem icon={ Ungroup } onClick={ onConvertFromGroup }> + { _x( + 'Ungroup', + 'Ungrouping blocks from within a Group block back into individual blocks within the Editor ' + ) } </MenuItem> ) } </Fragment> @@ -54,35 +51,40 @@ export default compose( [ canInsertBlockType, } = select( 'core/block-editor' ); - const { - getGroupingBlockName, - } = select( 'core/blocks' ); + const { getGroupingBlockName } = select( 'core/blocks' ); const groupingBlockName = getGroupingBlockName(); - const rootClientId = clientIds && clientIds.length > 0 ? - getBlockRootClientId( clientIds[ 0 ] ) : - undefined; + const rootClientId = + clientIds && clientIds.length > 0 + ? getBlockRootClientId( clientIds[ 0 ] ) + : undefined; - const groupingBlockAvailable = canInsertBlockType( groupingBlockName, rootClientId ); + const groupingBlockAvailable = canInsertBlockType( + groupingBlockName, + rootClientId + ); const blocksSelection = getBlocksByClientId( clientIds ); - const isSingleGroupingBlock = blocksSelection.length === 1 && blocksSelection[ 0 ] && blocksSelection[ 0 ].name === groupingBlockName; + const isSingleGroupingBlock = + blocksSelection.length === 1 && + blocksSelection[ 0 ] && + blocksSelection[ 0 ].name === groupingBlockName; // Do we have // 1. Grouping block available to be inserted? // 2. One or more blocks selected // (we allow single Blocks to become groups unless // they are a soltiary group block themselves) - const isGroupable = ( + const isGroupable = groupingBlockAvailable && blocksSelection.length && - ! isSingleGroupingBlock - ); + ! isSingleGroupingBlock; // Do we have a single Group Block selected and does that group have inner blocks? - const isUngroupable = isSingleGroupingBlock && !! blocksSelection[ 0 ].innerBlocks.length; + const isUngroupable = + isSingleGroupingBlock && !! blocksSelection[ 0 ].innerBlocks.length; return { isGroupable, @@ -91,47 +93,52 @@ export default compose( [ groupingBlockName, }; } ), - withDispatch( ( dispatch, { clientIds, onToggle = noop, blocksSelection = [], groupingBlockName } ) => { - const { - replaceBlocks, - } = dispatch( 'core/block-editor' ); - - return { - onConvertToGroup() { - if ( ! blocksSelection.length ) { - return; - } - - // Activate the `transform` on the Grouping Block which does the conversion - const newBlocks = switchToBlockType( blocksSelection, groupingBlockName ); - - if ( newBlocks ) { - replaceBlocks( - clientIds, - newBlocks + withDispatch( + ( + dispatch, + { + clientIds, + onToggle = noop, + blocksSelection = [], + groupingBlockName, + } + ) => { + const { replaceBlocks } = dispatch( 'core/block-editor' ); + + return { + onConvertToGroup() { + if ( ! blocksSelection.length ) { + return; + } + + // Activate the `transform` on the Grouping Block which does the conversion + const newBlocks = switchToBlockType( + blocksSelection, + groupingBlockName ); - } - onToggle(); - }, - onConvertFromGroup() { - if ( ! blocksSelection.length ) { - return; - } + if ( newBlocks ) { + replaceBlocks( clientIds, newBlocks ); + } - const innerBlocks = blocksSelection[ 0 ].innerBlocks; + onToggle(); + }, + onConvertFromGroup() { + if ( ! blocksSelection.length ) { + return; + } - if ( ! innerBlocks.length ) { - return; - } + const innerBlocks = blocksSelection[ 0 ].innerBlocks; - replaceBlocks( - clientIds, - innerBlocks - ); + if ( ! innerBlocks.length ) { + return; + } - onToggle(); - }, - }; - } ), + replaceBlocks( clientIds, innerBlocks ); + + onToggle(); + }, + }; + } + ), ] )( ConvertToGroupButton ); diff --git a/packages/editor/src/components/convert-to-group-buttons/icons.js b/packages/editor/src/components/convert-to-group-buttons/icons.js index 990d15dd5ac1cd..321dac7a828052 100644 --- a/packages/editor/src/components/convert-to-group-buttons/icons.js +++ b/packages/editor/src/components/convert-to-group-buttons/icons.js @@ -3,11 +3,46 @@ */ import { Icon, SVG, Path } from '@wordpress/components'; -const GroupSVG = <SVG width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M8 5a1 1 0 0 0-1 1v3H6a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1v-3h1a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H8zm3 6H7v2h4v-2zM9 9V7h4v2H9z" /><Path fillRule="evenodd" clipRule="evenodd" d="M1 3a2 2 0 0 0 1 1.732v10.536A2 2 0 1 0 4.732 18h10.536A2 2 0 1 0 18 15.268V4.732A2 2 0 1 0 15.268 2H4.732A2 2 0 0 0 1 3zm14.268 1H4.732A2.01 2.01 0 0 1 4 4.732v10.536c.304.175.557.428.732.732h10.536a2.01 2.01 0 0 1 .732-.732V4.732A2.01 2.01 0 0 1 15.268 4z" /></SVG>; +const GroupSVG = ( + <SVG + width="20" + height="20" + viewBox="0 0 20 20" + xmlns="http://www.w3.org/2000/svg" + > + <Path + fillRule="evenodd" + clipRule="evenodd" + d="M8 5a1 1 0 0 0-1 1v3H6a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1v-3h1a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H8zm3 6H7v2h4v-2zM9 9V7h4v2H9z" + /> + <Path + fillRule="evenodd" + clipRule="evenodd" + d="M1 3a2 2 0 0 0 1 1.732v10.536A2 2 0 1 0 4.732 18h10.536A2 2 0 1 0 18 15.268V4.732A2 2 0 1 0 15.268 2H4.732A2 2 0 0 0 1 3zm14.268 1H4.732A2.01 2.01 0 0 1 4 4.732v10.536c.304.175.557.428.732.732h10.536a2.01 2.01 0 0 1 .732-.732V4.732A2.01 2.01 0 0 1 15.268 4z" + /> + </SVG> +); export const Group = <Icon icon={ GroupSVG } />; -const UngroupSVG = <SVG width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><Path fillRule="evenodd" clipRule="evenodd" d="M9 2H15C16.1 2 17 2.9 17 4V7C17 8.1 16.1 9 15 9H9C7.9 9 7 8.1 7 7V4C7 2.9 7.9 2 9 2ZM9 7H15V4H9V7Z" /><Path fillRule="evenodd" clipRule="evenodd" d="M5 11H11C12.1 11 13 11.9 13 13V16C13 17.1 12.1 18 11 18H5C3.9 18 3 17.1 3 16V13C3 11.9 3.9 11 5 11ZM5 16H11V13H5V16Z" /></SVG>; +const UngroupSVG = ( + <SVG + width="20" + height="20" + viewBox="0 0 20 20" + xmlns="http://www.w3.org/2000/svg" + > + <Path + fillRule="evenodd" + clipRule="evenodd" + d="M9 2H15C16.1 2 17 2.9 17 4V7C17 8.1 16.1 9 15 9H9C7.9 9 7 8.1 7 7V4C7 2.9 7.9 2 9 2ZM9 7H15V4H9V7Z" + /> + <Path + fillRule="evenodd" + clipRule="evenodd" + d="M5 11H11C12.1 11 13 11.9 13 13V16C13 17.1 12.1 18 11 18H5C3.9 18 3 17.1 3 16V13C3 11.9 3.9 11 5 11ZM5 16H11V13H5V16Z" + /> + </SVG> +); export const Ungroup = <Icon icon={ UngroupSVG } />; - diff --git a/packages/editor/src/components/convert-to-group-buttons/index.native.js b/packages/editor/src/components/convert-to-group-buttons/index.native.js index bd0c2f440d06f2..461f67a0a4bcbe 100644 --- a/packages/editor/src/components/convert-to-group-buttons/index.native.js +++ b/packages/editor/src/components/convert-to-group-buttons/index.native.js @@ -1,2 +1 @@ - export default () => null; diff --git a/packages/editor/src/components/deprecated.js b/packages/editor/src/components/deprecated.js index 022da5c4023fa2..7fa2d89f35cad7 100644 --- a/packages/editor/src/components/deprecated.js +++ b/packages/editor/src/components/deprecated.js @@ -69,7 +69,7 @@ function deprecateComponent( name, Wrapped, staticsToHoist = [] ) { alternative: 'wp.blockEditor.' + name, } ); - return <Wrapped ref={ ref }{ ...props } />; + return <Wrapped ref={ ref } { ...props } />; } ); staticsToHoist.forEach( ( staticName ) => { @@ -93,60 +93,182 @@ function deprecateFunction( name, func ) { } const RichText = deprecateComponent( 'RichText', RootRichText, [ 'Content' ] ); -RichText.isEmpty = deprecateFunction( 'RichText.isEmpty', RootRichText.isEmpty ); +RichText.isEmpty = deprecateFunction( + 'RichText.isEmpty', + RootRichText.isEmpty +); export { RichText }; -export const Autocomplete = deprecateComponent( 'Autocomplete', RootAutocomplete ); -export const AlignmentToolbar = deprecateComponent( 'AlignmentToolbar', RootAlignmentToolbar ); -export const BlockAlignmentToolbar = deprecateComponent( 'BlockAlignmentToolbar', RootBlockAlignmentToolbar ); -export const BlockControls = deprecateComponent( 'BlockControls', RootBlockControls, [ 'Slot' ] ); +export const Autocomplete = deprecateComponent( + 'Autocomplete', + RootAutocomplete +); +export const AlignmentToolbar = deprecateComponent( + 'AlignmentToolbar', + RootAlignmentToolbar +); +export const BlockAlignmentToolbar = deprecateComponent( + 'BlockAlignmentToolbar', + RootBlockAlignmentToolbar +); +export const BlockControls = deprecateComponent( + 'BlockControls', + RootBlockControls, + [ 'Slot' ] +); export const BlockEdit = deprecateComponent( 'BlockEdit', RootBlockEdit ); -export const BlockEditorKeyboardShortcuts = deprecateComponent( 'BlockEditorKeyboardShortcuts', RootBlockEditorKeyboardShortcuts ); -export const BlockFormatControls = deprecateComponent( 'BlockFormatControls', RootBlockFormatControls, [ 'Slot' ] ); +export const BlockEditorKeyboardShortcuts = deprecateComponent( + 'BlockEditorKeyboardShortcuts', + RootBlockEditorKeyboardShortcuts +); +export const BlockFormatControls = deprecateComponent( + 'BlockFormatControls', + RootBlockFormatControls, + [ 'Slot' ] +); export const BlockIcon = deprecateComponent( 'BlockIcon', RootBlockIcon ); -export const BlockInspector = deprecateComponent( 'BlockInspector', RootBlockInspector ); +export const BlockInspector = deprecateComponent( + 'BlockInspector', + RootBlockInspector +); export const BlockList = deprecateComponent( 'BlockList', RootBlockList ); export const BlockMover = deprecateComponent( 'BlockMover', RootBlockMover ); -export const BlockNavigationDropdown = deprecateComponent( 'BlockNavigationDropdown', RootBlockNavigationDropdown ); -export const BlockSelectionClearer = deprecateComponent( 'BlockSelectionClearer', RootBlockSelectionClearer ); -export const BlockSettingsMenu = deprecateComponent( 'BlockSettingsMenu', RootBlockSettingsMenu ); +export const BlockNavigationDropdown = deprecateComponent( + 'BlockNavigationDropdown', + RootBlockNavigationDropdown +); +export const BlockSelectionClearer = deprecateComponent( + 'BlockSelectionClearer', + RootBlockSelectionClearer +); +export const BlockSettingsMenu = deprecateComponent( + 'BlockSettingsMenu', + RootBlockSettingsMenu +); export const BlockTitle = deprecateComponent( 'BlockTitle', RootBlockTitle ); -export const BlockToolbar = deprecateComponent( 'BlockToolbar', RootBlockToolbar ); -export const ColorPalette = deprecateComponent( 'ColorPalette', RootColorPalette ); -export const ContrastChecker = deprecateComponent( 'ContrastChecker', RootContrastChecker ); +export const BlockToolbar = deprecateComponent( + 'BlockToolbar', + RootBlockToolbar +); +export const ColorPalette = deprecateComponent( + 'ColorPalette', + RootColorPalette +); +export const ContrastChecker = deprecateComponent( + 'ContrastChecker', + RootContrastChecker +); export const CopyHandler = deprecateComponent( 'CopyHandler', RootCopyHandler ); -export const DefaultBlockAppender = deprecateComponent( 'DefaultBlockAppender', RootDefaultBlockAppender ); -export const FontSizePicker = deprecateComponent( 'FontSizePicker', RootFontSizePicker ); +export const DefaultBlockAppender = deprecateComponent( + 'DefaultBlockAppender', + RootDefaultBlockAppender +); +export const FontSizePicker = deprecateComponent( + 'FontSizePicker', + RootFontSizePicker +); export const Inserter = deprecateComponent( 'Inserter', RootInserter ); -export const InnerBlocks = deprecateComponent( 'InnerBlocks', RootInnerBlocks, [ 'ButtonBlockAppender', 'DefaultBlockAppender', 'Content' ] ); -export const InspectorAdvancedControls = deprecateComponent( 'InspectorAdvancedControls', RootInspectorAdvancedControls, [ 'Slot' ] ); -export const InspectorControls = deprecateComponent( 'InspectorControls', RootInspectorControls, [ 'Slot' ] ); -export const PanelColorSettings = deprecateComponent( 'PanelColorSettings', RootPanelColorSettings ); +export const InnerBlocks = deprecateComponent( 'InnerBlocks', RootInnerBlocks, [ + 'ButtonBlockAppender', + 'DefaultBlockAppender', + 'Content', +] ); +export const InspectorAdvancedControls = deprecateComponent( + 'InspectorAdvancedControls', + RootInspectorAdvancedControls, + [ 'Slot' ] +); +export const InspectorControls = deprecateComponent( + 'InspectorControls', + RootInspectorControls, + [ 'Slot' ] +); +export const PanelColorSettings = deprecateComponent( + 'PanelColorSettings', + RootPanelColorSettings +); export const PlainText = deprecateComponent( 'PlainText', RootPlainText ); -export const RichTextShortcut = deprecateComponent( 'RichTextShortcut', RootRichTextShortcut ); -export const RichTextToolbarButton = deprecateComponent( 'RichTextToolbarButton', RootRichTextToolbarButton ); -export const __unstableRichTextInputEvent = deprecateComponent( '__unstableRichTextInputEvent', __unstableRootRichTextInputEvent ); -export const MediaPlaceholder = deprecateComponent( 'MediaPlaceholder', RootMediaPlaceholder ); +export const RichTextShortcut = deprecateComponent( + 'RichTextShortcut', + RootRichTextShortcut +); +export const RichTextToolbarButton = deprecateComponent( + 'RichTextToolbarButton', + RootRichTextToolbarButton +); +export const __unstableRichTextInputEvent = deprecateComponent( + '__unstableRichTextInputEvent', + __unstableRootRichTextInputEvent +); +export const MediaPlaceholder = deprecateComponent( + 'MediaPlaceholder', + RootMediaPlaceholder +); export const MediaUpload = deprecateComponent( 'MediaUpload', RootMediaUpload ); -export const MediaUploadCheck = deprecateComponent( 'MediaUploadCheck', RootMediaUploadCheck ); -export const MultiBlocksSwitcher = deprecateComponent( 'MultiBlocksSwitcher', RootMultiBlocksSwitcher ); -export const MultiSelectScrollIntoView = deprecateComponent( 'MultiSelectScrollIntoView', RootMultiSelectScrollIntoView ); -export const NavigableToolbar = deprecateComponent( 'NavigableToolbar', RootNavigableToolbar ); -export const ObserveTyping = deprecateComponent( 'ObserveTyping', RootObserveTyping ); -export const PreserveScrollInReorder = deprecateComponent( 'PreserveScrollInReorder', RootPreserveScrollInReorder ); -export const SkipToSelectedBlock = deprecateComponent( 'SkipToSelectedBlock', RootSkipToSelectedBlock ); +export const MediaUploadCheck = deprecateComponent( + 'MediaUploadCheck', + RootMediaUploadCheck +); +export const MultiBlocksSwitcher = deprecateComponent( + 'MultiBlocksSwitcher', + RootMultiBlocksSwitcher +); +export const MultiSelectScrollIntoView = deprecateComponent( + 'MultiSelectScrollIntoView', + RootMultiSelectScrollIntoView +); +export const NavigableToolbar = deprecateComponent( + 'NavigableToolbar', + RootNavigableToolbar +); +export const ObserveTyping = deprecateComponent( + 'ObserveTyping', + RootObserveTyping +); +export const PreserveScrollInReorder = deprecateComponent( + 'PreserveScrollInReorder', + RootPreserveScrollInReorder +); +export const SkipToSelectedBlock = deprecateComponent( + 'SkipToSelectedBlock', + RootSkipToSelectedBlock +); export const URLInput = deprecateComponent( 'URLInput', RootURLInput ); -export const URLInputButton = deprecateComponent( 'URLInputButton', RootURLInputButton ); +export const URLInputButton = deprecateComponent( + 'URLInputButton', + RootURLInputButton +); export const URLPopover = deprecateComponent( 'URLPopover', RootURLPopover ); export const Warning = deprecateComponent( 'Warning', RootWarning ); export const WritingFlow = deprecateComponent( 'WritingFlow', RootWritingFlow ); -export const createCustomColorsHOC = deprecateFunction( 'createCustomColorsHOC', rootCreateCustomColorsHOC ); -export const getColorClassName = deprecateFunction( 'getColorClassName', rootGetColorClassName ); -export const getColorObjectByAttributeValues = deprecateFunction( 'getColorObjectByAttributeValues', rootGetColorObjectByAttributeValues ); -export const getColorObjectByColorValue = deprecateFunction( 'getColorObjectByColorValue', rootGetColorObjectByColorValue ); +export const createCustomColorsHOC = deprecateFunction( + 'createCustomColorsHOC', + rootCreateCustomColorsHOC +); +export const getColorClassName = deprecateFunction( + 'getColorClassName', + rootGetColorClassName +); +export const getColorObjectByAttributeValues = deprecateFunction( + 'getColorObjectByAttributeValues', + rootGetColorObjectByAttributeValues +); +export const getColorObjectByColorValue = deprecateFunction( + 'getColorObjectByColorValue', + rootGetColorObjectByColorValue +); export const getFontSize = deprecateFunction( 'getFontSize', rootGetFontSize ); -export const getFontSizeClass = deprecateFunction( 'getFontSizeClass', rootGetFontSizeClass ); -export const withColorContext = deprecateFunction( 'withColorContext', rootWithColorContext ); +export const getFontSizeClass = deprecateFunction( + 'getFontSizeClass', + rootGetFontSizeClass +); +export const withColorContext = deprecateFunction( + 'withColorContext', + rootWithColorContext +); export const withColors = deprecateFunction( 'withColors', rootWithColors ); -export const withFontSizes = deprecateFunction( 'withFontSizes', rootWithFontSizes ); +export const withFontSizes = deprecateFunction( + 'withFontSizes', + rootWithFontSizes +); diff --git a/packages/editor/src/components/document-outline/check.js b/packages/editor/src/components/document-outline/check.js index ccbf66c27d1502..84d988b0a510f1 100644 --- a/packages/editor/src/components/document-outline/check.js +++ b/packages/editor/src/components/document-outline/check.js @@ -9,7 +9,10 @@ import { filter } from 'lodash'; import { withSelect } from '@wordpress/data'; function DocumentOutlineCheck( { blocks, children } ) { - const headings = filter( blocks, ( block ) => block.name === 'core/heading' ); + const headings = filter( + blocks, + ( block ) => block.name === 'core/heading' + ); if ( headings.length < 1 ) { return null; diff --git a/packages/editor/src/components/document-outline/index.js b/packages/editor/src/components/document-outline/index.js index 5a61503909a88b..3609f51c42a637 100644 --- a/packages/editor/src/components/document-outline/index.js +++ b/packages/editor/src/components/document-outline/index.js @@ -9,10 +9,7 @@ import { countBy, flatMap, get } from 'lodash'; import { __ } from '@wordpress/i18n'; import { compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; -import { - create, - getTextContent, -} from '@wordpress/rich-text'; +import { create, getTextContent } from '@wordpress/rich-text'; /** * Internal dependencies @@ -29,11 +26,15 @@ const incorrectLevelContent = [ ]; const singleH1Headings = [ <br key="incorrect-break-h1" />, - <em key="incorrect-message-h1">{ __( '(Your theme may already use a H1 for the post title)' ) }</em>, + <em key="incorrect-message-h1"> + { __( '(Your theme may already use a H1 for the post title)' ) } + </em>, ]; const multipleH1Headings = [ <br key="incorrect-break-multiple-h1" />, - <em key="incorrect-message-multiple-h1">{ __( '(Multiple H1 headings are not recommended)' ) }</em>, + <em key="incorrect-message-multiple-h1"> + { __( '(Multiple H1 headings are not recommended)' ) } + </em>, ]; /** @@ -62,9 +63,16 @@ const computeOutlineHeadings = ( blocks = [], path = [] ) => { } ); }; -const isEmptyHeading = ( heading ) => ! heading.attributes.content || heading.attributes.content.length === 0; +const isEmptyHeading = ( heading ) => + ! heading.attributes.content || heading.attributes.content.length === 0; -export const DocumentOutline = ( { blocks = [], title, onSelect, isTitleSupported, hasOutlineItemsDisabled } ) => { +export const DocumentOutline = ( { + blocks = [], + title, + onSelect, + isTitleSupported, + hasOutlineItemsDisabled, +} ) => { const headings = computeOutlineHeadings( blocks ); if ( headings.length < 1 ) { @@ -98,12 +106,12 @@ export const DocumentOutline = ( { blocks = [], title, onSelect, isTitleSupporte // Otherwise there are missing levels. const isIncorrectLevel = item.level > prevHeadingLevel + 1; - const isValid = ( + const isValid = ! item.isEmpty && ! isIncorrectLevel && !! item.level && - ( item.level !== 1 || ( ! hasMultipleH1 && ! hasTitle ) ) - ); + ( item.level !== 1 || + ( ! hasMultipleH1 && ! hasTitle ) ); prevHeadingLevel = item.level; return ( @@ -116,15 +124,21 @@ export const DocumentOutline = ( { blocks = [], title, onSelect, isTitleSupporte href={ `#block-${ item.clientId }` } onSelect={ onSelect } > - { item.isEmpty ? - emptyHeadingContent : - getTextContent( - create( { html: item.attributes.content } ) - ) - } + { item.isEmpty + ? emptyHeadingContent + : getTextContent( + create( { + html: item.attributes.content, + } ) + ) } { isIncorrectLevel && incorrectLevelContent } - { item.level === 1 && hasMultipleH1 && multipleH1Headings } - { hasTitle && item.level === 1 && ! hasMultipleH1 && singleH1Headings } + { item.level === 1 && + hasMultipleH1 && + multipleH1Headings } + { hasTitle && + item.level === 1 && + ! hasMultipleH1 && + singleH1Headings } </DocumentOutlineItem> ); } ) } diff --git a/packages/editor/src/components/document-outline/item.js b/packages/editor/src/components/document-outline/item.js index 1a92e7dbafc4e0..efed6fd733faff 100644 --- a/packages/editor/src/components/document-outline/item.js +++ b/packages/editor/src/components/document-outline/item.js @@ -30,22 +30,19 @@ const TableOfContentsItem = ( { className="document-outline__button" onClick={ onSelect } > - <span className="document-outline__emdash" aria-hidden="true"></span> - { - // path is an array of nodes that are ancestors of the heading starting in the top level node. - // This mapping renders each ancestor to make it easier for the user to know where the headings are nested. - path.map( ( { clientId }, index ) => ( - <strong key={ index } className="document-outline__level"> - <BlockTitle clientId={ clientId } /> - </strong> - ) ) - } - <strong className="document-outline__level"> - { level } - </strong> - <span className="document-outline__item-content"> - { children } - </span> + <span + className="document-outline__emdash" + aria-hidden="true" + ></span> + { // path is an array of nodes that are ancestors of the heading starting in the top level node. + // This mapping renders each ancestor to make it easier for the user to know where the headings are nested. + path.map( ( { clientId }, index ) => ( + <strong key={ index } className="document-outline__level"> + <BlockTitle clientId={ clientId } /> + </strong> + ) ) } + <strong className="document-outline__level">{ level }</strong> + <span className="document-outline__item-content">{ children }</span> </a> </li> ); diff --git a/packages/editor/src/components/document-outline/test/index.js b/packages/editor/src/components/document-outline/test/index.js index 5ee66565d2dad8..9277e7c4afd3a0 100644 --- a/packages/editor/src/components/document-outline/test/index.js +++ b/packages/editor/src/components/document-outline/test/index.js @@ -6,7 +6,11 @@ import { mount, shallow } from 'enzyme'; /** * WordPress dependencies */ -import { createBlock, registerBlockType, unregisterBlockType } from '@wordpress/blocks'; +import { + createBlock, + registerBlockType, + unregisterBlockType, +} from '@wordpress/blocks'; /** * Internal dependencies @@ -23,8 +27,8 @@ describe( 'DocumentOutline', () => { registerBlockType( 'core/heading', { category: 'common', title: 'Heading', - edit: () => { }, - save: () => { }, + edit: () => {}, + save: () => {}, attributes: { level: { type: 'number', @@ -39,14 +43,14 @@ describe( 'DocumentOutline', () => { registerBlockType( 'core/paragraph', { category: 'common', title: 'Paragraph', - edit: () => { }, + edit: () => {}, save: () => {}, } ); registerBlockType( 'core/columns', { category: 'common', title: 'Paragraph', - edit: () => { }, + edit: () => {}, save: () => {}, } ); @@ -64,7 +68,9 @@ describe( 'DocumentOutline', () => { level: 3, } ); - nestedHeading = createBlock( 'core/columns', undefined, [ headingChild ] ); + nestedHeading = createBlock( 'core/columns', undefined, [ + headingChild, + ] ); } ); afterAll( () => { @@ -92,10 +98,12 @@ describe( 'DocumentOutline', () => { describe( 'header blocks present', () => { it( 'should match snapshot', () => { - const blocks = [ headingParent, headingChild ].map( ( block, index ) => { - // Set client IDs to a predictable value. - return { ...block, clientId: `clientId_${ index }` }; - } ); + const blocks = [ headingParent, headingChild ].map( + ( block, index ) => { + // Set client IDs to a predictable value. + return { ...block, clientId: `clientId_${ index }` }; + } + ); const wrapper = shallow( <DocumentOutline blocks={ blocks } /> ); expect( wrapper ).toMatchSnapshot(); @@ -109,17 +117,25 @@ describe( 'DocumentOutline', () => { } ); it( 'should render two items when two headings and some paragraphs provided', () => { - const blocks = [ paragraph, headingParent, paragraph, headingChild, paragraph ]; + const blocks = [ + paragraph, + headingParent, + paragraph, + headingChild, + paragraph, + ]; const wrapper = shallow( <DocumentOutline blocks={ blocks } /> ); expect( wrapper.find( 'TableOfContentsItem' ) ).toHaveLength( 2 ); } ); it( 'should render warnings for multiple h1 headings', () => { - const blocks = [ headingH1, paragraph, headingH1, paragraph ].map( ( block, index ) => { - // Set client IDs to a predictable value. - return { ...block, clientId: `clientId_${ index }` }; - } ); + const blocks = [ headingH1, paragraph, headingH1, paragraph ].map( + ( block, index ) => { + // Set client IDs to a predictable value. + return { ...block, clientId: `clientId_${ index }` }; + } + ); const wrapper = shallow( <DocumentOutline blocks={ blocks } /> ); expect( wrapper ).toMatchSnapshot(); @@ -130,27 +146,44 @@ describe( 'DocumentOutline', () => { it( 'should render even if the heading is nested', () => { const tableOfContentItemsSelector = 'TableOfContentsItem'; const outlineLevelsSelector = '.document-outline__level'; - const outlineItemContentSelector = '.document-outline__item-content'; + const outlineItemContentSelector = + '.document-outline__item-content'; const blocks = [ headingParent, nestedHeading ]; const wrapper = mount( <DocumentOutline blocks={ blocks } /> ); //heading parent and nested heading should appear as items - const tableOfContentItems = wrapper.find( tableOfContentItemsSelector ); + const tableOfContentItems = wrapper.find( + tableOfContentItemsSelector + ); expect( tableOfContentItems ).toHaveLength( 2 ); //heading parent test - const firstItemLevels = tableOfContentItems.at( 0 ).find( outlineLevelsSelector ); + const firstItemLevels = tableOfContentItems + .at( 0 ) + .find( outlineLevelsSelector ); expect( firstItemLevels ).toHaveLength( 1 ); expect( firstItemLevels.at( 0 ).text() ).toEqual( 'H2' ); - expect( tableOfContentItems.at( 0 ).find( outlineItemContentSelector ).text() ).toEqual( 'Heading parent' ); + expect( + tableOfContentItems + .at( 0 ) + .find( outlineItemContentSelector ) + .text() + ).toEqual( 'Heading parent' ); //nested heading test - const secondItemLevels = tableOfContentItems.at( 1 ).find( outlineLevelsSelector ); + const secondItemLevels = tableOfContentItems + .at( 1 ) + .find( outlineLevelsSelector ); expect( secondItemLevels ).toHaveLength( 2 ); expect( secondItemLevels.at( 0 ).text() ).toEqual( 'Block Title' ); expect( secondItemLevels.at( 1 ).text() ).toEqual( 'H3' ); - expect( tableOfContentItems.at( 1 ).find( outlineItemContentSelector ).text() ).toEqual( 'Heading child' ); + expect( + tableOfContentItems + .at( 1 ) + .find( outlineItemContentSelector ) + .text() + ).toEqual( 'Heading child' ); } ); } ); } ); diff --git a/packages/editor/src/components/entities-saved-states/index.js b/packages/editor/src/components/entities-saved-states/index.js index 8c404ebb58862e..621a8e9a037583 100644 --- a/packages/editor/src/components/entities-saved-states/index.js +++ b/packages/editor/src/components/entities-saved-states/index.js @@ -38,7 +38,9 @@ export default function EntitiesSavedStates( { ); const { saveEditedEntityRecord } = useDispatch( 'core' ); - const [ checkedById, _setCheckedById ] = useState( () => new EquivalentKeyMap() ); + const [ checkedById, _setCheckedById ] = useState( + () => new EquivalentKeyMap() + ); const setCheckedById = ( id, checked ) => _setCheckedById( ( prevCheckedById ) => { const nextCheckedById = new EquivalentKeyMap( prevCheckedById ); @@ -53,7 +55,9 @@ export default function EntitiesSavedStates( { checkedById.forEach( ( _checked, id ) => { if ( ! ignoredForSave.has( id ) ) { saveEditedEntityRecord( - ...id.filter( ( s, i ) => i !== id.length - 1 || s !== 'undefined' ) + ...id.filter( + ( s, i ) => i !== id.length - 1 || s !== 'undefined' + ) ); } } ); @@ -66,29 +70,37 @@ export default function EntitiesSavedStates( { onRequestClose={ () => onRequestClose() } contentLabel={ __( 'Select items to save.' ) } > - { Object.keys( entityRecordChangesByRecord ).map( ( changedKind ) => - Object.keys( entityRecordChangesByRecord[ changedKind ] ).map( - ( changedName ) => + { Object.keys( entityRecordChangesByRecord ).map( + ( changedKind ) => + Object.keys( + entityRecordChangesByRecord[ changedKind ] + ).map( ( changedName ) => Object.keys( - entityRecordChangesByRecord[ changedKind ][ changedName ] + entityRecordChangesByRecord[ changedKind ][ + changedName + ] ).map( ( changedKey ) => { - const id = [ changedKind, changedName, changedKey ]; + const id = [ + changedKind, + changedName, + changedKey, + ]; return ( <EntitiesSavedStatesCheckbox key={ id.join( ' | ' ) } id={ id } name={ changedName } changes={ - entityRecordChangesByRecord[ changedKind ][ changedName ][ - changedKey - ] + entityRecordChangesByRecord[ + changedKind + ][ changedName ][ changedKey ] } checked={ checkedById.get( id ) } setCheckedById={ setCheckedById } /> ); } ) - ) + ) ) } <Button isPrimary diff --git a/packages/editor/src/components/error-boundary/index.js b/packages/editor/src/components/error-boundary/index.js index f235950ebcbeba..6040e3b190d887 100644 --- a/packages/editor/src/components/error-boundary/index.js +++ b/packages/editor/src/components/error-boundary/index.js @@ -52,10 +52,18 @@ class ErrorBoundary extends Component { <Button key="recovery" onClick={ this.reboot } isSecondary> { __( 'Attempt Recovery' ) } </Button>, - <ClipboardButton key="copy-post" text={ this.getContent } isSecondary> + <ClipboardButton + key="copy-post" + text={ this.getContent } + isSecondary + > { __( 'Copy Post Text' ) } </ClipboardButton>, - <ClipboardButton key="copy-error" text={ error.stack } isSecondary> + <ClipboardButton + key="copy-error" + text={ error.stack } + isSecondary + > { __( 'Copy Error' ) } </ClipboardButton>, ] } diff --git a/packages/editor/src/components/global-keyboard-shortcuts/save-shortcut.js b/packages/editor/src/components/global-keyboard-shortcuts/save-shortcut.js index 64cd864f81ddc2..7b96aab26abfb5 100644 --- a/packages/editor/src/components/global-keyboard-shortcuts/save-shortcut.js +++ b/packages/editor/src/components/global-keyboard-shortcuts/save-shortcut.js @@ -6,22 +6,29 @@ import { useDispatch, useSelect } from '@wordpress/data'; function SaveShortcut() { const { savePost } = useDispatch( 'core/editor' ); - const isEditedPostDirty = useSelect( ( select ) => select( 'core/editor' ).isEditedPostDirty, [] ); + const isEditedPostDirty = useSelect( + ( select ) => select( 'core/editor' ).isEditedPostDirty, + [] + ); - useShortcut( 'core/editor/save', ( event ) => { - event.preventDefault(); + useShortcut( + 'core/editor/save', + ( event ) => { + event.preventDefault(); - // TODO: This should be handled in the `savePost` effect in - // considering `isSaveable`. See note on `isEditedPostSaveable` - // selector about dirtiness and meta-boxes. - // - // See: `isEditedPostSaveable` - if ( ! isEditedPostDirty() ) { - return; - } + // TODO: This should be handled in the `savePost` effect in + // considering `isSaveable`. See note on `isEditedPostSaveable` + // selector about dirtiness and meta-boxes. + // + // See: `isEditedPostSaveable` + if ( ! isEditedPostDirty() ) { + return; + } - savePost(); - }, { bindGlobal: true } ); + savePost(); + }, + { bindGlobal: true } + ); return null; } diff --git a/packages/editor/src/components/global-keyboard-shortcuts/text-editor-shortcuts.js b/packages/editor/src/components/global-keyboard-shortcuts/text-editor-shortcuts.js index 2d2d0510fd3167..21f76daec567f6 100644 --- a/packages/editor/src/components/global-keyboard-shortcuts/text-editor-shortcuts.js +++ b/packages/editor/src/components/global-keyboard-shortcuts/text-editor-shortcuts.js @@ -4,7 +4,5 @@ import SaveShortcut from './save-shortcut'; export default function TextEditorGlobalKeyboardShortcuts() { - return ( - <SaveShortcut /> - ); + return <SaveShortcut />; } diff --git a/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js b/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js index d94a7dc4ebbd95..6ca37c90f0fec6 100644 --- a/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js +++ b/packages/editor/src/components/global-keyboard-shortcuts/visual-editor-shortcuts.js @@ -13,32 +13,47 @@ import SaveShortcut from './save-shortcut'; function VisualEditorGlobalKeyboardShortcuts() { const { redo, undo, savePost } = useDispatch( 'core/editor' ); - const isEditedPostDirty = useSelect( ( select ) => select( 'core/editor' ).isEditedPostDirty, [] ); - - useShortcut( 'core/editor/undo', ( event ) => { - undo(); - event.preventDefault(); - }, { bindGlobal: true } ); - - useShortcut( 'core/editor/redo', ( event ) => { - redo(); - event.preventDefault(); - }, { bindGlobal: true } ); - - useShortcut( 'core/editor/save', ( event ) => { - event.preventDefault(); - - // TODO: This should be handled in the `savePost` effect in - // considering `isSaveable`. See note on `isEditedPostSaveable` - // selector about dirtiness and meta-boxes. - // - // See: `isEditedPostSaveable` - if ( ! isEditedPostDirty() ) { - return; - } - - savePost(); - }, { bindGlobal: true } ); + const isEditedPostDirty = useSelect( + ( select ) => select( 'core/editor' ).isEditedPostDirty, + [] + ); + + useShortcut( + 'core/editor/undo', + ( event ) => { + undo(); + event.preventDefault(); + }, + { bindGlobal: true } + ); + + useShortcut( + 'core/editor/redo', + ( event ) => { + redo(); + event.preventDefault(); + }, + { bindGlobal: true } + ); + + useShortcut( + 'core/editor/save', + ( event ) => { + event.preventDefault(); + + // TODO: This should be handled in the `savePost` effect in + // considering `isSaveable`. See note on `isEditedPostSaveable` + // selector about dirtiness and meta-boxes. + // + // See: `isEditedPostSaveable` + if ( ! isEditedPostDirty() ) { + return; + } + + savePost(); + }, + { bindGlobal: true } + ); return ( <> diff --git a/packages/editor/src/components/index.native.js b/packages/editor/src/components/index.native.js index 45156604efe6f4..1c916b4e82c0e8 100644 --- a/packages/editor/src/components/index.native.js +++ b/packages/editor/src/components/index.native.js @@ -1,4 +1,3 @@ - // Post Related Components export { default as AutosaveMonitor } from './autosave-monitor'; export { default as PostTitle } from './post-title'; diff --git a/packages/editor/src/components/local-autosave-monitor/index.js b/packages/editor/src/components/local-autosave-monitor/index.js index 3f48bc57de5e37..5dd800530f3b4f 100644 --- a/packages/editor/src/components/local-autosave-monitor/index.js +++ b/packages/editor/src/components/local-autosave-monitor/index.js @@ -16,12 +16,11 @@ import { parse } from '@wordpress/blocks'; * Internal dependencies */ import AutosaveMonitor from '../autosave-monitor'; -import { - localAutosaveGet, - localAutosaveClear, -} from '../../store/controls'; +import { localAutosaveGet, localAutosaveClear } from '../../store/controls'; -const requestIdleCallback = window.requestIdleCallback ? window.requestIdleCallback : window.requestAnimationFrame; +const requestIdleCallback = window.requestIdleCallback + ? window.requestIdleCallback + : window.requestAnimationFrame; /** * Function which returns true if the current environment supports browser @@ -46,15 +45,16 @@ const hasSessionStorageSupport = once( () => { * restore a local autosave, if one exists. */ function useAutosaveNotice() { - const { - postId, - getEditedPostAttribute, - hasRemoteAutosave, - } = useSelect( ( select ) => ( { - postId: select( 'core/editor' ).getCurrentPostId(), - getEditedPostAttribute: select( 'core/editor' ).getEditedPostAttribute, - hasRemoteAutosave: !! select( 'core/editor' ).getEditorSettings().autosave, - } ), [] ); + const { postId, getEditedPostAttribute, hasRemoteAutosave } = useSelect( + ( select ) => ( { + postId: select( 'core/editor' ).getCurrentPostId(), + getEditedPostAttribute: select( 'core/editor' ) + .getEditedPostAttribute, + hasRemoteAutosave: !! select( 'core/editor' ).getEditorSettings() + .autosave, + } ), + [] + ); const { createWarningNotice, removeNotice } = useDispatch( 'core/notices' ); const { editPost, resetEditorBlocks } = useDispatch( 'core/editor' ); @@ -94,19 +94,24 @@ function useAutosaveNotice() { } const noticeId = uniqueId( 'wpEditorAutosaveRestore' ); - createWarningNotice( __( 'The backup of this post in your browser is different from the version below.' ), { - id: noticeId, - actions: [ - { - label: __( 'Restore the backup' ), - onClick() { - editPost( omit( edits, [ 'content' ] ) ); - resetEditorBlocks( parse( edits.content ) ); - removeNotice( noticeId ); + createWarningNotice( + __( + 'The backup of this post in your browser is different from the version below.' + ), + { + id: noticeId, + actions: [ + { + label: __( 'Restore the backup' ), + onClick() { + editPost( omit( edits, [ 'content' ] ) ); + resetEditorBlocks( parse( edits.content ) ); + removeNotice( noticeId ); + }, }, - }, - ], - } ); + ], + } + ); }, [ postId ] ); } @@ -114,27 +119,24 @@ function useAutosaveNotice() { * Custom hook which ejects a local autosave after a successful save occurs. */ function useAutosavePurge() { - const { - postId, - isDirty, - isAutosaving, - didError, - } = useSelect( ( select ) => ( { - postId: select( 'core/editor' ).getCurrentPostId(), - isDirty: select( 'core/editor' ).isEditedPostDirty(), - isAutosaving: select( 'core/editor' ).isAutosavingPost(), - didError: select( 'core/editor' ).didPostSaveRequestFail(), - } ), [] ); + const { postId, isDirty, isAutosaving, didError } = useSelect( + ( select ) => ( { + postId: select( 'core/editor' ).getCurrentPostId(), + isDirty: select( 'core/editor' ).isEditedPostDirty(), + isAutosaving: select( 'core/editor' ).isAutosavingPost(), + didError: select( 'core/editor' ).didPostSaveRequestFail(), + } ), + [] + ); const lastIsDirty = useRef( isDirty ); const lastIsAutosaving = useRef( isAutosaving ); useEffect( () => { if ( - ! didError && ( - ( lastIsAutosaving.current && ! isAutosaving ) || - ( lastIsDirty.current && ! isDirty ) - ) + ! didError && + ( ( lastIsAutosaving.current && ! isAutosaving ) || + ( lastIsDirty.current && ! isDirty ) ) ) { localAutosaveClear( postId ); } @@ -152,10 +154,13 @@ function LocalAutosaveMonitor() { useAutosaveNotice(); useAutosavePurge(); - const { localAutosaveInterval } = useSelect( ( select ) => ( { - localAutosaveInterval: select( 'core/editor' ) - .getEditorSettings().__experimentalLocalAutosaveInterval, - } ), [] ); + const { localAutosaveInterval } = useSelect( + ( select ) => ( { + localAutosaveInterval: select( 'core/editor' ).getEditorSettings() + .__experimentalLocalAutosaveInterval, + } ), + [] + ); return ( <AutosaveMonitor diff --git a/packages/editor/src/components/page-attributes/check.js b/packages/editor/src/components/page-attributes/check.js index 56be444a049851..a3c529fc0a0a0a 100644 --- a/packages/editor/src/components/page-attributes/check.js +++ b/packages/editor/src/components/page-attributes/check.js @@ -8,8 +8,16 @@ import { get, isEmpty } from 'lodash'; */ import { withSelect } from '@wordpress/data'; -export function PageAttributesCheck( { availableTemplates, postType, children } ) { - const supportsPageAttributes = get( postType, [ 'supports', 'page-attributes' ], false ); +export function PageAttributesCheck( { + availableTemplates, + postType, + children, +} ) { + const supportsPageAttributes = get( + postType, + [ 'supports', 'page-attributes' ], + false + ); // Only render fields if post type supports page attributes or available templates exist. if ( ! supportsPageAttributes && isEmpty( availableTemplates ) ) { @@ -20,7 +28,9 @@ export function PageAttributesCheck( { availableTemplates, postType, children } } export default withSelect( ( select ) => { - const { getEditedPostAttribute, getEditorSettings } = select( 'core/editor' ); + const { getEditedPostAttribute, getEditorSettings } = select( + 'core/editor' + ); const { getPostType } = select( 'core' ); const { availableTemplates } = getEditorSettings(); return { diff --git a/packages/editor/src/components/page-attributes/order.js b/packages/editor/src/components/page-attributes/order.js index bd14ab0ced9826..16d6eb681a6e28 100644 --- a/packages/editor/src/components/page-attributes/order.js +++ b/packages/editor/src/components/page-attributes/order.js @@ -18,35 +18,36 @@ import PostTypeSupportCheck from '../post-type-support-check'; export const PageAttributesOrder = withState( { orderInput: null, -} )( - ( { onUpdateOrder, order = 0, orderInput, setState } ) => { - const setUpdatedOrder = ( value ) => { - setState( { - orderInput: value, - } ); - const newOrder = Number( value ); - if ( Number.isInteger( newOrder ) && invoke( value, [ 'trim' ] ) !== '' ) { - onUpdateOrder( Number( value ) ); - } - }; - const value = orderInput === null ? order : orderInput; - return ( - <TextControl - className="editor-page-attributes__order" - type="number" - label={ __( 'Order' ) } - value={ value } - onChange={ setUpdatedOrder } - size={ 6 } - onBlur={ () => { - setState( { - orderInput: null, - } ); - } } - /> - ); - } -); +} )( ( { onUpdateOrder, order = 0, orderInput, setState } ) => { + const setUpdatedOrder = ( value ) => { + setState( { + orderInput: value, + } ); + const newOrder = Number( value ); + if ( + Number.isInteger( newOrder ) && + invoke( value, [ 'trim' ] ) !== '' + ) { + onUpdateOrder( Number( value ) ); + } + }; + const value = orderInput === null ? order : orderInput; + return ( + <TextControl + className="editor-page-attributes__order" + type="number" + label={ __( 'Order' ) } + value={ value } + onChange={ setUpdatedOrder } + size={ 6 } + onBlur={ () => { + setState( { + orderInput: null, + } ); + } } + /> + ); +} ); function PageAttributesOrderWithChecks( props ) { return ( @@ -59,7 +60,9 @@ function PageAttributesOrderWithChecks( props ) { export default compose( [ withSelect( ( select ) => { return { - order: select( 'core/editor' ).getEditedPostAttribute( 'menu_order' ), + order: select( 'core/editor' ).getEditedPostAttribute( + 'menu_order' + ), }; } ), withDispatch( ( dispatch ) => ( { diff --git a/packages/editor/src/components/page-attributes/parent.js b/packages/editor/src/components/page-attributes/parent.js index ae6aa49736dbab..b2aa7b6579ced2 100644 --- a/packages/editor/src/components/page-attributes/parent.js +++ b/packages/editor/src/components/page-attributes/parent.js @@ -16,7 +16,12 @@ import { withSelect, withDispatch } from '@wordpress/data'; */ import { buildTermsTree } from '../../utils/terms'; -export function PageAttributesParent( { parent, postType, items, onUpdateParent } ) { +export function PageAttributesParent( { + parent, + postType, + items, + onUpdateParent, +} ) { const isHierarchical = get( postType, [ 'hierarchical' ], false ); const parentPageLabel = get( postType, [ 'labels', 'parent_item_colon' ] ); const pageItems = items || []; @@ -24,11 +29,16 @@ export function PageAttributesParent( { parent, postType, items, onUpdateParent return null; } - const pagesTree = buildTermsTree( pageItems.map( ( item ) => ( { - id: item.id, - parent: item.parent, - name: ( item.title && item.title.raw ) ? item.title.raw : `#${ item.id } (${ __( 'no title' ) })`, - } ) ) ); + const pagesTree = buildTermsTree( + pageItems.map( ( item ) => ( { + id: item.id, + parent: item.parent, + name: + item.title && item.title.raw + ? item.title.raw + : `#${ item.id } (${ __( 'no title' ) })`, + } ) ) + ); return ( <TreeSelect className="editor-page-attributes__parent" @@ -43,7 +53,9 @@ export function PageAttributesParent( { parent, postType, items, onUpdateParent const applyWithSelect = withSelect( ( select ) => { const { getPostType, getEntityRecords } = select( 'core' ); - const { getCurrentPostId, getEditedPostAttribute } = select( 'core/editor' ); + const { getCurrentPostId, getEditedPostAttribute } = select( + 'core/editor' + ); const postTypeSlug = getEditedPostAttribute( 'type' ); const postType = getPostType( postTypeSlug ); const postId = getCurrentPostId(); @@ -58,7 +70,9 @@ const applyWithSelect = withSelect( ( select ) => { return { parent: getEditedPostAttribute( 'parent' ), - items: isHierarchical ? getEntityRecords( 'postType', postTypeSlug, query ) : [], + items: isHierarchical + ? getEntityRecords( 'postType', postTypeSlug, query ) + : [], postType, }; } ); @@ -72,7 +86,6 @@ const applyWithDispatch = withDispatch( ( dispatch ) => { }; } ); -export default compose( [ - applyWithSelect, - applyWithDispatch, -] )( PageAttributesParent ); +export default compose( [ applyWithSelect, applyWithDispatch ] )( + PageAttributesParent +); diff --git a/packages/editor/src/components/page-attributes/template.js b/packages/editor/src/components/page-attributes/template.js index 3646a05e3206ff..a2c7e45fad593d 100644 --- a/packages/editor/src/components/page-attributes/template.js +++ b/packages/editor/src/components/page-attributes/template.js @@ -11,7 +11,11 @@ import { SelectControl } from '@wordpress/components'; import { compose } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; -export function PageTemplate( { availableTemplates, selectedTemplate, onUpdate } ) { +export function PageTemplate( { + availableTemplates, + selectedTemplate, + onUpdate, +} ) { if ( isEmpty( availableTemplates ) ) { return null; } @@ -21,19 +25,22 @@ export function PageTemplate( { availableTemplates, selectedTemplate, onUpdate } value={ selectedTemplate } onChange={ onUpdate } className="editor-page-attributes__template" - options={ - map( availableTemplates, ( templateName, templateSlug ) => ( { + options={ map( + availableTemplates, + ( templateName, templateSlug ) => ( { value: templateSlug, label: templateName, - } ) ) - } + } ) + ) } /> ); } export default compose( withSelect( ( select ) => { - const { getEditedPostAttribute, getEditorSettings } = select( 'core/editor' ); + const { getEditedPostAttribute, getEditorSettings } = select( + 'core/editor' + ); const { availableTemplates } = getEditorSettings(); return { selectedTemplate: getEditedPostAttribute( 'template' ), @@ -42,7 +49,9 @@ export default compose( } ), withDispatch( ( dispatch ) => ( { onUpdate( templateSlug ) { - dispatch( 'core/editor' ).editPost( { template: templateSlug || '' } ); + dispatch( 'core/editor' ).editPost( { + template: templateSlug || '', + } ); }, - } ) ), + } ) ) )( PageTemplate ); diff --git a/packages/editor/src/components/page-attributes/test/check.js b/packages/editor/src/components/page-attributes/test/check.js index 9519d0540b78a9..af815beeb86154 100644 --- a/packages/editor/src/components/page-attributes/test/check.js +++ b/packages/editor/src/components/page-attributes/test/check.js @@ -16,20 +16,25 @@ describe( 'PageAttributesCheck', () => { }; it( 'should not render anything if unknown page attributes and available templates support', () => { - const wrapper = shallow( <PageAttributesCheck postType={ {} }>content</PageAttributesCheck> ); + const wrapper = shallow( + <PageAttributesCheck postType={ {} }>content</PageAttributesCheck> + ); expect( wrapper.type() ).toBe( null ); } ); it( 'should not render anything if no page attributes support and no available templates exist', () => { const wrapper = shallow( - <PageAttributesCheck availableTemplates={ {} } postType={ { - data: { - supports: { - 'page-attributes': false, + <PageAttributesCheck + availableTemplates={ {} } + postType={ { + data: { + supports: { + 'page-attributes': false, + }, }, - }, - } }> + } } + > content </PageAttributesCheck> ); @@ -38,19 +43,36 @@ describe( 'PageAttributesCheck', () => { } ); it( 'should render if page attributes support is true and no available templates exist', () => { - const wrapper = shallow( <PageAttributesCheck postType={ postType }>content</PageAttributesCheck> ); + const wrapper = shallow( + <PageAttributesCheck postType={ postType }> + content + </PageAttributesCheck> + ); expect( wrapper.text() ).toContain( 'content' ); } ); it( 'should render if page attributes support is false/unknown and available templates exist', () => { - const wrapper = shallow( <PageAttributesCheck availableTemplates={ { 'example.php': 'Example template' } } >content</PageAttributesCheck> ); + const wrapper = shallow( + <PageAttributesCheck + availableTemplates={ { 'example.php': 'Example template' } } + > + content + </PageAttributesCheck> + ); expect( wrapper.text() ).toContain( 'content' ); } ); it( 'should render if page attributes support is true and available templates exist', () => { - const wrapper = shallow( <PageAttributesCheck availableTemplates={ { 'example.php': 'Example template' } } postType={ postType }>content</PageAttributesCheck> ); + const wrapper = shallow( + <PageAttributesCheck + availableTemplates={ { 'example.php': 'Example template' } } + postType={ postType } + > + content + </PageAttributesCheck> + ); expect( wrapper.text() ).toContain( 'content' ); } ); diff --git a/packages/editor/src/components/post-author/check.js b/packages/editor/src/components/post-author/check.js index 4f725e792a44f9..45475476545a04 100644 --- a/packages/editor/src/components/post-author/check.js +++ b/packages/editor/src/components/post-author/check.js @@ -14,19 +14,31 @@ import { withSelect } from '@wordpress/data'; */ import PostTypeSupportCheck from '../post-type-support-check'; -export function PostAuthorCheck( { hasAssignAuthorAction, authors, children } ) { +export function PostAuthorCheck( { + hasAssignAuthorAction, + authors, + children, +} ) { if ( ! hasAssignAuthorAction || authors.length < 2 ) { return null; } - return <PostTypeSupportCheck supportKeys="author">{ children }</PostTypeSupportCheck>; + return ( + <PostTypeSupportCheck supportKeys="author"> + { children } + </PostTypeSupportCheck> + ); } export default compose( [ withSelect( ( select ) => { const post = select( 'core/editor' ).getCurrentPost(); return { - hasAssignAuthorAction: get( post, [ '_links', 'wp:action-assign-author' ], false ), + hasAssignAuthorAction: get( + post, + [ '_links', 'wp:action-assign-author' ], + false + ), postType: select( 'core/editor' ).getCurrentPostType(), authors: select( 'core' ).getAuthors(), }; diff --git a/packages/editor/src/components/post-author/index.js b/packages/editor/src/components/post-author/index.js index 22f43ffd43eb90..b99f3f80ce4510 100644 --- a/packages/editor/src/components/post-author/index.js +++ b/packages/editor/src/components/post-author/index.js @@ -42,7 +42,9 @@ export class PostAuthor extends Component { className="editor-post-author__select" > { authors.map( ( author ) => ( - <option key={ author.id } value={ author.id }>{ decodeEntities( author.name ) }</option> + <option key={ author.id } value={ author.id }> + { decodeEntities( author.name ) } + </option> ) ) } </select> </PostAuthorCheck> @@ -54,7 +56,9 @@ export class PostAuthor extends Component { export default compose( [ withSelect( ( select ) => { return { - postAuthor: select( 'core/editor' ).getEditedPostAttribute( 'author' ), + postAuthor: select( 'core/editor' ).getEditedPostAttribute( + 'author' + ), authors: select( 'core' ).getAuthors(), }; } ), diff --git a/packages/editor/src/components/post-author/test/check.js b/packages/editor/src/components/post-author/test/check.js index 891068ef12c52c..c39d83ef50afc9 100644 --- a/packages/editor/src/components/post-author/test/check.js +++ b/packages/editor/src/components/post-author/test/check.js @@ -36,13 +36,20 @@ describe( 'PostAuthorCheck', () => { }; it( 'should not render anything if users unknown', () => { - const wrapper = shallow( <PostAuthorCheck authors={ [] } hasAssignAuthorAction={ true }>authors</PostAuthorCheck> ); + const wrapper = shallow( + <PostAuthorCheck authors={ [] } hasAssignAuthorAction={ true }> + authors + </PostAuthorCheck> + ); expect( wrapper.type() ).toBe( null ); } ); it( 'should not render anything if single user', () => { const wrapper = shallow( - <PostAuthorCheck authors={ users.data.slice( 0, 1 ) } hasAssignAuthorAction={ true }> + <PostAuthorCheck + authors={ users.data.slice( 0, 1 ) } + hasAssignAuthorAction={ true } + > authors </PostAuthorCheck> ); diff --git a/packages/editor/src/components/post-author/test/index.js b/packages/editor/src/components/post-author/test/index.js index 4bce35e977b0bb..368be4fda7d26c 100644 --- a/packages/editor/src/components/post-author/test/index.js +++ b/packages/editor/src/components/post-author/test/index.js @@ -48,7 +48,8 @@ describe( 'PostAuthor', () => { <PostAuthor authors={ authors } user={ user } - onUpdateAuthor={ onUpdateAuthor } /> + onUpdateAuthor={ onUpdateAuthor } + /> ); wrapper.find( 'select' ).simulate( 'change', { diff --git a/packages/editor/src/components/post-comments/index.js b/packages/editor/src/components/post-comments/index.js index f4c412b616d594..21a4994c907e0e 100644 --- a/packages/editor/src/components/post-comments/index.js +++ b/packages/editor/src/components/post-comments/index.js @@ -7,7 +7,10 @@ import { compose } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; function PostComments( { commentStatus = 'open', ...props } ) { - const onToggleComments = () => props.editPost( { comment_status: commentStatus === 'open' ? 'closed' : 'open' } ); + const onToggleComments = () => + props.editPost( { + comment_status: commentStatus === 'open' ? 'closed' : 'open', + } ); return ( <CheckboxControl @@ -21,7 +24,9 @@ function PostComments( { commentStatus = 'open', ...props } ) { export default compose( [ withSelect( ( select ) => { return { - commentStatus: select( 'core/editor' ).getEditedPostAttribute( 'comment_status' ), + commentStatus: select( 'core/editor' ).getEditedPostAttribute( + 'comment_status' + ), }; } ), withDispatch( ( dispatch ) => ( { diff --git a/packages/editor/src/components/post-excerpt/index.js b/packages/editor/src/components/post-excerpt/index.js index 5877844b942ac6..80c6816b00b81f 100644 --- a/packages/editor/src/components/post-excerpt/index.js +++ b/packages/editor/src/components/post-excerpt/index.js @@ -15,7 +15,9 @@ function PostExcerpt( { excerpt, onUpdateExcerpt } ) { onChange={ ( value ) => onUpdateExcerpt( value ) } value={ excerpt } /> - <ExternalLink href={ __( 'https://wordpress.org/support/article/excerpt/' ) }> + <ExternalLink + href={ __( 'https://wordpress.org/support/article/excerpt/' ) } + > { __( 'Learn more about manual excerpts' ) } </ExternalLink> </div> @@ -25,7 +27,9 @@ function PostExcerpt( { excerpt, onUpdateExcerpt } ) { export default compose( [ withSelect( ( select ) => { return { - excerpt: select( 'core/editor' ).getEditedPostAttribute( 'excerpt' ), + excerpt: select( 'core/editor' ).getEditedPostAttribute( + 'excerpt' + ), }; } ), withDispatch( ( dispatch ) => ( { diff --git a/packages/editor/src/components/post-featured-image/index.js b/packages/editor/src/components/post-featured-image/index.js index 98e21e2fbc59b8..7196358157b9ac 100644 --- a/packages/editor/src/components/post-featured-image/index.js +++ b/packages/editor/src/components/post-featured-image/index.js @@ -43,11 +43,22 @@ function PostFeaturedImage( { noticeUI, } ) { const postLabel = get( postType, [ 'labels' ], {} ); - const instructions = <p>{ __( 'To edit the featured image, you need permission to upload media.' ) }</p>; + const instructions = ( + <p> + { __( + 'To edit the featured image, you need permission to upload media.' + ) } + </p> + ); let mediaWidth, mediaHeight, mediaSourceUrl; if ( media ) { - const mediaSize = applyFilters( 'editor.PostFeaturedImage.imageSize', 'post-thumbnail', media.id, currentPostId ); + const mediaSize = applyFilters( + 'editor.PostFeaturedImage.imageSize', + 'post-thumbnail', + media.id, + currentPostId + ); if ( has( media, [ 'media_details', 'sizes', mediaSize ] ) ) { // use mediaSize when available mediaWidth = media.media_details.sizes[ mediaSize ].width; @@ -55,12 +66,22 @@ function PostFeaturedImage( { mediaSourceUrl = media.media_details.sizes[ mediaSize ].source_url; } else { // get fallbackMediaSize if mediaSize is not available - const fallbackMediaSize = applyFilters( 'editor.PostFeaturedImage.imageSize', 'thumbnail', media.id, currentPostId ); - if ( has( media, [ 'media_details', 'sizes', fallbackMediaSize ] ) ) { + const fallbackMediaSize = applyFilters( + 'editor.PostFeaturedImage.imageSize', + 'thumbnail', + media.id, + currentPostId + ); + if ( + has( media, [ 'media_details', 'sizes', fallbackMediaSize ] ) + ) { // use fallbackMediaSize when mediaSize is not available - mediaWidth = media.media_details.sizes[ fallbackMediaSize ].width; - mediaHeight = media.media_details.sizes[ fallbackMediaSize ].height; - mediaSourceUrl = media.media_details.sizes[ fallbackMediaSize ].source_url; + mediaWidth = + media.media_details.sizes[ fallbackMediaSize ].width; + mediaHeight = + media.media_details.sizes[ fallbackMediaSize ].height; + mediaSourceUrl = + media.media_details.sizes[ fallbackMediaSize ].source_url; } else { // use full image size when mediaFallbackSize and mediaSize are not available mediaWidth = media.media_details.width; @@ -76,28 +97,51 @@ function PostFeaturedImage( { <div className="editor-post-featured-image"> <MediaUploadCheck fallback={ instructions }> <MediaUpload - title={ postLabel.featured_image || DEFAULT_FEATURE_IMAGE_LABEL } + title={ + postLabel.featured_image || + DEFAULT_FEATURE_IMAGE_LABEL + } onSelect={ onUpdateImage } unstableFeaturedImageFlow allowedTypes={ ALLOWED_MEDIA_TYPES } - modalClass={ ! featuredImageId ? 'editor-post-featured-image__media-modal' : 'editor-post-featured-image__media-modal' } + modalClass={ + ! featuredImageId + ? 'editor-post-featured-image__media-modal' + : 'editor-post-featured-image__media-modal' + } render={ ( { open } ) => ( <div className="editor-post-featured-image__container"> <Button - className={ ! featuredImageId ? 'editor-post-featured-image__toggle' : 'editor-post-featured-image__preview' } + className={ + ! featuredImageId + ? 'editor-post-featured-image__toggle' + : 'editor-post-featured-image__preview' + } onClick={ open } - aria-label={ ! featuredImageId ? null : __( 'Edit or update the image' ) }> - { !! featuredImageId && media && + aria-label={ + ! featuredImageId + ? null + : __( 'Edit or update the image' ) + } + > + { !! featuredImageId && media && ( <ResponsiveWrapper naturalWidth={ mediaWidth } naturalHeight={ mediaHeight } isInline > - <img src={ mediaSourceUrl } alt="" /> + <img + src={ mediaSourceUrl } + alt="" + /> </ResponsiveWrapper> - } - { !! featuredImageId && ! media && <Spinner /> } - { ! featuredImageId && ( postLabel.set_featured_image || DEFAULT_SET_FEATURE_IMAGE_LABEL ) } + ) } + { !! featuredImageId && ! media && ( + <Spinner /> + ) } + { ! featuredImageId && + ( postLabel.set_featured_image || + DEFAULT_SET_FEATURE_IMAGE_LABEL ) } </Button> <DropZone onFilesDrop={ onDropImage } /> </div> @@ -105,10 +149,13 @@ function PostFeaturedImage( { value={ featuredImageId } /> </MediaUploadCheck> - { !! featuredImageId && media && ! media.isLoading && + { !! featuredImageId && media && ! media.isLoading && ( <MediaUploadCheck> <MediaUpload - title={ postLabel.featured_image || DEFAULT_FEATURE_IMAGE_LABEL } + title={ + postLabel.featured_image || + DEFAULT_FEATURE_IMAGE_LABEL + } onSelect={ onUpdateImage } unstableFeaturedImageFlow allowedTypes={ ALLOWED_MEDIA_TYPES } @@ -120,14 +167,15 @@ function PostFeaturedImage( { ) } /> </MediaUploadCheck> - } - { !! featuredImageId && + ) } + { !! featuredImageId && ( <MediaUploadCheck> <Button onClick={ onRemoveImage } isLink isDestructive> - { postLabel.remove_featured_image || DEFAULT_REMOVE_FEATURE_IMAGE_LABEL } + { postLabel.remove_featured_image || + DEFAULT_REMOVE_FEATURE_IMAGE_LABEL } </Button> </MediaUploadCheck> - } + ) } </div> </PostFeaturedImageCheck> ); @@ -135,7 +183,9 @@ function PostFeaturedImage( { const applyWithSelect = withSelect( ( select ) => { const { getMedia, getPostType } = select( 'core' ); - const { getCurrentPostId, getEditedPostAttribute } = select( 'core/editor' ); + const { getCurrentPostId, getEditedPostAttribute } = select( + 'core/editor' + ); const featuredImageId = getEditedPostAttribute( 'featured_media' ); return { @@ -146,36 +196,38 @@ const applyWithSelect = withSelect( ( select ) => { }; } ); -const applyWithDispatch = withDispatch( ( dispatch, { noticeOperations }, { select } ) => { - const { editPost } = dispatch( 'core/editor' ); - return { - onUpdateImage( image ) { - editPost( { featured_media: image.id } ); - }, - onDropImage( filesList ) { - select( 'core/block-editor' ) - .getSettings() - .mediaUpload( { - allowedTypes: [ 'image' ], - filesList, - onFileChange( [ image ] ) { - editPost( { featured_media: image.id } ); - }, - onError( message ) { - noticeOperations.removeAllNotices(); - noticeOperations.createErrorNotice( message ); - }, - } ); - }, - onRemoveImage() { - editPost( { featured_media: 0 } ); - }, - }; -} ); +const applyWithDispatch = withDispatch( + ( dispatch, { noticeOperations }, { select } ) => { + const { editPost } = dispatch( 'core/editor' ); + return { + onUpdateImage( image ) { + editPost( { featured_media: image.id } ); + }, + onDropImage( filesList ) { + select( 'core/block-editor' ) + .getSettings() + .mediaUpload( { + allowedTypes: [ 'image' ], + filesList, + onFileChange( [ image ] ) { + editPost( { featured_media: image.id } ); + }, + onError( message ) { + noticeOperations.removeAllNotices(); + noticeOperations.createErrorNotice( message ); + }, + } ); + }, + onRemoveImage() { + editPost( { featured_media: 0 } ); + }, + }; + } +); export default compose( withNotices, applyWithSelect, applyWithDispatch, - withFilters( 'editor.PostFeaturedImage' ), + withFilters( 'editor.PostFeaturedImage' ) )( PostFeaturedImage ); diff --git a/packages/editor/src/components/post-format/check.js b/packages/editor/src/components/post-format/check.js index cd124553a7cf14..d344546f0b5533 100644 --- a/packages/editor/src/components/post-format/check.js +++ b/packages/editor/src/components/post-format/check.js @@ -9,16 +9,16 @@ import { withSelect } from '@wordpress/data'; import PostTypeSupportCheck from '../post-type-support-check'; function PostFormatCheck( { disablePostFormats, ...props } ) { - return ! disablePostFormats && - <PostTypeSupportCheck { ...props } supportKeys="post-formats" />; + return ( + ! disablePostFormats && ( + <PostTypeSupportCheck { ...props } supportKeys="post-formats" /> + ) + ); } -export default withSelect( - ( select ) => { - const editorSettings = select( 'core/editor' ).getEditorSettings(); - return { - disablePostFormats: editorSettings.disablePostFormats, - }; - } -)( PostFormatCheck ); - +export default withSelect( ( select ) => { + const editorSettings = select( 'core/editor' ).getEditorSettings(); + return { + disablePostFormats: editorSettings.disablePostFormats, + }; +} )( PostFormatCheck ); diff --git a/packages/editor/src/components/post-format/index.js b/packages/editor/src/components/post-format/index.js index d3402f953e0247..76156d8a29415c 100644 --- a/packages/editor/src/components/post-format/index.js +++ b/packages/editor/src/components/post-format/index.js @@ -29,10 +29,21 @@ export const POST_FORMATS = [ { id: 'chat', caption: __( 'Chat' ) }, ]; -function PostFormat( { onUpdatePostFormat, postFormat = 'standard', supportedFormats, suggestedFormat, instanceId } ) { +function PostFormat( { + onUpdatePostFormat, + postFormat = 'standard', + supportedFormats, + suggestedFormat, + instanceId, +} ) { const postFormatSelectorId = 'post-format-selector-' + instanceId; - const formats = POST_FORMATS.filter( ( format ) => includes( supportedFormats, format.id ) ); - const suggestion = find( formats, ( format ) => format.id === suggestedFormat ); + const formats = POST_FORMATS.filter( ( format ) => + includes( supportedFormats, format.id ) + ); + const suggestion = find( + formats, + ( format ) => format.id === suggestedFormat + ); // Disable reason: We need to change the value immiediately to show/hide the suggestion if needed @@ -40,7 +51,9 @@ function PostFormat( { onUpdatePostFormat, postFormat = 'standard', supportedFor <PostFormatCheck> <div className="editor-post-format"> <div className="editor-post-format__content"> - <label htmlFor={ postFormatSelectorId }>{ __( 'Post Format' ) }</label> + <label htmlFor={ postFormatSelectorId }> + { __( 'Post Format' ) } + </label> <SelectControl value={ postFormat } onChange={ ( format ) => onUpdatePostFormat( format ) } @@ -55,7 +68,12 @@ function PostFormat( { onUpdatePostFormat, postFormat = 'standard', supportedFor { suggestion && suggestion.id !== postFormat && ( <div className="editor-post-format__suggestion"> { __( 'Suggestion:' ) }{ ' ' } - <Button isLink onClick={ () => onUpdatePostFormat( suggestion.id ) }> + <Button + isLink + onClick={ () => + onUpdatePostFormat( suggestion.id ) + } + > { suggestion.caption } </Button> </div> @@ -67,12 +85,17 @@ function PostFormat( { onUpdatePostFormat, postFormat = 'standard', supportedFor export default compose( [ withSelect( ( select ) => { - const { getEditedPostAttribute, getSuggestedPostFormat } = select( 'core/editor' ); + const { getEditedPostAttribute, getSuggestedPostFormat } = select( + 'core/editor' + ); const postFormat = getEditedPostAttribute( 'format' ); const themeSupports = select( 'core' ).getThemeSupports(); // Ensure current format is always in the set. // The current format may not be a format supported by the theme. - const supportedFormats = union( [ postFormat ], get( themeSupports, [ 'formats' ], [] ) ); + const supportedFormats = union( + [ postFormat ], + get( themeSupports, [ 'formats' ], [] ) + ); return { postFormat, supportedFormats, diff --git a/packages/editor/src/components/post-last-revision/check.js b/packages/editor/src/components/post-last-revision/check.js index 4d63dc7adc6bcb..e47382f52a575f 100644 --- a/packages/editor/src/components/post-last-revision/check.js +++ b/packages/editor/src/components/post-last-revision/check.js @@ -8,20 +8,29 @@ import { withSelect } from '@wordpress/data'; */ import PostTypeSupportCheck from '../post-type-support-check'; -export function PostLastRevisionCheck( { lastRevisionId, revisionsCount, children } ) { +export function PostLastRevisionCheck( { + lastRevisionId, + revisionsCount, + children, +} ) { if ( ! lastRevisionId || revisionsCount < 2 ) { return null; } - return <PostTypeSupportCheck supportKeys="revisions" >{ children }</PostTypeSupportCheck>; + return ( + <PostTypeSupportCheck supportKeys="revisions"> + { children } + </PostTypeSupportCheck> + ); } -export default withSelect( - ( select ) => { - const { getCurrentPostLastRevisionId, getCurrentPostRevisionsCount } = select( 'core/editor' ); - return { - lastRevisionId: getCurrentPostLastRevisionId(), - revisionsCount: getCurrentPostRevisionsCount(), - }; - } -)( PostLastRevisionCheck ); +export default withSelect( ( select ) => { + const { + getCurrentPostLastRevisionId, + getCurrentPostRevisionsCount, + } = select( 'core/editor' ); + return { + lastRevisionId: getCurrentPostLastRevisionId(), + revisionsCount: getCurrentPostRevisionsCount(), + }; +} )( PostLastRevisionCheck ); diff --git a/packages/editor/src/components/post-last-revision/index.js b/packages/editor/src/components/post-last-revision/index.js index e61fd8add07f3c..10e073b217cafa 100644 --- a/packages/editor/src/components/post-last-revision/index.js +++ b/packages/editor/src/components/post-last-revision/index.js @@ -15,30 +15,29 @@ function LastRevision( { lastRevisionId, revisionsCount } ) { return ( <PostLastRevisionCheck> <Button - href={ getWPAdminURL( 'revision.php', { revision: lastRevisionId, gutenberg: true } ) } + href={ getWPAdminURL( 'revision.php', { + revision: lastRevisionId, + gutenberg: true, + } ) } className="editor-post-last-revision__title" icon="backup" > - { - sprintf( - _n( '%d Revision', '%d Revisions', revisionsCount ), - revisionsCount - ) - } + { sprintf( + _n( '%d Revision', '%d Revisions', revisionsCount ), + revisionsCount + ) } </Button> </PostLastRevisionCheck> ); } -export default withSelect( - ( select ) => { - const { - getCurrentPostLastRevisionId, - getCurrentPostRevisionsCount, - } = select( 'core/editor' ); - return { - lastRevisionId: getCurrentPostLastRevisionId(), - revisionsCount: getCurrentPostRevisionsCount(), - }; - } -)( LastRevision ); +export default withSelect( ( select ) => { + const { + getCurrentPostLastRevisionId, + getCurrentPostRevisionsCount, + } = select( 'core/editor' ); + return { + lastRevisionId: getCurrentPostLastRevisionId(), + revisionsCount: getCurrentPostRevisionsCount(), + }; +} )( LastRevision ); diff --git a/packages/editor/src/components/post-locked-modal/index.js b/packages/editor/src/components/post-locked-modal/index.js index 5749db90428c5d..99f32e3ae147c3 100644 --- a/packages/editor/src/components/post-locked-modal/index.js +++ b/packages/editor/src/components/post-locked-modal/index.js @@ -131,7 +131,14 @@ class PostLockedModal extends Component { } render() { - const { user, postId, isLocked, isTakeover, postLockUtils, postType } = this.props; + const { + user, + postId, + isLocked, + isTakeover, + postLockUtils, + postType, + } = this.props; if ( ! isLocked ) { return null; } @@ -152,7 +159,11 @@ class PostLockedModal extends Component { const allPostsLabel = __( 'Exit the Editor' ); return ( <Modal - title={ isTakeover ? __( 'Someone else has taken over this post.' ) : __( 'This post is already being edited.' ) } + title={ + isTakeover + ? __( 'Someone else has taken over this post.' ) + : __( 'This post is already being edited.' ) + } focusOnMount={ true } shouldCloseOnClickOutside={ false } shouldCloseOnEsc={ false } @@ -169,14 +180,17 @@ class PostLockedModal extends Component { { !! isTakeover && ( <div> <div> - { userDisplayName ? - sprintf( - /* translators: %s: user's display name */ - __( '%s now has editing control of this post. Don’t worry, your changes up to this moment have been saved.' ), - userDisplayName - ) : - __( 'Another user now has editing control of this post. Don’t worry, your changes up to this moment have been saved.' ) - } + { userDisplayName + ? sprintf( + /* translators: %s: user's display name */ + __( + '%s now has editing control of this post. Don’t worry, your changes up to this moment have been saved.' + ), + userDisplayName + ) + : __( + 'Another user now has editing control of this post. Don’t worry, your changes up to this moment have been saved.' + ) } </div> <div className="editor-post-locked-modal__buttons"> @@ -189,14 +203,17 @@ class PostLockedModal extends Component { { ! isTakeover && ( <div> <div> - { userDisplayName ? - sprintf( - /* translators: %s: user's display name */ - __( '%s is currently working on this post, which means you cannot make changes, unless you take over.' ), - userDisplayName - ) : - __( 'Another user is currently working on this post, which means you cannot make changes, unless you take over.' ) - } + { userDisplayName + ? sprintf( + /* translators: %s: user's display name */ + __( + '%s is currently working on this post, which means you cannot make changes, unless you take over.' + ), + userDisplayName + ) + : __( + 'Another user is currently working on this post, which means you cannot make changes, unless you take over.' + ) } </div> <div className="editor-post-locked-modal__buttons"> diff --git a/packages/editor/src/components/post-pending-status/check.js b/packages/editor/src/components/post-pending-status/check.js index 2c6c19b928042b..03e526921e06f8 100644 --- a/packages/editor/src/components/post-pending-status/check.js +++ b/packages/editor/src/components/post-pending-status/check.js @@ -9,7 +9,11 @@ import { get } from 'lodash'; import { compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; -export function PostPendingStatusCheck( { hasPublishAction, isPublished, children } ) { +export function PostPendingStatusCheck( { + hasPublishAction, + isPublished, + children, +} ) { if ( isPublished || ! hasPublishAction ) { return null; } @@ -19,9 +23,17 @@ export function PostPendingStatusCheck( { hasPublishAction, isPublished, childre export default compose( withSelect( ( select ) => { - const { isCurrentPostPublished, getCurrentPostType, getCurrentPost } = select( 'core/editor' ); + const { + isCurrentPostPublished, + getCurrentPostType, + getCurrentPost, + } = select( 'core/editor' ); return { - hasPublishAction: get( getCurrentPost(), [ '_links', 'wp:action-publish' ], false ), + hasPublishAction: get( + getCurrentPost(), + [ '_links', 'wp:action-publish' ], + false + ), isPublished: isCurrentPostPublished(), postType: getCurrentPostType(), }; diff --git a/packages/editor/src/components/post-pending-status/index.js b/packages/editor/src/components/post-pending-status/index.js index e79b7b1bf41869..ba30da1d9498a0 100644 --- a/packages/editor/src/components/post-pending-status/index.js +++ b/packages/editor/src/components/post-pending-status/index.js @@ -36,5 +36,5 @@ export default compose( onUpdateStatus( status ) { dispatch( 'core/editor' ).editPost( { status } ); }, - } ) ), + } ) ) )( PostPendingStatus ); diff --git a/packages/editor/src/components/post-pending-status/test/check.js b/packages/editor/src/components/post-pending-status/test/check.js index b26eebb7331be2..bfeba96446fa4b 100644 --- a/packages/editor/src/components/post-pending-status/test/check.js +++ b/packages/editor/src/components/post-pending-status/test/check.js @@ -19,7 +19,11 @@ describe( 'PostPendingStatusCheck', () => { } ); it( 'should render if the user has the correct capability', () => { - const wrapper = shallow( <PostPendingStatusCheck hasPublishAction={ true }>status</PostPendingStatusCheck> ); + const wrapper = shallow( + <PostPendingStatusCheck hasPublishAction={ true }> + status + </PostPendingStatusCheck> + ); expect( wrapper.type() ).not.toBe( null ); } ); } ); diff --git a/packages/editor/src/components/post-permalink/editor.js b/packages/editor/src/components/post-permalink/editor.js index 408deba736e661..a09ee66ed984a8 100644 --- a/packages/editor/src/components/post-permalink/editor.js +++ b/packages/editor/src/components/post-permalink/editor.js @@ -62,7 +62,11 @@ class PostPermalinkEditor extends Component { className="editor-post-permalink-editor__edit" aria-label={ __( 'Edit post permalink' ) } value={ editedPostName } - onChange={ ( event ) => this.setState( { editedPostName: event.target.value } ) } + onChange={ ( event ) => + this.setState( { + editedPostName: event.target.value, + } ) + } type="text" autoFocus /> @@ -96,4 +100,3 @@ export default compose( [ return { editPost }; } ), ] )( PostPermalinkEditor ); - diff --git a/packages/editor/src/components/post-permalink/index.js b/packages/editor/src/components/post-permalink/index.js index ff9d0a8be1a5e9..ab82766dbacc66 100644 --- a/packages/editor/src/components/post-permalink/index.js +++ b/packages/editor/src/components/post-permalink/index.js @@ -54,7 +54,10 @@ class PostPermalink extends Component { } componentWillUnmount() { - window.removeEventListener( 'visibilitychange', this.addVisibilityCheck ); + window.removeEventListener( + 'visibilitychange', + this.addVisibilityCheck + ); } render() { @@ -75,16 +78,23 @@ class PostPermalink extends Component { } const { isCopied, isEditingPermalink } = this.state; - const ariaLabel = isCopied ? __( 'Permalink copied' ) : __( 'Copy the permalink' ); + const ariaLabel = isCopied + ? __( 'Permalink copied' ) + : __( 'Copy the permalink' ); const { prefix, suffix } = permalinkParts; - const slug = safeDecodeURIComponent( postSlug ) || cleanForSlug( postTitle ) || postID; - const samplePermalink = ( isEditable ) ? prefix + slug + suffix : prefix; + const slug = + safeDecodeURIComponent( postSlug ) || + cleanForSlug( postTitle ) || + postID; + const samplePermalink = isEditable ? prefix + slug + suffix : prefix; return ( <div className="editor-post-permalink"> <ClipboardButton - className={ classnames( 'editor-post-permalink__copy', { 'is-copied': isCopied } ) } + className={ classnames( 'editor-post-permalink__copy', { + 'is-copied': isCopied, + } ) } text={ samplePermalink } label={ ariaLabel } onCopy={ () => this.setState( { isCopied: true } ) } @@ -92,36 +102,44 @@ class PostPermalink extends Component { icon="admin-links" /> - <span className="editor-post-permalink__label">{ __( 'Permalink:' ) }</span> + <span className="editor-post-permalink__label"> + { __( 'Permalink:' ) } + </span> - { ! isEditingPermalink && + { ! isEditingPermalink && ( <ExternalLink className="editor-post-permalink__link" href={ ! isPublished ? postLink : samplePermalink } target="_blank" - ref={ ( linkElement ) => this.linkElement = linkElement } + ref={ ( linkElement ) => + ( this.linkElement = linkElement ) + } > { safeDecodeURI( samplePermalink ) } &lrm; </ExternalLink> - } + ) } - { isEditingPermalink && + { isEditingPermalink && ( <PostPermalinkEditor slug={ slug } - onSave={ () => this.setState( { isEditingPermalink: false } ) } + onSave={ () => + this.setState( { isEditingPermalink: false } ) + } /> - } + ) } - { isEditable && ! isEditingPermalink && + { isEditable && ! isEditingPermalink && ( <Button className="editor-post-permalink__edit" isSecondary - onClick={ () => this.setState( { isEditingPermalink: true } ) } + onClick={ () => + this.setState( { isEditingPermalink: true } ) + } > { __( 'Edit' ) } </Button> - } + ) } </div> ); } @@ -137,9 +155,7 @@ export default compose( [ getEditedPostAttribute, isCurrentPostPublished, } = select( 'core/editor' ); - const { - getPostType, - } = select( 'core' ); + const { getPostType } = select( 'core' ); const { id, link } = getCurrentPost(); diff --git a/packages/editor/src/components/post-pingbacks/index.js b/packages/editor/src/components/post-pingbacks/index.js index f18a0368214cb1..f181d45ef2ad43 100644 --- a/packages/editor/src/components/post-pingbacks/index.js +++ b/packages/editor/src/components/post-pingbacks/index.js @@ -7,7 +7,10 @@ import { withSelect, withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; function PostPingbacks( { pingStatus = 'open', ...props } ) { - const onTogglePingback = () => props.editPost( { ping_status: pingStatus === 'open' ? 'closed' : 'open' } ); + const onTogglePingback = () => + props.editPost( { + ping_status: pingStatus === 'open' ? 'closed' : 'open', + } ); return ( <CheckboxControl @@ -21,7 +24,9 @@ function PostPingbacks( { pingStatus = 'open', ...props } ) { export default compose( [ withSelect( ( select ) => { return { - pingStatus: select( 'core/editor' ).getEditedPostAttribute( 'ping_status' ), + pingStatus: select( 'core/editor' ).getEditedPostAttribute( + 'ping_status' + ), }; } ), withDispatch( ( dispatch ) => ( { diff --git a/packages/editor/src/components/post-preview-button/index.js b/packages/editor/src/components/post-preview-button/index.js index cd77cf96280bd7..620bb9aaf0180a 100644 --- a/packages/editor/src/components/post-preview-button/index.js +++ b/packages/editor/src/components/post-preview-button/index.js @@ -17,8 +17,16 @@ function writeInterstitialMessage( targetDocument ) { let markup = renderToString( <div className="editor-post-preview-button__interstitial-message"> <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96"> - <Path className="outer" d="M48 12c19.9 0 36 16.1 36 36S67.9 84 48 84 12 67.9 12 48s16.1-36 36-36" fill="none" /> - <Path className="inner" d="M69.5 46.4c0-3.9-1.4-6.7-2.6-8.8-1.6-2.6-3.1-4.9-3.1-7.5 0-2.9 2.2-5.7 5.4-5.7h.4C63.9 19.2 56.4 16 48 16c-11.2 0-21 5.7-26.7 14.4h2.1c3.3 0 8.5-.4 8.5-.4 1.7-.1 1.9 2.4.2 2.6 0 0-1.7.2-3.7.3L40 67.5l7-20.9L42 33c-1.7-.1-3.3-.3-3.3-.3-1.7-.1-1.5-2.7.2-2.6 0 0 5.3.4 8.4.4 3.3 0 8.5-.4 8.5-.4 1.7-.1 1.9 2.4.2 2.6 0 0-1.7.2-3.7.3l11.5 34.3 3.3-10.4c1.6-4.5 2.4-7.8 2.4-10.5zM16.1 48c0 12.6 7.3 23.5 18 28.7L18.8 35c-1.7 4-2.7 8.4-2.7 13zm32.5 2.8L39 78.6c2.9.8 5.9 1.3 9 1.3 3.7 0 7.3-.6 10.6-1.8-.1-.1-.2-.3-.2-.4l-9.8-26.9zM76.2 36c0 3.2-.6 6.9-2.4 11.4L64 75.6c9.5-5.5 15.9-15.8 15.9-27.6 0-5.5-1.4-10.8-3.9-15.3.1 1 .2 2.1.2 3.3z" fill="none" /> + <Path + className="outer" + d="M48 12c19.9 0 36 16.1 36 36S67.9 84 48 84 12 67.9 12 48s16.1-36 36-36" + fill="none" + /> + <Path + className="inner" + d="M69.5 46.4c0-3.9-1.4-6.7-2.6-8.8-1.6-2.6-3.1-4.9-3.1-7.5 0-2.9 2.2-5.7 5.4-5.7h.4C63.9 19.2 56.4 16 48 16c-11.2 0-21 5.7-26.7 14.4h2.1c3.3 0 8.5-.4 8.5-.4 1.7-.1 1.9 2.4.2 2.6 0 0-1.7.2-3.7.3L40 67.5l7-20.9L42 33c-1.7-.1-3.3-.3-3.3-.3-1.7-.1-1.5-2.7.2-2.6 0 0 5.3.4 8.4.4 3.3 0 8.5-.4 8.5-.4 1.7-.1 1.9 2.4.2 2.6 0 0-1.7.2-3.7.3l11.5 34.3 3.3-10.4c1.6-4.5 2.4-7.8 2.4-10.5zM16.1 48c0 12.6 7.3 23.5 18 28.7L18.8 35c-1.7 4-2.7 8.4-2.7 13zm32.5 2.8L39 78.6c2.9.8 5.9 1.3 9 1.3 3.7 0 7.3-.6 10.6-1.8-.1-.1-.2-.3-.2-.4l-9.8-26.9zM76.2 36c0 3.2-.6 6.9-2.4 11.4L64 75.6c9.5-5.5 15.9-15.8 15.9-27.6 0-5.5-1.4-10.8-3.9-15.3.1 1 .2 2.1.2 3.3z" + fill="none" + /> </SVG> <p>{ __( 'Generating preview…' ) }</p> </div> @@ -185,10 +193,8 @@ export class PostPreviewButton extends Component { > { _x( 'Preview', 'imperative verb' ) } <span className="screen-reader-text"> - { - /* translators: accessibility text */ - __( '(opens in a new tab)' ) - } + { /* translators: accessibility text */ + __( '(opens in a new tab)' ) } </span> </Button> ); @@ -205,9 +211,7 @@ export default compose( [ isEditedPostAutosaveable, getEditedPostPreviewLink, } = select( 'core/editor' ); - const { - getPostType, - } = select( 'core' ); + const { getPostType } = select( 'core' ); const previewLink = getEditedPostPreviewLink(); const postType = getPostType( getEditedPostAttribute( 'type' ) ); @@ -215,11 +219,15 @@ export default compose( [ return { postId: getCurrentPostId(), currentPostLink: getCurrentPostAttribute( 'link' ), - previewLink: forcePreviewLink !== undefined ? forcePreviewLink : previewLink, + previewLink: + forcePreviewLink !== undefined ? forcePreviewLink : previewLink, isSaveable: isEditedPostSaveable(), isAutosaveable: forceIsAutosaveable || isEditedPostAutosaveable(), isViewable: get( postType, [ 'viewable' ], false ), - isDraft: [ 'draft', 'auto-draft' ].indexOf( getEditedPostAttribute( 'status' ) ) !== -1, + isDraft: + [ 'draft', 'auto-draft' ].indexOf( + getEditedPostAttribute( 'status' ) + ) !== -1, }; } ), withDispatch( ( dispatch ) => ( { diff --git a/packages/editor/src/components/post-preview-button/test/index.js b/packages/editor/src/components/post-preview-button/test/index.js index 7bd9856de05c77..674957e0d330f3 100644 --- a/packages/editor/src/components/post-preview-button/test/index.js +++ b/packages/editor/src/components/post-preview-button/test/index.js @@ -58,7 +58,8 @@ describe( 'PostPreviewButton', () => { postId={ 1 } currentPostLink="https://wordpress.org/?p=1" isSaveable - modified="2017-08-03T15:05:50" /> + modified="2017-08-03T15:05:50" + /> ); const previewWindow = { location: {} }; @@ -67,7 +68,9 @@ describe( 'PostPreviewButton', () => { wrapper.setProps( { previewLink: 'https://wordpress.org/?p=1' } ); - expect( previewWindow.location ).toBe( 'https://wordpress.org/?p=1' ); + expect( previewWindow.location ).toBe( + 'https://wordpress.org/?p=1' + ); } ); } ); @@ -92,10 +95,7 @@ describe( 'PostPreviewButton', () => { } ) ); const wrapper = shallow( - <PostPreviewButton - postId={ 1 } - autosave={ autosave } - /> + <PostPreviewButton postId={ 1 } autosave={ autosave } /> ); wrapper.simulate( 'click', { @@ -107,7 +107,9 @@ describe( 'PostPreviewButton', () => { expect( window.open ).toHaveBeenCalledWith( '', 'wp-preview-1' ); expect( wrapper.instance().previewWindow.focus ).toHaveBeenCalled(); expect( autosave ).not.toHaveBeenCalled(); - expect( setLocation ).toHaveBeenCalledWith( 'https://wordpress.org/?p=1' ); + expect( setLocation ).toHaveBeenCalledWith( + 'https://wordpress.org/?p=1' + ); } ); it( 'autosaves the post if autosaveable', () => { @@ -136,8 +138,13 @@ describe( 'PostPreviewButton', () => { expect( window.open ).toHaveBeenCalledWith( '', 'wp-preview-1' ); expect( wrapper.instance().previewWindow.focus ).toHaveBeenCalled(); expect( autosave ).toHaveBeenCalled(); - expect( wrapper.instance().previewWindow.document.write.mock.calls[ 0 ][ 0 ] ).toContain( 'Generating preview…' ); - expect( wrapper.instance().previewWindow.document.close ).toHaveBeenCalled(); + expect( + wrapper.instance().previewWindow.document.write.mock + .calls[ 0 ][ 0 ] + ).toContain( 'Generating preview…' ); + expect( + wrapper.instance().previewWindow.document.close + ).toHaveBeenCalled(); } ); } ); diff --git a/packages/editor/src/components/post-publish-button/index.js b/packages/editor/src/components/post-publish-button/index.js index de7b3069d17265..788625851fad92 100644 --- a/packages/editor/src/components/post-publish-button/index.js +++ b/packages/editor/src/components/post-publish-button/index.js @@ -27,14 +27,18 @@ export class PostPublishButton extends Component { this.buttonNode = createRef(); this.createOnClick = this.createOnClick.bind( this ); - this.closeEntitiesSavedStates = this.closeEntitiesSavedStates.bind( this ); + this.closeEntitiesSavedStates = this.closeEntitiesSavedStates.bind( + this + ); this.state = { entitiesSavedStatesCallback: false, }; this.createIgnoredForSave = memoize( ( postType, postId ) => - new EquivalentKeyMap( [ [ [ 'postType', postType, String( postId ) ], true ] ] ), + new EquivalentKeyMap( [ + [ [ 'postType', postType, String( postId ) ], true ], + ] ), { maxSize: 1 } ); } @@ -65,7 +69,10 @@ export class PostPublishButton extends Component { const { postType, postId } = this.props; const { entitiesSavedStatesCallback } = this.state; this.setState( { entitiesSavedStatesCallback: false }, () => { - if ( savedById && savedById.has( [ 'postType', postType, String( postId ) ] ) ) { + if ( + savedById && + savedById.has( [ 'postType', postType, String( postId ) ] ) + ) { // The post entity was checked, call the held callback from `createOnClick`. entitiesSavedStatesCallback(); } @@ -94,9 +101,7 @@ export class PostPublishButton extends Component { postType, postId, } = this.props; - const { - entitiesSavedStatesCallback, - } = this.state; + const { entitiesSavedStatesCallback } = this.state; const isButtonDisabled = isSaving || @@ -156,7 +161,9 @@ export class PostPublishButton extends Component { onClick: this.createOnClick( onClickToggle ), }; - const toggleChildren = isBeingScheduled ? __( 'Schedule…' ) : __( 'Publish…' ); + const toggleChildren = isBeingScheduled + ? __( 'Schedule…' ) + : __( 'Publish…' ); const buttonChildren = ( <PublishButtonLabel forceIsSaving={ forceIsSaving } @@ -171,7 +178,10 @@ export class PostPublishButton extends Component { <EntitiesSavedStates isOpen={ Boolean( entitiesSavedStatesCallback ) } onRequestClose={ this.closeEntitiesSavedStates } - ignoredForSave={ this.createIgnoredForSave( postType, postId ) } + ignoredForSave={ this.createIgnoredForSave( + postType, + postId + ) } /> <Button ref={ this.buttonNode } @@ -214,7 +224,11 @@ export default compose( [ isPostSavingLocked: isPostSavingLocked(), isPublishable: isEditedPostPublishable(), isPublished: isCurrentPostPublished(), - hasPublishAction: get( getCurrentPost(), [ '_links', 'wp:action-publish' ], false ), + hasPublishAction: get( + getCurrentPost(), + [ '_links', 'wp:action-publish' ], + false + ), postType: getCurrentPostType(), postId: getCurrentPostId(), hasNonPostEntityChanges: hasNonPostEntityChanges(), @@ -223,7 +237,8 @@ export default compose( [ withDispatch( ( dispatch ) => { const { editPost, savePost } = dispatch( 'core/editor' ); return { - onStatusChange: ( status ) => editPost( { status }, { undoIgnore: true } ), + onStatusChange: ( status ) => + editPost( { status }, { undoIgnore: true } ), onSave: savePost, }; } ), diff --git a/packages/editor/src/components/post-publish-button/label.js b/packages/editor/src/components/post-publish-button/label.js index 5373d39322558f..c8801eb85051bf 100644 --- a/packages/editor/src/components/post-publish-button/label.js +++ b/packages/editor/src/components/post-publish-button/label.js @@ -28,9 +28,9 @@ export function PublishButtonLabel( { } if ( ! hasPublishAction ) { - return hasNonPostEntityChanges ? - __( 'Submit for Review…' ) : - __( 'Submit for Review' ); + return hasNonPostEntityChanges + ? __( 'Submit for Review…' ) + : __( 'Submit for Review' ); } else if ( isPublished ) { return hasNonPostEntityChanges ? __( 'Update…' ) : __( 'Update' ); } else if ( isBeingScheduled ) { @@ -56,7 +56,11 @@ export default compose( [ isBeingScheduled: isEditedPostBeingScheduled(), isSaving: forceIsSaving || isSavingPost(), isPublishing: isPublishingPost(), - hasPublishAction: get( getCurrentPost(), [ '_links', 'wp:action-publish' ], false ), + hasPublishAction: get( + getCurrentPost(), + [ '_links', 'wp:action-publish' ], + false + ), postType: getCurrentPostType(), isAutosaving: isAutosavingPost(), }; diff --git a/packages/editor/src/components/post-publish-button/test/index.js b/packages/editor/src/components/post-publish-button/test/index.js index 6c6684ea375b6b..7dbcc34036c02a 100644 --- a/packages/editor/src/components/post-publish-button/test/index.js +++ b/packages/editor/src/components/post-publish-button/test/index.js @@ -17,26 +17,22 @@ describe( 'PostPublishButton', () => { describe( 'aria-disabled', () => { it( 'should be true if post is currently saving', () => { const wrapper = shallow( - <PostPublishButton - isPublishable - isSaveable - isSaving - /> + <PostPublishButton isPublishable isSaveable isSaving /> ); - expect( wrapper.find( Button ).prop( 'aria-disabled' ) ).toBe( true ); + expect( wrapper.find( Button ).prop( 'aria-disabled' ) ).toBe( + true + ); } ); it( 'should be true if forceIsSaving is true', () => { const wrapper = shallow( - <PostPublishButton - isPublishable - isSaveable - forceIsSaving - /> + <PostPublishButton isPublishable isSaveable forceIsSaving /> ); - expect( wrapper.find( Button ).prop( 'aria-disabled' ) ).toBe( true ); + expect( wrapper.find( Button ).prop( 'aria-disabled' ) ).toBe( + true + ); } ); it( 'should be true if post is not publishable and not forceIsDirty', () => { @@ -48,18 +44,19 @@ describe( 'PostPublishButton', () => { /> ); - expect( wrapper.find( Button ).prop( 'aria-disabled' ) ).toBe( true ); + expect( wrapper.find( Button ).prop( 'aria-disabled' ) ).toBe( + true + ); } ); it( 'should be true if post is not saveable', () => { const wrapper = shallow( - <PostPublishButton - isPublishable - isSaveable={ false } - /> + <PostPublishButton isPublishable isSaveable={ false } /> ); - expect( wrapper.find( Button ).prop( 'aria-disabled' ) ).toBe( true ); + expect( wrapper.find( Button ).prop( 'aria-disabled' ) ).toBe( + true + ); } ); it( 'should be true if post saving is locked', () => { @@ -71,7 +68,9 @@ describe( 'PostPublishButton', () => { /> ); - expect( wrapper.find( Button ).prop( 'aria-disabled' ) ).toBe( true ); + expect( wrapper.find( Button ).prop( 'aria-disabled' ) ).toBe( + true + ); } ); it( 'should be false if post is saveable but not publishable and forceIsDirty is true', () => { @@ -83,18 +82,19 @@ describe( 'PostPublishButton', () => { /> ); - expect( wrapper.find( Button ).prop( 'aria-disabled' ) ).toBe( false ); + expect( wrapper.find( Button ).prop( 'aria-disabled' ) ).toBe( + false + ); } ); it( 'should be false if post is publishave and saveable', () => { const wrapper = shallow( - <PostPublishButton - isPublishable - isSaveable - /> + <PostPublishButton isPublishable isSaveable /> ); - expect( wrapper.find( Button ).prop( 'aria-disabled' ) ).toBe( false ); + expect( wrapper.find( Button ).prop( 'aria-disabled' ) ).toBe( + false + ); } ); } ); @@ -127,7 +127,8 @@ describe( 'PostPublishButton', () => { onSave={ onSave } isBeingScheduled isSaveable={ true } - isPublishable={ true } /> + isPublishable={ true } + /> ); wrapper.find( Button ).simulate( 'click' ); @@ -145,7 +146,8 @@ describe( 'PostPublishButton', () => { onSave={ onSave } visibility="private" isSaveable={ true } - isPublishable={ true } /> + isPublishable={ true } + /> ); wrapper.find( Button ).simulate( 'click' ); @@ -162,7 +164,8 @@ describe( 'PostPublishButton', () => { onStatusChange={ onStatusChange } onSave={ onSave } isSaveable={ true } - isPublishable={ true } /> + isPublishable={ true } + /> ); wrapper.find( Button ).simulate( 'click' ); @@ -181,7 +184,8 @@ describe( 'PostPublishButton', () => { onStatusChange={ onStatusChange } onSave={ onSave } isSaveable={ true } - isPublishable={ true } /> + isPublishable={ true } + /> ); wrapper.find( Button ).simulate( 'click' ); @@ -192,12 +196,7 @@ describe( 'PostPublishButton', () => { } ); it( 'should have save modifier class', () => { - const wrapper = shallow( - <PostPublishButton - isSaving - isPublished - /> - ); + const wrapper = shallow( <PostPublishButton isSaving isPublished /> ); expect( wrapper.find( Button ).prop( 'isBusy' ) ).toBe( true ); } ); diff --git a/packages/editor/src/components/post-publish-button/test/label.js b/packages/editor/src/components/post-publish-button/test/label.js index b3e8eff9725ac5..5990e94e36282d 100644 --- a/packages/editor/src/components/post-publish-button/test/label.js +++ b/packages/editor/src/components/post-publish-button/test/label.js @@ -5,22 +5,37 @@ import { PublishButtonLabel } from '../label'; describe( 'PublishButtonLabel', () => { it( 'should show publishing if publishing in progress', () => { - const label = PublishButtonLabel( { hasPublishAction: true, isPublishing: true } ); + const label = PublishButtonLabel( { + hasPublishAction: true, + isPublishing: true, + } ); expect( label ).toBe( 'Publishing…' ); } ); it( 'should show updating if published and saving in progress', () => { - const label = PublishButtonLabel( { hasPublishAction: true, isPublished: true, isSaving: true } ); + const label = PublishButtonLabel( { + hasPublishAction: true, + isPublished: true, + isSaving: true, + } ); expect( label ).toBe( 'Updating…' ); } ); it( 'should show scheduling if scheduled and saving in progress', () => { - const label = PublishButtonLabel( { hasPublishAction: true, isBeingScheduled: true, isSaving: true } ); + const label = PublishButtonLabel( { + hasPublishAction: true, + isBeingScheduled: true, + isSaving: true, + } ); expect( label ).toBe( 'Scheduling…' ); } ); it( 'should show publish if not published and saving in progress', () => { - const label = PublishButtonLabel( { hasPublishAction: true, isPublished: false, isSaving: true } ); + const label = PublishButtonLabel( { + hasPublishAction: true, + isPublished: false, + isSaving: true, + } ); expect( label ).toBe( 'Publish' ); } ); @@ -30,12 +45,18 @@ describe( 'PublishButtonLabel', () => { } ); it( 'should show update for already published', () => { - const label = PublishButtonLabel( { hasPublishAction: true, isPublished: true } ); + const label = PublishButtonLabel( { + hasPublishAction: true, + isPublished: true, + } ); expect( label ).toBe( 'Update' ); } ); it( 'should show schedule for scheduled', () => { - const label = PublishButtonLabel( { hasPublishAction: true, isBeingScheduled: true } ); + const label = PublishButtonLabel( { + hasPublishAction: true, + isBeingScheduled: true, + } ); expect( label ).toBe( 'Schedule' ); } ); diff --git a/packages/editor/src/components/post-publish-panel/index.js b/packages/editor/src/components/post-publish-panel/index.js index 1a2db72556f94c..4502e798828378 100644 --- a/packages/editor/src/components/post-publish-panel/index.js +++ b/packages/editor/src/components/post-publish-panel/index.js @@ -34,7 +34,11 @@ export class PostPublishPanel extends Component { componentDidUpdate( prevProps ) { // Automatically collapse the publish sidebar when a post // is published and the user makes an edit. - if ( prevProps.isPublished && ! this.props.isSaving && this.props.isDirty ) { + if ( + prevProps.isPublished && + ! this.props.isSaving && + this.props.isDirty + ) { this.props.onClose(); } } @@ -61,8 +65,13 @@ export class PostPublishPanel extends Component { PrePublishExtension, ...additionalProps } = this.props; - const propsForPanel = omit( additionalProps, [ 'hasPublishAction', 'isDirty', 'isPostTypeViewable' ] ); - const isPublishedOrScheduled = isPublished || ( isScheduled && isBeingScheduled ); + const propsForPanel = omit( additionalProps, [ + 'hasPublishAction', + 'isDirty', + 'isPostTypeViewable', + ] ); + const isPublishedOrScheduled = + isPublished || ( isScheduled && isBeingScheduled ); const isPrePublish = ! isPublishedOrScheduled && ! isSaving; const isPostPublish = isPublishedOrScheduled && ! isSaving; return ( @@ -70,11 +79,18 @@ export class PostPublishPanel extends Component { <div className="editor-post-publish-panel__header"> { isPostPublish ? ( <div className="editor-post-publish-panel__header-published"> - { isScheduled ? __( 'Scheduled' ) : __( 'Published' ) } + { isScheduled + ? __( 'Scheduled' ) + : __( 'Published' ) } </div> ) : ( <div className="editor-post-publish-panel__header-publish-button"> - <PostPublishButton focusOnMount={ true } onSubmit={ this.onSubmit } forceIsDirty={ forceIsDirty } forceIsSaving={ forceIsSaving } /> + <PostPublishButton + focusOnMount={ true } + onSubmit={ this.onSubmit } + forceIsDirty={ forceIsDirty } + forceIsSaving={ forceIsSaving } + /> </div> ) } <Button @@ -91,11 +107,11 @@ export class PostPublishPanel extends Component { </PostPublishPanelPrepublish> ) } { isPostPublish && ( - <PostPublishPanelPostpublish focusOnMount={ true } > + <PostPublishPanelPostpublish focusOnMount={ true }> { PostPublishExtension && <PostPublishExtension /> } </PostPublishPanelPostpublish> ) } - { isSaving && ( <Spinner /> ) } + { isSaving && <Spinner /> } </div> <div className="editor-post-publish-panel__footer"> <CheckboxControl @@ -125,7 +141,11 @@ export default compose( [ const postType = getPostType( getEditedPostAttribute( 'type' ) ); return { - hasPublishAction: get( getCurrentPost(), [ '_links', 'wp:action-publish' ], false ), + hasPublishAction: get( + getCurrentPost(), + [ '_links', 'wp:action-publish' ], + false + ), isPostTypeViewable: get( postType, [ 'viewable' ], false ), isBeingScheduled: isEditedPostBeingScheduled(), isDirty: isEditedPostDirty(), @@ -136,9 +156,11 @@ export default compose( [ }; } ), withDispatch( ( dispatch, { isPublishSidebarEnabled } ) => { - const { disablePublishSidebar, enablePublishSidebar } = dispatch( 'core/editor' ); + const { disablePublishSidebar, enablePublishSidebar } = dispatch( + 'core/editor' + ); return { - onTogglePublishSidebar: ( ) => { + onTogglePublishSidebar: () => { if ( isPublishSidebarEnabled ) { disablePublishSidebar(); } else { diff --git a/packages/editor/src/components/post-publish-panel/maybe-post-format-panel.js b/packages/editor/src/components/post-publish-panel/maybe-post-format-panel.js index d3a853b4eb7136..fede6a0a33186d 100644 --- a/packages/editor/src/components/post-publish-panel/maybe-post-format-panel.js +++ b/packages/editor/src/components/post-publish-panel/maybe-post-format-panel.js @@ -16,7 +16,11 @@ import { Button, PanelBody } from '@wordpress/components'; */ import { POST_FORMATS } from '../post-format'; -const PostFormatSuggestion = ( { suggestedPostFormat, suggestionText, onUpdatePostFormat } ) => ( +const PostFormatSuggestion = ( { + suggestedPostFormat, + suggestionText, + onUpdatePostFormat, +} ) => ( <Button isLink onClick={ () => onUpdatePostFormat( suggestedPostFormat ) }> { suggestionText } </Button> @@ -25,17 +29,17 @@ const PostFormatSuggestion = ( { suggestedPostFormat, suggestionText, onUpdatePo const PostFormatPanel = ( { suggestion, onUpdatePostFormat } ) => { const panelBodyTitle = [ __( 'Suggestion:' ), - ( - <span className="editor-post-publish-panel__link" key="label"> - { __( 'Use a post format' ) } - </span> - ), + <span className="editor-post-publish-panel__link" key="label"> + { __( 'Use a post format' ) } + </span>, ]; return ( - <PanelBody initialOpen={ false } title={ panelBodyTitle } > + <PanelBody initialOpen={ false } title={ panelBodyTitle }> <p> - { __( 'Your theme uses post formats to highlight different kinds of content, like images or videos. Apply a post format to see this special styling.' ) } + { __( + 'Your theme uses post formats to highlight different kinds of content, like images or videos. Apply a post format to see this special styling.' + ) } </p> <p> <PostFormatSuggestion @@ -52,17 +56,28 @@ const PostFormatPanel = ( { suggestion, onUpdatePostFormat } ) => { }; const getSuggestion = ( supportedFormats, suggestedPostFormat ) => { - const formats = POST_FORMATS.filter( ( format ) => includes( supportedFormats, format.id ) ); + const formats = POST_FORMATS.filter( ( format ) => + includes( supportedFormats, format.id ) + ); return find( formats, ( format ) => format.id === suggestedPostFormat ); }; export default compose( withSelect( ( select ) => { - const { getEditedPostAttribute, getSuggestedPostFormat } = select( 'core/editor' ); - const supportedFormats = get( select( 'core' ).getThemeSupports(), [ 'formats' ], [] ); + const { getEditedPostAttribute, getSuggestedPostFormat } = select( + 'core/editor' + ); + const supportedFormats = get( + select( 'core' ).getThemeSupports(), + [ 'formats' ], + [] + ); return { currentPostFormat: getEditedPostAttribute( 'format' ), - suggestion: getSuggestion( supportedFormats, getSuggestedPostFormat() ), + suggestion: getSuggestion( + supportedFormats, + getSuggestedPostFormat() + ), }; } ), withDispatch( ( dispatch ) => ( { @@ -70,5 +85,8 @@ export default compose( dispatch( 'core/editor' ).editPost( { format: postFormat } ); }, } ) ), - ifCondition( ( { suggestion, currentPostFormat } ) => suggestion && suggestion.id !== currentPostFormat ), + ifCondition( + ( { suggestion, currentPostFormat } ) => + suggestion && suggestion.id !== currentPostFormat + ) )( PostFormatPanel ); diff --git a/packages/editor/src/components/post-publish-panel/maybe-tags-panel.js b/packages/editor/src/components/post-publish-panel/maybe-tags-panel.js index 69d600fcae9e43..4f7a7296889015 100644 --- a/packages/editor/src/components/post-publish-panel/maybe-tags-panel.js +++ b/packages/editor/src/components/post-publish-panel/maybe-tags-panel.js @@ -20,17 +20,17 @@ import FlatTermSelector from '../post-taxonomies/flat-term-selector'; const TagsPanel = () => { const panelBodyTitle = [ __( 'Suggestion:' ), - ( - <span className="editor-post-publish-panel__link" key="label"> - { __( 'Add tags' ) } - </span> - ), + <span className="editor-post-publish-panel__link" key="label"> + { __( 'Add tags' ) } + </span>, ]; return ( <PanelBody initialOpen={ false } title={ panelBodyTitle }> <p> - { __( 'Tags help users and search engines navigate your site and find your content. Add a few keywords to describe your post.' ) } + { __( + 'Tags help users and search engines navigate your site and find your content. Add a few keywords to describe your post.' + ) } </p> <FlatTermSelector slug={ 'post_tag' } /> </PanelBody> @@ -68,12 +68,21 @@ export default compose( withSelect( ( select ) => { const postType = select( 'core/editor' ).getCurrentPostType(); const tagsTaxonomy = select( 'core' ).getTaxonomy( 'post_tag' ); - const tags = tagsTaxonomy && select( 'core/editor' ).getEditedPostAttribute( tagsTaxonomy.rest_base ); + const tags = + tagsTaxonomy && + select( 'core/editor' ).getEditedPostAttribute( + tagsTaxonomy.rest_base + ); return { areTagsFetched: tagsTaxonomy !== undefined, - isPostTypeSupported: tagsTaxonomy && some( tagsTaxonomy.types, ( type ) => type === postType ), + isPostTypeSupported: + tagsTaxonomy && + some( tagsTaxonomy.types, ( type ) => type === postType ), hasTags: tags && tags.length, }; } ), - ifCondition( ( { areTagsFetched, isPostTypeSupported } ) => isPostTypeSupported && areTagsFetched ), + ifCondition( + ( { areTagsFetched, isPostTypeSupported } ) => + isPostTypeSupported && areTagsFetched + ) )( MaybeTagsPanel ); diff --git a/packages/editor/src/components/post-publish-panel/postpublish.js b/packages/editor/src/components/post-publish-panel/postpublish.js index ea6ee6657672a3..21ea2144d052f0 100644 --- a/packages/editor/src/components/post-publish-panel/postpublish.js +++ b/packages/editor/src/components/post-publish-panel/postpublish.js @@ -6,7 +6,12 @@ import { get } from 'lodash'; /** * WordPress dependencies */ -import { PanelBody, Button, ClipboardButton, TextControl } from '@wordpress/components'; +import { + PanelBody, + Button, + ClipboardButton, + TextControl, +} from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { Component, createRef } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; @@ -61,14 +66,22 @@ class PostPublishPanelPostpublish extends Component { const postLabel = get( postType, [ 'labels', 'singular_name' ] ); const viewPostLabel = get( postType, [ 'labels', 'view_item' ] ); - const postPublishNonLinkHeader = isScheduled ? - <>{ __( 'is now scheduled. It will go live on' ) } <PostScheduleLabel />.</> : - __( 'is now live.' ); + const postPublishNonLinkHeader = isScheduled ? ( + <> + { __( 'is now scheduled. It will go live on' ) }{ ' ' } + <PostScheduleLabel />. + </> + ) : ( + __( 'is now live.' ) + ); return ( <div className="post-publish-panel__postpublish"> <PanelBody className="post-publish-panel__postpublish-header"> - <a ref={ this.postLink } href={ post.link }>{ decodeEntities( post.title ) || __( '(no title)' ) }</a> { postPublishNonLinkHeader } + <a ref={ this.postLink } href={ post.link }> + { decodeEntities( post.title ) || __( '(no title)' ) } + </a>{ ' ' } + { postPublishNonLinkHeader } </PanelBody> <PanelBody> <p className="post-publish-panel__postpublish-subheader"> @@ -79,7 +92,8 @@ class PostPublishPanelPostpublish extends Component { readOnly label={ sprintf( /* translators: %s: post type singular name */ - __( '%s address' ), postLabel + __( '%s address' ), + postLabel ) } value={ safeDecodeURIComponent( post.link ) } onFocus={ this.onSelectInput } @@ -91,8 +105,14 @@ class PostPublishPanelPostpublish extends Component { </Button> ) } - <ClipboardButton isSecondary text={ post.link } onCopy={ this.onCopy }> - { this.state.showCopyConfirmation ? __( 'Copied!' ) : __( 'Copy Link' ) } + <ClipboardButton + isSecondary + text={ post.link } + onCopy={ this.onCopy } + > + { this.state.showCopyConfirmation + ? __( 'Copied!' ) + : __( 'Copy Link' ) } </ClipboardButton> </div> </PanelBody> @@ -103,7 +123,11 @@ class PostPublishPanelPostpublish extends Component { } export default withSelect( ( select ) => { - const { getEditedPostAttribute, getCurrentPost, isCurrentPostScheduled } = select( 'core/editor' ); + const { + getEditedPostAttribute, + getCurrentPost, + isCurrentPostScheduled, + } = select( 'core/editor' ); const { getPostType } = select( 'core' ); return { diff --git a/packages/editor/src/components/post-publish-panel/prepublish.js b/packages/editor/src/components/post-publish-panel/prepublish.js index 9368db03008db2..916f42239fe2e8 100644 --- a/packages/editor/src/components/post-publish-panel/prepublish.js +++ b/packages/editor/src/components/post-publish-panel/prepublish.js @@ -29,31 +29,55 @@ function PostPublishPanelPrepublish( { if ( ! hasPublishAction ) { prePublishTitle = __( 'Are you ready to submit for review?' ); - prePublishBodyText = __( 'When you’re ready, submit your work for review, and an Editor will be able to approve it for you.' ); + prePublishBodyText = __( + 'When you’re ready, submit your work for review, and an Editor will be able to approve it for you.' + ); } else if ( isBeingScheduled ) { prePublishTitle = __( 'Are you ready to schedule?' ); - prePublishBodyText = __( 'Your work will be published at the specified date and time.' ); + prePublishBodyText = __( + 'Your work will be published at the specified date and time.' + ); } else { prePublishTitle = __( 'Are you ready to publish?' ); - prePublishBodyText = __( 'Double-check your settings before publishing.' ); + prePublishBodyText = __( + 'Double-check your settings before publishing.' + ); } return ( <div className="editor-post-publish-panel__prepublish"> - <div><strong>{ prePublishTitle }</strong></div> + <div> + <strong>{ prePublishTitle }</strong> + </div> <p>{ prePublishBodyText }</p> { hasPublishAction && ( <> - <PanelBody initialOpen={ false } title={ [ - __( 'Visibility:' ), - <span className="editor-post-publish-panel__link" key="label"><PostVisibilityLabel /></span>, - ] }> + <PanelBody + initialOpen={ false } + title={ [ + __( 'Visibility:' ), + <span + className="editor-post-publish-panel__link" + key="label" + > + <PostVisibilityLabel /> + </span>, + ] } + > <PostVisibility /> </PanelBody> - <PanelBody initialOpen={ false } title={ [ - __( 'Publish:' ), - <span className="editor-post-publish-panel__link" key="label"><PostScheduleLabel /></span>, - ] }> + <PanelBody + initialOpen={ false } + title={ [ + __( 'Publish:' ), + <span + className="editor-post-publish-panel__link" + key="label" + > + <PostScheduleLabel /> + </span>, + ] } + > <PostSchedule /> </PanelBody> </> @@ -65,15 +89,16 @@ function PostPublishPanelPrepublish( { ); } -export default withSelect( - ( select ) => { - const { - getCurrentPost, - isEditedPostBeingScheduled, - } = select( 'core/editor' ); - return { - hasPublishAction: get( getCurrentPost(), [ '_links', 'wp:action-publish' ], false ), - isBeingScheduled: isEditedPostBeingScheduled(), - }; - } -)( PostPublishPanelPrepublish ); +export default withSelect( ( select ) => { + const { getCurrentPost, isEditedPostBeingScheduled } = select( + 'core/editor' + ); + return { + hasPublishAction: get( + getCurrentPost(), + [ '_links', 'wp:action-publish' ], + false + ), + isBeingScheduled: isEditedPostBeingScheduled(), + }; +} )( PostPublishPanelPrepublish ); diff --git a/packages/editor/src/components/post-publish-panel/test/index.js b/packages/editor/src/components/post-publish-panel/test/index.js index ee0ac2411d3e23..96bea2969611dd 100644 --- a/packages/editor/src/components/post-publish-panel/test/index.js +++ b/packages/editor/src/components/post-publish-panel/test/index.js @@ -22,39 +22,25 @@ describe( 'PostPublishPanel', () => { it( 'should render the pre-publish panel if post status is scheduled but date is before now', () => { const wrapper = shallow( - <PostPublishPanel - isScheduled={ true } - isBeingScheduled={ false } - /> + <PostPublishPanel isScheduled={ true } isBeingScheduled={ false } /> ); expect( wrapper ).toMatchSnapshot(); } ); it( 'should render the spinner if the post is being saved', () => { - const wrapper = shallow( - <PostPublishPanel - isSaving={ true } - /> - ); + const wrapper = shallow( <PostPublishPanel isSaving={ true } /> ); expect( wrapper ).toMatchSnapshot(); } ); it( 'should render the post-publish panel if the post is published', () => { - const wrapper = shallow( - <PostPublishPanel - isPublished={ true } - /> - ); + const wrapper = shallow( <PostPublishPanel isPublished={ true } /> ); expect( wrapper ).toMatchSnapshot(); } ); it( 'should render the post-publish panel if the post is scheduled', () => { const wrapper = shallow( - <PostPublishPanel - isScheduled={ true } - isBeingScheduled={ true } - /> + <PostPublishPanel isScheduled={ true } isBeingScheduled={ true } /> ); expect( wrapper ).toMatchSnapshot(); } ); diff --git a/packages/editor/src/components/post-saved-state/index.js b/packages/editor/src/components/post-saved-state/index.js index a097955b292e3d..5d7cf3d4750089 100644 --- a/packages/editor/src/components/post-saved-state/index.js +++ b/packages/editor/src/components/post-saved-state/index.js @@ -62,16 +62,27 @@ export class PostSavedState extends Component { // TODO: Classes generation should be common across all return // paths of this function, including proper naming convention for // the "Save Draft" button. - const classes = classnames( 'editor-post-saved-state', 'is-saving', { - 'is-autosaving': isAutosaving, - } ); + const classes = classnames( + 'editor-post-saved-state', + 'is-saving', + { + 'is-autosaving': isAutosaving, + } + ); return ( <Animate type="loading"> { ( { className: animateClassName } ) => ( - <span className={ classnames( classes, animateClassName ) }> + <span + className={ classnames( + classes, + animateClassName + ) } + > <Dashicon icon="cloud" /> - { isAutosaving ? __( 'Autosaving' ) : __( 'Saving' ) } + { isAutosaving + ? __( 'Autosaving' ) + : __( 'Saving' ) } </span> ) } </Animate> @@ -97,7 +108,11 @@ export class PostSavedState extends Component { // Once the post has been submitted for review this button // is not needed for the contributor role. - const hasPublishAction = get( post, [ '_links', 'wp:action-publish' ], false ); + const hasPublishAction = get( + post, + [ '_links', 'wp:action-publish' ], + false + ); if ( ! hasPublishAction && isPending ) { return null; } diff --git a/packages/editor/src/components/post-saved-state/test/index.js b/packages/editor/src/components/post-saved-state/test/index.js index 8466002b8b6145..79636fdf0267bc 100644 --- a/packages/editor/src/components/post-saved-state/test/index.js +++ b/packages/editor/src/components/post-saved-state/test/index.js @@ -15,7 +15,8 @@ describe( 'PostSavedState', () => { isNew isDirty={ false } isSaving={ true } - isSaveable={ false } /> + isSaveable={ false } + /> ); expect( wrapper.text() ).toContain( 'Saving' ); @@ -27,7 +28,8 @@ describe( 'PostSavedState', () => { isNew isDirty={ false } isSaving={ false } - isSaveable={ false } /> + isSaveable={ false } + /> ); expect( wrapper.type() ).toBeNull(); @@ -45,7 +47,8 @@ describe( 'PostSavedState', () => { isNew={ false } isDirty={ false } isSaving={ false } - isSaveable={ true } /> + isSaveable={ true } + /> ); expect( wrapper.childAt( 0 ).name() ).toBe( 'Icon' ); diff --git a/packages/editor/src/components/post-schedule/check.js b/packages/editor/src/components/post-schedule/check.js index 7495f216b014c5..bc30eefe08e266 100644 --- a/packages/editor/src/components/post-schedule/check.js +++ b/packages/editor/src/components/post-schedule/check.js @@ -21,7 +21,11 @@ export default compose( [ withSelect( ( select ) => { const { getCurrentPost, getCurrentPostType } = select( 'core/editor' ); return { - hasPublishAction: get( getCurrentPost(), [ '_links', 'wp:action-publish' ], false ), + hasPublishAction: get( + getCurrentPost(), + [ '_links', 'wp:action-publish' ], + false + ), postType: getCurrentPostType(), }; } ), diff --git a/packages/editor/src/components/post-schedule/index.js b/packages/editor/src/components/post-schedule/index.js index 78a8a60c583b25..b22d2b94d809cb 100644 --- a/packages/editor/src/components/post-schedule/index.js +++ b/packages/editor/src/components/post-schedule/index.js @@ -14,7 +14,9 @@ export function PostSchedule( { date, onUpdateDate } ) { settings.formats.time .toLowerCase() // Test only the lower case a .replace( /\\\\/g, '' ) // Replace "//" with empty strings - .split( '' ).reverse().join( '' ) // Reverse the string and test for "a" not followed by a slash + .split( '' ) + .reverse() + .join( '' ) // Reverse the string and test for "a" not followed by a slash ); return ( diff --git a/packages/editor/src/components/post-schedule/label.js b/packages/editor/src/components/post-schedule/label.js index d00f69d2d9ecf5..91a025b703d13b 100644 --- a/packages/editor/src/components/post-schedule/label.js +++ b/packages/editor/src/components/post-schedule/label.js @@ -8,9 +8,12 @@ import { withSelect } from '@wordpress/data'; export function PostScheduleLabel( { date, isFloating } ) { const settings = __experimentalGetSettings(); - return date && ! isFloating ? - dateI18n( `${ settings.formats.date } ${ settings.formats.time }`, date ) : - __( 'Immediately' ); + return date && ! isFloating + ? dateI18n( + `${ settings.formats.date } ${ settings.formats.time }`, + date + ) + : __( 'Immediately' ); } export default withSelect( ( select ) => { diff --git a/packages/editor/src/components/post-schedule/test/check.js b/packages/editor/src/components/post-schedule/test/check.js index 6d0ce5be795156..768d8e8301ab95 100644 --- a/packages/editor/src/components/post-schedule/test/check.js +++ b/packages/editor/src/components/post-schedule/test/check.js @@ -10,12 +10,18 @@ import { PostScheduleCheck } from '../check'; describe( 'PostScheduleCheck', () => { it( "should not render anything if the user doesn't have the right capabilities", () => { - const wrapper = shallow( <PostScheduleCheck hasPublishAction={ false } >yes</PostScheduleCheck> ); + const wrapper = shallow( + <PostScheduleCheck hasPublishAction={ false }> + yes + </PostScheduleCheck> + ); expect( wrapper.type() ).toBe( null ); } ); it( 'should render if the user has the correct capability', () => { - const wrapper = shallow( <PostScheduleCheck hasPublishAction={ true }>yes</PostScheduleCheck> ); + const wrapper = shallow( + <PostScheduleCheck hasPublishAction={ true }>yes</PostScheduleCheck> + ); expect( wrapper.type() ).not.toBe( null ); } ); } ); diff --git a/packages/editor/src/components/post-schedule/test/label.js b/packages/editor/src/components/post-schedule/test/label.js index f1d063a0d0fcdf..eec11bfe964874 100644 --- a/packages/editor/src/components/post-schedule/test/label.js +++ b/packages/editor/src/components/post-schedule/test/label.js @@ -16,13 +16,17 @@ describe( 'PostScheduleLabel', () => { it( 'should show the post will be published immediately if it has a floating date', () => { const date = '2018-09-17T01:23:45.678Z'; - const wrapper = shallow( <PostScheduleLabel date={ date } isFloating={ true } /> ); + const wrapper = shallow( + <PostScheduleLabel date={ date } isFloating={ true } /> + ); expect( wrapper.text() ).toBe( 'Immediately' ); } ); it( 'should show the scheduled publish date if a date has been set', () => { const date = '2018-09-17T01:23:45.678Z'; - const wrapper = shallow( <PostScheduleLabel date={ date } isFloating={ false } /> ); + const wrapper = shallow( + <PostScheduleLabel date={ date } isFloating={ false } /> + ); expect( wrapper.text() ).not.toBe( 'Immediately' ); } ); } ); diff --git a/packages/editor/src/components/post-slug/check.js b/packages/editor/src/components/post-slug/check.js index 6b243bd6b9f641..80928e4cf1dece 100644 --- a/packages/editor/src/components/post-slug/check.js +++ b/packages/editor/src/components/post-slug/check.js @@ -5,6 +5,8 @@ import PostTypeSupportCheck from '../post-type-support-check'; export default function PostSlugCheck( { children } ) { return ( - <PostTypeSupportCheck supportKeys="slug">{ children }</PostTypeSupportCheck> + <PostTypeSupportCheck supportKeys="slug"> + { children } + </PostTypeSupportCheck> ); } diff --git a/packages/editor/src/components/post-slug/index.js b/packages/editor/src/components/post-slug/index.js index 9a9b2200e72be0..6a578c1c7bc200 100644 --- a/packages/editor/src/components/post-slug/index.js +++ b/packages/editor/src/components/post-slug/index.js @@ -18,7 +18,10 @@ export class PostSlug extends Component { super( ...arguments ); this.state = { - editedSlug: safeDecodeURIComponent( postSlug ) || cleanForSlug( postTitle ) || postID, + editedSlug: + safeDecodeURIComponent( postSlug ) || + cleanForSlug( postTitle ) || + postID, }; this.setSlug = this.setSlug.bind( this ); @@ -50,7 +53,9 @@ export class PostSlug extends Component { type="text" id={ inputId } value={ editedSlug } - onChange={ ( event ) => this.setState( { editedSlug: event.target.value } ) } + onChange={ ( event ) => + this.setState( { editedSlug: event.target.value } ) + } onBlur={ this.setSlug } className="editor-post-slug__input" /> @@ -61,10 +66,9 @@ export class PostSlug extends Component { export default compose( [ withSelect( ( select ) => { - const { - getCurrentPost, - getEditedPostAttribute, - } = select( 'core/editor' ); + const { getCurrentPost, getEditedPostAttribute } = select( + 'core/editor' + ); const { id } = getCurrentPost(); return { diff --git a/packages/editor/src/components/post-slug/test/check.js b/packages/editor/src/components/post-slug/test/check.js index 90fe356236adbc..bb8bd3bf1f07eb 100644 --- a/packages/editor/src/components/post-slug/test/check.js +++ b/packages/editor/src/components/post-slug/test/check.js @@ -10,11 +10,7 @@ import PostSlugCheck from '../check'; describe( 'PostSlugCheck', () => { it( 'should render control', () => { - const wrapper = shallow( - <PostSlugCheck> - slug - </PostSlugCheck> - ); + const wrapper = shallow( <PostSlugCheck>slug</PostSlugCheck> ); expect( wrapper.type() ).not.toBe( null ); } ); diff --git a/packages/editor/src/components/post-slug/test/index.js b/packages/editor/src/components/post-slug/test/index.js index cf25f25e0fe377..80bdf0b072e9fd 100644 --- a/packages/editor/src/components/post-slug/test/index.js +++ b/packages/editor/src/components/post-slug/test/index.js @@ -11,10 +11,7 @@ import { PostSlug } from '../'; describe( 'PostSlug', () => { describe( '#render()', () => { it( 'should update internal slug', () => { - const wrapper = shallow( - <PostSlug - postSlug="index" /> - ); + const wrapper = shallow( <PostSlug postSlug="index" /> ); wrapper.find( 'input' ).simulate( 'change', { target: { @@ -28,9 +25,7 @@ describe( 'PostSlug', () => { it( 'should update slug', () => { const onUpdateSlug = jest.fn(); const wrapper = shallow( - <PostSlug - postSlug="index" - onUpdateSlug={ onUpdateSlug } /> + <PostSlug postSlug="index" onUpdateSlug={ onUpdateSlug } /> ); wrapper.find( 'input' ).simulate( 'blur', { diff --git a/packages/editor/src/components/post-sticky/check.js b/packages/editor/src/components/post-sticky/check.js index 949c18cf43efdf..4757ece4da48f6 100644 --- a/packages/editor/src/components/post-sticky/check.js +++ b/packages/editor/src/components/post-sticky/check.js @@ -10,10 +10,7 @@ import { compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; export function PostStickyCheck( { hasStickyAction, postType, children } ) { - if ( - postType !== 'post' || - ! hasStickyAction - ) { + if ( postType !== 'post' || ! hasStickyAction ) { return null; } @@ -24,7 +21,11 @@ export default compose( [ withSelect( ( select ) => { const post = select( 'core/editor' ).getCurrentPost(); return { - hasStickyAction: get( post, [ '_links', 'wp:action-sticky' ], false ), + hasStickyAction: get( + post, + [ '_links', 'wp:action-sticky' ], + false + ), postType: select( 'core/editor' ).getCurrentPostType(), }; } ), diff --git a/packages/editor/src/components/post-sticky/index.js b/packages/editor/src/components/post-sticky/index.js index 9cc787d19a8b19..6458c4fdcaeda6 100644 --- a/packages/editor/src/components/post-sticky/index.js +++ b/packages/editor/src/components/post-sticky/index.js @@ -26,7 +26,9 @@ export function PostSticky( { onUpdateSticky, postSticky = false } ) { export default compose( [ withSelect( ( select ) => { return { - postSticky: select( 'core/editor' ).getEditedPostAttribute( 'sticky' ), + postSticky: select( 'core/editor' ).getEditedPostAttribute( + 'sticky' + ), }; } ), withDispatch( ( dispatch ) => { diff --git a/packages/editor/src/components/post-switch-to-draft-button/index.js b/packages/editor/src/components/post-switch-to-draft-button/index.js index d1b9f360c0a5c9..122b5f8377e763 100644 --- a/packages/editor/src/components/post-switch-to-draft-button/index.js +++ b/packages/editor/src/components/post-switch-to-draft-button/index.js @@ -21,9 +21,13 @@ function PostSwitchToDraftButton( { const onSwitch = () => { let alertMessage; if ( isPublished ) { - alertMessage = __( 'Are you sure you want to unpublish this post?' ); + alertMessage = __( + 'Are you sure you want to unpublish this post?' + ); } else if ( isScheduled ) { - alertMessage = __( 'Are you sure you want to unschedule this post?' ); + alertMessage = __( + 'Are you sure you want to unschedule this post?' + ); } // eslint-disable-next-line no-alert if ( window.confirm( alertMessage ) ) { @@ -45,7 +49,11 @@ function PostSwitchToDraftButton( { export default compose( [ withSelect( ( select ) => { - const { isSavingPost, isCurrentPostPublished, isCurrentPostScheduled } = select( 'core/editor' ); + const { + isSavingPost, + isCurrentPostPublished, + isCurrentPostScheduled, + } = select( 'core/editor' ); return { isSaving: isSavingPost(), isPublished: isCurrentPostPublished(), @@ -62,4 +70,3 @@ export default compose( [ }; } ), ] )( PostSwitchToDraftButton ); - diff --git a/packages/editor/src/components/post-taxonomies/check.js b/packages/editor/src/components/post-taxonomies/check.js index ab078b1bc70da4..75877745b0da7f 100644 --- a/packages/editor/src/components/post-taxonomies/check.js +++ b/packages/editor/src/components/post-taxonomies/check.js @@ -10,7 +10,9 @@ import { compose } from '@wordpress/compose'; import { withSelect } from '@wordpress/data'; export function PostTaxonomiesCheck( { postType, taxonomies, children } ) { - const hasTaxonomies = some( taxonomies, ( taxonomy ) => includes( taxonomy.types, postType ) ); + const hasTaxonomies = some( taxonomies, ( taxonomy ) => + includes( taxonomy.types, postType ) + ); if ( ! hasTaxonomies ) { return null; } @@ -26,4 +28,3 @@ export default compose( [ }; } ), ] )( PostTaxonomiesCheck ); - diff --git a/packages/editor/src/components/post-taxonomies/flat-term-selector.js b/packages/editor/src/components/post-taxonomies/flat-term-selector.js index da6b0782966d4b..299245a5e45aad 100644 --- a/packages/editor/src/components/post-taxonomies/flat-term-selector.js +++ b/packages/editor/src/components/post-taxonomies/flat-term-selector.js @@ -34,7 +34,8 @@ const DEFAULT_QUERY = { _fields: 'id,name', }; const MAX_TERMS_SUGGESTIONS = 20; -const isSameTermName = ( termA, termB ) => termA.toLowerCase() === termB.toLowerCase(); +const isSameTermName = ( termA, termB ) => + termA.toLowerCase() === termB.toLowerCase(); /** * Returns a term object with name unescaped. @@ -118,7 +119,14 @@ class FlatTermSelector extends Component { request.then( unescapeTerms ).then( ( terms ) => { this.setState( ( state ) => ( { availableTerms: state.availableTerms.concat( - terms.filter( ( term ) => ! find( state.availableTerms, ( availableTerm ) => availableTerm.id === term.id ) ) + terms.filter( + ( term ) => + ! find( + state.availableTerms, + ( availableTerm ) => + availableTerm.id === term.id + ) + ) ), } ) ); this.updateSelectedTerms( this.props.terms ); @@ -129,7 +137,10 @@ class FlatTermSelector extends Component { updateSelectedTerms( terms = [] ) { const selectedTerms = terms.reduce( ( accumulator, termId ) => { - const termObject = find( this.state.availableTerms, ( term ) => term.id === termId ); + const termObject = find( + this.state.availableTerms, + ( term ) => term.id === termId + ); if ( termObject ) { accumulator.push( termObject.name ); } @@ -149,32 +160,44 @@ class FlatTermSelector extends Component { path: `/wp/v2/${ taxonomy.rest_base }`, method: 'POST', data: { name: termNameEscaped }, - } ).catch( ( error ) => { - const errorCode = error.code; - if ( errorCode === 'term_exists' ) { - // If the terms exist, fetch it instead of creating a new one. - this.addRequest = apiFetch( { - path: addQueryArgs( `/wp/v2/${ taxonomy.rest_base }`, { ...DEFAULT_QUERY, search: termNameEscaped } ), - } ).then( unescapeTerms ); - return this.addRequest.then( ( searchResult ) => { - return find( searchResult, ( result ) => isSameTermName( result.name, termName ) ); - } ); - } - return Promise.reject( error ); - } ).then( unescapeTerm ); + } ) + .catch( ( error ) => { + const errorCode = error.code; + if ( errorCode === 'term_exists' ) { + // If the terms exist, fetch it instead of creating a new one. + this.addRequest = apiFetch( { + path: addQueryArgs( `/wp/v2/${ taxonomy.rest_base }`, { + ...DEFAULT_QUERY, + search: termNameEscaped, + } ), + } ).then( unescapeTerms ); + return this.addRequest.then( ( searchResult ) => { + return find( searchResult, ( result ) => + isSameTermName( result.name, termName ) + ); + } ); + } + return Promise.reject( error ); + } ) + .then( unescapeTerm ); } onChange( termNames ) { const uniqueTerms = uniqBy( termNames, ( term ) => term.toLowerCase() ); this.setState( { selectedTerms: uniqueTerms } ); - const newTermNames = uniqueTerms.filter( ( termName ) => - ! find( this.state.availableTerms, ( term ) => isSameTermName( term.name, termName ) ) + const newTermNames = uniqueTerms.filter( + ( termName ) => + ! find( this.state.availableTerms, ( term ) => + isSameTermName( term.name, termName ) + ) ); const termNamesToIds = ( names, availableTerms ) => { - return names - .map( ( termName ) => - find( availableTerms, ( term ) => isSameTermName( term.name, termName ) ).id - ); + return names.map( + ( termName ) => + find( availableTerms, ( term ) => + isSameTermName( term.name, termName ) + ).id + ); }; if ( newTermNames.length === 0 ) { @@ -183,16 +206,18 @@ class FlatTermSelector extends Component { this.props.taxonomy.rest_base ); } - Promise - .all( newTermNames.map( this.findOrCreateTerm ) ) - .then( ( newTerms ) => { - const newAvailableTerms = this.state.availableTerms.concat( newTerms ); + Promise.all( newTermNames.map( this.findOrCreateTerm ) ).then( + ( newTerms ) => { + const newAvailableTerms = this.state.availableTerms.concat( + newTerms + ); this.setState( { availableTerms: newAvailableTerms } ); return this.props.onUpdateTerms( termNamesToIds( uniqueTerms, newAvailableTerms ), this.props.taxonomy.rest_base ); - } ); + } + ); } searchTerms( search = '' ) { @@ -219,9 +244,18 @@ class FlatTermSelector extends Component { [ 'labels', 'singular_name' ], slug === 'post_tag' ? __( 'Tag' ) : __( 'Term' ) ); - const termAddedLabel = sprintf( _x( '%s added', 'term' ), singularName ); - const termRemovedLabel = sprintf( _x( '%s removed', 'term' ), singularName ); - const removeTermLabel = sprintf( _x( 'Remove %s', 'term' ), singularName ); + const termAddedLabel = sprintf( + _x( '%s added', 'term' ), + singularName + ); + const termRemovedLabel = sprintf( + _x( '%s removed', 'term' ), + singularName + ); + const removeTermLabel = sprintf( + _x( 'Remove %s', 'term' ), + singularName + ); return ( <FormTokenField @@ -248,9 +282,25 @@ export default compose( const { getTaxonomy } = select( 'core' ); const taxonomy = getTaxonomy( slug ); return { - hasCreateAction: taxonomy ? get( getCurrentPost(), [ '_links', 'wp:action-create-' + taxonomy.rest_base ], false ) : false, - hasAssignAction: taxonomy ? get( getCurrentPost(), [ '_links', 'wp:action-assign-' + taxonomy.rest_base ], false ) : false, - terms: taxonomy ? select( 'core/editor' ).getEditedPostAttribute( taxonomy.rest_base ) : [], + hasCreateAction: taxonomy + ? get( + getCurrentPost(), + [ '_links', 'wp:action-create-' + taxonomy.rest_base ], + false + ) + : false, + hasAssignAction: taxonomy + ? get( + getCurrentPost(), + [ '_links', 'wp:action-assign-' + taxonomy.rest_base ], + false + ) + : false, + terms: taxonomy + ? select( 'core/editor' ).getEditedPostAttribute( + taxonomy.rest_base + ) + : [], taxonomy, }; } ), @@ -261,5 +311,5 @@ export default compose( }, }; } ), - withFilters( 'editor.PostTaxonomyType' ), + withFilters( 'editor.PostTaxonomyType' ) )( FlatTermSelector ); diff --git a/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js b/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js index e7563ba296a3db..c8897753492f13 100644 --- a/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js +++ b/packages/editor/src/components/post-taxonomies/hierarchical-term-selector.js @@ -1,14 +1,27 @@ /** * External dependencies */ -import { get, unescape as unescapeString, without, find, some, invoke } from 'lodash'; +import { + get, + unescape as unescapeString, + without, + find, + some, + invoke, +} from 'lodash'; /** * WordPress dependencies */ import { __, _x, _n, sprintf } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; -import { CheckboxControl, TreeSelect, withSpokenMessages, withFilters, Button } from '@wordpress/components'; +import { + CheckboxControl, + TreeSelect, + withSpokenMessages, + withFilters, + Button, +} from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; import { withInstanceId, compose } from '@wordpress/compose'; import apiFetch from '@wordpress/api-fetch'; @@ -58,14 +71,15 @@ class HierarchicalTermSelector extends Component { onChange( termId ) { const { onUpdateTerms, terms = [], taxonomy } = this.props; const hasTerm = terms.indexOf( termId ) !== -1; - const newTerms = hasTerm ? - without( terms, termId ) : - [ ...terms, termId ]; + const newTerms = hasTerm + ? without( terms, termId ) + : [ ...terms, termId ]; onUpdateTerms( newTerms, taxonomy.rest_base ); } onChangeFormName( event ) { - const newValue = event.target.value.trim() === '' ? '' : event.target.value; + const newValue = + event.target.value.trim() === '' ? '' : event.target.value; this.setState( { formName: newValue } ); } @@ -81,8 +95,11 @@ class HierarchicalTermSelector extends Component { findTerm( terms, parent, name ) { return find( terms, ( term ) => { - return ( ( ! term.parent && ! parent ) || parseInt( term.parent ) === parseInt( parent ) ) && - term.name.toLowerCase() === name.toLowerCase(); + return ( + ( ( ! term.parent && ! parent ) || + parseInt( term.parent ) === parseInt( parent ) ) && + term.name.toLowerCase() === name.toLowerCase() + ); } ); } @@ -95,11 +112,18 @@ class HierarchicalTermSelector extends Component { } // check if the term we are adding already exists - const existingTerm = this.findTerm( availableTerms, formParent, formName ); + const existingTerm = this.findTerm( + availableTerms, + formParent, + formName + ); if ( existingTerm ) { // if the term we are adding exists but is not selected select it if ( ! some( terms, ( term ) => term === existingTerm.id ) ) { - onUpdateTerms( [ ...terms, existingTerm.id ], taxonomy.rest_base ); + onUpdateTerms( + [ ...terms, existingTerm.id ], + taxonomy.rest_base + ); } this.setState( { formName: '', @@ -120,28 +144,32 @@ class HierarchicalTermSelector extends Component { }, } ); // Tries to create a term or fetch it if it already exists - const findOrCreatePromise = this.addRequest - .catch( ( error ) => { - const errorCode = error.code; - if ( errorCode === 'term_exists' ) { - // search the new category created since last fetch - this.addRequest = apiFetch( { - path: addQueryArgs( - `/wp/v2/${ taxonomy.rest_base }`, - { ...DEFAULT_QUERY, parent: formParent || 0, search: formName } - ), - } ); - return this.addRequest - .then( ( searchResult ) => { - return this.findTerm( searchResult, formParent, formName ); - } ); - } - return Promise.reject( error ); - } ); - findOrCreatePromise - .then( ( term ) => { - const hasTerm = !! find( this.state.availableTerms, ( availableTerm ) => availableTerm.id === term.id ); - const newAvailableTerms = hasTerm ? this.state.availableTerms : [ term, ...this.state.availableTerms ]; + const findOrCreatePromise = this.addRequest.catch( ( error ) => { + const errorCode = error.code; + if ( errorCode === 'term_exists' ) { + // search the new category created since last fetch + this.addRequest = apiFetch( { + path: addQueryArgs( `/wp/v2/${ taxonomy.rest_base }`, { + ...DEFAULT_QUERY, + parent: formParent || 0, + search: formName, + } ), + } ); + return this.addRequest.then( ( searchResult ) => { + return this.findTerm( searchResult, formParent, formName ); + } ); + } + return Promise.reject( error ); + } ); + findOrCreatePromise.then( + ( term ) => { + const hasTerm = !! find( + this.state.availableTerms, + ( availableTerm ) => availableTerm.id === term.id + ); + const newAvailableTerms = hasTerm + ? this.state.availableTerms + : [ term, ...this.state.availableTerms ]; const termAddedMessage = sprintf( _x( '%s added', 'term' ), get( @@ -157,10 +185,13 @@ class HierarchicalTermSelector extends Component { formName: '', formParent: '', availableTerms: newAvailableTerms, - availableTermsTree: this.sortBySelected( buildTermsTree( newAvailableTerms ) ), + availableTermsTree: this.sortBySelected( + buildTermsTree( newAvailableTerms ) + ), } ); onUpdateTerms( [ ...terms, term.id ], taxonomy.rest_base ); - }, ( xhr ) => { + }, + ( xhr ) => { if ( xhr.statusText === 'abort' ) { return; } @@ -168,7 +199,8 @@ class HierarchicalTermSelector extends Component { this.setState( { adding: false, } ); - } ); + } + ); } componentDidMount() { @@ -192,11 +224,17 @@ class HierarchicalTermSelector extends Component { return; } this.fetchRequest = apiFetch( { - path: addQueryArgs( `/wp/v2/${ taxonomy.rest_base }`, DEFAULT_QUERY ), + path: addQueryArgs( + `/wp/v2/${ taxonomy.rest_base }`, + DEFAULT_QUERY + ), } ); this.fetchRequest.then( - ( terms ) => { // resolve - const availableTermsTree = this.sortBySelected( buildTermsTree( terms ) ); + ( terms ) => { + // resolve + const availableTermsTree = this.sortBySelected( + buildTermsTree( terms ) + ); this.fetchRequest = null; this.setState( { @@ -205,7 +243,8 @@ class HierarchicalTermSelector extends Component { availableTerms: terms, } ); }, - ( xhr ) => { // reject + ( xhr ) => { + // reject if ( xhr.statusText === 'abort' ) { return; } @@ -226,7 +265,10 @@ class HierarchicalTermSelector extends Component { if ( undefined === termTree.children ) { return false; } - const anyChildIsSelected = termTree.children.map( treeHasSelection ).filter( ( child ) => child ).length > 0; + const anyChildIsSelected = + termTree.children + .map( treeHasSelection ) + .filter( ( child ) => child ).length > 0; if ( anyChildIsSelected ) { return true; } @@ -257,7 +299,9 @@ class HierarchicalTermSelector extends Component { setFilterValue( event ) { const { availableTermsTree } = this.state; const filterValue = event.target.value; - const filteredTermsTree = availableTermsTree.map( this.getFilterMatcher( filterValue ) ).filter( ( term ) => term ); + const filteredTermsTree = availableTermsTree + .map( this.getFilterMatcher( filterValue ) ) + .filter( ( term ) => term ); const getResultCount = ( terms ) => { let count = 0; for ( let i = 0; i < terms.length; i++ ) { @@ -268,12 +312,10 @@ class HierarchicalTermSelector extends Component { } return count; }; - this.setState( - { - filterValue, - filteredTermsTree, - } - ); + this.setState( { + filterValue, + filteredTermsTree, + } ); const resultCount = getResultCount( filteredTermsTree ); const resultsFoundMessage = sprintf( @@ -296,12 +338,20 @@ class HierarchicalTermSelector extends Component { // Map and filter the children, recursive so we deal with grandchildren // and any deeper levels. if ( term.children.length > 0 ) { - term.children = term.children.map( matchTermsForFilter ).filter( ( child ) => child ); + term.children = term.children + .map( matchTermsForFilter ) + .filter( ( child ) => child ); } // If the term's name contains the filterValue, or it has children // (i.e. some child matched at some point in the tree) then return it. - if ( -1 !== term.name.toLowerCase().indexOf( filterValue.toLowerCase() ) || term.children.length > 0 ) { + if ( + -1 !== + term.name + .toLowerCase() + .indexOf( filterValue.toLowerCase() ) || + term.children.length > 0 + ) { return term; } @@ -316,7 +366,10 @@ class HierarchicalTermSelector extends Component { const { terms = [] } = this.props; return renderedTerms.map( ( term ) => { return ( - <div key={ term.id } className="editor-post-taxonomies__hierarchical-terms-choice"> + <div + key={ term.id } + className="editor-post-taxonomies__hierarchical-terms-choice" + > <CheckboxControl checked={ terms.indexOf( term.id ) !== -1 } onChange={ () => { @@ -336,18 +389,38 @@ class HierarchicalTermSelector extends Component { } render() { - const { slug, taxonomy, instanceId, hasCreateAction, hasAssignAction } = this.props; + const { + slug, + taxonomy, + instanceId, + hasCreateAction, + hasAssignAction, + } = this.props; if ( ! hasAssignAction ) { return null; } - const { availableTermsTree, availableTerms, filteredTermsTree, formName, formParent, loading, showForm, filterValue } = this.state; - const labelWithFallback = ( labelProperty, fallbackIsCategory, fallbackIsNotCategory ) => get( - taxonomy, - [ 'labels', labelProperty ], - slug === 'category' ? fallbackIsCategory : fallbackIsNotCategory - ); + const { + availableTermsTree, + availableTerms, + filteredTermsTree, + formName, + formParent, + loading, + showForm, + filterValue, + } = this.state; + const labelWithFallback = ( + labelProperty, + fallbackIsCategory, + fallbackIsNotCategory + ) => + get( + taxonomy, + [ 'labels', labelProperty ], + slug === 'category' ? fallbackIsCategory : fallbackIsNotCategory + ); const newTermButtonLabel = labelWithFallback( 'add_new_item', __( 'Add new category' ), @@ -380,19 +453,21 @@ class HierarchicalTermSelector extends Component { const showFilter = availableTerms.length >= MIN_TERMS_COUNT_FOR_FILTER; return [ - showFilter && <label - key="filter-label" - htmlFor={ filterInputId }> - { filterLabel } - </label>, - showFilter && <input - type="search" - id={ filterInputId } - value={ filterValue } - onChange={ this.setFilterValue } - className="editor-post-taxonomies__hierarchical-terms-filter" - key="term-filter-input" - />, + showFilter && ( + <label key="filter-label" htmlFor={ filterInputId }> + { filterLabel } + </label> + ), + showFilter && ( + <input + type="search" + id={ filterInputId } + value={ filterValue } + onChange={ this.setFilterValue } + className="editor-post-taxonomies__hierarchical-terms-filter" + key="term-filter-input" + /> + ), <div className="editor-post-taxonomies__hierarchical-terms-list" key="term-list" @@ -400,7 +475,9 @@ class HierarchicalTermSelector extends Component { role="group" aria-label={ groupLabel } > - { this.renderTerms( '' !== filterValue ? filteredTermsTree : availableTermsTree ) } + { this.renderTerms( + '' !== filterValue ? filteredTermsTree : availableTermsTree + ) } </div>, ! loading && hasCreateAction && ( <Button @@ -429,7 +506,7 @@ class HierarchicalTermSelector extends Component { onChange={ this.onChangeFormName } required /> - { !! availableTerms.length && + { !! availableTerms.length && ( <TreeSelect label={ parentSelectLabel } noOptionLabel={ noParentOption } @@ -437,7 +514,7 @@ class HierarchicalTermSelector extends Component { selectedId={ formParent } tree={ availableTermsTree } /> - } + ) } <Button isSecondary type="submit" @@ -457,9 +534,25 @@ export default compose( [ const { getTaxonomy } = select( 'core' ); const taxonomy = getTaxonomy( slug ); return { - hasCreateAction: taxonomy ? get( getCurrentPost(), [ '_links', 'wp:action-create-' + taxonomy.rest_base ], false ) : false, - hasAssignAction: taxonomy ? get( getCurrentPost(), [ '_links', 'wp:action-assign-' + taxonomy.rest_base ], false ) : false, - terms: taxonomy ? select( 'core/editor' ).getEditedPostAttribute( taxonomy.rest_base ) : [], + hasCreateAction: taxonomy + ? get( + getCurrentPost(), + [ '_links', 'wp:action-create-' + taxonomy.rest_base ], + false + ) + : false, + hasAssignAction: taxonomy + ? get( + getCurrentPost(), + [ '_links', 'wp:action-assign-' + taxonomy.rest_base ], + false + ) + : false, + terms: taxonomy + ? select( 'core/editor' ).getEditedPostAttribute( + taxonomy.rest_base + ) + : [], taxonomy, }; } ), diff --git a/packages/editor/src/components/post-taxonomies/index.js b/packages/editor/src/components/post-taxonomies/index.js index 75159bd2f6875e..9fa6fbd6cb5257 100644 --- a/packages/editor/src/components/post-taxonomies/index.js +++ b/packages/editor/src/components/post-taxonomies/index.js @@ -16,19 +16,28 @@ import { compose } from '@wordpress/compose'; import HierarchicalTermSelector from './hierarchical-term-selector'; import FlatTermSelector from './flat-term-selector'; -export function PostTaxonomies( { postType, taxonomies, taxonomyWrapper = identity } ) { - const availableTaxonomies = filter( taxonomies, ( taxonomy ) => includes( taxonomy.types, postType ) ); - const visibleTaxonomies = filter( availableTaxonomies, ( taxonomy ) => taxonomy.visibility.show_ui ); +export function PostTaxonomies( { + postType, + taxonomies, + taxonomyWrapper = identity, +} ) { + const availableTaxonomies = filter( taxonomies, ( taxonomy ) => + includes( taxonomy.types, postType ) + ); + const visibleTaxonomies = filter( + availableTaxonomies, + ( taxonomy ) => taxonomy.visibility.show_ui + ); return visibleTaxonomies.map( ( taxonomy ) => { - const TaxonomyComponent = taxonomy.hierarchical ? HierarchicalTermSelector : FlatTermSelector; + const TaxonomyComponent = taxonomy.hierarchical + ? HierarchicalTermSelector + : FlatTermSelector; return ( <Fragment key={ `taxonomy-${ taxonomy.slug }` }> - { - taxonomyWrapper( - <TaxonomyComponent slug={ taxonomy.slug } />, - taxonomy - ) - } + { taxonomyWrapper( + <TaxonomyComponent slug={ taxonomy.slug } />, + taxonomy + ) } </Fragment> ); } ); @@ -42,4 +51,3 @@ export default compose( [ }; } ), ] )( PostTaxonomies ); - diff --git a/packages/editor/src/components/post-taxonomies/test/index.js b/packages/editor/src/components/post-taxonomies/test/index.js index b7a941eb658011..40d043072013ca 100644 --- a/packages/editor/src/components/post-taxonomies/test/index.js +++ b/packages/editor/src/components/post-taxonomies/test/index.js @@ -43,7 +43,8 @@ describe( 'PostTaxonomies', () => { }; const wrapperOne = shallow( - <PostTaxonomies postType="book" + <PostTaxonomies + postType="book" taxonomies={ [ genresTaxonomy, categoriesTaxonomy ] } /> ); @@ -51,7 +52,8 @@ describe( 'PostTaxonomies', () => { expect( wrapperOne ).toHaveLength( 1 ); const wrapperTwo = shallow( - <PostTaxonomies postType="book" + <PostTaxonomies + postType="book" taxonomies={ [ genresTaxonomy, { @@ -78,15 +80,14 @@ describe( 'PostTaxonomies', () => { }; const wrapperOne = shallow( - <PostTaxonomies postType="book" - taxonomies={ [ genresTaxonomy ] } - /> + <PostTaxonomies postType="book" taxonomies={ [ genresTaxonomy ] } /> ); expect( wrapperOne.at( 0 ) ).toHaveLength( 1 ); const wrapperTwo = shallow( - <PostTaxonomies postType="book" + <PostTaxonomies + postType="book" taxonomies={ [ { ...genresTaxonomy, diff --git a/packages/editor/src/components/post-text-editor/index.js b/packages/editor/src/components/post-text-editor/index.js index 667aaa76fcd700..dcfd611ea60115 100644 --- a/packages/editor/src/components/post-text-editor/index.js +++ b/packages/editor/src/components/post-text-editor/index.js @@ -67,7 +67,10 @@ export class PostTextEditor extends Component { const { instanceId } = this.props; return ( <> - <label htmlFor={ `post-content-${ instanceId }` } className="screen-reader-text"> + <label + htmlFor={ `post-content-${ instanceId }` } + className="screen-reader-text" + > { __( 'Type text or HTML' ) } </label> <Textarea diff --git a/packages/editor/src/components/post-text-editor/test/index.js b/packages/editor/src/components/post-text-editor/test/index.js index 63f8a250a409d5..6ca88cacb78df8 100644 --- a/packages/editor/src/components/post-text-editor/test/index.js +++ b/packages/editor/src/components/post-text-editor/test/index.js @@ -11,7 +11,9 @@ import { PostTextEditor } from '../'; // "Downgrade" ReactAutosizeTextarea to a regular textarea. Assumes aligned // props interface. -jest.mock( 'react-autosize-textarea', () => ( props ) => <textarea { ...props } /> ); +jest.mock( 'react-autosize-textarea', () => ( props ) => ( + <textarea { ...props } /> +) ); describe( 'PostTextEditor', () => { it( 'should render via the prop value', () => { @@ -24,10 +26,7 @@ describe( 'PostTextEditor', () => { it( 'should render via the state value when edits made', () => { const onChange = jest.fn(); const wrapper = create( - <PostTextEditor - value="Hello World" - onChange={ onChange } - /> + <PostTextEditor value="Hello World" onChange={ onChange } /> ); const textarea = wrapper.root.findByType( Textarea ); @@ -40,20 +39,14 @@ describe( 'PostTextEditor', () => { it( 'should render via the state value when edits made, even if prop value changes', () => { const onChange = jest.fn(); const wrapper = create( - <PostTextEditor - value="Hello World" - onChange={ onChange } - /> + <PostTextEditor value="Hello World" onChange={ onChange } /> ); const textarea = wrapper.root.findByType( Textarea ); textarea.props.onChange( { target: { value: 'Hello Chicken' } } ); wrapper.update( - <PostTextEditor - value="Goodbye World" - onChange={ onChange } - /> + <PostTextEditor value="Goodbye World" onChange={ onChange } /> ); expect( textarea.props.value ).toBe( 'Hello Chicken' ); @@ -63,20 +56,14 @@ describe( 'PostTextEditor', () => { it( 'should render via the state value when edits made, even if prop value changes and state value empty', () => { const onChange = jest.fn(); const wrapper = create( - <PostTextEditor - value="Hello World" - onChange={ onChange } - /> + <PostTextEditor value="Hello World" onChange={ onChange } /> ); const textarea = wrapper.root.findByType( Textarea ); textarea.props.onChange( { target: { value: '' } } ); wrapper.update( - <PostTextEditor - value="Goodbye World" - onChange={ onChange } - /> + <PostTextEditor value="Goodbye World" onChange={ onChange } /> ); expect( textarea.props.value ).toBe( '' ); diff --git a/packages/editor/src/components/post-title/index.js b/packages/editor/src/components/post-title/index.js index ac225c616f019a..e1702076f81463 100644 --- a/packages/editor/src/components/post-title/index.js +++ b/packages/editor/src/components/post-title/index.js @@ -3,7 +3,7 @@ */ import Textarea from 'react-autosize-textarea'; import classnames from 'classnames'; -import { get, escape } from 'lodash'; +import { get } from 'lodash'; /** * WordPress dependencies @@ -91,15 +91,20 @@ class PostTitle extends Component { <div className="editor-post-title"> <div className={ className }> <div> - <label htmlFor={ `post-title-${ instanceId }` } className="screen-reader-text"> + <label + htmlFor={ `post-title-${ instanceId }` } + className="screen-reader-text" + > { decodedPlaceholder || __( 'Add title' ) } </label> <Textarea id={ `post-title-${ instanceId }` } className="editor-post-title__input" - value={ decodeEntities( title ) } + value={ title } onChange={ this.onChange } - placeholder={ decodedPlaceholder || __( 'Add title' ) } + placeholder={ + decodedPlaceholder || __( 'Add title' ) + } onFocus={ this.onSelect } onKeyDown={ this.onKeyDown } onKeyPress={ this.onUnselect } @@ -110,11 +115,16 @@ class PostTitle extends Component { right away, without needing to click anything. */ /* eslint-disable jsx-a11y/no-autofocus */ - autoFocus={ document.body === document.activeElement && isCleanNewPost } + autoFocus={ + document.body === document.activeElement && + isCleanNewPost + } /* eslint-enable jsx-a11y/no-autofocus */ /> </div> - { isSelected && isPostTypeViewable && <PostPermalink /> } + { isSelected && isPostTypeViewable && ( + <PostPermalink /> + ) } </div> </div> </PostTypeSupportCheck> @@ -140,10 +150,9 @@ const applyWithSelect = withSelect( ( select ) => { } ); const applyWithDispatch = withDispatch( ( dispatch ) => { - const { - insertDefaultBlock, - clearSelectedBlock, - } = dispatch( 'core/block-editor' ); + const { insertDefaultBlock, clearSelectedBlock } = dispatch( + 'core/block-editor' + ); const { editPost } = dispatch( 'core/editor' ); return { @@ -151,7 +160,7 @@ const applyWithDispatch = withDispatch( ( dispatch ) => { insertDefaultBlock( undefined, undefined, 0 ); }, onUpdate( title ) { - editPost( { title: escape( title ) } ); + editPost( { title } ); }, clearSelectedBlock, }; diff --git a/packages/editor/src/components/post-title/index.native.js b/packages/editor/src/components/post-title/index.native.js index b2355b52e15b8f..d1b106b2a4d655 100644 --- a/packages/editor/src/components/post-title/index.native.js +++ b/packages/editor/src/components/post-title/index.native.js @@ -8,7 +8,11 @@ import { isEmpty } from 'lodash'; * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { __experimentalRichText as RichText, create, insert } from '@wordpress/rich-text'; +import { + __experimentalRichText as RichText, + create, + insert, +} from '@wordpress/rich-text'; import { decodeEntities } from '@wordpress/html-entities'; import { withDispatch, withSelect } from '@wordpress/data'; import { withFocusOutside } from '@wordpress/components'; @@ -24,7 +28,11 @@ import styles from './style.scss'; class PostTitle extends Component { componentDidUpdate( prevProps ) { // Unselect if any other block is selected - if ( this.props.isSelected && ! prevProps.isAnyBlockSelected && this.props.isAnyBlockSelected ) { + if ( + this.props.isSelected && + ! prevProps.isAnyBlockSelected && + this.props.isAnyBlockSelected + ) { this.props.onUnselect(); } } @@ -67,21 +75,28 @@ class PostTitle extends Component { } = this.props; const decodedPlaceholder = decodeEntities( placeholder ); - const borderColor = this.props.isSelected ? focusedBorderColor : 'transparent'; + const borderColor = this.props.isSelected + ? focusedBorderColor + : 'transparent'; return ( <View - style={ [ styles.titleContainer, borderStyle, { borderColor }, isDimmed && styles.dimmed ] } + style={ [ + styles.titleContainer, + borderStyle, + { borderColor }, + isDimmed && styles.dimmed, + ] } accessible={ ! this.props.isSelected } accessibilityLabel={ - isEmpty( title ) ? - /* translators: accessibility text. empty post title. */ - __( 'Post title. Empty' ) : - sprintf( - /* translators: accessibility text. %s: text content of the post title. */ - __( 'Post title. %s' ), - title - ) + isEmpty( title ) + ? /* translators: accessibility text. empty post title. */ + __( 'Post title. Empty' ) + : sprintf( + /* translators: accessibility text. %s: text content of the post title. */ + __( 'Post title. %s' ), + title + ) } > <RichText @@ -101,13 +116,12 @@ class PostTitle extends Component { onPaste={ this.onPaste } placeholder={ decodedPlaceholder } value={ title } - onSelectionChange={ () => { } } + onSelectionChange={ () => {} } onEnter={ this.props.onEnterPress } disableEditingMenu={ true } __unstableIsSelected={ this.props.isSelected } - __unstableOnCreateUndoLevel={ () => { } } - > - </RichText> + __unstableOnCreateUndoLevel={ () => {} } + ></RichText> </View> ); } @@ -115,11 +129,11 @@ class PostTitle extends Component { export default compose( withSelect( ( select ) => { - const { - isPostTitleSelected, - } = select( 'core/editor' ); + const { isPostTitleSelected } = select( 'core/editor' ); - const { getSelectedBlockClientId, getBlockRootClientId } = select( 'core/block-editor' ); + const { getSelectedBlockClientId, getBlockRootClientId } = select( + 'core/block-editor' + ); const selectedId = getSelectedBlockClientId(); const selectionIsNested = !! getBlockRootClientId( selectedId ); @@ -131,16 +145,13 @@ export default compose( }; } ), withDispatch( ( dispatch ) => { - const { - undo, - redo, - togglePostTitleSelection, - } = dispatch( 'core/editor' ); + const { undo, redo, togglePostTitleSelection } = dispatch( + 'core/editor' + ); - const { - clearSelectedBlock, - insertDefaultBlock, - } = dispatch( 'core/block-editor' ); + const { clearSelectedBlock, insertDefaultBlock } = dispatch( + 'core/block-editor' + ); return { onEnterPress() { diff --git a/packages/editor/src/components/post-type-support-check/test/index.js b/packages/editor/src/components/post-type-support-check/test/index.js index adf75906614069..251cf1a0052225 100644 --- a/packages/editor/src/components/post-type-support-check/test/index.js +++ b/packages/editor/src/components/post-type-support-check/test/index.js @@ -12,9 +12,7 @@ describe( 'PostTypeSupportCheck', () => { it( 'renders its children when post type is not known', () => { let postType; const tree = create( - <PostTypeSupportCheck - postType={ postType } - supportKeys="title"> + <PostTypeSupportCheck postType={ postType } supportKeys="title"> Supported </PostTypeSupportCheck> ); @@ -27,9 +25,7 @@ describe( 'PostTypeSupportCheck', () => { supports: {}, }; const tree = create( - <PostTypeSupportCheck - postType={ postType } - supportKeys="title"> + <PostTypeSupportCheck postType={ postType } supportKeys="title"> Supported </PostTypeSupportCheck> ); @@ -44,9 +40,7 @@ describe( 'PostTypeSupportCheck', () => { }, }; const tree = create( - <PostTypeSupportCheck - postType={ postType } - supportKeys="title"> + <PostTypeSupportCheck postType={ postType } supportKeys="title"> Supported </PostTypeSupportCheck> ); @@ -63,7 +57,8 @@ describe( 'PostTypeSupportCheck', () => { const tree = create( <PostTypeSupportCheck postType={ postType } - supportKeys={ [ 'title', 'thumbnail' ] }> + supportKeys={ [ 'title', 'thumbnail' ] } + > Supported </PostTypeSupportCheck> ); @@ -78,7 +73,8 @@ describe( 'PostTypeSupportCheck', () => { const tree = create( <PostTypeSupportCheck postType={ postType } - supportKeys={ [ 'title', 'thumbnail' ] }> + supportKeys={ [ 'title', 'thumbnail' ] } + > Supported </PostTypeSupportCheck> ); diff --git a/packages/editor/src/components/post-visibility/check.js b/packages/editor/src/components/post-visibility/check.js index 134694b142916b..2b5141a13e90ad 100644 --- a/packages/editor/src/components/post-visibility/check.js +++ b/packages/editor/src/components/post-visibility/check.js @@ -18,7 +18,11 @@ export default compose( [ withSelect( ( select ) => { const { getCurrentPost, getCurrentPostType } = select( 'core/editor' ); return { - hasPublishAction: get( getCurrentPost(), [ '_links', 'wp:action-publish' ], false ), + hasPublishAction: get( + getCurrentPost(), + [ '_links', 'wp:action-publish' ], + false + ), postType: getCurrentPostType(), }; } ), diff --git a/packages/editor/src/components/post-visibility/index.js b/packages/editor/src/components/post-visibility/index.js index 58fc17850f2eea..2003bbd7bfcdcb 100644 --- a/packages/editor/src/components/post-visibility/index.js +++ b/packages/editor/src/components/post-visibility/index.js @@ -33,7 +33,12 @@ export class PostVisibility extends Component { } setPrivate() { - if ( ! window.confirm( __( 'Would you like to privately publish this post now?' ) ) ) { // eslint-disable-line no-alert + if ( + // eslint-disable-next-line no-alert + ! window.confirm( + __( 'Would you like to privately publish this post now?' ) + ) + ) { return; } @@ -47,7 +52,10 @@ export class PostVisibility extends Component { setPasswordProtected() { const { visibility, onUpdateVisibility, status, password } = this.props; - onUpdateVisibility( visibility === 'private' ? 'draft' : status, password || '' ); + onUpdateVisibility( + visibility === 'private' ? 'draft' : status, + password || '' + ); this.setState( { hasPassword: true } ); } @@ -75,12 +83,18 @@ export class PostVisibility extends Component { }; return [ - <fieldset key="visibility-selector" className="editor-post-visibility__dialog-fieldset"> + <fieldset + key="visibility-selector" + className="editor-post-visibility__dialog-fieldset" + > <legend className="editor-post-visibility__dialog-legend"> { __( 'Post Visibility' ) } </legend> { visibilityOptions.map( ( { value, label, info } ) => ( - <div key={ value } className="editor-post-visibility__choice"> + <div + key={ value } + className="editor-post-visibility__choice" + > <input type="radio" name={ `editor-post-visibility__setting-${ instanceId }` } @@ -97,12 +111,22 @@ export class PostVisibility extends Component { > { label } </label> - { <p id={ `editor-post-${ value }-${ instanceId }-description` } className="editor-post-visibility__dialog-info">{ info }</p> } + { + <p + id={ `editor-post-${ value }-${ instanceId }-description` } + className="editor-post-visibility__dialog-info" + > + { info } + </p> + } </div> ) ) } </fieldset>, this.state.hasPassword && ( - <div className="editor-post-visibility__dialog-password" key="password-selector"> + <div + className="editor-post-visibility__dialog-password" + key="password-selector" + > <label htmlFor={ `editor-post-visibility__dialog-password-input-${ instanceId }` } className="screen-reader-text" @@ -125,10 +149,9 @@ export class PostVisibility extends Component { export default compose( [ withSelect( ( select ) => { - const { - getEditedPostAttribute, - getEditedPostVisibility, - } = select( 'core/editor' ); + const { getEditedPostAttribute, getEditedPostVisibility } = select( + 'core/editor' + ); return { status: getEditedPostAttribute( 'status' ), visibility: getEditedPostVisibility(), diff --git a/packages/editor/src/components/post-visibility/label.js b/packages/editor/src/components/post-visibility/label.js index 7c54c3f5b2de85..2e0946547ce98c 100644 --- a/packages/editor/src/components/post-visibility/label.js +++ b/packages/editor/src/components/post-visibility/label.js @@ -14,7 +14,8 @@ import { withSelect } from '@wordpress/data'; import { visibilityOptions } from './utils'; function PostVisibilityLabel( { visibility } ) { - const getVisibilityLabel = () => find( visibilityOptions, { value: visibility } ).label; + const getVisibilityLabel = () => + find( visibilityOptions, { value: visibility } ).label; return getVisibilityLabel( visibility ); } diff --git a/packages/editor/src/components/post-visibility/test/check.js b/packages/editor/src/components/post-visibility/test/check.js index c64e73f6631637..7cdefbba50236a 100644 --- a/packages/editor/src/components/post-visibility/test/check.js +++ b/packages/editor/src/components/post-visibility/test/check.js @@ -12,12 +12,16 @@ describe( 'PostVisibilityCheck', () => { const render = ( { canEdit } ) => ( canEdit ? 'yes' : 'no' ); it( "should not render the edit link if the user doesn't have the right capability", () => { - const wrapper = shallow( <PostVisibilityCheck hasPublishAction={ false } render={ render } /> ); + const wrapper = shallow( + <PostVisibilityCheck hasPublishAction={ false } render={ render } /> + ); expect( wrapper.text() ).toBe( 'no' ); } ); it( 'should render if the user has the correct capability', () => { - const wrapper = shallow( <PostVisibilityCheck hasPublishAction={ true } render={ render } /> ); + const wrapper = shallow( + <PostVisibilityCheck hasPublishAction={ true } render={ render } /> + ); expect( wrapper.text() ).toBe( 'yes' ); } ); } ); diff --git a/packages/editor/src/components/post-visibility/utils.js b/packages/editor/src/components/post-visibility/utils.js index 42268ff062ad2d..76f34703274e18 100644 --- a/packages/editor/src/components/post-visibility/utils.js +++ b/packages/editor/src/components/post-visibility/utils.js @@ -17,6 +17,8 @@ export const visibilityOptions = [ { value: 'password', label: __( 'Password Protected' ), - info: __( 'Protected with a password you choose. Only those with the password can view this post.' ), + info: __( + 'Protected with a password you choose. Only those with the password can view this post.' + ), }, ]; diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index d3a2930080eaca..600ab00a3d4892 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -25,9 +25,7 @@ import { mediaUpload } from '../../utils'; import ReusableBlocksButtons from '../reusable-blocks-buttons'; import ConvertToGroupButtons from '../convert-to-group-buttons'; -const fetchLinkSuggestions = async ( search, { - perPage = 20, -} = {} ) => { +const fetchLinkSuggestions = async ( search, { perPage = 20 } = {} ) => { const posts = await apiFetch( { path: addQueryArgs( '/wp/v2/search', { search, @@ -58,11 +56,17 @@ class EditorProvider extends Component { } props.updatePostLock( props.settings.postLock ); - props.setupEditor( props.post, props.initialEdits, props.settings.template ); + props.setupEditor( + props.post, + props.initialEdits, + props.settings.template + ); if ( props.settings.autosave ) { props.createWarningNotice( - __( 'There is an autosave of this post that is more recent than the version below.' ), + __( + 'There is an autosave of this post that is more recent than the version below.' + ), { id: 'autosave-exists', actions: [ @@ -83,7 +87,7 @@ class EditorProvider extends Component { hasUploadPermissions, canUserUseUnfilteredHTML, undo, - shouldInsertAtTheTop, + shouldInsertAtTheTop ) { return { ...pick( settings, [ @@ -134,7 +138,10 @@ class EditorProvider extends Component { return; } - const updatedStyles = transformStyles( this.props.settings.styles, '.editor-styles-wrapper' ); + const updatedStyles = transformStyles( + this.props.settings.styles, + '.editor-styles-wrapper' + ); map( updatedStyles, ( updatedCSS ) => { if ( updatedCSS ) { @@ -185,12 +192,16 @@ class EditorProvider extends Component { hasUploadPermissions, canUserUseUnfilteredHTML, undo, - isPostTitleSelected, + isPostTitleSelected ); return ( <EntityProvider kind="root" type="site"> - <EntityProvider kind="postType" type={ post.type } id={ post.id }> + <EntityProvider + kind="postType" + type={ post.type } + id={ post.id } + > <BlockEditorProvider value={ blocks } onInput={ resetEditorBlocksWithoutUndoLevel } @@ -231,7 +242,10 @@ export default compose( [ selectionStart: getEditorSelectionStart(), selectionEnd: getEditorSelectionEnd(), reusableBlocks: __experimentalGetReusableBlocks(), - hasUploadPermissions: defaultTo( canUser( 'create', 'media' ), true ), + hasUploadPermissions: defaultTo( + canUser( 'create', 'media' ), + true + ), // This selector is only defined on mobile. isPostTitleSelected: isPostTitleSelected && isPostTitleSelected(), }; diff --git a/packages/editor/src/components/provider/index.native.js b/packages/editor/src/components/provider/index.native.js index 70b6f3e744f5bc..7a38383db74d6b 100644 --- a/packages/editor/src/components/provider/index.native.js +++ b/packages/editor/src/components/provider/index.native.js @@ -13,7 +13,12 @@ import RNReactNativeGutenbergBridge, { * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { parse, serialize, getUnregisteredTypeHandlerName, createBlock } from '@wordpress/blocks'; +import { + parse, + serialize, + getUnregisteredTypeHandlerName, + createBlock, +} from '@wordpress/blocks'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { doAction } from '@wordpress/hooks'; @@ -46,7 +51,11 @@ class NativeEditorProvider extends Component { // Keep a local reference to `post` to detect changes this.post = this.props.post; this.props.addEntities( postTypeEntities ); - this.props.receiveEntityRecords( 'postType', this.post.type, this.post ); + this.props.receiveEntityRecords( + 'postType', + this.post.type, + this.post + ); } componentDidMount() { @@ -54,30 +63,39 @@ class NativeEditorProvider extends Component { this.serializeToNativeAction(); } ); - this.subscriptionParentToggleHTMLMode = subscribeParentToggleHTMLMode( () => { - this.toggleMode(); - } ); + this.subscriptionParentToggleHTMLMode = subscribeParentToggleHTMLMode( + () => { + this.toggleMode(); + } + ); this.subscriptionParentSetTitle = subscribeSetTitle( ( payload ) => { this.props.editTitle( payload.title ); } ); - this.subscriptionParentUpdateHtml = subscribeUpdateHtml( ( payload ) => { - this.updateHtmlAction( payload.html ); - } ); + this.subscriptionParentUpdateHtml = subscribeUpdateHtml( + ( payload ) => { + this.updateHtmlAction( payload.html ); + } + ); - this.subscriptionParentMediaAppend = subscribeMediaAppend( ( payload ) => { - const blockName = 'core/' + payload.mediaType; - const newBlock = createBlock( blockName, { - id: payload.mediaId, - [ payload.mediaType === 'image' ? 'url' : 'src' ]: payload.mediaUrl, - } ); + this.subscriptionParentMediaAppend = subscribeMediaAppend( + ( payload ) => { + const blockName = 'core/' + payload.mediaType; + const newBlock = createBlock( blockName, { + id: payload.mediaId, + [ payload.mediaType === 'image' + ? 'url' + : 'src' ]: payload.mediaUrl, + } ); - const indexAfterSelected = this.props.selectedBlockIndex + 1; - const insertionIndex = indexAfterSelected || this.props.blockCount; + const indexAfterSelected = this.props.selectedBlockIndex + 1; + const insertionIndex = + indexAfterSelected || this.props.blockCount; - this.props.insertBlock( newBlock, insertionIndex ); - } ); + this.props.insertBlock( newBlock, insertionIndex ); + } + ); } componentWillUnmount() { @@ -105,9 +123,14 @@ class NativeEditorProvider extends Component { componentDidUpdate( prevProps ) { if ( ! prevProps.isReady && this.props.isReady ) { const blocks = this.props.blocks; - const isUnsupportedBlock = ( { name } ) => name === getUnregisteredTypeHandlerName(); - const unsupportedBlockNames = blocks.filter( isUnsupportedBlock ).map( ( block ) => block.attributes.originalName ); - RNReactNativeGutenbergBridge.editorDidMount( unsupportedBlockNames ); + const isUnsupportedBlock = ( { name } ) => + name === getUnregisteredTypeHandlerName(); + const unsupportedBlockNames = blocks + .filter( isUnsupportedBlock ) + .map( ( block ) => block.attributes.originalName ); + RNReactNativeGutenbergBridge.editorDidMount( + unsupportedBlockNames + ); } } @@ -121,9 +144,14 @@ class NativeEditorProvider extends Component { const html = serialize( this.props.blocks ); const title = this.props.title; - const hasChanges = title !== this.post.title.raw || html !== this.post.content.raw; + const hasChanges = + title !== this.post.title.raw || html !== this.post.content.raw; - RNReactNativeGutenbergBridge.provideToNative_Html( html, title, hasChanges ); + RNReactNativeGutenbergBridge.provideToNative_Html( + html, + title, + hasChanges + ); if ( hasChanges ) { this.post.title.raw = title; @@ -168,9 +196,7 @@ export default compose( [ getEditedPostAttribute, getEditedPostContent, } = select( 'core/editor' ); - const { - getEditorMode, - } = select( 'core/edit-post' ); + const { getEditorMode } = select( 'core/edit-post' ); const { getBlockCount, @@ -190,21 +216,12 @@ export default compose( [ }; } ), withDispatch( ( dispatch ) => { - const { - editPost, - resetEditorBlocks, - } = dispatch( 'core/editor' ); - const { - clearSelectedBlock, - insertBlock, - } = dispatch( 'core/block-editor' ); - const { - switchEditorMode, - } = dispatch( 'core/edit-post' ); - const { - addEntities, - receiveEntityRecords, - } = dispatch( 'core' ); + const { editPost, resetEditorBlocks } = dispatch( 'core/editor' ); + const { clearSelectedBlock, insertBlock } = dispatch( + 'core/block-editor' + ); + const { switchEditorMode } = dispatch( 'core/edit-post' ); + const { addEntities, receiveEntityRecords } = dispatch( 'core' ); return { addEntities, diff --git a/packages/editor/src/components/provider/with-registry-provider.js b/packages/editor/src/components/provider/with-registry-provider.js index 367782a82b4a42..62624d7c33e53c 100644 --- a/packages/editor/src/components/provider/with-registry-provider.js +++ b/packages/editor/src/components/provider/with-registry-provider.js @@ -2,7 +2,11 @@ * WordPress dependencies */ import { useState, useEffect } from '@wordpress/element'; -import { withRegistry, createRegistry, RegistryProvider } from '@wordpress/data'; +import { + withRegistry, + createRegistry, + RegistryProvider, +} from '@wordpress/data'; import { createHigherOrderComponent } from '@wordpress/compose'; import { storeConfig as blockEditorStoreConfig } from '@wordpress/block-editor'; @@ -13,33 +17,44 @@ import { storeConfig } from '../../store'; import applyMiddlewares from '../../store/middlewares'; const withRegistryProvider = createHigherOrderComponent( - ( WrappedComponent ) => withRegistry( ( props ) => { - const { useSubRegistry = true, registry, ...additionalProps } = props; - if ( ! useSubRegistry ) { - return <WrappedComponent { ...additionalProps } />; - } + ( WrappedComponent ) => + withRegistry( ( props ) => { + const { + useSubRegistry = true, + registry, + ...additionalProps + } = props; + if ( ! useSubRegistry ) { + return <WrappedComponent { ...additionalProps } />; + } - const [ subRegistry, setSubRegistry ] = useState( null ); - useEffect( () => { - const newRegistry = createRegistry( { - 'core/block-editor': blockEditorStoreConfig, - }, registry ); - const store = newRegistry.registerStore( 'core/editor', storeConfig ); - // This should be removed after the refactoring of the effects to controls. - applyMiddlewares( store ); - setSubRegistry( newRegistry ); - }, [ registry ] ); + const [ subRegistry, setSubRegistry ] = useState( null ); + useEffect( () => { + const newRegistry = createRegistry( + { + 'core/block-editor': blockEditorStoreConfig, + }, + registry + ); + const store = newRegistry.registerStore( + 'core/editor', + storeConfig + ); + // This should be removed after the refactoring of the effects to controls. + applyMiddlewares( store ); + setSubRegistry( newRegistry ); + }, [ registry ] ); - if ( ! subRegistry ) { - return null; - } + if ( ! subRegistry ) { + return null; + } - return ( - <RegistryProvider value={ subRegistry }> - <WrappedComponent { ...additionalProps } /> - </RegistryProvider> - ); - } ), + return ( + <RegistryProvider value={ subRegistry }> + <WrappedComponent { ...additionalProps } /> + </RegistryProvider> + ); + } ), 'withRegistryProvider' ); diff --git a/packages/editor/src/components/reusable-blocks-buttons/index.native.js b/packages/editor/src/components/reusable-blocks-buttons/index.native.js index bd0c2f440d06f2..461f67a0a4bcbe 100644 --- a/packages/editor/src/components/reusable-blocks-buttons/index.native.js +++ b/packages/editor/src/components/reusable-blocks-buttons/index.native.js @@ -1,2 +1 @@ - export default () => null; diff --git a/packages/editor/src/components/reusable-blocks-buttons/reusable-block-convert-button.js b/packages/editor/src/components/reusable-blocks-buttons/reusable-block-convert-button.js index 9ef9d4bb96a7eb..c00a7941945cd2 100644 --- a/packages/editor/src/components/reusable-blocks-buttons/reusable-block-convert-button.js +++ b/packages/editor/src/components/reusable-blocks-buttons/reusable-block-convert-button.js @@ -33,10 +33,7 @@ export function ReusableBlockConvertButton( { </MenuItem> ) } { isReusable && ( - <MenuItem - icon="controls-repeat" - onClick={ onConvertToStatic } - > + <MenuItem icon="controls-repeat" onClick={ onConvertToStatic }> { __( 'Convert to Regular Block' ) } </MenuItem> ) } @@ -46,43 +43,39 @@ export function ReusableBlockConvertButton( { export default compose( [ withSelect( ( select, { clientIds } ) => { - const { - getBlocksByClientId, - canInsertBlockType, - } = select( 'core/block-editor' ); - const { - __experimentalGetReusableBlock: getReusableBlock, - } = select( 'core/editor' ); + const { getBlocksByClientId, canInsertBlockType } = select( + 'core/block-editor' + ); + const { __experimentalGetReusableBlock: getReusableBlock } = select( + 'core/editor' + ); const { canUser } = select( 'core' ); const blocks = getBlocksByClientId( clientIds ); - const isReusable = ( + const isReusable = blocks.length === 1 && blocks[ 0 ] && isReusableBlock( blocks[ 0 ] ) && - !! getReusableBlock( blocks[ 0 ].attributes.ref ) - ); + !! getReusableBlock( blocks[ 0 ].attributes.ref ); // Show 'Convert to Regular Block' when selected block is a reusable block - const isVisible = isReusable || ( + const isVisible = + isReusable || // Hide 'Add to Reusable blocks' when reusable blocks are disabled - canInsertBlockType( 'core/block' ) && - - every( blocks, ( block ) => ( - // Guard against the case where a regular block has *just* been converted - !! block && - - // Hide 'Add to Reusable blocks' on invalid blocks - block.isValid && - - // Hide 'Add to Reusable blocks' when block doesn't support being made reusable - hasBlockSupport( block.name, 'reusable', true ) - ) ) && - - // Hide 'Add to Reusable blocks' when current doesn't have permission to do that - !! canUser( 'create', 'blocks' ) - ); + ( canInsertBlockType( 'core/block' ) && + every( + blocks, + ( block ) => + // Guard against the case where a regular block has *just* been converted + !! block && + // Hide 'Add to Reusable blocks' on invalid blocks + block.isValid && + // Hide 'Add to Reusable blocks' when block doesn't support being made reusable + hasBlockSupport( block.name, 'reusable', true ) + ) && + // Hide 'Add to Reusable blocks' when current doesn't have permission to do that + !! canUser( 'create', 'blocks' ) ); return { isReusable, diff --git a/packages/editor/src/components/reusable-blocks-buttons/reusable-block-delete-button.js b/packages/editor/src/components/reusable-blocks-buttons/reusable-block-delete-button.js index 34ec4987e16ee8..df3bdfa2b5f6b9 100644 --- a/packages/editor/src/components/reusable-blocks-buttons/reusable-block-delete-button.js +++ b/packages/editor/src/components/reusable-blocks-buttons/reusable-block-delete-button.js @@ -12,7 +12,11 @@ import { __ } from '@wordpress/i18n'; import { isReusableBlock } from '@wordpress/blocks'; import { withSelect, withDispatch } from '@wordpress/data'; -export function ReusableBlockDeleteButton( { isVisible, isDisabled, onDelete } ) { +export function ReusableBlockDeleteButton( { + isVisible, + isDisabled, + onDelete, +} ) { if ( ! isVisible ) { return null; } @@ -32,15 +36,20 @@ export default compose( [ withSelect( ( select, { clientId } ) => { const { getBlock } = select( 'core/block-editor' ); const { canUser } = select( 'core' ); - const { __experimentalGetReusableBlock: getReusableBlock } = select( 'core/editor' ); + const { __experimentalGetReusableBlock: getReusableBlock } = select( + 'core/editor' + ); const block = getBlock( clientId ); - const reusableBlock = block && isReusableBlock( block ) ? - getReusableBlock( block.attributes.ref ) : - null; + const reusableBlock = + block && isReusableBlock( block ) + ? getReusableBlock( block.attributes.ref ) + : null; return { - isVisible: !! reusableBlock && !! canUser( 'delete', 'blocks', reusableBlock.id ), + isVisible: + !! reusableBlock && + !! canUser( 'delete', 'blocks', reusableBlock.id ), isDisabled: reusableBlock && reusableBlock.isTemporary, }; } ), @@ -54,10 +63,12 @@ export default compose( [ onDelete() { // TODO: Make this a <Confirm /> component or similar // eslint-disable-next-line no-alert - const hasConfirmed = window.confirm( __( - 'Are you sure you want to delete this Reusable Block?\n\n' + - 'It will be permanently removed from all posts and pages that use it.' - ) ); + const hasConfirmed = window.confirm( + __( + 'Are you sure you want to delete this Reusable Block?\n\n' + + 'It will be permanently removed from all posts and pages that use it.' + ) + ); if ( hasConfirmed ) { const block = getBlock( clientId ); diff --git a/packages/editor/src/components/table-of-contents/index.js b/packages/editor/src/components/table-of-contents/index.js index c41f0c899c3785..b34f0daaf0f97f 100644 --- a/packages/editor/src/components/table-of-contents/index.js +++ b/packages/editor/src/components/table-of-contents/index.js @@ -26,7 +26,12 @@ function TableOfContents( { hasBlocks, hasOutlineItemsDisabled } ) { aria-disabled={ ! hasBlocks } /> ) } - renderContent={ ( { onClose } ) => <TableOfContentsPanel onRequestClose={ onClose } hasOutlineItemsDisabled={ hasOutlineItemsDisabled } /> } + renderContent={ ( { onClose } ) => ( + <TableOfContentsPanel + onRequestClose={ onClose } + hasOutlineItemsDisabled={ hasOutlineItemsDisabled } + /> + ) } /> ); } diff --git a/packages/editor/src/components/table-of-contents/panel.js b/packages/editor/src/components/table-of-contents/panel.js index cd88216810d1ee..355d16c46ccfce 100644 --- a/packages/editor/src/components/table-of-contents/panel.js +++ b/packages/editor/src/components/table-of-contents/panel.js @@ -10,12 +10,18 @@ import { withSelect } from '@wordpress/data'; import WordCount from '../word-count'; import DocumentOutline from '../document-outline'; -function TableOfContentsPanel( { headingCount, paragraphCount, numberOfBlocks, hasOutlineItemsDisabled, onRequestClose } ) { +function TableOfContentsPanel( { + headingCount, + paragraphCount, + numberOfBlocks, + hasOutlineItemsDisabled, + onRequestClose, +} ) { return ( /* - * Disable reason: The `list` ARIA role is redundant but - * Safari+VoiceOver won't announce the list otherwise. - */ + * Disable reason: The `list` ARIA role is redundant but + * Safari+VoiceOver won't announce the list otherwise. + */ /* eslint-disable jsx-a11y/no-redundant-roles */ <> <div @@ -24,10 +30,7 @@ function TableOfContentsPanel( { headingCount, paragraphCount, numberOfBlocks, h aria-label={ __( 'Document Statistics' ) } tabIndex="0" > - <ul - role="list" - className="table-of-contents__counts" - > + <ul role="list" className="table-of-contents__counts"> <li className="table-of-contents__count"> { __( 'Words' ) } <WordCount /> @@ -58,7 +61,10 @@ function TableOfContentsPanel( { headingCount, paragraphCount, numberOfBlocks, h <h2 className="table-of-contents__title"> { __( 'Document Outline' ) } </h2> - <DocumentOutline onSelect={ onRequestClose } hasOutlineItemsDisabled={ hasOutlineItemsDisabled } /> + <DocumentOutline + onSelect={ onRequestClose } + hasOutlineItemsDisabled={ hasOutlineItemsDisabled } + /> </> ) } </> diff --git a/packages/editor/src/components/template-validation-notice/index.js b/packages/editor/src/components/template-validation-notice/index.js index 2efa8eb3a0abd7..a71833466abea8 100644 --- a/packages/editor/src/components/template-validation-notice/index.js +++ b/packages/editor/src/components/template-validation-notice/index.js @@ -12,18 +12,36 @@ function TemplateValidationNotice( { isValid, ...props } ) { } const confirmSynchronization = () => { - // eslint-disable-next-line no-alert - if ( window.confirm( __( 'Resetting the template may result in loss of content, do you want to continue?' ) ) ) { + if ( + // eslint-disable-next-line no-alert + window.confirm( + __( + 'Resetting the template may result in loss of content, do you want to continue?' + ) + ) + ) { props.synchronizeTemplate(); } }; return ( - <Notice className="editor-template-validation-notice" isDismissible={ false } status="warning"> - <p>{ __( 'The content of your post doesn’t match the template assigned to your post type.' ) }</p> + <Notice + className="editor-template-validation-notice" + isDismissible={ false } + status="warning" + > + <p> + { __( + 'The content of your post doesn’t match the template assigned to your post type.' + ) } + </p> <div> - <Button isSecondary onClick={ props.resetTemplateValidity }>{ __( 'Keep it as is' ) }</Button> - <Button onClick={ confirmSynchronization } isPrimary>{ __( 'Reset the template' ) }</Button> + <Button isSecondary onClick={ props.resetTemplateValidity }> + { __( 'Keep it as is' ) } + </Button> + <Button onClick={ confirmSynchronization } isPrimary> + { __( 'Reset the template' ) } + </Button> </div> </Notice> ); @@ -34,7 +52,9 @@ export default compose( [ isValid: select( 'core/block-editor' ).isValidTemplate(), } ) ), withDispatch( ( dispatch ) => { - const { setTemplateValidity, synchronizeTemplate } = dispatch( 'core/block-editor' ); + const { setTemplateValidity, synchronizeTemplate } = dispatch( + 'core/block-editor' + ); return { resetTemplateValidity: () => setTemplateValidity( true ), synchronizeTemplate, diff --git a/packages/editor/src/components/theme-support-check/index.js b/packages/editor/src/components/theme-support-check/index.js index d06acccbb6f91a..c705ad15e78728 100644 --- a/packages/editor/src/components/theme-support-check/index.js +++ b/packages/editor/src/components/theme-support-check/index.js @@ -1,33 +1,30 @@ /** * External dependencies */ -import { - castArray, - includes, - isArray, - get, - some, -} from 'lodash'; +import { castArray, includes, isArray, get, some } from 'lodash'; /** * WordPress dependencies */ import { withSelect } from '@wordpress/data'; -export function ThemeSupportCheck( { themeSupports, children, postType, supportKeys } ) { - const isSupported = some( - castArray( supportKeys ), ( key ) => { - const supported = get( themeSupports, [ key ], false ); - // 'post-thumbnails' can be boolean or an array of post types. - // In the latter case, we need to verify `postType` exists - // within `supported`. If `postType` isn't passed, then the check - // should fail. - if ( 'post-thumbnails' === key && isArray( supported ) ) { - return includes( supported, postType ); - } - return supported; +export function ThemeSupportCheck( { + themeSupports, + children, + postType, + supportKeys, +} ) { + const isSupported = some( castArray( supportKeys ), ( key ) => { + const supported = get( themeSupports, [ key ], false ); + // 'post-thumbnails' can be boolean or an array of post types. + // In the latter case, we need to verify `postType` exists + // within `supported`. If `postType` isn't passed, then the check + // should fail. + if ( 'post-thumbnails' === key && isArray( supported ) ) { + return includes( supported, postType ); } - ); + return supported; + } ); if ( ! isSupported ) { return null; diff --git a/packages/editor/src/components/theme-support-check/test/index.js b/packages/editor/src/components/theme-support-check/test/index.js index 0d1d2d9df13a74..6edd77333f519b 100644 --- a/packages/editor/src/components/theme-support-check/test/index.js +++ b/packages/editor/src/components/theme-support-check/test/index.js @@ -10,7 +10,9 @@ import { ThemeSupportCheck } from '../index'; describe( 'ThemeSupportCheck', () => { it( "should not render if there's no support check provided", () => { - const wrapper = shallow( <ThemeSupportCheck>foobar</ThemeSupportCheck> ); + const wrapper = shallow( + <ThemeSupportCheck>foobar</ThemeSupportCheck> + ); expect( wrapper.type() ).toBe( null ); } ); @@ -19,9 +21,14 @@ describe( 'ThemeSupportCheck', () => { 'post-thumbnails': true, }; const supportKeys = 'post-thumbnails'; - const wrapper = shallow( <ThemeSupportCheck - supportKeys={ supportKeys } - themeSupports={ themeSupports }>foobar</ThemeSupportCheck> ); + const wrapper = shallow( + <ThemeSupportCheck + supportKeys={ supportKeys } + themeSupports={ themeSupports } + > + foobar + </ThemeSupportCheck> + ); expect( wrapper.type() ).not.toBe( null ); } ); @@ -30,10 +37,15 @@ describe( 'ThemeSupportCheck', () => { 'post-thumbnails': [ 'post' ], }; const supportKeys = 'post-thumbnails'; - const wrapper = shallow( <ThemeSupportCheck - supportKeys={ supportKeys } - postType={ 'post' } - themeSupports={ themeSupports }>foobar</ThemeSupportCheck> ); + const wrapper = shallow( + <ThemeSupportCheck + supportKeys={ supportKeys } + postType={ 'post' } + themeSupports={ themeSupports } + > + foobar + </ThemeSupportCheck> + ); expect( wrapper.type() ).not.toBe( null ); } ); @@ -42,10 +54,15 @@ describe( 'ThemeSupportCheck', () => { 'post-thumbnails': [ 'post' ], }; const supportKeys = 'post-thumbnails'; - const wrapper = shallow( <ThemeSupportCheck - supportKeys={ supportKeys } - postType={ 'page' } - themeSupports={ themeSupports }>foobar</ThemeSupportCheck> ); + const wrapper = shallow( + <ThemeSupportCheck + supportKeys={ supportKeys } + postType={ 'page' } + themeSupports={ themeSupports } + > + foobar + </ThemeSupportCheck> + ); expect( wrapper.type() ).toBe( null ); } ); @@ -54,10 +71,15 @@ describe( 'ThemeSupportCheck', () => { 'post-thumbnails': [ 'post' ], }; const supportKeys = 'post-thumbnails'; - const wrapper = shallow( <ThemeSupportCheck - supportKeys={ supportKeys } - postType={ false } - themeSupports={ themeSupports }>foobar</ThemeSupportCheck> ); + const wrapper = shallow( + <ThemeSupportCheck + supportKeys={ supportKeys } + postType={ false } + themeSupports={ themeSupports } + > + foobar + </ThemeSupportCheck> + ); expect( wrapper.type() ).toBe( null ); } ); @@ -66,9 +88,14 @@ describe( 'ThemeSupportCheck', () => { 'post-thumbnails': false, }; const supportKeys = 'post-thumbnails'; - const wrapper = shallow( <ThemeSupportCheck - supportKeys={ supportKeys } - themeSupports={ themeSupports }>foobar</ThemeSupportCheck> ); + const wrapper = shallow( + <ThemeSupportCheck + supportKeys={ supportKeys } + themeSupports={ themeSupports } + > + foobar + </ThemeSupportCheck> + ); expect( wrapper.type() ).toBe( null ); } ); } ); diff --git a/packages/editor/src/components/unsaved-changes-warning/index.js b/packages/editor/src/components/unsaved-changes-warning/index.js index 66cd294bd61fbe..4a39c28e881719 100644 --- a/packages/editor/src/components/unsaved-changes-warning/index.js +++ b/packages/editor/src/components/unsaved-changes-warning/index.js @@ -30,7 +30,9 @@ class UnsavedChangesWarning extends Component { const { isEditedPostDirty } = this.props; if ( isEditedPostDirty() ) { - event.returnValue = __( 'You have unsaved changes. If you proceed, they will be lost.' ); + event.returnValue = __( + 'You have unsaved changes. If you proceed, they will be lost.' + ); return event.returnValue; } } diff --git a/packages/editor/src/components/word-count/index.js b/packages/editor/src/components/word-count/index.js index 719351ba323829..07fc62cac05838 100644 --- a/packages/editor/src/components/word-count/index.js +++ b/packages/editor/src/components/word-count/index.js @@ -14,7 +14,9 @@ function WordCount( { content } ) { const wordCountType = _x( 'words', 'Word count type. Do not translate!' ); return ( - <span className="word-count">{ wordCount( content, wordCountType ) }</span> + <span className="word-count"> + { wordCount( content, wordCountType ) } + </span> ); } diff --git a/packages/editor/src/hooks/custom-sources-backwards-compatibility.js b/packages/editor/src/hooks/custom-sources-backwards-compatibility.js index ff52b478f105f0..8c91cd625da62e 100644 --- a/packages/editor/src/hooks/custom-sources-backwards-compatibility.js +++ b/packages/editor/src/hooks/custom-sources-backwards-compatibility.js @@ -34,44 +34,59 @@ import { addFilter } from '@wordpress/hooks'; * * @return {WPHigherOrderComponent} Higher-order component. */ -const createWithMetaAttributeSource = ( metaAttributes ) => createHigherOrderComponent( - ( BlockEdit ) => ( { attributes, setAttributes, ...props } ) => { - const postType = useSelect( ( select ) => select( 'core/editor' ).getCurrentPostType(), [] ); - const [ meta, setMeta ] = useEntityProp( 'postType', postType, 'meta' ); +const createWithMetaAttributeSource = ( metaAttributes ) => + createHigherOrderComponent( + ( BlockEdit ) => ( { attributes, setAttributes, ...props } ) => { + const postType = useSelect( + ( select ) => select( 'core/editor' ).getCurrentPostType(), + [] + ); + const [ meta, setMeta ] = useEntityProp( + 'postType', + postType, + 'meta' + ); - const mergedAttributes = useMemo( - () => ( { - ...attributes, - ...mapValues( metaAttributes, ( metaKey ) => meta[ metaKey ] ), - } ), - [ attributes, meta ] - ); + const mergedAttributes = useMemo( + () => ( { + ...attributes, + ...mapValues( + metaAttributes, + ( metaKey ) => meta[ metaKey ] + ), + } ), + [ attributes, meta ] + ); - return ( - <BlockEdit - attributes={ mergedAttributes } - setAttributes={ ( nextAttributes ) => { - const nextMeta = mapKeys( - // Filter to intersection of keys between the updated - // attributes and those with an associated meta key. - pickBy( nextAttributes, ( value, key ) => metaAttributes[ key ] ), + return ( + <BlockEdit + attributes={ mergedAttributes } + setAttributes={ ( nextAttributes ) => { + const nextMeta = mapKeys( + // Filter to intersection of keys between the updated + // attributes and those with an associated meta key. + pickBy( + nextAttributes, + ( value, key ) => metaAttributes[ key ] + ), - // Rename the keys to the expected meta key name. - ( value, attributeKey ) => metaAttributes[ attributeKey ], - ); + // Rename the keys to the expected meta key name. + ( value, attributeKey ) => + metaAttributes[ attributeKey ] + ); - if ( ! isEmpty( nextMeta ) ) { - setMeta( nextMeta ); - } + if ( ! isEmpty( nextMeta ) ) { + setMeta( nextMeta ); + } - setAttributes( nextAttributes ); - } } - { ...props } - /> - ); - }, - 'withMetaAttributeSource' -); + setAttributes( nextAttributes ); + } } + { ...props } + /> + ); + }, + 'withMetaAttributeSource' + ); /** * Filters a registered block's settings to enhance a block's `edit` component @@ -83,9 +98,14 @@ const createWithMetaAttributeSource = ( metaAttributes ) => createHigherOrderCom */ function shimAttributeSource( settings ) { /** @type {WPMetaAttributeMapping} */ - const metaAttributes = mapValues( pickBy( settings.attributes, { source: 'meta' } ), 'meta' ); + const metaAttributes = mapValues( + pickBy( settings.attributes, { source: 'meta' } ), + 'meta' + ); if ( ! isEmpty( metaAttributes ) ) { - settings.edit = createWithMetaAttributeSource( metaAttributes )( settings.edit ); + settings.edit = createWithMetaAttributeSource( metaAttributes )( + settings.edit + ); } return settings; diff --git a/packages/editor/src/hooks/test/default-autocompleters.js b/packages/editor/src/hooks/test/default-autocompleters.js index 54c9d4df482a2a..347210887c148b 100644 --- a/packages/editor/src/hooks/test/default-autocompleters.js +++ b/packages/editor/src/hooks/test/default-autocompleters.js @@ -14,7 +14,11 @@ describe( 'default-autocompleters', () => { const defaultAutocompleters = [ userAutocompleter ]; it( 'provides default completers if none are provided', () => { - const result = applyFilters( 'editor.Autocomplete.completers', undefined, BLOCK_NAME ); + const result = applyFilters( + 'editor.Autocomplete.completers', + undefined, + BLOCK_NAME + ); /* * Assert structural equality because defaults are provided as a * list of cloned completers (and not referentially equal). @@ -24,20 +28,32 @@ describe( 'default-autocompleters', () => { it( 'does not provide default completers for empty completer list', () => { const emptyList = []; - const result = applyFilters( 'editor.Autocomplete.completers', emptyList, BLOCK_NAME ); + const result = applyFilters( + 'editor.Autocomplete.completers', + emptyList, + BLOCK_NAME + ); // Assert referential equality because the list should be unchanged. expect( result ).toBe( emptyList ); } ); it( 'does not provide default completers for a populated completer list', () => { const populatedList = [ {}, {} ]; - const result = applyFilters( 'editor.Autocomplete.completers', populatedList, BLOCK_NAME ); + const result = applyFilters( + 'editor.Autocomplete.completers', + populatedList, + BLOCK_NAME + ); // Assert referential equality because the list should be unchanged. expect( result ).toBe( populatedList ); } ); it( 'provides copies of defaults so they may be directly modified', () => { - const result = applyFilters( 'editor.Autocomplete.completers', undefined, BLOCK_NAME ); + const result = applyFilters( + 'editor.Autocomplete.completers', + undefined, + BLOCK_NAME + ); result.forEach( ( completer, i ) => { const defaultCompleter = defaultAutocompleters[ i ]; expect( completer ).not.toBe( defaultCompleter ); diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 82a91c3617a4d2..453a606c86206d 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -59,13 +59,16 @@ export function* setupEditor( post, edits, template ) { edits, template, }; - yield resetEditorBlocks( blocks, { __unstableShouldCreateUndoLevel: false } ); + yield resetEditorBlocks( blocks, { + __unstableShouldCreateUndoLevel: false, + } ); yield setupEditorState( post ); if ( edits && Object.keys( edits ).some( ( key ) => - edits[ key ] !== ( has( post, [ key, 'raw' ] ) ? post[ key ].raw : post[ key ] ) + edits[ key ] !== + ( has( post, [ key, 'raw' ] ) ? post[ key ].raw : post[ key ] ) ) ) { yield editPost( edits ); @@ -284,7 +287,10 @@ export function* savePost( options = {} ) { // Make sure that any edits after saving create an undo level and are // considered for change detection. if ( ! options.isAutosave ) { - yield dispatch( 'core/block-editor', '__unstableMarkLastChangeAsPersistent' ); + yield dispatch( + 'core/block-editor', + '__unstableMarkLastChangeAsPersistent' + ); } } } @@ -293,73 +299,39 @@ export function* savePost( options = {} ) { * Action generator for handling refreshing the current post. */ export function* refreshPost() { - const post = yield select( - STORE_KEY, - 'getCurrentPost' - ); - const postTypeSlug = yield select( - STORE_KEY, - 'getCurrentPostType' - ); - const postType = yield select( - 'core', - 'getPostType', - postTypeSlug - ); - const newPost = yield apiFetch( - { - // Timestamp arg allows caller to bypass browser caching, which is - // expected for this specific function. - path: `/wp/v2/${ postType.rest_base }/${ post.id }` + - `?context=edit&_timestamp=${ Date.now() }`, - } - ); - yield dispatch( - STORE_KEY, - 'resetPost', - newPost - ); + const post = yield select( STORE_KEY, 'getCurrentPost' ); + const postTypeSlug = yield select( STORE_KEY, 'getCurrentPostType' ); + const postType = yield select( 'core', 'getPostType', postTypeSlug ); + const newPost = yield apiFetch( { + // Timestamp arg allows caller to bypass browser caching, which is + // expected for this specific function. + path: + `/wp/v2/${ postType.rest_base }/${ post.id }` + + `?context=edit&_timestamp=${ Date.now() }`, + } ); + yield dispatch( STORE_KEY, 'resetPost', newPost ); } /** * Action generator for trashing the current post in the editor. */ export function* trashPost() { - const postTypeSlug = yield select( - STORE_KEY, - 'getCurrentPostType' - ); - const postType = yield select( - 'core', - 'getPostType', - postTypeSlug - ); - yield dispatch( - 'core/notices', - 'removeNotice', - TRASH_POST_NOTICE_ID - ); + const postTypeSlug = yield select( STORE_KEY, 'getCurrentPostType' ); + const postType = yield select( 'core', 'getPostType', postTypeSlug ); + yield dispatch( 'core/notices', 'removeNotice', TRASH_POST_NOTICE_ID ); try { - const post = yield select( - STORE_KEY, - 'getCurrentPost' - ); - yield apiFetch( - { - path: `/wp/v2/${ postType.rest_base }/${ post.id }`, - method: 'DELETE', - } - ); + const post = yield select( STORE_KEY, 'getCurrentPost' ); + yield apiFetch( { + path: `/wp/v2/${ postType.rest_base }/${ post.id }`, + method: 'DELETE', + } ); - yield dispatch( - STORE_KEY, - 'savePost' - ); + yield dispatch( STORE_KEY, 'savePost' ); } catch ( error ) { yield dispatch( 'core/notices', 'createErrorNotice', - ...getNotificationArgumentsForTrashFail( { error } ), + ...getNotificationArgumentsForTrashFail( { error } ) ); } } @@ -370,18 +342,22 @@ export function* trashPost() { * @param {Object?} options Extra flags to identify the autosave. */ export function* autosave( options ) { - yield dispatch( - STORE_KEY, - 'savePost', - { isAutosave: true, ...options } - ); + yield dispatch( STORE_KEY, 'savePost', { isAutosave: true, ...options } ); } export function* __experimentalLocalAutosave() { const post = yield select( STORE_KEY, 'getCurrentPost' ); const title = yield select( STORE_KEY, 'getEditedPostAttribute', 'title' ); - const content = yield select( STORE_KEY, 'getEditedPostAttribute', 'content' ); - const excerpt = yield select( STORE_KEY, 'getEditedPostAttribute', 'excerpt' ); + const content = yield select( + STORE_KEY, + 'getEditedPostAttribute', + 'content' + ); + const excerpt = yield select( + STORE_KEY, + 'getEditedPostAttribute', + 'excerpt' + ); yield { type: 'LOCAL_AUTOSAVE_SET', postId: post.id, @@ -696,8 +672,13 @@ export function* resetEditorBlocks( blocks, options = {} ) { if ( __unstableShouldCreateUndoLevel !== false ) { const { id, type } = yield select( STORE_KEY, 'getCurrentPost' ); const noChange = - ( yield select( 'core', 'getEditedEntityRecord', 'postType', type, id ) ) - .blocks === edits.blocks; + ( yield select( + 'core', + 'getEditedEntityRecord', + 'postType', + type, + id + ) ).blocks === edits.blocks; if ( noChange ) { return yield dispatch( 'core', @@ -735,12 +716,14 @@ export function updateEditorSettings( settings ) { * Backward compatibility */ -const getBlockEditorAction = ( name ) => function* ( ...args ) { - deprecated( '`wp.data.dispatch( \'core/editor\' ).' + name + '`', { - alternative: '`wp.data.dispatch( \'core/block-editor\' ).' + name + '`', - } ); - yield dispatch( 'core/block-editor', name, ...args ); -}; +const getBlockEditorAction = ( name ) => + function*( ...args ) { + deprecated( "`wp.data.dispatch( 'core/editor' )." + name + '`', { + alternative: + "`wp.data.dispatch( 'core/block-editor' )." + name + '`', + } ); + yield dispatch( 'core/block-editor', name, ...args ); + }; /** * @see resetBlocks in core/block-editor store. @@ -760,7 +743,9 @@ export const updateBlock = getBlockEditorAction( 'updateBlock' ); /** * @see updateBlockAttributes in core/block-editor store. */ -export const updateBlockAttributes = getBlockEditorAction( 'updateBlockAttributes' ); +export const updateBlockAttributes = getBlockEditorAction( + 'updateBlockAttributes' +); /** * @see selectBlock in core/block-editor store. @@ -815,7 +800,9 @@ export const moveBlocksUp = getBlockEditorAction( 'moveBlocksUp' ); /** * @see moveBlockToPosition in core/block-editor store. */ -export const moveBlockToPosition = getBlockEditorAction( 'moveBlockToPosition' ); +export const moveBlockToPosition = getBlockEditorAction( + 'moveBlockToPosition' +); /** * @see insertBlock in core/block-editor store. @@ -840,12 +827,16 @@ export const hideInsertionPoint = getBlockEditorAction( 'hideInsertionPoint' ); /** * @see setTemplateValidity in core/block-editor store. */ -export const setTemplateValidity = getBlockEditorAction( 'setTemplateValidity' ); +export const setTemplateValidity = getBlockEditorAction( + 'setTemplateValidity' +); /** * @see synchronizeTemplate in core/block-editor store. */ -export const synchronizeTemplate = getBlockEditorAction( 'synchronizeTemplate' ); +export const synchronizeTemplate = getBlockEditorAction( + 'synchronizeTemplate' +); /** * @see mergeBlocks in core/block-editor store. @@ -895,4 +886,6 @@ export const insertDefaultBlock = getBlockEditorAction( 'insertDefaultBlock' ); /** * @see updateBlockListSettings in core/block-editor store. */ -export const updateBlockListSettings = getBlockEditorAction( 'updateBlockListSettings' ); +export const updateBlockListSettings = getBlockEditorAction( + 'updateBlockListSettings' +); diff --git a/packages/editor/src/store/constants.js b/packages/editor/src/store/constants.js index 6d12c68cc2af66..1946c19d75b80e 100644 --- a/packages/editor/src/store/constants.js +++ b/packages/editor/src/store/constants.js @@ -4,9 +4,7 @@ * * @type {Set} */ -export const EDIT_MERGE_PROPERTIES = new Set( [ - 'meta', -] ); +export const EDIT_MERGE_PROPERTIES = new Set( [ 'meta' ] ); /** * Constant for the store module (or reducer) key. diff --git a/packages/editor/src/store/controls.js b/packages/editor/src/store/controls.js index 9db3f0d8aab9a1..4f8435d26fb3a1 100644 --- a/packages/editor/src/store/controls.js +++ b/packages/editor/src/store/controls.js @@ -44,11 +44,14 @@ export function localAutosaveGet( postId ) { } export function localAutosaveSet( postId, title, content, excerpt ) { - window.sessionStorage.setItem( postKey( postId ), JSON.stringify( { - post_title: title, - content, - excerpt, - } ) ); + window.sessionStorage.setItem( + postKey( postId ), + JSON.stringify( { + post_title: title, + content, + excerpt, + } ) + ); } export function localAutosaveClear( postId ) { @@ -56,8 +59,8 @@ export function localAutosaveClear( postId ) { } const controls = { - AWAIT_NEXT_STATE_CHANGE: createRegistryControl( - ( registry ) => () => new Promise( ( resolve ) => { + AWAIT_NEXT_STATE_CHANGE: createRegistryControl( ( registry ) => () => + new Promise( ( resolve ) => { const unsubscribe = registry.subscribe( () => { unsubscribe(); resolve(); diff --git a/packages/editor/src/store/defaults.js b/packages/editor/src/store/defaults.js index f158a4664dec7c..5399999001d42e 100644 --- a/packages/editor/src/store/defaults.js +++ b/packages/editor/src/store/defaults.js @@ -28,4 +28,3 @@ export const EDITOR_SETTINGS_DEFAULTS = { codeEditingEnabled: true, enableCustomFields: false, }; - diff --git a/packages/editor/src/store/effects/reusable-blocks.js b/packages/editor/src/store/effects/reusable-blocks.js index 0a4973b65a4485..4cef632d7c0ba3 100644 --- a/packages/editor/src/store/effects/reusable-blocks.js +++ b/packages/editor/src/store/effects/reusable-blocks.js @@ -27,9 +27,7 @@ import { __experimentalReceiveReusableBlocks as receiveReusableBlocksAction, __experimentalSaveReusableBlock as saveReusableBlock, } from '../actions'; -import { - __experimentalGetReusableBlock as getReusableBlock, -} from '../selectors'; +import { __experimentalGetReusableBlock as getReusableBlock } from '../selectors'; /** * Module Constants @@ -57,22 +55,30 @@ export const fetchReusableBlocks = async ( action, store ) => { let posts; if ( id ) { - posts = [ await apiFetch( { path: `/wp/v2/${ postType.rest_base }/${ id }` } ) ]; + posts = [ + await apiFetch( { + path: `/wp/v2/${ postType.rest_base }/${ id }`, + } ), + ]; } else { - posts = await apiFetch( { path: `/wp/v2/${ postType.rest_base }?per_page=-1` } ); + posts = await apiFetch( { + path: `/wp/v2/${ postType.rest_base }?per_page=-1`, + } ); } - const results = compact( map( posts, ( post ) => { - if ( post.status !== 'publish' || post.content.protected ) { - return null; - } + const results = compact( + map( posts, ( post ) => { + if ( post.status !== 'publish' || post.content.protected ) { + return null; + } - return { - ...post, - content: post.content.raw, - title: post.title.raw, - }; - } ) ); + return { + ...post, + content: post.content.raw, + title: post.title.raw, + }; + } ) + ); if ( results.length ) { dispatch( receiveReusableBlocksAction( results ) ); @@ -110,8 +116,12 @@ export const saveReusableBlocks = async ( action, store ) => { const state = store.getState(); const { title, content, isTemporary } = getReusableBlock( state, id ); - const data = isTemporary ? { title, content, status: 'publish' } : { id, title, content, status: 'publish' }; - const path = isTemporary ? `/wp/v2/${ postType.rest_base }` : `/wp/v2/${ postType.rest_base }/${ id }`; + const data = isTemporary + ? { title, content, status: 'publish' } + : { id, title, content, status: 'publish' }; + const path = isTemporary + ? `/wp/v2/${ postType.rest_base }` + : `/wp/v2/${ postType.rest_base }/${ id }`; const method = isTemporary ? 'POST' : 'PUT'; try { @@ -121,13 +131,18 @@ export const saveReusableBlocks = async ( action, store ) => { updatedId: updatedReusableBlock.id, id, } ); - const message = isTemporary ? __( 'Block created.' ) : __( 'Block updated.' ); + const message = isTemporary + ? __( 'Block created.' ) + : __( 'Block updated.' ); dataDispatch( 'core/notices' ).createSuccessNotice( message, { id: REUSABLE_BLOCK_NOTICE_ID, type: 'snackbar', } ); - dataDispatch( 'core/block-editor' ).__unstableSaveReusableBlock( id, updatedReusableBlock.id ); + dataDispatch( 'core/block-editor' ).__unstableSaveReusableBlock( + id, + updatedReusableBlock.id + ); } catch ( error ) { dispatch( { type: 'SAVE_REUSABLE_BLOCK_FAILURE', id } ); dataDispatch( 'core/notices' ).createErrorNotice( error.message, { @@ -160,8 +175,12 @@ export const deleteReusableBlocks = async ( action, store ) => { } // Remove any other blocks that reference this reusable block const allBlocks = select( 'core/block-editor' ).getBlocks(); - const associatedBlocks = allBlocks.filter( ( block ) => isReusableBlock( block ) && block.attributes.ref === id ); - const associatedBlockClientIds = associatedBlocks.map( ( block ) => block.clientId ); + const associatedBlocks = allBlocks.filter( + ( block ) => isReusableBlock( block ) && block.attributes.ref === id + ); + const associatedBlockClientIds = associatedBlocks.map( + ( block ) => block.clientId + ); const transactionId = uniqueId(); @@ -173,7 +192,9 @@ export const deleteReusableBlocks = async ( action, store ) => { // Remove the parsed block. if ( associatedBlockClientIds.length ) { - dataDispatch( 'core/block-editor' ).removeBlocks( associatedBlockClientIds ); + dataDispatch( 'core/block-editor' ).removeBlocks( + associatedBlockClientIds + ); } try { @@ -214,7 +235,10 @@ export const convertBlockToStatic = ( action, store ) => { const oldBlock = select( 'core/block-editor' ).getBlock( action.clientId ); const reusableBlock = getReusableBlock( state, oldBlock.attributes.ref ); const newBlocks = parse( reusableBlock.content ); - dataDispatch( 'core/block-editor' ).replaceBlocks( oldBlock.clientId, newBlocks ); + dataDispatch( 'core/block-editor' ).replaceBlocks( + oldBlock.clientId, + newBlocks + ); }; /** @@ -228,12 +252,14 @@ export const convertBlockToReusable = ( action, store ) => { const reusableBlock = { id: uniqueId( 'reusable' ), title: __( 'Untitled Reusable Block' ), - content: serialize( select( 'core/block-editor' ).getBlocksByClientId( action.clientIds ) ), + content: serialize( + select( 'core/block-editor' ).getBlocksByClientId( + action.clientIds + ) + ), }; - dispatch( receiveReusableBlocksAction( [ - reusableBlock, - ] ) ); + dispatch( receiveReusableBlocksAction( [ reusableBlock ] ) ); dispatch( saveReusableBlock( reusableBlock.id ) ); dataDispatch( 'core/block-editor' ).replaceBlocks( diff --git a/packages/editor/src/store/effects/test/reusable-blocks.js b/packages/editor/src/store/effects/test/reusable-blocks.js index 97ae30feaf9494..40ed6d5bdc25d1 100644 --- a/packages/editor/src/store/effects/test/reusable-blocks.js +++ b/packages/editor/src/store/effects/test/reusable-blocks.js @@ -12,7 +12,10 @@ import { unregisterBlockType, createBlock, } from '@wordpress/blocks'; -import { dispatch as dataDispatch, select as dataSelect } from '@wordpress/data'; +import { + dispatch as dataDispatch, + select as dataSelect, +} from '@wordpress/data'; /** * Internal dependencies @@ -79,7 +82,8 @@ describe( 'reusable blocks effects', () => { }, ] ); const postTypePromise = Promise.resolve( { - slug: 'wp_block', rest_base: 'blocks', + slug: 'wp_block', + rest_base: 'blocks', } ); apiFetch.mockImplementation( ( options ) => { @@ -124,7 +128,8 @@ describe( 'reusable blocks effects', () => { }, } ); const postTypePromise = Promise.resolve( { - slug: 'wp_block', rest_base: 'blocks', + slug: 'wp_block', + rest_base: 'blocks', } ); apiFetch.mockImplementation( ( options ) => { @@ -138,15 +143,20 @@ describe( 'reusable blocks effects', () => { const dispatch = jest.fn(); const store = { getState: noop, dispatch }; - await fetchReusableBlocks( fetchReusableBlocksAction( 123 ), store ); + await fetchReusableBlocks( + fetchReusableBlocksAction( 123 ), + store + ); expect( dispatch ).toHaveBeenCalledWith( - receiveReusableBlocksAction( [ { - id: 123, - title: 'My cool block', - content: '<!-- wp:test-block {"name":"Big Bird"} /-->', - status: 'publish', - } ] ) + receiveReusableBlocksAction( [ + { + id: 123, + title: 'My cool block', + content: '<!-- wp:test-block {"name":"Big Bird"} /-->', + status: 'publish', + }, + ] ) ); expect( dispatch ).toHaveBeenCalledWith( { type: 'FETCH_REUSABLE_BLOCKS_SUCCESS', @@ -167,7 +177,8 @@ describe( 'reusable blocks effects', () => { }, } ); const postTypePromise = Promise.resolve( { - slug: 'wp_block', rest_base: 'blocks', + slug: 'wp_block', + rest_base: 'blocks', } ); apiFetch.mockImplementation( ( options ) => { @@ -181,7 +192,10 @@ describe( 'reusable blocks effects', () => { const dispatch = jest.fn(); const store = { getState: noop, dispatch }; - await fetchReusableBlocks( fetchReusableBlocksAction( 123 ), store ); + await fetchReusableBlocks( + fetchReusableBlocksAction( 123 ), + store + ); expect( dispatch ).toHaveBeenCalledTimes( 1 ); expect( dispatch ).toHaveBeenCalledWith( { @@ -196,7 +210,8 @@ describe( 'reusable blocks effects', () => { message: 'An unknown error occurred.', } ); const postTypePromise = Promise.resolve( { - slug: 'wp_block', rest_base: 'blocks', + slug: 'wp_block', + rest_base: 'blocks', } ); apiFetch.mockImplementation( ( options ) => { @@ -226,7 +241,8 @@ describe( 'reusable blocks effects', () => { it( 'should save a reusable block and swap its id', async () => { const savePromise = Promise.resolve( { id: 456 } ); const postTypePromise = Promise.resolve( { - slug: 'wp_block', rest_base: 'blocks', + slug: 'wp_block', + rest_base: 'blocks', } ); apiFetch.mockImplementation( ( options ) => { @@ -237,8 +253,15 @@ describe( 'reusable blocks effects', () => { return savePromise; } ); - const reusableBlock = { id: 123, title: 'My cool block', content: '<!-- wp:test-block {"name":"Big Bird"} /-->' }; - const state = reducer( undefined, receiveReusableBlocksAction( [ reusableBlock ] ) ); + const reusableBlock = { + id: 123, + title: 'My cool block', + content: '<!-- wp:test-block {"name":"Big Bird"} /-->', + }; + const state = reducer( + undefined, + receiveReusableBlocksAction( [ reusableBlock ] ) + ); const dispatch = jest.fn(); const store = { getState: () => state, dispatch }; @@ -255,7 +278,8 @@ describe( 'reusable blocks effects', () => { it( 'should handle an API error', async () => { const savePromise = Promise.reject( {} ); const postTypePromise = Promise.resolve( { - slug: 'wp_block', rest_base: 'blocks', + slug: 'wp_block', + rest_base: 'blocks', } ); apiFetch.mockImplementation( ( options ) => { @@ -266,8 +290,15 @@ describe( 'reusable blocks effects', () => { return savePromise; } ); - const reusableBlock = { id: 123, title: 'My cool block', content: '<!-- wp:test-block {"name":"Big Bird"} /-->' }; - const state = reducer( undefined, receiveReusableBlocksAction( [ reusableBlock ] ) ); + const reusableBlock = { + id: 123, + title: 'My cool block', + content: '<!-- wp:test-block {"name":"Big Bird"} /-->', + }; + const state = reducer( + undefined, + receiveReusableBlocksAction( [ reusableBlock ] ) + ); const dispatch = jest.fn(); const store = { getState: () => state, dispatch }; @@ -284,7 +315,8 @@ describe( 'reusable blocks effects', () => { it( 'should delete a reusable block', async () => { const deletePromise = Promise.resolve( {} ); const postTypePromise = Promise.resolve( { - slug: 'wp_block', rest_base: 'blocks', + slug: 'wp_block', + rest_base: 'blocks', } ); apiFetch.mockImplementation( ( options ) => { @@ -295,13 +327,24 @@ describe( 'reusable blocks effects', () => { return deletePromise; } ); - const reusableBlock = { id: 123, title: 'My cool block', content: '<!-- wp:test-block {"name":"Big Bird"} /-->' }; - const state = reducer( undefined, receiveReusableBlocksAction( [ reusableBlock ] ) ); + const reusableBlock = { + id: 123, + title: 'My cool block', + content: '<!-- wp:test-block {"name":"Big Bird"} /-->', + }; + const state = reducer( + undefined, + receiveReusableBlocksAction( [ reusableBlock ] ) + ); const associatedBlock = createBlock( 'core/block', { ref: 123 } ); - jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlocks' ).mockImplementation( () => [ - associatedBlock, - ] ); - jest.spyOn( dataDispatch( 'core/block-editor' ), 'removeBlocks' ).mockImplementation( () => {} ); + jest.spyOn( + dataSelect( 'core/block-editor' ), + 'getBlocks' + ).mockImplementation( () => [ associatedBlock ] ); + jest.spyOn( + dataDispatch( 'core/block-editor' ), + 'removeBlocks' + ).mockImplementation( () => {} ); const dispatch = jest.fn(); const store = { getState: () => state, dispatch }; @@ -314,9 +357,9 @@ describe( 'reusable blocks effects', () => { optimist: expect.any( Object ), } ); - expect( dataDispatch( 'core/block-editor' ).removeBlocks ).toHaveBeenCalledWith( - [ associatedBlock.clientId ] - ); + expect( + dataDispatch( 'core/block-editor' ).removeBlocks + ).toHaveBeenCalledWith( [ associatedBlock.clientId ] ); expect( dispatch ).toHaveBeenCalledWith( { type: 'DELETE_REUSABLE_BLOCK_SUCCESS', @@ -331,7 +374,8 @@ describe( 'reusable blocks effects', () => { it( 'should handle an API error', async () => { const deletePromise = Promise.reject( {} ); const postTypePromise = Promise.resolve( { - slug: 'wp_block', rest_base: 'blocks', + slug: 'wp_block', + rest_base: 'blocks', } ); apiFetch.mockImplementation( ( options ) => { @@ -342,10 +386,23 @@ describe( 'reusable blocks effects', () => { return deletePromise; } ); - const reusableBlock = { id: 123, title: 'My cool block', content: '<!-- wp:test-block {"name":"Big Bird"} /-->' }; - const state = reducer( undefined, receiveReusableBlocksAction( [ reusableBlock ] ) ); - jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlocks' ).mockImplementation( () => [] ); - jest.spyOn( dataDispatch( 'core/block-editor' ), 'removeBlocks' ).mockImplementation( () => {} ); + const reusableBlock = { + id: 123, + title: 'My cool block', + content: '<!-- wp:test-block {"name":"Big Bird"} /-->', + }; + const state = reducer( + undefined, + receiveReusableBlocksAction( [ reusableBlock ] ) + ); + jest.spyOn( + dataSelect( 'core/block-editor' ), + 'getBlocks' + ).mockImplementation( () => [] ); + jest.spyOn( + dataDispatch( 'core/block-editor' ), + 'removeBlocks' + ).mockImplementation( () => {} ); const dispatch = jest.fn(); const store = { getState: () => state, dispatch }; @@ -363,14 +420,26 @@ describe( 'reusable blocks effects', () => { it( 'should not save reusable blocks with temporary IDs', async () => { const reusableBlock = { id: 'reusable1', title: 'My cool block' }; - const state = reducer( undefined, receiveReusableBlocksAction( [ reusableBlock ] ) ); - jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlocks' ).mockImplementation( () => [] ); - jest.spyOn( dataDispatch( 'core/block-editor' ), 'removeBlocks' ).mockImplementation( () => {} ); + const state = reducer( + undefined, + receiveReusableBlocksAction( [ reusableBlock ] ) + ); + jest.spyOn( + dataSelect( 'core/block-editor' ), + 'getBlocks' + ).mockImplementation( () => [] ); + jest.spyOn( + dataDispatch( 'core/block-editor' ), + 'removeBlocks' + ).mockImplementation( () => {} ); const dispatch = jest.fn(); const store = { getState: () => state, dispatch }; - await deleteReusableBlocks( deleteReusableBlock( 'reusable1' ), store ); + await deleteReusableBlocks( + deleteReusableBlock( 'reusable1' ), + store + ); expect( dispatch ).not.toHaveBeenCalled(); dataDispatch( 'core/block-editor' ).removeBlocks.mockReset(); @@ -381,28 +450,43 @@ describe( 'reusable blocks effects', () => { describe( 'convertBlockToStatic', () => { it( 'should convert a reusable block into a static block', () => { const associatedBlock = createBlock( 'core/block', { ref: 123 } ); - const reusableBlock = { id: 123, title: 'My cool block', content: '<!-- wp:test-block {"name":"Big Bird"} /-->' }; - const state = reducer( undefined, receiveReusableBlocksAction( [ reusableBlock ] ) ); - jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlock' ).mockImplementation( ( id ) => + const reusableBlock = { + id: 123, + title: 'My cool block', + content: '<!-- wp:test-block {"name":"Big Bird"} /-->', + }; + const state = reducer( + undefined, + receiveReusableBlocksAction( [ reusableBlock ] ) + ); + jest.spyOn( + dataSelect( 'core/block-editor' ), + 'getBlock' + ).mockImplementation( ( id ) => associatedBlock.clientId === id ? associatedBlock : null ); - jest.spyOn( dataDispatch( 'core/block-editor' ), 'replaceBlocks' ).mockImplementation( () => {} ); + jest.spyOn( + dataDispatch( 'core/block-editor' ), + 'replaceBlocks' + ).mockImplementation( () => {} ); const dispatch = jest.fn(); const store = { getState: () => state, dispatch }; - convertBlockToStatic( convertBlockToStaticAction( associatedBlock.clientId ), store ); - - expect( dataDispatch( 'core/block-editor' ).replaceBlocks ).toHaveBeenCalledWith( - associatedBlock.clientId, - [ - expect.objectContaining( { - name: 'core/test-block', - attributes: { name: 'Big Bird' }, - } ), - ] + convertBlockToStatic( + convertBlockToStaticAction( associatedBlock.clientId ), + store ); + expect( + dataDispatch( 'core/block-editor' ).replaceBlocks + ).toHaveBeenCalledWith( associatedBlock.clientId, [ + expect.objectContaining( { + name: 'core/test-block', + attributes: { name: 'Big Bird' }, + } ), + ] ); + dataDispatch( 'core/block-editor' ).replaceBlocks.mockReset(); dataSelect( 'core/block-editor' ).getBlock.mockReset(); } ); @@ -412,37 +496,49 @@ describe( 'reusable blocks effects', () => { const reusableBlock = { id: 123, title: 'My cool block', - content: '<!-- wp:test-block {"name":"Big Bird"} --><!-- wp:test-block {"name":"Oscar the Grouch"} /--><!-- wp:test-block {"name":"Cookie Monster"} /--><!-- /wp:test-block -->', + content: + '<!-- wp:test-block {"name":"Big Bird"} --><!-- wp:test-block {"name":"Oscar the Grouch"} /--><!-- wp:test-block {"name":"Cookie Monster"} /--><!-- /wp:test-block -->', }; - const state = reducer( undefined, receiveReusableBlocksAction( [ reusableBlock ] ) ); - jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlock' ).mockImplementation( ( id ) => + const state = reducer( + undefined, + receiveReusableBlocksAction( [ reusableBlock ] ) + ); + jest.spyOn( + dataSelect( 'core/block-editor' ), + 'getBlock' + ).mockImplementation( ( id ) => associatedBlock.clientId === id ? associatedBlock : null ); - jest.spyOn( dataDispatch( 'core/block-editor' ), 'replaceBlocks' ).mockImplementation( () => {} ); + jest.spyOn( + dataDispatch( 'core/block-editor' ), + 'replaceBlocks' + ).mockImplementation( () => {} ); const dispatch = jest.fn(); const store = { getState: () => state, dispatch }; - convertBlockToStatic( convertBlockToStaticAction( associatedBlock.clientId ), store ); - - expect( dataDispatch( 'core/block-editor' ).replaceBlocks ).toHaveBeenCalledWith( - associatedBlock.clientId, - [ - expect.objectContaining( { - name: 'core/test-block', - attributes: { name: 'Big Bird' }, - innerBlocks: [ - expect.objectContaining( { - attributes: { name: 'Oscar the Grouch' }, - } ), - expect.objectContaining( { - attributes: { name: 'Cookie Monster' }, - } ), - ], - } ), - ] + convertBlockToStatic( + convertBlockToStaticAction( associatedBlock.clientId ), + store ); + expect( + dataDispatch( 'core/block-editor' ).replaceBlocks + ).toHaveBeenCalledWith( associatedBlock.clientId, [ + expect.objectContaining( { + name: 'core/test-block', + attributes: { name: 'Big Bird' }, + innerBlocks: [ + expect.objectContaining( { + attributes: { name: 'Oscar the Grouch' }, + } ), + expect.objectContaining( { + attributes: { name: 'Cookie Monster' }, + } ), + ], + } ), + ] ); + dataDispatch( 'core/block-editor' ).replaceBlocks.mockReset(); dataSelect( 'core/block-editor' ).getBlock.mockReset(); } ); @@ -451,35 +547,49 @@ describe( 'reusable blocks effects', () => { describe( 'convertBlockToReusable', () => { it( 'should convert a static block into a reusable block', () => { const staticBlock = createBlock( 'core/test-block' ); - jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlocksByClientId' ).mockImplementation( ( ) => - [ staticBlock ] - ); - jest.spyOn( dataDispatch( 'core/block-editor' ), 'replaceBlocks' ).mockImplementation( () => {} ); - jest.spyOn( dataDispatch( 'core/block-editor' ), 'receiveBlocks' ).mockImplementation( () => {} ); + jest.spyOn( + dataSelect( 'core/block-editor' ), + 'getBlocksByClientId' + ).mockImplementation( () => [ staticBlock ] ); + jest.spyOn( + dataDispatch( 'core/block-editor' ), + 'replaceBlocks' + ).mockImplementation( () => {} ); + jest.spyOn( + dataDispatch( 'core/block-editor' ), + 'receiveBlocks' + ).mockImplementation( () => {} ); const dispatch = jest.fn(); const store = { getState: () => {}, dispatch }; - convertBlockToReusable( convertBlockToReusableAction( staticBlock.clientId ), store ); + convertBlockToReusable( + convertBlockToReusableAction( staticBlock.clientId ), + store + ); expect( dispatch ).toHaveBeenCalledWith( - receiveReusableBlocksAction( [ { - id: expect.stringMatching( /^reusable/ ), - title: 'Untitled Reusable Block', - content: '<!-- wp:test-block /-->', - } ] ) + receiveReusableBlocksAction( [ + { + id: expect.stringMatching( /^reusable/ ), + title: 'Untitled Reusable Block', + content: '<!-- wp:test-block /-->', + }, + ] ) ); expect( dispatch ).toHaveBeenCalledWith( - saveReusableBlock( expect.stringMatching( /^reusable/ ) ), + saveReusableBlock( expect.stringMatching( /^reusable/ ) ) ); - expect( dataDispatch( 'core/block-editor' ).replaceBlocks ).toHaveBeenCalledWith( + expect( + dataDispatch( 'core/block-editor' ).replaceBlocks + ).toHaveBeenCalledWith( [ staticBlock.clientId ], expect.objectContaining( { name: 'core/block', attributes: { ref: expect.stringMatching( /^reusable/ ) }, - } ), + } ) ); dataDispatch( 'core/block-editor' ).replaceBlocks.mockReset(); diff --git a/packages/editor/src/store/middlewares.js b/packages/editor/src/store/middlewares.js index b19b1b9bb5817b..f7d73bb0a828c9 100644 --- a/packages/editor/src/store/middlewares.js +++ b/packages/editor/src/store/middlewares.js @@ -19,7 +19,7 @@ function applyMiddlewares( store ) { let enhancedDispatch = () => { throw new Error( 'Dispatching while constructing your middleware is not allowed. ' + - 'Other middleware would not be applied to this dispatch.' + 'Other middleware would not be applied to this dispatch.' ); }; diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index 0bf0ddf3f1bb38..9fd9c910360d3d 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -2,12 +2,7 @@ * External dependencies */ import optimist from 'redux-optimist'; -import { - omit, - keys, - isEqual, - keyBy, -} from 'lodash'; +import { omit, keys, isEqual, keyBy } from 'lodash'; /** * WordPress dependencies @@ -380,16 +375,18 @@ export function editorSettings( state = EDITOR_SETTINGS_DEFAULTS, action ) { return state; } -export default optimist( combineReducers( { - postId, - postType, - preferences, - saving, - postLock, - reusableBlocks, - template, - postSavingLock, - isReady, - editorSettings, - postAutosavingLock, -} ) ); +export default optimist( + combineReducers( { + postId, + postType, + preferences, + saving, + postLock, + reusableBlocks, + template, + postSavingLock, + isReady, + editorSettings, + postAutosavingLock, + } ) +); diff --git a/packages/editor/src/store/reducer.native.js b/packages/editor/src/store/reducer.native.js index a0f0be9c3cb052..049af394a291a9 100644 --- a/packages/editor/src/store/reducer.native.js +++ b/packages/editor/src/store/reducer.native.js @@ -49,16 +49,18 @@ export const postTitle = combineReducers( { }, } ); -export default optimist( combineReducers( { - postId, - postType, - postTitle, - preferences, - saving, - postLock, - postSavingLock, - reusableBlocks, - template, - isReady, - editorSettings, -} ) ); +export default optimist( + combineReducers( { + postId, + postType, + postTitle, + preferences, + saving, + postLock, + postSavingLock, + reusableBlocks, + template, + isReady, + editorSettings, + } ) +); diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index d8777cb5b590a4..94c5c9fc958095 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -1,15 +1,7 @@ /** * External dependencies */ -import { - find, - get, - has, - map, - pick, - mapValues, - includes, -} from 'lodash'; +import { find, get, has, map, pick, mapValues, includes } from 'lodash'; import createSelector from 'rememo'; /** @@ -104,7 +96,6 @@ export function hasChangedContent( state ) { return ( 'blocks' in edits || - // `edits` is intended to contain only values which are different from // the saved post, so the mere presence of a property is an indicator // that the value is different than what is known to be saved. While @@ -122,17 +113,25 @@ export function hasChangedContent( state ) { * * @return {boolean} Whether unsaved values exist. */ -export const isEditedPostDirty = createRegistrySelector( ( select ) => ( state ) => { - // Edits should contain only fields which differ from the saved post (reset - // at initial load and save complete). Thus, a non-empty edits state can be - // inferred to contain unsaved values. - const postType = getCurrentPostType( state ); - const postId = getCurrentPostId( state ); - if ( select( 'core' ).hasEditsForEntityRecord( 'postType', postType, postId ) ) { - return true; +export const isEditedPostDirty = createRegistrySelector( + ( select ) => ( state ) => { + // Edits should contain only fields which differ from the saved post (reset + // at initial load and save complete). Thus, a non-empty edits state can be + // inferred to contain unsaved values. + const postType = getCurrentPostType( state ); + const postId = getCurrentPostId( state ); + if ( + select( 'core' ).hasEditsForEntityRecord( + 'postType', + postType, + postId + ) + ) { + return true; + } + return false; } - return false; -} ); +); /** * Returns true if there are unsaved edits for entities other than @@ -156,7 +155,8 @@ export const hasNonPostEntityChanges = createRegistrySelector( const changedKinds = Object.keys( entityRecordChangesByRecord ); if ( changedKinds.length > 1 || - ( changedKinds.length === 1 && ! entityRecordChangesByRecord.postType ) + ( changedKinds.length === 1 && + ! entityRecordChangesByRecord.postType ) ) { // Return true if there is more than one edited entity kind // or the edited entity kind is not the editor's post's kind. @@ -167,7 +167,9 @@ export const hasNonPostEntityChanges = createRegistrySelector( } const { type, id } = getCurrentPost( state ); - const changedPostTypes = Object.keys( entityRecordChangesByRecord.postType ); + const changedPostTypes = Object.keys( + entityRecordChangesByRecord.postType + ); if ( changedPostTypes.length > 1 || ( changedPostTypes.length === 1 && @@ -178,7 +180,9 @@ export const hasNonPostEntityChanges = createRegistrySelector( return true; } - const changedPosts = Object.keys( entityRecordChangesByRecord.postType[ type ] ); + const changedPosts = Object.keys( + entityRecordChangesByRecord.postType[ type ] + ); if ( changedPosts.length > 1 || ( changedPosts.length === 1 && @@ -214,20 +218,26 @@ export function isCleanNewPost( state ) { * * @return {Object} Post object. */ -export const getCurrentPost = createRegistrySelector( ( select ) => ( state ) => { - const postId = getCurrentPostId( state ); - const postType = getCurrentPostType( state ); +export const getCurrentPost = createRegistrySelector( + ( select ) => ( state ) => { + const postId = getCurrentPostId( state ); + const postType = getCurrentPostType( state ); - const post = select( 'core' ).getRawEntityRecord( 'postType', postType, postId ); - if ( post ) { - return post; - } + const post = select( 'core' ).getRawEntityRecord( + 'postType', + postType, + postId + ); + if ( post ) { + return post; + } - // This exists for compatibility with the previous selector behavior - // which would guarantee an object return based on the editor reducer's - // default empty object state. - return EMPTY_OBJECT; -} ); + // This exists for compatibility with the previous selector behavior + // which would guarantee an object return based on the editor reducer's + // default empty object state. + return EMPTY_OBJECT; + } +); /** * Returns the post type of the post currently being edited. @@ -260,7 +270,11 @@ export function getCurrentPostId( state ) { * @return {number} Number of revisions. */ export function getCurrentPostRevisionsCount( state ) { - return get( getCurrentPost( state ), [ '_links', 'version-history', 0, 'count' ], 0 ); + return get( + getCurrentPost( state ), + [ '_links', 'version-history', 0, 'count' ], + 0 + ); } /** @@ -272,7 +286,11 @@ export function getCurrentPostRevisionsCount( state ) { * @return {?number} ID of the last revision. */ export function getCurrentPostLastRevisionId( state ) { - return get( getCurrentPost( state ), [ '_links', 'predecessor-version', 0, 'id' ], null ); + return get( + getCurrentPost( state ), + [ '_links', 'predecessor-version', 0, 'id' ], + null + ); } /** @@ -286,7 +304,10 @@ export function getCurrentPostLastRevisionId( state ) { export const getPostEdits = createRegistrySelector( ( select ) => ( state ) => { const postType = getCurrentPostType( state ); const postId = getCurrentPostId( state ); - return select( 'core' ).getEntityRecordEdits( 'postType', postType, postId ) || EMPTY_OBJECT; + return ( + select( 'core' ).getEntityRecordEdits( 'postType', postType, postId ) || + EMPTY_OBJECT + ); } ); /** @@ -310,10 +331,14 @@ export const getPostEdits = createRegistrySelector( ( select ) => ( state ) => { * @return {*} A value whose reference will change only when an edit occurs. */ export const getReferenceByDistinctEdits = createRegistrySelector( - ( select ) => ( /* state */ ) => { - deprecated( '`wp.data.select( \'core/editor\' ).getReferenceByDistinctEdits`', { - alternative: '`wp.data.select( \'core\' ).getReferenceByDistinctEdits`', - } ); + ( select ) => (/* state */) => { + deprecated( + "`wp.data.select( 'core/editor' ).getReferenceByDistinctEdits`", + { + alternative: + "`wp.data.select( 'core' ).getReferenceByDistinctEdits`", + } + ); return select( 'core' ).getReferenceByDistinctEdits(); } @@ -412,20 +437,31 @@ export function getEditedPostAttribute( state, attributeName ) { * * @return {*} Autosave attribute value. */ -export const getAutosaveAttribute = createRegistrySelector( ( select ) => ( state, attributeName ) => { - if ( ! includes( AUTOSAVE_PROPERTIES, attributeName ) && attributeName !== 'preview_link' ) { - return; - } +export const getAutosaveAttribute = createRegistrySelector( + ( select ) => ( state, attributeName ) => { + if ( + ! includes( AUTOSAVE_PROPERTIES, attributeName ) && + attributeName !== 'preview_link' + ) { + return; + } - const postType = getCurrentPostType( state ); - const postId = getCurrentPostId( state ); - const currentUserId = get( select( 'core' ).getCurrentUser(), [ 'id' ] ); - const autosave = select( 'core' ).getAutosave( postType, postId, currentUserId ); + const postType = getCurrentPostType( state ); + const postId = getCurrentPostId( state ); + const currentUserId = get( select( 'core' ).getCurrentUser(), [ + 'id', + ] ); + const autosave = select( 'core' ).getAutosave( + postType, + postId, + currentUserId + ); - if ( autosave ) { - return getPostRawValue( autosave[ attributeName ] ); + if ( autosave ) { + return getPostRawValue( autosave[ attributeName ] ); + } } -} ); +); /** * Returns the current visibility of the post being edited, preferring the @@ -475,7 +511,9 @@ export function isCurrentPostPublished( state, currentPost ) { return ( [ 'publish', 'private' ].indexOf( post.status ) !== -1 || ( post.status === 'future' && - ! isInTheFuture( new Date( Number( getDate( post.date ) ) - ONE_MINUTE_IN_MS ) ) ) + ! isInTheFuture( + new Date( Number( getDate( post.date ) ) - ONE_MINUTE_IN_MS ) + ) ) ); } @@ -487,7 +525,10 @@ export function isCurrentPostPublished( state, currentPost ) { * @return {boolean} Whether current post is scheduled to be posted. */ export function isCurrentPostScheduled( state ) { - return getCurrentPost( state ).status === 'future' && ! isCurrentPostPublished( state ); + return ( + getCurrentPost( state ).status === 'future' && + ! isCurrentPostPublished( state ) + ); } /** @@ -505,7 +546,10 @@ export function isEditedPostPublishable( state ) { // // See: <PostPublishButton /> (`isButtonEnabled` assigned by `isSaveable`) - return isEditedPostDirty( state ) || [ 'publish', 'private', 'future' ].indexOf( post.status ) === -1; + return ( + isEditedPostDirty( state ) || + [ 'publish', 'private', 'future' ].indexOf( post.status ) === -1 + ); } /** @@ -595,52 +639,66 @@ export function isEditedPostEmpty( state ) { * * @return {boolean} Whether the post can be autosaved. */ -export const isEditedPostAutosaveable = createRegistrySelector( ( select ) => function( state ) { - // A post must contain a title, an excerpt, or non-empty content to be valid for autosaving. - if ( ! isEditedPostSaveable( state ) ) { - return false; - } - - // A post is not autosavable when there is a post autosave lock. - if ( isPostAutosavingLocked( state ) ) { - return false; - } - - const postType = getCurrentPostType( state ); - const postId = getCurrentPostId( state ); - const hasFetchedAutosave = select( 'core' ).hasFetchedAutosaves( postType, postId ); - const currentUserId = get( select( 'core' ).getCurrentUser(), [ 'id' ] ); +export const isEditedPostAutosaveable = createRegistrySelector( + ( select ) => + function( state ) { + // A post must contain a title, an excerpt, or non-empty content to be valid for autosaving. + if ( ! isEditedPostSaveable( state ) ) { + return false; + } - // Disable reason - this line causes the side-effect of fetching the autosave - // via a resolver, moving below the return would result in the autosave never - // being fetched. - // eslint-disable-next-line @wordpress/no-unused-vars-before-return - const autosave = select( 'core' ).getAutosave( postType, postId, currentUserId ); + // A post is not autosavable when there is a post autosave lock. + if ( isPostAutosavingLocked( state ) ) { + return false; + } - // If any existing autosaves have not yet been fetched, this function is - // unable to determine if the post is autosaveable, so return false. - if ( ! hasFetchedAutosave ) { - return false; - } + const postType = getCurrentPostType( state ); + const postId = getCurrentPostId( state ); + const hasFetchedAutosave = select( 'core' ).hasFetchedAutosaves( + postType, + postId + ); + const currentUserId = get( select( 'core' ).getCurrentUser(), [ + 'id', + ] ); + + // Disable reason - this line causes the side-effect of fetching the autosave + // via a resolver, moving below the return would result in the autosave never + // being fetched. + // eslint-disable-next-line @wordpress/no-unused-vars-before-return + const autosave = select( 'core' ).getAutosave( + postType, + postId, + currentUserId + ); + + // If any existing autosaves have not yet been fetched, this function is + // unable to determine if the post is autosaveable, so return false. + if ( ! hasFetchedAutosave ) { + return false; + } - // If we don't already have an autosave, the post is autosaveable. - if ( ! autosave ) { - return true; - } + // If we don't already have an autosave, the post is autosaveable. + if ( ! autosave ) { + return true; + } - // To avoid an expensive content serialization, use the content dirtiness - // flag in place of content field comparison against the known autosave. - // This is not strictly accurate, and relies on a tolerance toward autosave - // request failures for unnecessary saves. - if ( hasChangedContent( state ) ) { - return true; - } + // To avoid an expensive content serialization, use the content dirtiness + // flag in place of content field comparison against the known autosave. + // This is not strictly accurate, and relies on a tolerance toward autosave + // request failures for unnecessary saves. + if ( hasChangedContent( state ) ) { + return true; + } - // If the title or excerpt has changed, the post is autosaveable. - return [ 'title', 'excerpt' ].some( ( field ) => ( - getPostRawValue( autosave[ field ] ) !== getEditedPostAttribute( state, field ) - ) ); -} ); + // If the title or excerpt has changed, the post is autosaveable. + return [ 'title', 'excerpt' ].some( + ( field ) => + getPostRawValue( autosave[ field ] ) !== + getEditedPostAttribute( state, field ) + ); + } +); /** * Returns the current autosave, or null if one is not set (i.e. if the post @@ -655,15 +713,20 @@ export const isEditedPostAutosaveable = createRegistrySelector( ( select ) => fu * @return {?Object} Current autosave, if exists. */ export const getAutosave = createRegistrySelector( ( select ) => ( state ) => { - deprecated( '`wp.data.select( \'core/editor\' ).getAutosave()`', { - alternative: '`wp.data.select( \'core\' ).getAutosave( postType, postId, userId )`', + deprecated( "`wp.data.select( 'core/editor' ).getAutosave()`", { + alternative: + "`wp.data.select( 'core' ).getAutosave( postType, postId, userId )`", plugin: 'Gutenberg', } ); const postType = getCurrentPostType( state ); const postId = getCurrentPostId( state ); const currentUserId = get( select( 'core' ).getCurrentUser(), [ 'id' ] ); - const autosave = select( 'core' ).getAutosave( postType, postId, currentUserId ); + const autosave = select( 'core' ).getAutosave( + postType, + postId, + currentUserId + ); return mapValues( pick( autosave, AUTOSAVE_PROPERTIES ), getPostRawValue ); } ); @@ -678,8 +741,9 @@ export const getAutosave = createRegistrySelector( ( select ) => ( state ) => { * @return {boolean} Whether there is an existing autosave. */ export const hasAutosave = createRegistrySelector( ( select ) => ( state ) => { - deprecated( '`wp.data.select( \'core/editor\' ).hasAutosave()`', { - alternative: '`!! wp.data.select( \'core\' ).getAutosave( postType, postId, userId )`', + deprecated( "`wp.data.select( 'core/editor' ).hasAutosave()`", { + alternative: + "`!! wp.data.select( 'core' ).getAutosave( postType, postId, userId )`", plugin: 'Gutenberg', } ); @@ -700,7 +764,9 @@ export const hasAutosave = createRegistrySelector( ( select ) => ( state ) => { export function isEditedPostBeingScheduled( state ) { const date = getEditedPostAttribute( state, 'date' ); // Offset the date by one minute (network latency) - const checkedDate = new Date( Number( getDate( date ) ) - ONE_MINUTE_IN_MS ); + const checkedDate = new Date( + Number( getDate( date ) ) - ONE_MINUTE_IN_MS + ); return isInTheFuture( checkedDate ); } @@ -722,7 +788,11 @@ export function isEditedPostDateFloating( state ) { const date = getEditedPostAttribute( state, 'date' ); const modified = getEditedPostAttribute( state, 'modified' ); const status = getEditedPostAttribute( state, 'status' ); - if ( status === 'draft' || status === 'auto-draft' || status === 'pending' ) { + if ( + status === 'draft' || + status === 'auto-draft' || + status === 'pending' + ) { return date === modified; } return false; @@ -738,7 +808,11 @@ export function isEditedPostDateFloating( state ) { export const isSavingPost = createRegistrySelector( ( select ) => ( state ) => { const postType = getCurrentPostType( state ); const postId = getCurrentPostId( state ); - return select( 'core' ).isSavingEntityRecord( 'postType', postType, postId ); + return select( 'core' ).isSavingEntityRecord( + 'postType', + postType, + postId + ); } ); /** @@ -753,7 +827,11 @@ export const didPostSaveRequestSucceed = createRegistrySelector( ( select ) => ( state ) => { const postType = getCurrentPostType( state ); const postId = getCurrentPostId( state ); - return ! select( 'core' ).getLastEntitySaveError( 'postType', postType, postId ); + return ! select( 'core' ).getLastEntitySaveError( + 'postType', + postType, + postId + ); } ); @@ -769,7 +847,11 @@ export const didPostSaveRequestFail = createRegistrySelector( ( select ) => ( state ) => { const postType = getCurrentPostType( state ); const postId = getCurrentPostId( state ); - return !! select( 'core' ).getLastEntitySaveError( 'postType', postType, postId ); + return !! select( 'core' ).getLastEntitySaveError( + 'postType', + postType, + postId + ); } ); @@ -904,10 +986,8 @@ export function getBlocksForSerialization( state ) { // A single unmodified default block is assumed to be equivalent to an // empty post. - const isSingleUnmodifiedDefaultBlock = ( - blocks.length === 1 && - isUnmodifiedDefaultBlock( blocks[ 0 ] ) - ); + const isSingleUnmodifiedDefaultBlock = + blocks.length === 1 && isUnmodifiedDefaultBlock( blocks[ 0 ] ); if ( isSingleUnmodifiedDefaultBlock ) { return []; @@ -923,25 +1003,27 @@ export function getBlocksForSerialization( state ) { * * @return {string} Post content. */ -export const getEditedPostContent = createRegistrySelector( ( select ) => ( state ) => { - const postId = getCurrentPostId( state ); - const postType = getCurrentPostType( state ); - const record = select( 'core' ).getEditedEntityRecord( - 'postType', - postType, - postId - ); - if ( record ) { - if ( typeof record.content === 'function' ) { - return record.content( record ); - } else if ( record.blocks ) { - return serializeBlocks( record.blocks ); - } else if ( record.content ) { - return record.content; +export const getEditedPostContent = createRegistrySelector( + ( select ) => ( state ) => { + const postId = getCurrentPostId( state ); + const postType = getCurrentPostType( state ); + const record = select( 'core' ).getEditedEntityRecord( + 'postType', + postType, + postId + ); + if ( record ) { + if ( typeof record.content === 'function' ) { + return record.content( record ); + } else if ( record.blocks ) { + return serializeBlocks( record.blocks ); + } else if ( record.content ) { + return record.content; + } } + return ''; } - return ''; -} ); +); /** * Returns the reusable block with the given ID. @@ -966,9 +1048,7 @@ export const __experimentalGetReusableBlock = createSelector( isTemporary, }; }, - ( state, ref ) => [ - state.reusableBlocks.data[ ref ], - ], + ( state, ref ) => [ state.reusableBlocks.data[ ref ] ] ); /** @@ -1005,14 +1085,11 @@ export function __experimentalIsFetchingReusableBlock( state, ref ) { */ export const __experimentalGetReusableBlocks = createSelector( ( state ) => { - return map( - state.reusableBlocks.data, - ( value, ref ) => __experimentalGetReusableBlock( state, ref ) + return map( state.reusableBlocks.data, ( value, ref ) => + __experimentalGetReusableBlock( state, ref ) ); }, - ( state ) => [ - state.reusableBlocks.data, - ] + ( state ) => [ state.reusableBlocks.data ] ); /** @@ -1025,10 +1102,12 @@ export const __experimentalGetReusableBlocks = createSelector( * @return {Object} Global application state prior to transaction. */ export function getStateBeforeOptimisticTransaction( state, transactionId ) { - const transaction = find( state.optimist, ( entry ) => ( - entry.beforeState && - get( entry.action, [ 'optimist', 'id' ] ) === transactionId - ) ); + const transaction = find( + state.optimist, + ( entry ) => + entry.beforeState && + get( entry.action, [ 'optimist', 'id' ] ) === transactionId + ); return transaction ? transaction.beforeState : null; } @@ -1074,7 +1153,10 @@ export function isPublishingPost( state ) { * @return {boolean} Whether or not the permalink is editable. */ export function isPermalinkEditable( state ) { - const permalinkTemplate = getEditedPostAttribute( state, 'permalink_template' ); + const permalinkTemplate = getEditedPostAttribute( + state, + 'permalink_template' + ); return PERMALINK_POSTNAME_REGEX.test( permalinkTemplate ); } @@ -1111,14 +1193,21 @@ export function getPermalink( state ) { * the permalink, or null if the post is not viewable. */ export function getPermalinkParts( state ) { - const permalinkTemplate = getEditedPostAttribute( state, 'permalink_template' ); + const permalinkTemplate = getEditedPostAttribute( + state, + 'permalink_template' + ); if ( ! permalinkTemplate ) { return null; } - const postName = getEditedPostAttribute( state, 'slug' ) || getEditedPostAttribute( state, 'generated_slug' ); + const postName = + getEditedPostAttribute( state, 'slug' ) || + getEditedPostAttribute( state, 'generated_slug' ); - const [ prefix, suffix ] = permalinkTemplate.split( PERMALINK_POSTNAME_REGEX ); + const [ prefix, suffix ] = permalinkTemplate.split( + PERMALINK_POSTNAME_REGEX + ); return { prefix, @@ -1144,9 +1233,9 @@ export function inSomeHistory( state, predicate ) { return false; } - return optimist.some( ( { beforeState } ) => ( - beforeState && predicate( beforeState ) - ) ); + return optimist.some( + ( { beforeState } ) => beforeState && predicate( beforeState ) + ); } /** @@ -1223,7 +1312,10 @@ export function getActivePostLock( state ) { * @return {boolean} Whether the user can or can't post unfiltered HTML. */ export function canUserUseUnfilteredHTML( state ) { - return has( getCurrentPost( state ), [ '_links', 'wp:action-unfiltered-html' ] ); + return has( getCurrentPost( state ), [ + '_links', + 'wp:action-unfiltered-html', + ] ); } /** @@ -1309,8 +1401,8 @@ export function getEditorSettings( state ) { function getBlockEditorSelector( name ) { return createRegistrySelector( ( select ) => ( state, ...args ) => { - deprecated( '`wp.data.select( \'core/editor\' ).' + name + '`', { - alternative: '`wp.data.select( \'core/block-editor\' ).' + name + '`', + deprecated( "`wp.data.select( 'core/editor' )." + name + '`', { + alternative: "`wp.data.select( 'core/block-editor' )." + name + '`', } ); return select( 'core/block-editor' )[ name ]( ...args ); @@ -1330,7 +1422,9 @@ export const isBlockValid = getBlockEditorSelector( 'isBlockValid' ); /** * @see getBlockAttributes in core/block-editor store. */ -export const getBlockAttributes = getBlockEditorSelector( 'getBlockAttributes' ); +export const getBlockAttributes = getBlockEditorSelector( + 'getBlockAttributes' +); /** * @see getBlock in core/block-editor store. @@ -1345,27 +1439,37 @@ export const getBlocks = getBlockEditorSelector( 'getBlocks' ); /** * @see __unstableGetBlockWithoutInnerBlocks in core/block-editor store. */ -export const __unstableGetBlockWithoutInnerBlocks = getBlockEditorSelector( '__unstableGetBlockWithoutInnerBlocks' ); +export const __unstableGetBlockWithoutInnerBlocks = getBlockEditorSelector( + '__unstableGetBlockWithoutInnerBlocks' +); /** * @see getClientIdsOfDescendants in core/block-editor store. */ -export const getClientIdsOfDescendants = getBlockEditorSelector( 'getClientIdsOfDescendants' ); +export const getClientIdsOfDescendants = getBlockEditorSelector( + 'getClientIdsOfDescendants' +); /** * @see getClientIdsWithDescendants in core/block-editor store. */ -export const getClientIdsWithDescendants = getBlockEditorSelector( 'getClientIdsWithDescendants' ); +export const getClientIdsWithDescendants = getBlockEditorSelector( + 'getClientIdsWithDescendants' +); /** * @see getGlobalBlockCount in core/block-editor store. */ -export const getGlobalBlockCount = getBlockEditorSelector( 'getGlobalBlockCount' ); +export const getGlobalBlockCount = getBlockEditorSelector( + 'getGlobalBlockCount' +); /** * @see getBlocksByClientId in core/block-editor store. */ -export const getBlocksByClientId = getBlockEditorSelector( 'getBlocksByClientId' ); +export const getBlocksByClientId = getBlockEditorSelector( + 'getBlocksByClientId' +); /** * @see getBlockCount in core/block-editor store. @@ -1375,17 +1479,23 @@ export const getBlockCount = getBlockEditorSelector( 'getBlockCount' ); /** * @see getBlockSelectionStart in core/block-editor store. */ -export const getBlockSelectionStart = getBlockEditorSelector( 'getBlockSelectionStart' ); +export const getBlockSelectionStart = getBlockEditorSelector( + 'getBlockSelectionStart' +); /** * @see getBlockSelectionEnd in core/block-editor store. */ -export const getBlockSelectionEnd = getBlockEditorSelector( 'getBlockSelectionEnd' ); +export const getBlockSelectionEnd = getBlockEditorSelector( + 'getBlockSelectionEnd' +); /** * @see getSelectedBlockCount in core/block-editor store. */ -export const getSelectedBlockCount = getBlockEditorSelector( 'getSelectedBlockCount' ); +export const getSelectedBlockCount = getBlockEditorSelector( + 'getSelectedBlockCount' +); /** * @see hasSelectedBlock in core/block-editor store. @@ -1395,7 +1505,9 @@ export const hasSelectedBlock = getBlockEditorSelector( 'hasSelectedBlock' ); /** * @see getSelectedBlockClientId in core/block-editor store. */ -export const getSelectedBlockClientId = getBlockEditorSelector( 'getSelectedBlockClientId' ); +export const getSelectedBlockClientId = getBlockEditorSelector( + 'getSelectedBlockClientId' +); /** * @see getSelectedBlock in core/block-editor store. @@ -1405,77 +1517,107 @@ export const getSelectedBlock = getBlockEditorSelector( 'getSelectedBlock' ); /** * @see getBlockRootClientId in core/block-editor store. */ -export const getBlockRootClientId = getBlockEditorSelector( 'getBlockRootClientId' ); +export const getBlockRootClientId = getBlockEditorSelector( + 'getBlockRootClientId' +); /** * @see getBlockHierarchyRootClientId in core/block-editor store. */ -export const getBlockHierarchyRootClientId = getBlockEditorSelector( 'getBlockHierarchyRootClientId' ); +export const getBlockHierarchyRootClientId = getBlockEditorSelector( + 'getBlockHierarchyRootClientId' +); /** * @see getAdjacentBlockClientId in core/block-editor store. */ -export const getAdjacentBlockClientId = getBlockEditorSelector( 'getAdjacentBlockClientId' ); +export const getAdjacentBlockClientId = getBlockEditorSelector( + 'getAdjacentBlockClientId' +); /** * @see getPreviousBlockClientId in core/block-editor store. */ -export const getPreviousBlockClientId = getBlockEditorSelector( 'getPreviousBlockClientId' ); +export const getPreviousBlockClientId = getBlockEditorSelector( + 'getPreviousBlockClientId' +); /** * @see getNextBlockClientId in core/block-editor store. */ -export const getNextBlockClientId = getBlockEditorSelector( 'getNextBlockClientId' ); +export const getNextBlockClientId = getBlockEditorSelector( + 'getNextBlockClientId' +); /** * @see getSelectedBlocksInitialCaretPosition in core/block-editor store. */ -export const getSelectedBlocksInitialCaretPosition = getBlockEditorSelector( 'getSelectedBlocksInitialCaretPosition' ); +export const getSelectedBlocksInitialCaretPosition = getBlockEditorSelector( + 'getSelectedBlocksInitialCaretPosition' +); /** * @see getMultiSelectedBlockClientIds in core/block-editor store. */ -export const getMultiSelectedBlockClientIds = getBlockEditorSelector( 'getMultiSelectedBlockClientIds' ); +export const getMultiSelectedBlockClientIds = getBlockEditorSelector( + 'getMultiSelectedBlockClientIds' +); /** * @see getMultiSelectedBlocks in core/block-editor store. */ -export const getMultiSelectedBlocks = getBlockEditorSelector( 'getMultiSelectedBlocks' ); +export const getMultiSelectedBlocks = getBlockEditorSelector( + 'getMultiSelectedBlocks' +); /** * @see getFirstMultiSelectedBlockClientId in core/block-editor store. */ -export const getFirstMultiSelectedBlockClientId = getBlockEditorSelector( 'getFirstMultiSelectedBlockClientId' ); +export const getFirstMultiSelectedBlockClientId = getBlockEditorSelector( + 'getFirstMultiSelectedBlockClientId' +); /** * @see getLastMultiSelectedBlockClientId in core/block-editor store. */ -export const getLastMultiSelectedBlockClientId = getBlockEditorSelector( 'getLastMultiSelectedBlockClientId' ); +export const getLastMultiSelectedBlockClientId = getBlockEditorSelector( + 'getLastMultiSelectedBlockClientId' +); /** * @see isFirstMultiSelectedBlock in core/block-editor store. */ -export const isFirstMultiSelectedBlock = getBlockEditorSelector( 'isFirstMultiSelectedBlock' ); +export const isFirstMultiSelectedBlock = getBlockEditorSelector( + 'isFirstMultiSelectedBlock' +); /** * @see isBlockMultiSelected in core/block-editor store. */ -export const isBlockMultiSelected = getBlockEditorSelector( 'isBlockMultiSelected' ); +export const isBlockMultiSelected = getBlockEditorSelector( + 'isBlockMultiSelected' +); /** * @see isAncestorMultiSelected in core/block-editor store. */ -export const isAncestorMultiSelected = getBlockEditorSelector( 'isAncestorMultiSelected' ); +export const isAncestorMultiSelected = getBlockEditorSelector( + 'isAncestorMultiSelected' +); /** * @see getMultiSelectedBlocksStartClientId in core/block-editor store. */ -export const getMultiSelectedBlocksStartClientId = getBlockEditorSelector( 'getMultiSelectedBlocksStartClientId' ); +export const getMultiSelectedBlocksStartClientId = getBlockEditorSelector( + 'getMultiSelectedBlocksStartClientId' +); /** * @see getMultiSelectedBlocksEndClientId in core/block-editor store. */ -export const getMultiSelectedBlocksEndClientId = getBlockEditorSelector( 'getMultiSelectedBlocksEndClientId' ); +export const getMultiSelectedBlocksEndClientId = getBlockEditorSelector( + 'getMultiSelectedBlocksEndClientId' +); /** * @see getBlockOrder in core/block-editor store. @@ -1495,12 +1637,16 @@ export const isBlockSelected = getBlockEditorSelector( 'isBlockSelected' ); /** * @see hasSelectedInnerBlock in core/block-editor store. */ -export const hasSelectedInnerBlock = getBlockEditorSelector( 'hasSelectedInnerBlock' ); +export const hasSelectedInnerBlock = getBlockEditorSelector( + 'hasSelectedInnerBlock' +); /** * @see isBlockWithinSelection in core/block-editor store. */ -export const isBlockWithinSelection = getBlockEditorSelector( 'isBlockWithinSelection' ); +export const isBlockWithinSelection = getBlockEditorSelector( + 'isBlockWithinSelection' +); /** * @see hasMultiSelection in core/block-editor store. @@ -1515,7 +1661,9 @@ export const isMultiSelecting = getBlockEditorSelector( 'isMultiSelecting' ); /** * @see isSelectionEnabled in core/block-editor store. */ -export const isSelectionEnabled = getBlockEditorSelector( 'isSelectionEnabled' ); +export const isSelectionEnabled = getBlockEditorSelector( + 'isSelectionEnabled' +); /** * @see getBlockMode in core/block-editor store. @@ -1530,17 +1678,23 @@ export const isTyping = getBlockEditorSelector( 'isTyping' ); /** * @see isCaretWithinFormattedText in core/block-editor store. */ -export const isCaretWithinFormattedText = getBlockEditorSelector( 'isCaretWithinFormattedText' ); +export const isCaretWithinFormattedText = getBlockEditorSelector( + 'isCaretWithinFormattedText' +); /** * @see getBlockInsertionPoint in core/block-editor store. */ -export const getBlockInsertionPoint = getBlockEditorSelector( 'getBlockInsertionPoint' ); +export const getBlockInsertionPoint = getBlockEditorSelector( + 'getBlockInsertionPoint' +); /** * @see isBlockInsertionPointVisible in core/block-editor store. */ -export const isBlockInsertionPointVisible = getBlockEditorSelector( 'isBlockInsertionPointVisible' ); +export const isBlockInsertionPointVisible = getBlockEditorSelector( + 'isBlockInsertionPointVisible' +); /** * @see isValidTemplate in core/block-editor store. @@ -1560,7 +1714,9 @@ export const getTemplateLock = getBlockEditorSelector( 'getTemplateLock' ); /** * @see canInsertBlockType in core/block-editor store. */ -export const canInsertBlockType = getBlockEditorSelector( 'canInsertBlockType' ); +export const canInsertBlockType = getBlockEditorSelector( + 'canInsertBlockType' +); /** * @see getInserterItems in core/block-editor store. @@ -1575,4 +1731,6 @@ export const hasInserterItems = getBlockEditorSelector( 'hasInserterItems' ); /** * @see getBlockListSettings in core/block-editor store. */ -export const getBlockListSettings = getBlockEditorSelector( 'getBlockListSettings' ); +export const getBlockListSettings = getBlockEditorSelector( + 'getBlockListSettings' +); diff --git a/packages/editor/src/store/selectors.native.js b/packages/editor/src/store/selectors.native.js index ae84e7b5afe5a1..f60b2efa41f38a 100644 --- a/packages/editor/src/store/selectors.native.js +++ b/packages/editor/src/store/selectors.native.js @@ -33,23 +33,26 @@ export function isPostTitleSelected( state ) { * * @return {boolean} Whether the post can be autosaved. */ -export const isEditedPostAutosaveable = createRegistrySelector( ( ) => function( state ) { - // A post must contain a title, an excerpt, or non-empty content to be valid for autosaving. - if ( ! isEditedPostSaveable( state ) ) { - return false; - } +export const isEditedPostAutosaveable = createRegistrySelector( + () => + function( state ) { + // A post must contain a title, an excerpt, or non-empty content to be valid for autosaving. + if ( ! isEditedPostSaveable( state ) ) { + return false; + } - // To avoid an expensive content serialization, use the content dirtiness - // flag in place of content field comparison against the known autosave. - // This is not strictly accurate, and relies on a tolerance toward autosave - // request failures for unnecessary saves. - if ( hasChangedContent( state ) ) { - return true; - } + // To avoid an expensive content serialization, use the content dirtiness + // flag in place of content field comparison against the known autosave. + // This is not strictly accurate, and relies on a tolerance toward autosave + // request failures for unnecessary saves. + if ( hasChangedContent( state ) ) { + return true; + } - if ( isEditedPostDirty( state ) ) { - return true; - } + if ( isEditedPostDirty( state ) ) { + return true; + } - return false; -} ); + return false; + } +); diff --git a/packages/editor/src/store/test/actions.js b/packages/editor/src/store/test/actions.js index e1a4dbcbbe4648..c7359b90134afd 100644 --- a/packages/editor/src/store/test/actions.js +++ b/packages/editor/src/store/test/actions.js @@ -16,14 +16,16 @@ import { jest.mock( '@wordpress/data-controls' ); select.mockImplementation( ( ...args ) => { - const { select: actualSelect } = jest - .requireActual( '@wordpress/data-controls' ); + const { select: actualSelect } = jest.requireActual( + '@wordpress/data-controls' + ); return actualSelect( ...args ); } ); dispatch.mockImplementation( ( ...args ) => { - const { dispatch: actualDispatch } = jest - .requireActual( '@wordpress/data-controls' ); + const { dispatch: actualDispatch } = jest.requireActual( + '@wordpress/data-controls' + ); return actualDispatch( ...args ); } ); @@ -37,8 +39,9 @@ const apiFetchThrowError = ( error ) => { const apiFetchDoActual = () => { apiFetch.mockClear(); apiFetch.mockImplementation( ( ...args ) => { - const { apiFetch: fetch } = jest - .requireActual( '@wordpress/data-controls' ); + const { apiFetch: fetch } = jest.requireActual( + '@wordpress/data-controls' + ); return fetch( ...args ); } ); }; @@ -55,10 +58,7 @@ const postTypeSlug = 'post'; describe( 'Post generator actions', () => { describe( 'savePost()', () => { - let fulfillment, - currentPost, - currentPostStatus, - isAutosave; + let fulfillment, currentPost, currentPostStatus, isAutosave; beforeEach( () => { currentPost = () => ( { id: postId, @@ -69,9 +69,8 @@ describe( 'Post generator actions', () => { status: currentPostStatus, } ); } ); - const reset = ( isAutosaving ) => fulfillment = actions.savePost( - { isAutosave: isAutosaving } - ); + const reset = ( isAutosaving ) => + ( fulfillment = actions.savePost( { isAutosave: isAutosaving } ) ); const testConditions = [ [ 'yields an action for checking if the post is saveable', @@ -79,7 +78,9 @@ describe( 'Post generator actions', () => { () => { reset( isAutosave ); const { value } = fulfillment.next(); - expect( value ).toEqual( select( STORE_KEY, 'isEditedPostSaveable' ) ); + expect( value ).toEqual( + select( STORE_KEY, 'isEditedPostSaveable' ) + ); }, ], [ @@ -87,7 +88,9 @@ describe( 'Post generator actions', () => { () => true, () => { const { value } = fulfillment.next( true ); - expect( value ).toEqual( select( STORE_KEY, 'getEditedPostContent' ) ); + expect( value ).toEqual( + select( STORE_KEY, 'getEditedPostContent' ) + ); }, ], [ @@ -98,7 +101,9 @@ describe( 'Post generator actions', () => { const edits = { content: currentPost().content }; const { value } = fulfillment.next( edits.content ); expect( value ).toEqual( - dispatch( STORE_KEY, 'editPost', edits, { undoIgnore: true } ) + dispatch( STORE_KEY, 'editPost', edits, { + undoIgnore: true, + } ) ); } }, @@ -119,7 +124,9 @@ describe( 'Post generator actions', () => { () => true, () => { const { value } = fulfillment.next(); - expect( value ).toEqual( select( STORE_KEY, 'getCurrentPost' ) ); + expect( value ).toEqual( + select( STORE_KEY, 'getCurrentPost' ) + ); }, ], [ @@ -192,7 +199,9 @@ describe( 'Post generator actions', () => { () => true, () => { const { value } = fulfillment.next(); - expect( value ).toEqual( select( STORE_KEY, 'getCurrentPost' ) ); + expect( value ).toEqual( + select( STORE_KEY, 'getCurrentPost' ) + ); }, ], [ @@ -201,7 +210,9 @@ describe( 'Post generator actions', () => { () => { const post = currentPost(); const { value } = fulfillment.next( post ); - expect( value ).toEqual( select( 'core', 'getPostType', post.type ) ); + expect( value ).toEqual( + select( 'core', 'getPostType', post.type ) + ); }, ], [ @@ -298,7 +309,7 @@ describe( 'Post generator actions', () => { describe( 'trashPost()', () => { let fulfillment; const currentPost = { id: 10, content: 'foo', status: 'publish' }; - const reset = () => fulfillment = actions.trashPost(); + const reset = () => ( fulfillment = actions.trashPost() ); const rewind = () => { reset(); fulfillment.next(); @@ -306,51 +317,52 @@ describe( 'Post generator actions', () => { fulfillment.next( postType ); fulfillment.next(); }; - it( 'yields expected action for selecting the current post type slug', - () => { - reset(); - const { value } = fulfillment.next(); - expect( value ).toEqual( select( - STORE_KEY, - 'getCurrentPostType', - ) ); - } - ); + it( 'yields expected action for selecting the current post type slug', () => { + reset(); + const { value } = fulfillment.next(); + expect( value ).toEqual( + select( STORE_KEY, 'getCurrentPostType' ) + ); + } ); it( 'yields expected action for selecting the post type object', () => { const { value } = fulfillment.next( postTypeSlug ); - expect( value ).toEqual( select( - 'core', - 'getPostType', - postTypeSlug - ) ); - } ); - it( 'yields expected action for dispatching removing the trash notice ' + - 'for the post', () => { - const { value } = fulfillment.next( postType ); - expect( value ).toEqual( dispatch( - 'core/notices', - 'removeNotice', - TRASH_POST_NOTICE_ID - ) ); + expect( value ).toEqual( + select( 'core', 'getPostType', postTypeSlug ) + ); } ); + it( + 'yields expected action for dispatching removing the trash notice ' + + 'for the post', + () => { + const { value } = fulfillment.next( postType ); + expect( value ).toEqual( + dispatch( + 'core/notices', + 'removeNotice', + TRASH_POST_NOTICE_ID + ) + ); + } + ); it( 'yields expected action for selecting the currentPost', () => { const { value } = fulfillment.next(); - expect( value ).toEqual( select( - STORE_KEY, - 'getCurrentPost' - ) ); + expect( value ).toEqual( select( STORE_KEY, 'getCurrentPost' ) ); } ); describe( 'expected yields when fetch throws an error', () => { it( 'yields expected action for dispatching an error notice', () => { const error = { foo: 'bar', code: 'fail' }; apiFetchThrowError( error ); const { value } = fulfillment.next( currentPost ); - expect( value ).toEqual( dispatch( - 'core/notices', - 'createErrorNotice', - 'Trashing failed', - { id: TRASH_POST_NOTICE_ID }, - ) ); + expect( value ).toEqual( + dispatch( + 'core/notices', + 'createErrorNotice', + 'Trashing failed', + { + id: TRASH_POST_NOTICE_ID, + } + ) + ); } ); } ); describe( 'expected yields when fetch does not throw an error', () => { @@ -358,65 +370,56 @@ describe( 'Post generator actions', () => { apiFetchDoActual(); rewind(); const { value } = fulfillment.next( currentPost ); - expect( value ).toEqual( apiFetch( - { + expect( value ).toEqual( + apiFetch( { path: `/wp/v2/${ postType.rest_base }/${ currentPost.id }`, method: 'DELETE', - } - ) ); + } ) + ); } ); it( 'yields expected dispatch action for saving the post', () => { const { value } = fulfillment.next(); - expect( value ).toEqual( dispatch( - STORE_KEY, - 'savePost', - ) ); + expect( value ).toEqual( dispatch( STORE_KEY, 'savePost' ) ); } ); } ); } ); describe( 'refreshPost()', () => { let fulfillment; const currentPost = { id: 10, content: 'foo' }; - const reset = () => fulfillment = actions.refreshPost(); + const reset = () => ( fulfillment = actions.refreshPost() ); it( 'yields expected action for selecting the currentPost', () => { reset(); const { value } = fulfillment.next(); - expect( value ).toEqual( select( - STORE_KEY, - 'getCurrentPost', - ) ); + expect( value ).toEqual( select( STORE_KEY, 'getCurrentPost' ) ); } ); it( 'yields expected action for selecting the current post type', () => { const { value } = fulfillment.next( currentPost ); - expect( value ).toEqual( select( - STORE_KEY, - 'getCurrentPostType' - ) ); + expect( value ).toEqual( + select( STORE_KEY, 'getCurrentPostType' ) + ); } ); it( 'yields expected action for selecting the post type object', () => { const { value } = fulfillment.next( postTypeSlug ); - expect( value ).toEqual( select( - 'core', - 'getPostType', - postTypeSlug - ) ); + expect( value ).toEqual( + select( 'core', 'getPostType', postTypeSlug ) + ); } ); it( 'yields expected action for the api fetch call', () => { const { value } = fulfillment.next( postType ); apiFetchDoActual(); // since the timestamp is a computed value we can't do a direct comparison. // so we'll just see if the path has most of the value. - expect( value.request.path ).toEqual( expect.stringContaining( - `/wp/v2/${ postType.rest_base }/${ currentPost.id }?context=edit&_timestamp=` - ) ); + expect( value.request.path ).toEqual( + expect.stringContaining( + `/wp/v2/${ postType.rest_base }/${ currentPost.id }?context=edit&_timestamp=` + ) + ); } ); it( 'yields expected action for dispatching the reset of the post', () => { const { value } = fulfillment.next( currentPost ); - expect( value ).toEqual( dispatch( - STORE_KEY, - 'resetPost', - currentPost - ) ); + expect( value ).toEqual( + dispatch( STORE_KEY, 'resetPost', currentPost ) + ); } ); } ); } ); @@ -426,12 +429,8 @@ describe( 'Editor actions', () => { const post = { content: { raw: '' }, status: 'publish' }; let fulfillment; - const reset = ( edits, template ) => fulfillment = actions - .setupEditor( - post, - edits, - template, - ); + const reset = ( edits, template ) => + ( fulfillment = actions.setupEditor( post, edits, template ) ); beforeAll( () => { reset(); } ); @@ -570,10 +569,12 @@ describe( 'Editor actions', () => { } ); it( 'should take an optional id argument', () => { - expect( actions.__experimentalFetchReusableBlocks( 123 ) ).toEqual( { - type: 'FETCH_REUSABLE_BLOCKS', - id: 123, - } ); + expect( actions.__experimentalFetchReusableBlocks( 123 ) ).toEqual( + { + type: 'FETCH_REUSABLE_BLOCKS', + id: 123, + } + ); } ); } ); @@ -588,17 +589,21 @@ describe( 'Editor actions', () => { describe( 'deleteReusableBlock', () => { it( 'should return the DELETE_REUSABLE_BLOCK action', () => { - expect( actions.__experimentalDeleteReusableBlock( 123 ) ).toEqual( { - type: 'DELETE_REUSABLE_BLOCK', - id: 123, - } ); + expect( actions.__experimentalDeleteReusableBlock( 123 ) ).toEqual( + { + type: 'DELETE_REUSABLE_BLOCK', + id: 123, + } + ); } ); } ); describe( 'convertBlockToStatic', () => { it( 'should return the CONVERT_BLOCK_TO_STATIC action', () => { const clientId = '358b59ee-bab3-4d6f-8445-e8c6971a5605'; - expect( actions.__experimentalConvertBlockToStatic( clientId ) ).toEqual( { + expect( + actions.__experimentalConvertBlockToStatic( clientId ) + ).toEqual( { type: 'CONVERT_BLOCK_TO_STATIC', clientId, } ); @@ -608,7 +613,9 @@ describe( 'Editor actions', () => { describe( 'convertBlockToReusable', () => { it( 'should return the CONVERT_BLOCK_TO_REUSABLE action', () => { const clientId = '358b59ee-bab3-4d6f-8445-e8c6971a5605'; - expect( actions.__experimentalConvertBlockToReusable( clientId ) ).toEqual( { + expect( + actions.__experimentalConvertBlockToReusable( clientId ) + ).toEqual( { type: 'CONVERT_BLOCK_TO_REUSABLE', clientIds: [ clientId ], } ); diff --git a/packages/editor/src/store/test/actions.native.js b/packages/editor/src/store/test/actions.native.js index b39086244d98de..577a38e95fe8eb 100644 --- a/packages/editor/src/store/test/actions.native.js +++ b/packages/editor/src/store/test/actions.native.js @@ -1,4 +1,3 @@ - /** * Internal dependencies */ diff --git a/packages/editor/src/store/test/reducer.js b/packages/editor/src/store/test/reducer.js index 56428d44855b99..219fe26c7406cb 100644 --- a/packages/editor/src/store/test/reducer.js +++ b/packages/editor/src/store/test/reducer.js @@ -52,7 +52,9 @@ describe( 'state', () => { }, }; - expect( isUpdatingSamePostProperty( action, previousAction ) ).toBe( false ); + expect( isUpdatingSamePostProperty( action, previousAction ) ).toBe( + false + ); } ); it( 'should return false if not editing the same post properties', () => { @@ -69,7 +71,9 @@ describe( 'state', () => { }, }; - expect( isUpdatingSamePostProperty( action, previousAction ) ).toBe( false ); + expect( isUpdatingSamePostProperty( action, previousAction ) ).toBe( + false + ); } ); it( 'should return true if updating the same post properties', () => { @@ -86,7 +90,9 @@ describe( 'state', () => { }, }; - expect( isUpdatingSamePostProperty( action, previousAction ) ).toBe( true ); + expect( isUpdatingSamePostProperty( action, previousAction ) ).toBe( + true + ); } ); } ); @@ -100,7 +106,9 @@ describe( 'state', () => { }; const previousAction = undefined; - expect( shouldOverwriteState( action, previousAction ) ).toBe( false ); + expect( shouldOverwriteState( action, previousAction ) ).toBe( + false + ); } ); it( 'should return false if the action types are different', () => { @@ -117,7 +125,9 @@ describe( 'state', () => { }, }; - expect( shouldOverwriteState( action, previousAction ) ).toBe( false ); + expect( shouldOverwriteState( action, previousAction ) ).toBe( + false + ); } ); it( 'should return true if updating same post property', () => { @@ -134,7 +144,9 @@ describe( 'state', () => { }, }; - expect( shouldOverwriteState( action, previousAction ) ).toBe( true ); + expect( shouldOverwriteState( action, previousAction ) ).toBe( + true + ); } ); } ); @@ -162,7 +174,7 @@ describe( 'state', () => { } ); it( 'should disable the publish sidebar', () => { - const original = deepFreeze( preferences( undefined, { } ) ); + const original = deepFreeze( preferences( undefined, {} ) ); const state = preferences( original, { type: 'DISABLE_PUBLISH_SIDEBAR', } ); @@ -171,7 +183,9 @@ describe( 'state', () => { } ); it( 'should enable the publish sidebar', () => { - const original = deepFreeze( preferences( { isPublishSidebarEnabled: false }, { } ) ); + const original = deepFreeze( + preferences( { isPublishSidebarEnabled: false }, {} ) + ); const state = preferences( original, { type: 'ENABLE_PUBLISH_SIDEBAR', } ); @@ -204,13 +218,18 @@ describe( 'state', () => { } ); it( 'should add received reusable blocks', () => { - const state = reusableBlocks( {}, { - type: 'RECEIVE_REUSABLE_BLOCKS', - results: [ { - id: 123, - title: 'My cool block', - } ], - } ); + const state = reusableBlocks( + {}, + { + type: 'RECEIVE_REUSABLE_BLOCKS', + results: [ + { + id: 123, + title: 'My cool block', + }, + ], + } + ); expect( state ).toEqual( { data: { diff --git a/packages/editor/src/store/test/reducer.native.js b/packages/editor/src/store/test/reducer.native.js index d97ca0d9e5c9df..ba691a98513d93 100644 --- a/packages/editor/src/store/test/reducer.native.js +++ b/packages/editor/src/store/test/reducer.native.js @@ -1,9 +1,7 @@ /** * Internal dependencies */ -import { - postTitle, -} from '../reducer'; +import { postTitle } from '../reducer'; describe( 'state native', () => { describe( 'postTitle', () => { @@ -18,7 +16,9 @@ describe( 'state native', () => { isSelected: false, }; - expect( postTitle( { isSelected: true }, action ).isSelected ).toBe( false ); + expect( + postTitle( { isSelected: true }, action ).isSelected + ).toBe( false ); } ); it( 'should return true if selecting the post title', () => { @@ -27,7 +27,9 @@ describe( 'state native', () => { isSelected: true, }; - expect( postTitle( { isSelected: false }, action ).isSelected ).toBe( true ); + expect( + postTitle( { isSelected: false }, action ).isSelected + ).toBe( true ); } ); } ); } ); diff --git a/packages/editor/src/store/test/selectors.js b/packages/editor/src/store/test/selectors.js index a6608f28b6cd68..cfa7833ad4202e 100644 --- a/packages/editor/src/store/test/selectors.js +++ b/packages/editor/src/store/test/selectors.js @@ -51,7 +51,8 @@ selectorNames.forEach( ( name ) => { }; } - const { value: blocks, isDirty } = ( present && present.blocks ) || {}; + const { value: blocks, isDirty } = + ( present && present.blocks ) || {}; if ( blocks && isDirty !== false ) { edits = { ...edits, @@ -86,13 +87,17 @@ selectorNames.forEach( ( name ) => { hasUndo() { return Boolean( - state.editor && state.editor.past && state.editor.past.length + state.editor && + state.editor.past && + state.editor.past.length ); }, hasRedo() { return Boolean( - state.editor && state.editor.future && state.editor.future.length + state.editor && + state.editor.future && + state.editor.future.length ); }, @@ -117,7 +122,8 @@ selectorNames.forEach( ( name ) => { return _selectors[ name ]( state, ...args ); }; - selectors[ name ].isRegistrySelector = _selectors[ name ].isRegistrySelector; + selectors[ name ].isRegistrySelector = + _selectors[ name ].isRegistrySelector; if ( selectors[ name ].isRegistrySelector ) { selectors[ name ].registry = { select: () => _selectors[ name ].registry.select(), @@ -264,9 +270,7 @@ describe( 'selectors', () => { it( 'should return true when the past history is not empty', () => { const state = { editor: { - past: [ - {}, - ], + past: [ {} ], }, }; @@ -288,9 +292,7 @@ describe( 'selectors', () => { it( 'should return true when the future history is not empty', () => { const state = { editor: { - future: [ - {}, - ], + future: [ {} ], }, }; @@ -508,7 +510,9 @@ describe( 'selectors', () => { __experimentalEnableFullSiteEditing: true, }, getEntityRecordChangesByRecord() { - return { postType: { post: { 1: {} }, wp_template: { 1: {} } } }; + return { + postType: { post: { 1: {} }, wp_template: { 1: {} } }, + }; }, }; expect( hasNonPostEntityChanges( state ) ).toBe( true ); @@ -628,7 +632,9 @@ describe( 'selectors', () => { currentPost: {}, }; - expect( getCurrentPostAttribute( state, 'valueOf' ) ).toBeUndefined(); + expect( + getCurrentPostAttribute( state, 'valueOf' ) + ).toBeUndefined(); } ); it( 'should return the value of an attribute', () => { @@ -638,7 +644,9 @@ describe( 'selectors', () => { }, }; - expect( getCurrentPostAttribute( state, 'title' ) ).toBe( 'Hello World' ); + expect( getCurrentPostAttribute( state, 'title' ) ).toBe( + 'Hello World' + ); } ); } ); @@ -654,7 +662,9 @@ describe( 'selectors', () => { initialEdits: {}, }; - expect( getEditedPostAttribute( state, 'slug' ) ).toBe( 'post slug' ); + expect( getEditedPostAttribute( state, 'slug' ) ).toBe( + 'post slug' + ); } ); it( 'should return the latest slug if edits have been made to the post', () => { @@ -670,7 +680,9 @@ describe( 'selectors', () => { initialEdits: {}, }; - expect( getEditedPostAttribute( state, 'slug' ) ).toBe( 'new slug' ); + expect( getEditedPostAttribute( state, 'slug' ) ).toBe( + 'new slug' + ); } ); it( 'should return the post saved title if the title is not edited', () => { @@ -716,7 +728,9 @@ describe( 'selectors', () => { initialEdits: {}, }; - expect( getEditedPostAttribute( state, 'valueOf' ) ).toBeUndefined(); + expect( + getEditedPostAttribute( state, 'valueOf' ) + ).toBeUndefined(); } ); it( 'should merge mergeable properties with current post value', () => { @@ -1559,7 +1573,10 @@ describe( 'selectors', () => { it( 'should return true if title or excerpt have changed', () => { for ( const variantField of [ 'title', 'excerpt' ] ) { - for ( const constantField of without( [ 'title', 'excerpt' ], variantField ) ) { + for ( const constantField of without( + [ 'title', 'excerpt' ], + variantField + ) ) { const state = { editor: { present: { @@ -1628,14 +1645,16 @@ describe( 'selectors', () => { editor: { present: { blocks: { - value: [ { - clientId: 123, - name: 'core/test-block-a', - isValid: true, - attributes: { - text: '', + value: [ + { + clientId: 123, + name: 'core/test-block-a', + isValid: true, + attributes: { + text: '', + }, }, - } ], + ], }, edits: {}, }, @@ -1657,13 +1676,15 @@ describe( 'selectors', () => { editor: { present: { blocks: { - value: [ { - clientId: 'block1', - name: 'core/test-default', - attributes: { - modified: false, + value: [ + { + clientId: 'block1', + name: 'core/test-default', + attributes: { + modified: false, + }, }, - } ], + ], }, edits: {}, }, @@ -1680,14 +1701,16 @@ describe( 'selectors', () => { editor: { present: { blocks: { - value: [ { - clientId: 123, - name: 'core/test-block-a', - isValid: true, - attributes: { - text: '', + value: [ + { + clientId: 123, + name: 'core/test-block-a', + isValid: true, + attributes: { + text: '', + }, }, - } ], + ], }, edits: { content: '', @@ -1746,14 +1769,16 @@ describe( 'selectors', () => { editor: { present: { blocks: { - value: [ { - clientId: 123, - name: 'core/test-freeform', - isValid: true, - attributes: { - content: '', + value: [ + { + clientId: 123, + name: 'core/test-freeform', + isValid: true, + attributes: { + content: '', + }, }, - } ], + ], }, edits: {}, }, @@ -1770,14 +1795,16 @@ describe( 'selectors', () => { editor: { present: { blocks: { - value: [ { - clientId: 123, - name: 'core/test-freeform', - isValid: true, - attributes: { - content: '', + value: [ + { + clientId: 123, + name: 'core/test-freeform', + isValid: true, + attributes: { + content: '', + }, }, - } ], + ], }, edits: {}, }, @@ -1796,14 +1823,16 @@ describe( 'selectors', () => { editor: { present: { blocks: { - value: [ { - clientId: 123, - name: 'core/test-freeform', - isValid: true, - attributes: { - content: 'Test Data', + value: [ + { + clientId: 123, + name: 'core/test-freeform', + isValid: true, + attributes: { + content: 'Test Data', + }, }, - } ], + ], }, edits: {}, }, @@ -1856,7 +1885,7 @@ describe( 'selectors', () => { describe( 'isEditedPostBeingScheduled', () => { it( 'should return true for posts with a future date', () => { - const time = Date.now() + ( 1000 * 3600 * 24 * 7 ); // 7 days in the future + const time = Date.now() + 1000 * 3600 * 24 * 7; // 7 days in the future const date = new Date( time ); const state = { editor: { @@ -2275,7 +2304,7 @@ describe( 'selectors', () => { expect( content ).toBe( '<!-- wp:block /-->' ); } ); - it( 'returns removep\'d serialization of blocks for single unknown', () => { + it( "returns removep'd serialization of blocks for single unknown", () => { const unknownBlock = createBlock( 'core/test-freeform', { content: '<p>foo</p>', } ); @@ -2297,7 +2326,7 @@ describe( 'selectors', () => { expect( content ).toBe( 'foo' ); } ); - it( 'returns non-removep\'d serialization of blocks for multiple unknown', () => { + it( "returns non-removep'd serialization of blocks for multiple unknown", () => { const firstUnknown = createBlock( 'core/test-freeform', { content: '<p>foo</p>', } ); @@ -2348,13 +2377,15 @@ describe( 'selectors', () => { editor: { present: { blocks: { - value: [ { - ...defaultBlock, - attributes: { - ...defaultBlock.attributes, - modified: true, + value: [ + { + ...defaultBlock, + attributes: { + ...defaultBlock.attributes, + modified: true, + }, }, - } ], + ], }, edits: {}, }, @@ -2365,7 +2396,9 @@ describe( 'selectors', () => { const content = getEditedPostContent( state ); - expect( content ).toBe( '<!-- wp:test-default {\"modified\":true} /-->' ); + expect( content ).toBe( + '<!-- wp:test-default {"modified":true} /-->' + ); } ); } ); @@ -2457,7 +2490,9 @@ describe( 'selectors', () => { isPublishSidebarEnabled: true, }, }; - expect( isPublishSidebarEnabled( state ) ).toBe( state.preferences.isPublishSidebarEnabled ); + expect( isPublishSidebarEnabled( state ) ).toBe( + state.preferences.isPublishSidebarEnabled + ); } ); it( 'should return the value on state if it is falsy', () => { @@ -2466,14 +2501,18 @@ describe( 'selectors', () => { isPublishSidebarEnabled: false, }, }; - expect( isPublishSidebarEnabled( state ) ).toBe( state.preferences.isPublishSidebarEnabled ); + expect( isPublishSidebarEnabled( state ) ).toBe( + state.preferences.isPublishSidebarEnabled + ); } ); it( 'should return the default value if there is no isPublishSidebarEnabled key on state', () => { const state = { - preferences: { }, + preferences: {}, }; - expect( isPublishSidebarEnabled( state ) ).toBe( PREFERENCES_DEFAULTS.isPublishSidebarEnabled ); + expect( isPublishSidebarEnabled( state ) ).toBe( + PREFERENCES_DEFAULTS.isPublishSidebarEnabled + ); } ); } ); @@ -2535,43 +2574,52 @@ describe( 'selectors', () => { describe( 'getStateBeforeOptimisticTransaction', () => { it( 'should return null if no transaction can be found', () => { - const beforeState = getStateBeforeOptimisticTransaction( { - optimist: [], - }, 'foo' ); + const beforeState = getStateBeforeOptimisticTransaction( + { + optimist: [], + }, + 'foo' + ); expect( beforeState ).toBe( null ); } ); it( 'should return null if a transaction with ID can be found, but lacks before state', () => { - const beforeState = getStateBeforeOptimisticTransaction( { - optimist: [ - { - action: { - optimist: { - id: 'foo', + const beforeState = getStateBeforeOptimisticTransaction( + { + optimist: [ + { + action: { + optimist: { + id: 'foo', + }, }, }, - }, - ], - }, 'foo' ); + ], + }, + 'foo' + ); expect( beforeState ).toBe( null ); } ); it( 'should return the before state matching the given transaction id', () => { const expectedBeforeState = {}; - const beforeState = getStateBeforeOptimisticTransaction( { - optimist: [ - { - beforeState: expectedBeforeState, - action: { - optimist: { - id: 'foo', + const beforeState = getStateBeforeOptimisticTransaction( + { + optimist: [ + { + beforeState: expectedBeforeState, + action: { + optimist: { + id: 'foo', + }, }, }, - }, - ], - }, 'foo' ); + ], + }, + 'foo' + ); expect( beforeState ).toBe( expectedBeforeState ); } ); @@ -2698,7 +2746,9 @@ describe( 'selectors', () => { it( 'should be false if the permalink is not of an editable kind', () => { const state = { - currentPost: { permalink_template: 'http://foo.test/bar/%baz%/' }, + currentPost: { + permalink_template: 'http://foo.test/bar/%baz%/', + }, editor: { present: { edits: {}, @@ -2712,7 +2762,9 @@ describe( 'selectors', () => { it( 'should be true if the permalink has %postname%', () => { const state = { - currentPost: { permalink_template: 'http://foo.test/bar/%postname%/' }, + currentPost: { + permalink_template: 'http://foo.test/bar/%postname%/', + }, editor: { present: { edits: {}, @@ -2726,7 +2778,9 @@ describe( 'selectors', () => { it( 'should be true if the permalink has %pagename%', () => { const state = { - currentPost: { permalink_template: 'http://foo.test/bar/%pagename%/' }, + currentPost: { + permalink_template: 'http://foo.test/bar/%pagename%/', + }, editor: { present: { edits: {}, diff --git a/packages/editor/src/store/utils/notice-builder.js b/packages/editor/src/store/utils/notice-builder.js index 32ba7dacca5c25..e2878306797131 100644 --- a/packages/editor/src/store/utils/notice-builder.js +++ b/packages/editor/src/store/utils/notice-builder.js @@ -101,18 +101,22 @@ export function getNotificationArgumentsForSaveFail( data ) { private: __( 'Publishing failed.' ), future: __( 'Scheduling failed.' ), }; - let noticeMessage = ! isPublished && publishStatus.indexOf( edits.status ) !== -1 ? - messages[ edits.status ] : - __( 'Updating failed.' ); + let noticeMessage = + ! isPublished && publishStatus.indexOf( edits.status ) !== -1 + ? messages[ edits.status ] + : __( 'Updating failed.' ); // Check if message string contains HTML. Notice text is currently only // supported as plaintext, and stripping the tags may muddle the meaning. - if ( error.message && ! ( /<\/?[^>]*>/.test( error.message ) ) ) { + if ( error.message && ! /<\/?[^>]*>/.test( error.message ) ) { noticeMessage = [ noticeMessage, error.message ].join( ' ' ); } - return [ noticeMessage, { - id: SAVE_POST_NOTICE_ID, - } ]; + return [ + noticeMessage, + { + id: SAVE_POST_NOTICE_ID, + }, + ]; } /** @@ -124,9 +128,9 @@ export function getNotificationArgumentsForSaveFail( data ) { */ export function getNotificationArgumentsForTrashFail( data ) { return [ - data.error.message && data.error.code !== 'unknown_error' ? - data.error.message : - __( 'Trashing failed' ), + data.error.message && data.error.code !== 'unknown_error' + ? data.error.message + : __( 'Trashing failed' ), { id: TRASH_POST_NOTICE_ID, }, diff --git a/packages/editor/src/store/utils/test/notice-builder.js b/packages/editor/src/store/utils/test/notice-builder.js index 27113e6ee8cdb8..bc7df7ac526c6f 100644 --- a/packages/editor/src/store/utils/test/notice-builder.js +++ b/packages/editor/src/store/utils/test/notice-builder.js @@ -6,10 +6,7 @@ import { getNotificationArgumentsForSaveFail, getNotificationArgumentsForTrashFail, } from '../notice-builder'; -import { - SAVE_POST_NOTICE_ID, - TRASH_POST_NOTICE_ID, -} from '../../constants'; +import { SAVE_POST_NOTICE_ID, TRASH_POST_NOTICE_ID } from '../../constants'; describe( 'getNotificationArgumentsForSaveSuccess()', () => { const postType = { @@ -28,7 +25,11 @@ describe( 'getNotificationArgumentsForSaveSuccess()', () => { link: 'some_link', }; const post = { ...previousPost }; - const defaultExpectedAction = { id: SAVE_POST_NOTICE_ID, actions: [], type: 'snackbar' }; + const defaultExpectedAction = { + id: SAVE_POST_NOTICE_ID, + actions: [], + type: 'snackbar', + }; [ [ 'when previous post is not published and post will not be published', @@ -47,13 +48,13 @@ describe( 'getNotificationArgumentsForSaveSuccess()', () => { ], [ 'when previous post is not published and post will be privately ' + - 'published', + 'published', [ 'draft', 'private', false ], [ 'private', defaultExpectedAction ], ], [ 'when previous post is not published and post will be scheduled for ' + - 'publishing', + 'publishing', [ 'draft', 'future', false ], [ 'scheduled', defaultExpectedAction ], ], @@ -73,24 +74,26 @@ describe( 'getNotificationArgumentsForSaveSuccess()', () => { }, ], ], - ].forEach( ( [ - description, - [ previousPostStatus, postStatus, isViewable ], - expectedValue, - ] ) => { - it( description, () => { - previousPost.status = previousPostStatus; - post.status = postStatus; - postType.viewable = isViewable; - expect( getNotificationArgumentsForSaveSuccess( - { - previousPost, - post, - postType, - } - ) ).toEqual( expectedValue ); - } ); - } ); + ].forEach( + ( [ + description, + [ previousPostStatus, postStatus, isViewable ], + expectedValue, + ] ) => { + it( description, () => { + previousPost.status = previousPostStatus; + post.status = postStatus; + postType.viewable = isViewable; + expect( + getNotificationArgumentsForSaveSuccess( { + previousPost, + post, + postType, + } ) + ).toEqual( expectedValue ); + } ); + } + ); } ); describe( 'getNotificationArgumentsForSaveFail()', () => { const error = { code: '42', message: 'Something went wrong.' }; @@ -108,19 +111,28 @@ describe( 'getNotificationArgumentsForSaveFail()', () => { 'when post is not published and edits is published', '', [ 'draft', 'publish' ], - [ 'Publishing failed. Something went wrong.', defaultExpectedAction ], + [ + 'Publishing failed. Something went wrong.', + defaultExpectedAction, + ], ], [ 'when post is published and edits is privately published', '', [ 'draft', 'private' ], - [ 'Publishing failed. Something went wrong.', defaultExpectedAction ], + [ + 'Publishing failed. Something went wrong.', + defaultExpectedAction, + ], ], [ 'when post is published and edits is scheduled to be published', '', [ 'draft', 'future' ], - [ 'Scheduling failed. Something went wrong.', defaultExpectedAction ], + [ + 'Scheduling failed. Something went wrong.', + defaultExpectedAction, + ], ], [ 'when post is published and edits is published', @@ -128,25 +140,27 @@ describe( 'getNotificationArgumentsForSaveFail()', () => { [ 'publish', 'publish' ], [ 'Updating failed. Something went wrong.', defaultExpectedAction ], ], - ].forEach( ( [ - description, - errorCode, - [ postStatus, editsStatus ], - expectedValue, - ] ) => { - it( description, () => { - post.status = postStatus; - error.code = errorCode; - edits.status = editsStatus; - expect( getNotificationArgumentsForSaveFail( - { - post, - edits, - error, - } - ) ).toEqual( expectedValue ); - } ); - } ); + ].forEach( + ( [ + description, + errorCode, + [ postStatus, editsStatus ], + expectedValue, + ] ) => { + it( description, () => { + post.status = postStatus; + error.code = errorCode; + edits.status = editsStatus; + expect( + getNotificationArgumentsForSaveFail( { + post, + edits, + error, + } ) + ).toEqual( expectedValue ); + } ); + } + ); } ); describe( 'getNotificationArgumentsForTrashFail()', () => { [ @@ -165,18 +179,12 @@ describe( 'getNotificationArgumentsForTrashFail()', () => { { code: 42 }, 'Trashing failed', ], - ].forEach( ( [ - description, - error, - message, - ] ) => { + ].forEach( ( [ description, error, message ] ) => { it( description, () => { - const expectedValue = [ - message, - { id: TRASH_POST_NOTICE_ID }, - ]; - expect( getNotificationArgumentsForTrashFail( { error } ) ) - .toEqual( expectedValue ); + const expectedValue = [ message, { id: TRASH_POST_NOTICE_ID } ]; + expect( getNotificationArgumentsForTrashFail( { error } ) ).toEqual( + expectedValue + ); } ); } ); } ); diff --git a/packages/editor/src/utils/media-upload/index.js b/packages/editor/src/utils/media-upload/index.js index dc79320b703be8..a9be89f3354072 100644 --- a/packages/editor/src/utils/media-upload/index.js +++ b/packages/editor/src/utils/media-upload/index.js @@ -31,7 +31,8 @@ export default function( { } ) { const { getCurrentPostId, getEditorSettings } = select( 'core/editor' ); const wpAllowedMimeTypes = getEditorSettings().allowedMimeTypes; - maxUploadFileSize = maxUploadFileSize || getEditorSettings().maxUploadFileSize; + maxUploadFileSize = + maxUploadFileSize || getEditorSettings().maxUploadFileSize; uploadMedia( { allowedTypes, diff --git a/packages/editor/src/utils/media-upload/index.native.js b/packages/editor/src/utils/media-upload/index.native.js index 9b7a53d3376d55..6ab80bc8d76f77 100644 --- a/packages/editor/src/utils/media-upload/index.native.js +++ b/packages/editor/src/utils/media-upload/index.native.js @@ -1 +1 @@ -export default function() { } +export default function() {} diff --git a/packages/editor/src/utils/terms.js b/packages/editor/src/utils/terms.js index ba76e68980184e..60cfdd8563d0bf 100644 --- a/packages/editor/src/utils/terms.js +++ b/packages/editor/src/utils/terms.js @@ -28,9 +28,10 @@ export function buildTermsTree( flatTerms ) { const children = termsByParent[ term.id ]; return { ...term, - children: children && children.length ? - fillWithChildren( children ) : - [], + children: + children && children.length + ? fillWithChildren( children ) + : [], }; } ); }; diff --git a/packages/editor/src/utils/test/terms.js b/packages/editor/src/utils/test/terms.js index 11cf1505d4f27e..68a1a7f10fb190 100644 --- a/packages/editor/src/utils/test/terms.js +++ b/packages/editor/src/utils/test/terms.js @@ -34,9 +34,11 @@ describe( 'buildTermsTree()', () => { { id: 2245, parent: 2232 }, ] ); const output = [ - { id: 2232, parent: 0, children: [ - { id: 2245, parent: 2232, children: [] }, - ] }, + { + id: 2232, + parent: 0, + children: [ { id: 2245, parent: 2232, children: [] } ], + }, ]; const termsTreem = buildTermsTree( input ); expect( termsTreem ).toEqual( output ); @@ -49,10 +51,14 @@ describe( 'buildTermsTree()', () => { { id: 2246, parent: 2232 }, ] ); const output = [ - { id: 2232, parent: 0, children: [ - { id: 2245, parent: 2232, children: [] }, - { id: 2246, parent: 2232, children: [] }, - ] }, + { + id: 2232, + parent: 0, + children: [ + { id: 2245, parent: 2232, children: [] }, + { id: 2246, parent: 2232, children: [] }, + ], + }, { id: 2249, parent: 0, children: [] }, ]; const termsTreem = buildTermsTree( input ); diff --git a/packages/editor/src/utils/url.js b/packages/editor/src/utils/url.js index 1a0730fa82a5aa..8508329a8776e5 100644 --- a/packages/editor/src/utils/url.js +++ b/packages/editor/src/utils/url.js @@ -42,5 +42,7 @@ export function cleanForSlug( string ) { if ( ! string ) { return ''; } - return toLower( deburr( trim( string.replace( /[\s\./_]+/g, '-' ), '-' ) ) ); + return toLower( + deburr( trim( string.replace( /[\s\./_]+/g, '-' ), '-' ) ) + ); } diff --git a/packages/element/src/create-interpolate-element.js b/packages/element/src/create-interpolate-element.js index c32c3446ef98a4..e6d0eefdd3bfa3 100644 --- a/packages/element/src/create-interpolate-element.js +++ b/packages/element/src/create-interpolate-element.js @@ -3,10 +3,7 @@ */ import { createElement, cloneElement, Fragment, isValidElement } from 'react'; -let indoc, - offset, - output, - stack; +let indoc, offset, output, stack; /** * Matches tags in the localized string @@ -51,7 +48,7 @@ function Frame( tokenStart, tokenLength, prevOffset, - leadingTextStart, + leadingTextStart ) { return { element, @@ -124,9 +121,11 @@ const createInterpolateElement = ( interpolatedString, conversionMap ) => { const isValidConversionMap = ( conversionMap ) => { const isObject = typeof conversionMap === 'object'; const values = isObject && Object.values( conversionMap ); - return isObject && + return ( + isObject && values.length && - values.every( ( element ) => isValidElement( element ) ); + values.every( ( element ) => isValidElement( element ) ) + ); }; /** @@ -150,7 +149,10 @@ function proceed( conversionMap ) { switch ( tokenType ) { case 'no-more-tokens': if ( stackDepth !== 0 ) { - const { leadingTextStart: stackLeadingText, tokenStart } = stack.pop(); + const { + leadingTextStart: stackLeadingText, + tokenStart, + } = stack.pop(); output.push( indoc.substr( stackLeadingText, tokenStart ) ); } addText(); @@ -160,7 +162,10 @@ function proceed( conversionMap ) { if ( 0 === stackDepth ) { if ( null !== leadingTextStart ) { output.push( - indoc.substr( leadingTextStart, startOffset - leadingTextStart ) + indoc.substr( + leadingTextStart, + startOffset - leadingTextStart + ) ); } output.push( conversionMap[ name ] ); @@ -209,7 +214,7 @@ function proceed( conversionMap ) { stackTop.element, stackTop.tokenStart, stackTop.tokenLength, - startOffset + tokenLength, + startOffset + tokenLength ); frame.children = stackTop.children; addChild( frame ); @@ -274,15 +279,16 @@ function addText() { function addChild( frame ) { const { element, tokenStart, tokenLength, prevOffset, children } = frame; const parent = stack[ stack.length - 1 ]; - const text = indoc.substr( parent.prevOffset, tokenStart - parent.prevOffset ); + const text = indoc.substr( + parent.prevOffset, + tokenStart - parent.prevOffset + ); if ( text ) { parent.children.push( text ); } - parent.children.push( - cloneElement( element, null, ...children ) - ); + parent.children.push( cloneElement( element, null, ...children ) ); parent.prevOffset = prevOffset ? prevOffset : tokenStart + tokenLength; } @@ -299,27 +305,29 @@ function addChild( frame ) { * the element. */ function closeOuterElement( endOffset ) { - const { element, leadingTextStart, prevOffset, tokenStart, children } = stack.pop(); + const { + element, + leadingTextStart, + prevOffset, + tokenStart, + children, + } = stack.pop(); - const text = endOffset ? - indoc.substr( prevOffset, endOffset - prevOffset ) : - indoc.substr( prevOffset ); + const text = endOffset + ? indoc.substr( prevOffset, endOffset - prevOffset ) + : indoc.substr( prevOffset ); if ( text ) { children.push( text ); } if ( null !== leadingTextStart ) { - output.push( indoc.substr( leadingTextStart, tokenStart - leadingTextStart ) ); + output.push( + indoc.substr( leadingTextStart, tokenStart - leadingTextStart ) + ); } - output.push( - cloneElement( - element, - null, - ...children - ) - ); + output.push( cloneElement( element, null, ...children ) ); } export default createInterpolateElement; diff --git a/packages/element/src/react.js b/packages/element/src/react.js index b24f30c5fbafbb..449b799ef74f89 100644 --- a/packages/element/src/react.js +++ b/packages/element/src/react.js @@ -227,11 +227,18 @@ export function concatChildren( ...childrenArguments ) { * @return {?Object} The updated children object. */ export function switchChildrenNodeName( children, nodeName ) { - return children && Children.map( children, ( elt, index ) => { - if ( isString( elt ) ) { - return createElement( nodeName, { key: index }, elt ); - } - const { children: childrenProp, ...props } = elt.props; - return createElement( nodeName, { key: index, ...props }, childrenProp ); - } ); + return ( + children && + Children.map( children, ( elt, index ) => { + if ( isString( elt ) ) { + return createElement( nodeName, { key: index }, elt ); + } + const { children: childrenProp, ...props } = elt.props; + return createElement( + nodeName, + { key: index, ...props }, + childrenProp + ); + } ) + ); } diff --git a/packages/element/src/serialize.js b/packages/element/src/serialize.js index b700831f1dccd0..d5ef4e02a7758e 100644 --- a/packages/element/src/serialize.js +++ b/packages/element/src/serialize.js @@ -40,17 +40,16 @@ import { /** * WordPress dependencies */ -import { escapeHTML, escapeAttribute, isValidAttributeName } from '@wordpress/escape-html'; +import { + escapeHTML, + escapeAttribute, + isValidAttributeName, +} from '@wordpress/escape-html'; /** * Internal dependencies */ -import { - createContext, - Fragment, - StrictMode, - forwardRef, -} from './react'; +import { createContext, Fragment, StrictMode, forwardRef } from './react'; import RawHTML from './raw-html'; const { Provider, Consumer } = createContext(); @@ -63,11 +62,7 @@ const ForwardRef = forwardRef( () => { * * @type {Set} */ -const ATTRIBUTES_TYPES = new Set( [ - 'string', - 'boolean', - 'number', -] ); +const ATTRIBUTES_TYPES = new Set( [ 'string', 'boolean', 'number' ] ); /** * Element tags which can be self-closing. @@ -337,8 +332,11 @@ function getNormalStylePropertyName( property ) { * @return {*} Normalized property value. */ function getNormalStylePropertyValue( property, value ) { - if ( typeof value === 'number' && 0 !== value && - ! CSS_PROPERTIES_SUPPORTS_UNITLESS.has( property ) ) { + if ( + typeof value === 'number' && + 0 !== value && + ! CSS_PROPERTIES_SUPPORTS_UNITLESS.has( property ) + ) { return value + 'px'; } @@ -397,11 +395,18 @@ export function renderElement( element, context, legacyContext = {} ) { return renderNativeComponent( type, props, context, legacyContext ); case 'function': - if ( type.prototype && typeof type.prototype.render === 'function' ) { + if ( + type.prototype && + typeof type.prototype.render === 'function' + ) { return renderComponent( type, props, context, legacyContext ); } - return renderElement( type( props, legacyContext ), context, legacyContext ); + return renderElement( + type( props, legacyContext ), + context, + legacyContext + ); } switch ( type && type.$$typeof ) { @@ -409,10 +414,18 @@ export function renderElement( element, context, legacyContext = {} ) { return renderChildren( props.children, props.value, legacyContext ); case Consumer.$$typeof: - return renderElement( props.children( context || type._currentValue ), context, legacyContext ); + return renderElement( + props.children( context || type._currentValue ), + context, + legacyContext + ); case ForwardRef.$$typeof: - return renderElement( type.render( props ), context, legacyContext ); + return renderElement( + type.render( props ), + context, + legacyContext + ); } return ''; @@ -429,7 +442,12 @@ export function renderElement( element, context, legacyContext = {} ) { * * @return {string} Serialized element. */ -export function renderNativeComponent( type, props, context, legacyContext = {} ) { +export function renderNativeComponent( + type, + props, + context, + legacyContext = {} +) { let content = ''; if ( type === 'textarea' && props.hasOwnProperty( 'value' ) ) { // Textarea children can be assigned as value prop. If it is, render in @@ -437,8 +455,10 @@ export function renderNativeComponent( type, props, context, legacyContext = {} // as well. content = renderChildren( props.value, context, legacyContext ); props = omit( props, 'value' ); - } else if ( props.dangerouslySetInnerHTML && - typeof props.dangerouslySetInnerHTML.__html === 'string' ) { + } else if ( + props.dangerouslySetInnerHTML && + typeof props.dangerouslySetInnerHTML.__html === 'string' + ) { // Dangerous content is left unescaped. content = props.dangerouslySetInnerHTML.__html; } else if ( typeof props.children !== 'undefined' ) { @@ -468,7 +488,12 @@ export function renderNativeComponent( type, props, context, legacyContext = {} * * @return {string} Serialized element */ -export function renderComponent( Component, props, context, legacyContext = {} ) { +export function renderComponent( + Component, + props, + context, + legacyContext = {} +) { const instance = new Component( props, legacyContext ); if ( typeof instance.getChildContext === 'function' ) { @@ -538,11 +563,10 @@ export function renderAttributes( props ) { continue; } - const isMeaningfulAttribute = ( + const isMeaningfulAttribute = isBooleanAttribute || hasPrefix( key, [ 'data-', 'aria-' ] ) || - ENUMERATED_ATTRIBUTES.has( attribute ) - ); + ENUMERATED_ATTRIBUTES.has( attribute ); // Only write boolean value as attribute if meaningful. if ( typeof value === 'boolean' && ! isMeaningfulAttribute ) { diff --git a/packages/element/src/test/create-interpolate-element.js b/packages/element/src/test/create-interpolate-element.js index 006647d6b267a3..510c869e8c86bc 100644 --- a/packages/element/src/test/create-interpolate-element.js +++ b/packages/element/src/test/create-interpolate-element.js @@ -12,61 +12,57 @@ import createInterpolateElement from '../create-interpolate-element'; describe( 'createInterpolateElement', () => { it( 'throws an error when there is no conversion map', () => { const testString = 'This is a string'; - expect( - () => createInterpolateElement( testString, {} ) - ).toThrow( TypeError ); + expect( () => createInterpolateElement( testString, {} ) ).toThrow( + TypeError + ); } ); it( 'returns same string when there are no tokens in the string', () => { const testString = 'This is a string'; const expectedElement = <>{ testString }</>; expect( - createInterpolateElement( - testString, - { someValue: <em /> } - ) + createInterpolateElement( testString, { someValue: <em /> } ) ).toEqual( expectedElement ); } ); it( 'throws an error when there is an invalid conversion map', () => { const testString = 'This is a <someValue/> string'; - expect( - () => createInterpolateElement( - testString, - [ 'someValue', { value: 10 } ], - ) + expect( () => + createInterpolateElement( testString, [ + 'someValue', + { value: 10 }, + ] ) ).toThrow( TypeError ); } ); - it( 'throws an error when there is an invalid entry in the conversion ' + - 'map', () => { - const testString = 'This is a <item /> string and <somethingElse/>'; - expect( - () => createInterpolateElement( - testString, - { + it( + 'throws an error when there is an invalid entry in the conversion ' + + 'map', + () => { + const testString = 'This is a <item /> string and <somethingElse/>'; + expect( () => + createInterpolateElement( testString, { someValue: <em />, somethingElse: 10, - } - ) - ).toThrow( TypeError ); - } ); - it( 'returns same string when there is an non matching token in the ' + - 'string', () => { - const testString = 'This is a <non_parsed/> string'; - const expectedElement = <>{ testString }</>; - expect( - createInterpolateElement( - testString, - { someValue: <strong /> } - ) - ).toEqual( expectedElement ); - } ); + } ) + ).toThrow( TypeError ); + } + ); + it( + 'returns same string when there is an non matching token in the ' + + 'string', + () => { + const testString = 'This is a <non_parsed/> string'; + const expectedElement = <>{ testString }</>; + expect( + createInterpolateElement( testString, { + someValue: <strong />, + } ) + ).toEqual( expectedElement ); + } + ); it( 'returns same string when there is spaces in the token', () => { const testString = 'This is a <spaced token/>string'; const expectedElement = <>{ testString }</>; expect( - createInterpolateElement( - testString, - { 'spaced token': <em /> } - ) + createInterpolateElement( testString, { 'spaced token': <em /> } ) ).toEqual( expectedElement ); } ); it( 'returns expected react element for non nested components', () => { @@ -82,16 +78,11 @@ describe( 'createInterpolateElement', () => { ), '.' ); - const component = createInterpolateElement( - testString, - { - // eslint-disable-next-line jsx-a11y/anchor-has-content - a: <a href={ 'https://github.com' } className={ 'some_class' } />, - } - ); - expect( - JSON.stringify( component ) - ).toEqual( + const component = createInterpolateElement( testString, { + // eslint-disable-next-line jsx-a11y/anchor-has-content + a: <a href={ 'https://github.com' } className={ 'some_class' } />, + } ); + expect( JSON.stringify( component ) ).toEqual( JSON.stringify( expectedElement ) ); } ); @@ -105,67 +96,68 @@ describe( 'createInterpolateElement', () => { 'a', null, 'string that is ', - createElement( - 'em', - null, - 'linked' - ), + createElement( 'em', null, 'linked' ) ), '.' ); - expect( JSON.stringify( createInterpolateElement( - testString, - { - a: createElement( 'a' ), - em: <em />, - } - ) ) ).toEqual( JSON.stringify( expectedElement ) ); + expect( + JSON.stringify( + createInterpolateElement( testString, { + a: createElement( 'a' ), + em: <em />, + } ) + ) + ).toEqual( JSON.stringify( expectedElement ) ); } ); - it( 'returns expected output for a custom component with children ' + - 'replacement', () => { - const TestComponent = ( props ) => { - return <div { ...props } >{ props.children }</div>; - }; - const testString = 'This is a string with a <TestComponent>Custom Component</TestComponent>'; - const expectedElement = createElement( - Fragment, - null, - 'This is a string with a ', - createElement( - TestComponent, + it( + 'returns expected output for a custom component with children ' + + 'replacement', + () => { + const TestComponent = ( props ) => { + return <div { ...props }>{ props.children }</div>; + }; + const testString = + 'This is a string with a <TestComponent>Custom Component</TestComponent>'; + const expectedElement = createElement( + Fragment, null, - 'Custom Component' - ), - ); - expect( JSON.stringify( createInterpolateElement( - testString, - { - TestComponent: <TestComponent />, - } - ) ) ).toEqual( JSON.stringify( expectedElement ) ); - } ); + 'This is a string with a ', + createElement( TestComponent, null, 'Custom Component' ) + ); + expect( + JSON.stringify( + createInterpolateElement( testString, { + TestComponent: <TestComponent />, + } ) + ) + ).toEqual( JSON.stringify( expectedElement ) ); + } + ); it( 'returns expected output for self closing custom component', () => { const TestComponent = ( props ) => { return <div { ...props } />; }; - const testString = 'This is a string with a self closing custom component: <TestComponent/>'; + const testString = + 'This is a string with a self closing custom component: <TestComponent/>'; const expectedElement = createElement( Fragment, null, 'This is a string with a self closing custom component: ', - createElement( TestComponent ), + createElement( TestComponent ) ); - expect( JSON.stringify( createInterpolateElement( - testString, - { - TestComponent: <TestComponent />, - } - ) ) ).toEqual( JSON.stringify( expectedElement ) ); + expect( + JSON.stringify( + createInterpolateElement( testString, { + TestComponent: <TestComponent />, + } ) + ) + ).toEqual( JSON.stringify( expectedElement ) ); } ); it( 'throws an error with an invalid element in the conversion map', () => { - const test = () => ( - createInterpolateElement( 'This is a <invalid /> string', { invalid: 10 } ) - ); + const test = () => + createInterpolateElement( 'This is a <invalid /> string', { + invalid: 10, + } ); expect( test ).toThrow( TypeError ); } ); it( 'returns expected output for complex replacement', () => { @@ -174,7 +166,8 @@ describe( 'createInterpolateElement', () => { return <div { ...props } />; } } - const testString = 'This is a complex string with ' + + const testString = + 'This is a complex string with ' + 'a <a1>nested <em1>emphasized string</em1> link</a1> and value: <TestComponent/>'; const expectedElement = createElement( Fragment, @@ -184,35 +177,41 @@ describe( 'createInterpolateElement', () => { 'a', null, 'nested ', - createElement( - 'em', - null, - 'emphasized string' - ), - ' link', + createElement( 'em', null, 'emphasized string' ), + ' link' ), ' and value: ', - createElement( TestComponent ), + createElement( TestComponent ) ); - expect( JSON.stringify( createInterpolateElement( - testString, - { - TestComponent: <TestComponent />, - em1: <em />, - a1: createElement( 'a' ), - } - ) ) ).toEqual( JSON.stringify( expectedElement ) ); + expect( + JSON.stringify( + createInterpolateElement( testString, { + TestComponent: <TestComponent />, + em1: <em />, + a1: createElement( 'a' ), + } ) + ) + ).toEqual( JSON.stringify( expectedElement ) ); } ); it( 'renders expected components across renders for keys in use', () => { const TestComponent = ( { switchKey } ) => { - const elementConfig = switchKey ? { item: <em /> } : { item: <strong /> }; - return <div> - { createInterpolateElement( 'This is a <item>string!</item>', elementConfig ) } - </div>; + const elementConfig = switchKey + ? { item: <em /> } + : { item: <strong /> }; + return ( + <div> + { createInterpolateElement( + 'This is a <item>string!</item>', + elementConfig + ) } + </div> + ); }; let renderer; act( () => { - renderer = TestRenderer.create( <TestComponent switchKey={ true } /> ); + renderer = TestRenderer.create( + <TestComponent switchKey={ true } /> + ); } ); expect( () => renderer.root.findByType( 'em' ) ).not.toThrow(); expect( () => renderer.root.findByType( 'strong' ) ).toThrow(); @@ -228,18 +227,15 @@ describe( 'createInterpolateElement', () => { Fragment, null, '👳‍♀️', - createElement( - 'strong', - null, - '🚨🤷‍♂️⛈️fully', - ), - ' here', + createElement( 'strong', null, '🚨🤷‍♂️⛈️fully' ), + ' here' ); - expect( JSON.stringify( createInterpolateElement( - testString, - { - icon: <strong />, - } - ) ) ).toEqual( JSON.stringify( expectedElement ) ); + expect( + JSON.stringify( + createInterpolateElement( testString, { + icon: <strong />, + } ) + ) + ).toEqual( JSON.stringify( expectedElement ) ); } ); } ); diff --git a/packages/element/src/test/index.js b/packages/element/src/test/index.js index ef5bd4bb87542b..65f2347d4e731d 100644 --- a/packages/element/src/test/index.js +++ b/packages/element/src/test/index.js @@ -31,40 +31,48 @@ describe( 'element', () => { } ); it( 'should return a string from an array', () => { - expect( renderToString( [ - 'Zucchini ', - createElement( 'em', null, 'is a' ), - ' summer squash', - ] ) ).toBe( 'Zucchini <em>is a</em> summer squash' ); + expect( + renderToString( [ + 'Zucchini ', + createElement( 'em', null, 'is a' ), + ' summer squash', + ] ) + ).toBe( 'Zucchini <em>is a</em> summer squash' ); } ); it( 'should return a string from an element', () => { - expect( renderToString( - createElement( 'strong', null, 'Courgette' ) - ) ).toBe( '<strong>Courgette</strong>' ); + expect( + renderToString( createElement( 'strong', null, 'Courgette' ) ) + ).toBe( '<strong>Courgette</strong>' ); } ); it( 'should escape attributes and html', () => { - const result = renderToString( createElement( 'a', { - href: '/index.php?foo=bar&qux=<"scary">', - style: { - backgroundColor: 'red', - }, - }, '<"WordPress" & Friends>' ) ); + const result = renderToString( + createElement( + 'a', + { + href: '/index.php?foo=bar&qux=<"scary">', + style: { + backgroundColor: 'red', + }, + }, + '<"WordPress" & Friends>' + ) + ); expect( result ).toBe( '<a href="/index.php?foo=bar&amp;qux=<&quot;scary&quot;&gt;" style="background-color:red">' + - '&lt;"WordPress" &amp; Friends>' + - '</a>' + '&lt;"WordPress" &amp; Friends>' + + '</a>' ); } ); it( 'strips raw html wrapper', () => { const html = '<p>So scary!</p>'; - expect( renderToString( - <RawHTML>{ html }</RawHTML>, - ) ).toBe( html ); + expect( renderToString( <RawHTML>{ html }</RawHTML> ) ).toBe( + html + ); } ); } ); @@ -103,10 +111,13 @@ describe( 'element', () => { } ); it( 'should switch elements', () => { - const children = switchChildrenNodeName( [ - createElement( 'strong', { align: 'left' }, 'Courgette' ), - createElement( 'strong', {}, 'Concombre' ), - ], 'em' ); + const children = switchChildrenNodeName( + [ + createElement( 'strong', { align: 'left' }, 'Courgette' ), + createElement( 'strong', {}, 'Concombre' ), + ], + 'em' + ); expect( children ).toHaveLength( 2 ); expect( children[ 0 ].type ).toBe( 'em' ); expect( children[ 0 ].props.children ).toBe( 'Courgette' ); diff --git a/packages/element/src/test/raw-html.js b/packages/element/src/test/raw-html.js index 66d0b4bf11e44e..d1fea39486397c 100644 --- a/packages/element/src/test/raw-html.js +++ b/packages/element/src/test/raw-html.js @@ -11,11 +11,7 @@ import RawHTML from '../raw-html'; describe( 'RawHTML', () => { it( 'is dangerous', () => { const html = '<p>So scary!</p>'; - const element = shallow( - <RawHTML> - { html } - </RawHTML> - ); + const element = shallow( <RawHTML>{ html }</RawHTML> ); expect( element.type() ).toBe( 'div' ); expect( element.prop( 'dangerouslySetInnerHTML' ).__html ).toBe( html ); @@ -24,11 +20,7 @@ describe( 'RawHTML', () => { it( 'creates wrapper if assigned other props', () => { const html = '<p>So scary!</p>'; - const element = shallow( - <RawHTML className="foo"> - { html } - </RawHTML> - ); + const element = shallow( <RawHTML className="foo">{ html }</RawHTML> ); expect( element.type() ).toBe( 'div' ); expect( element.prop( 'className' ) ).toBe( 'foo' ); diff --git a/packages/element/src/test/serialize.js b/packages/element/src/test/serialize.js index b6a8c906751d97..d13cea967d41cc 100644 --- a/packages/element/src/test/serialize.js +++ b/packages/element/src/test/serialize.js @@ -26,15 +26,12 @@ import serialize, { describe( 'serialize()', () => { it( 'should allow only valid attribute names', () => { - const element = createElement( - 'div', - { - 'notok\u007F': 'bad', - 'notok"': 'bad', - ok: 'good', - 'notok\uFDD0': 'bad', - }, - ); + const element = createElement( 'div', { + 'notok\u007F': 'bad', + 'notok"': 'bad', + ok: 'good', + 'notok\uFDD0': 'bad', + } ); const result = serialize( element ); @@ -80,8 +77,7 @@ describe( 'serialize()', () => { ); expect( result ).toBe( - 'FunctionComponent: Hello!' + - 'ClassComponent: Hello!' + 'FunctionComponent: Hello!' + 'ClassComponent: Hello!' ); } ); @@ -90,13 +86,9 @@ describe( 'serialize()', () => { return <div>test</div>; } ); - const result = serialize( - <ForwardedComponent /> - ); + const result = serialize( <ForwardedComponent /> ); - expect( result ).toBe( - '<div>test</div>' - ); + expect( result ).toBe( '<div>test</div>' ); } ); describe( 'empty attributes', () => { @@ -128,7 +120,9 @@ describe( 'serialize()', () => { describe( 'boolean attributes', () => { it( 'should render elements with false boolean attributes', () => { [ false, null, undefined ].forEach( ( controls ) => { - const result = serialize( <video src="/" controls={ controls } /> ); + const result = serialize( + <video src="/" controls={ controls } /> + ); expect( result ).toBe( '<video src="/"></video>' ); } ); @@ -136,7 +130,9 @@ describe( 'serialize()', () => { it( 'should render elements with true boolean attributes', () => { [ true, 'true', 'false', '' ].forEach( ( controls ) => { - const result = serialize( <video src="/" controls={ controls } /> ); + const result = serialize( + <video src="/" controls={ controls } /> + ); expect( result ).toBe( '<video src="/" controls></video>' ); } ); @@ -261,9 +257,7 @@ describe( 'renderElement()', () => { } ); const result = renderElement( - <Consumer> - { ( context ) => context.value } - </Consumer> + <Consumer>{ ( context ) => context.value }</Consumer> ); expect( result ).toBe( 'default' ); @@ -276,9 +270,7 @@ describe( 'renderElement()', () => { const result = renderElement( <Provider value={ { value: 'provided' } }> - <Consumer> - { ( context ) => context.value } - </Consumer> + <Consumer>{ ( context ) => context.value }</Consumer> </Provider> ); @@ -293,20 +285,14 @@ describe( 'renderElement()', () => { const result = renderElement( <Fragment> <Provider value={ { value: '1st provided' } }> - <Consumer> - { ( context ) => context.value } - </Consumer> + <Consumer>{ ( context ) => context.value }</Consumer> </Provider> { '|' } <Provider value={ { value: '2nd provided' } }> - <Consumer> - { ( context ) => context.value } - </Consumer> + <Consumer>{ ( context ) => context.value }</Consumer> </Provider> { '|' } - <Consumer> - { ( context ) => context.value } - </Consumer> + <Consumer>{ ( context ) => context.value }</Consumer> </Fragment> ); @@ -321,14 +307,10 @@ describe( 'renderElement()', () => { const result = renderElement( <Provider value={ { value: 'outer provided' } }> <Provider value={ { value: 'inner provided' } }> - <Consumer> - { ( context ) => context.value } - </Consumer> + <Consumer>{ ( context ) => context.value }</Consumer> </Provider> { '|' } - <Consumer> - { ( context ) => context.value } - </Consumer> + <Consumer>{ ( context ) => context.value }</Consumer> </Provider> ); @@ -342,7 +324,9 @@ describe( 'renderElement()', () => { } ); it( 'renders RawHTML with wrapper if props passed', () => { - const result = renderElement( <RawHTML className="foo">{ '<img/>' }</RawHTML> ); + const result = renderElement( + <RawHTML className="foo">{ '<img/>' }</RawHTML> + ); expect( result ).toBe( '<div class="foo"><img/></div>' ); } ); @@ -363,13 +347,18 @@ describe( 'renderElement()', () => { describe( 'renderNativeComponent()', () => { describe( 'textarea', () => { it( 'should render textarea value as its content', () => { - const result = renderNativeComponent( 'textarea', { value: 'Hello', children: [] } ); + const result = renderNativeComponent( 'textarea', { + value: 'Hello', + children: [], + } ); expect( result ).toBe( '<textarea>Hello</textarea>' ); } ); it( 'should render textarea children as its content', () => { - const result = renderNativeComponent( 'textarea', { children: [ 'Hello' ] } ); + const result = renderNativeComponent( 'textarea', { + children: [ 'Hello' ], + } ); expect( result ).toBe( '<textarea>Hello</textarea>' ); } ); @@ -377,19 +366,25 @@ describe( 'renderNativeComponent()', () => { describe( 'escaping', () => { it( 'should escape children', () => { - const result = renderNativeComponent( 'div', { children: [ '<img/>' ] } ); + const result = renderNativeComponent( 'div', { + children: [ '<img/>' ], + } ); expect( result ).toBe( '<div>&lt;img/></div>' ); } ); it( 'should not render invalid dangerouslySetInnerHTML', () => { - const result = renderNativeComponent( 'div', { dangerouslySetInnerHTML: { __html: undefined } } ); + const result = renderNativeComponent( 'div', { + dangerouslySetInnerHTML: { __html: undefined }, + } ); expect( result ).toBe( '<div></div>' ); } ); it( 'should not escape children with dangerouslySetInnerHTML', () => { - const result = renderNativeComponent( 'div', { dangerouslySetInnerHTML: { __html: '<img/>' } } ); + const result = renderNativeComponent( 'div', { + dangerouslySetInnerHTML: { __html: '<img/>' }, + } ); expect( result ).toBe( '<div><img/></div>' ); } ); @@ -403,7 +398,10 @@ describe( 'renderNativeComponent()', () => { } ); it( 'should ignore self-closing elements children', () => { - const result = renderNativeComponent( 'img', { src: 'foo.png', children: [ 'Surprise!' ] } ); + const result = renderNativeComponent( 'img', { + src: 'foo.png', + children: [ 'Surprise!' ], + } ); expect( result ).toBe( '<img src="foo.png"/>' ); } ); @@ -411,16 +409,17 @@ describe( 'renderNativeComponent()', () => { describe( 'with children', () => { it( 'should render single literal child', () => { - const result = renderNativeComponent( 'div', { children: 'Hello' } ); + const result = renderNativeComponent( 'div', { + children: 'Hello', + } ); expect( result ).toBe( '<div>Hello</div>' ); } ); it( 'should render array of children', () => { - const result = renderNativeComponent( 'div', { children: [ - 'Hello ', - <Fragment key="toWhom">World</Fragment>, - ] } ); + const result = renderNativeComponent( 'div', { + children: [ 'Hello ', <Fragment key="toWhom">World</Fragment> ], + } ); expect( result ).toBe( '<div>Hello World</div>' ); } ); @@ -530,7 +529,9 @@ describe( 'renderAttributes()', () => { contentEditable: true, } ); - expect( result ).toBe( ' for="foo" class="bar" contenteditable="true"' ); + expect( result ).toBe( + ' for="foo" class="bar" contenteditable="true"' + ); } ); } ); @@ -543,7 +544,9 @@ describe( 'renderAttributes()', () => { href: '/index.php?foo=bar&qux=<"scary">', } ); - expect( result ).toBe( ' style="background:url(&quot;foo.png&quot;)" href="/index.php?foo=bar&amp;qux=<&quot;scary&quot;&gt;"' ); + expect( result ).toBe( + ' style="background:url(&quot;foo.png&quot;)" href="/index.php?foo=bar&amp;qux=<&quot;scary&quot;&gt;"' + ); } ); it( 'should render numeric attributes', () => { @@ -650,7 +653,9 @@ describe( 'renderStyle()', () => { WebkitTransform: 'none', } ); - expect( result ).toBe( '-ms-transform:none;-o-transform:none;-moz-transform:none;-webkit-transform:none' ); + expect( result ).toBe( + '-ms-transform:none;-o-transform:none;-moz-transform:none;-webkit-transform:none' + ); } ); describe( 'value unit', () => { diff --git a/packages/element/src/utils.js b/packages/element/src/utils.js index d7149b943535d8..ee5af6392ca64a 100644 --- a/packages/element/src/utils.js +++ b/packages/element/src/utils.js @@ -1,11 +1,7 @@ /** * External dependencies */ -import { - isArray, - isNumber, - isString, -} from 'lodash'; +import { isArray, isNumber, isString } from 'lodash'; /** * Checks if the provided WP element is empty. diff --git a/packages/env/lib/cli.js b/packages/env/lib/cli.js index d682ac6fab6eea..13ce5ccc3f347b 100644 --- a/packages/env/lib/cli.js +++ b/packages/env/lib/cli.js @@ -28,9 +28,9 @@ const withSpinner = ( command ) => ( ...args ) => { ( message ) => { time = process.hrtime( time ); spinner.succeed( - `${ message || spinner.text } (in ${ time[ 0 ] }s ${ ( time[ 1 ] / 1e6 ).toFixed( - 0 - ) }ms)` + `${ message || spinner.text } (in ${ time[ 0 ] }s ${ ( + time[ 1 ] / 1e6 + ).toFixed( 0 ) }ms)` ); }, ( err ) => { diff --git a/packages/env/lib/detect-context.js b/packages/env/lib/detect-context.js index 8e1f9551b659d3..b962287bddc238 100644 --- a/packages/env/lib/detect-context.js +++ b/packages/env/lib/detect-context.js @@ -37,15 +37,20 @@ module.exports = async function detectContext( directoryPath = process.cwd() ) { // Race multiple file read streams against each other until // a plugin or theme header is found. - const files = ( await readDir( absPath ) ).filter( - ( file ) => path.extname( file ) === '.php' || path.basename( file ) === 'style.css' - ).map( ( fileName ) => path.join( absPath, fileName ) ); + const files = ( await readDir( absPath ) ) + .filter( + ( file ) => + path.extname( file ) === '.php' || + path.basename( file ) === 'style.css' + ) + .map( ( fileName ) => path.join( absPath, fileName ) ); const streams = []; for ( const file of files ) { const fileStream = fs.createReadStream( file, 'utf8' ); fileStream.on( 'data', ( text ) => { - const [ , type ] = text.match( /(Plugin|Theme) Name: .*[\r\n]/ ) || []; + const [ , type ] = + text.match( /(Plugin|Theme) Name: .*[\r\n]/ ) || []; if ( type ) { context.type = type.toLowerCase(); context.path = absPath; @@ -54,13 +59,17 @@ module.exports = async function detectContext( directoryPath = process.cwd() ) { // Stop the creation of new streams by mutating the iterated array. We can't `break`, because we are inside a function. files.splice( 0 ); fileStream.destroy(); - streams.forEach( ( otherFileStream ) => otherFileStream.destroy() ); + streams.forEach( ( otherFileStream ) => + otherFileStream.destroy() + ); } } ); streams.push( fileStream ); } await Promise.all( - streams.map( ( fileStream ) => finished( fileStream ).catch( () => {} ) ) + streams.map( ( fileStream ) => + finished( fileStream ).catch( () => {} ) + ) ); return context; diff --git a/packages/env/lib/env.js b/packages/env/lib/env.js index 1dc207a134cd88..0610e90510308f 100644 --- a/packages/env/lib/env.js +++ b/packages/env/lib/env.js @@ -40,9 +40,9 @@ const wpCliRun = ( command, isTests = false ) => const setupSite = ( isTests = false ) => wpCliRun( `wp core install --url=localhost:${ - isTests ? - process.env.WP_ENV_TESTS_PORT || 8889 : - process.env.WP_ENV_PORT || 8888 + isTests + ? process.env.WP_ENV_TESTS_PORT || 8889 + : process.env.WP_ENV_PORT || 8888 } --title=${ cwdName } --admin_user=admin --admin_password=password --admin_email=admin@wordpress.org`, isTests ); @@ -65,7 +65,8 @@ module.exports = { // so received objects plus indexed objects should equal twice // the total number of objects when done. const percent = ( - ( ( progress.receivedObjects() + progress.indexedObjects() ) / + ( ( progress.receivedObjects() + + progress.indexedObjects() ) / ( progress.totalObjects() * 2 ) ) * 100 ).toFixed( 0 ); @@ -235,10 +236,14 @@ module.exports = { ); if ( result.out ) { // eslint-disable-next-line no-console - console.log( process.stdout.isTTY ? `\n\n${ result.out }\n\n` : result.out ); + console.log( + process.stdout.isTTY ? `\n\n${ result.out }\n\n` : result.out + ); } else if ( result.err ) { // eslint-disable-next-line no-console - console.error( process.stdout.isTTY ? `\n\n${ result.err }\n\n` : result.err ); + console.error( + process.stdout.isTTY ? `\n\n${ result.err }\n\n` : result.err + ); throw result.err; } diff --git a/packages/env/test/cli.js b/packages/env/test/cli.js index ab4493b548902c..8eaaf4b5eb1633 100644 --- a/packages/env/test/cli.js +++ b/packages/env/test/cli.js @@ -103,7 +103,9 @@ describe( 'env cli', () => { await env.start.mock.results[ 0 ].value.catch( () => {} ); expect( spinner.fail ).toHaveBeenCalledWith( 'failure message' ); - expect( console.error ).toHaveBeenCalledWith( '\n\nfailure message\n\n' ); + expect( console.error ).toHaveBeenCalledWith( + '\n\nfailure message\n\n' + ); expect( process.exit ).toHaveBeenCalledWith( 2 ); console.error = consoleError; process.exit = processExit; diff --git a/packages/escape-html/src/index.js b/packages/escape-html/src/index.js index 8b72830a74c4b9..4f40d18c136ca4 100644 --- a/packages/escape-html/src/index.js +++ b/packages/escape-html/src/index.js @@ -77,7 +77,9 @@ export function escapeLessThan( value ) { * @return {string} Escaped attribute value. */ export function escapeAttribute( value ) { - return __unstableEscapeGreaterThan( escapeQuotationMark( escapeAmpersand( value ) ) ); + return __unstableEscapeGreaterThan( + escapeQuotationMark( escapeAmpersand( value ) ) + ); } /** diff --git a/packages/escape-html/src/test/index.js b/packages/escape-html/src/test/index.js index 2c564a771f4bdc..8ba0fb41d2ea9f 100644 --- a/packages/escape-html/src/test/index.js +++ b/packages/escape-html/src/test/index.js @@ -21,9 +21,13 @@ function testUnstableEscapeGreaterThan( implementation ) { function testEscapeAmpersand( implementation ) { it( 'should escape ampersand', () => { - const result = implementation( 'foo & bar &amp; &AMP; baz &#931; &#bad; &#x3A3; &#X3a3; &#xevil;' ); + const result = implementation( + 'foo & bar &amp; &AMP; baz &#931; &#bad; &#x3A3; &#X3a3; &#xevil;' + ); - expect( result ).toBe( 'foo &amp; bar &amp; &AMP; baz &#931; &amp;#bad; &#x3A3; &#X3a3; &amp;#xevil;' ); + expect( result ).toBe( + 'foo &amp; bar &amp; &AMP; baz &#931; &amp;#bad; &#x3A3; &#X3a3; &amp;#xevil;' + ); } ); } @@ -98,8 +102,12 @@ describe( 'isValidAttributeName', () => { describe( 'escapeEditableHTML', () => { it( 'should escape < and all ampersands', () => { - const result = escapeEditableHTML( '<a href="https://w.org">WP</a> & &lt;strong&gt;' ); + const result = escapeEditableHTML( + '<a href="https://w.org">WP</a> & &lt;strong&gt;' + ); - expect( result ).toBe( '&lt;a href="https://w.org">WP&lt;/a> &amp; &amp;lt;strong&amp;gt;' ); + expect( result ).toBe( + '&lt;a href="https://w.org">WP&lt;/a> &amp; &amp;lt;strong&amp;gt;' + ); } ); } ); diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 496a551e30686c..3f8ef085dd3a8b 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -2,7 +2,7 @@ ### New Features -- The `recommended` ruleset no longer enables rules that check code formatting (whitespace, indenting, etc.) and that could conflict with Prettier. +- The `recommended` ruleset no longer enables rules that check code formatting (whitespace, indenting, etc.) and that could conflict with Prettier. These rules are now enforced by Prettier itself through a plugin that diffs the code with its formatted output and reports the differences as lint errors. `eslint-plugin-prettier` was chosen over options like `prettier-eslint` because we don't run `eslint --fix` in hooks as we'd rather leave certain linting errors to be resolved or ignored at the author's discretion. We also don't apply any additional formatting with `eslint` over `prettier`, so the overhead would be unnecessary. `eslint-plugin-prettier` was chosen over options like `prettier --check` because it's nice to see format errors as you type as it leads you to write code with a more optimal auto-formatted output and it avoids issues like comment directives being moved out of place by `prettier` and the author not realizing it. - There is a new `recommended-with-formatting` ruleset that has the code formatting rules still enabled, for projects that want to opt out from Prettier and continue checking code formatting with ESLint. ## 3.3.0 (2019-12-19) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 86136e43402266..31979f983b52f9 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -22,7 +22,7 @@ To opt-in to the default configuration, extend your own project's `.eslintrc` fi Refer to the [ESLint documentation on Shareable Configs](http://eslint.org/docs/developer-guide/shareable-configs) for more information. -The `recommended` preset will include rules governing an ES2015+ environment, and includes rules from the [`eslint-plugin-jsx-a11y`](https://github.com/evcohen/eslint-plugin-jsx-a11y), [`eslint-plugin-react`](https://github.com/yannickcr/eslint-plugin-react), and [`eslint-config-prettier`](https://github.com/prettier/eslint-config-prettier) projects. +The `recommended` preset will include rules governing an ES2015+ environment, and includes rules from the [`eslint-plugin-jsx-a11y`](https://github.com/evcohen/eslint-plugin-jsx-a11y), [`eslint-plugin-react`](https://github.com/yannickcr/eslint-plugin-react), and [`eslint-plugin-prettier`](https://github.com/prettier/eslint-plugin-prettier) projects. There is also `recommended-with-formatting` ruleset for projects that want to opt out from [Prettier](https://prettier.io). It has the native ESLint code formatting rules enabled instead. diff --git a/packages/eslint-plugin/configs/custom.js b/packages/eslint-plugin/configs/custom.js index 69ca991b7e56bb..8949b63f94d8dc 100644 --- a/packages/eslint-plugin/configs/custom.js +++ b/packages/eslint-plugin/configs/custom.js @@ -1,7 +1,5 @@ module.exports = { - plugins: [ - '@wordpress', - ], + plugins: [ '@wordpress' ], rules: { '@wordpress/no-unused-vars-before-return': 'error', '@wordpress/valid-sprintf': 'error', @@ -10,16 +8,22 @@ module.exports = { 'no-restricted-syntax': [ 'error', { - selector: 'CallExpression[callee.name=/^(__|_n|_nx|_x)$/]:not([arguments.0.type=/^Literal|BinaryExpression$/])', - message: 'Translate function arguments must be string literals.', + selector: + 'CallExpression[callee.name=/^(__|_n|_nx|_x)$/]:not([arguments.0.type=/^Literal|BinaryExpression$/])', + message: + 'Translate function arguments must be string literals.', }, { - selector: 'CallExpression[callee.name=/^(_n|_nx|_x)$/]:not([arguments.1.type=/^Literal|BinaryExpression$/])', - message: 'Translate function arguments must be string literals.', + selector: + 'CallExpression[callee.name=/^(_n|_nx|_x)$/]:not([arguments.1.type=/^Literal|BinaryExpression$/])', + message: + 'Translate function arguments must be string literals.', }, { - selector: 'CallExpression[callee.name=_nx]:not([arguments.3.type=/^Literal|BinaryExpression$/])', - message: 'Translate function arguments must be string literals.', + selector: + 'CallExpression[callee.name=_nx]:not([arguments.3.type=/^Literal|BinaryExpression$/])', + message: + 'Translate function arguments must be string literals.', }, ], }, diff --git a/packages/eslint-plugin/configs/es5.js b/packages/eslint-plugin/configs/es5.js index 2abee331d47ae6..773710d0b9c326 100644 --- a/packages/eslint-plugin/configs/es5.js +++ b/packages/eslint-plugin/configs/es5.js @@ -1,14 +1,15 @@ module.exports = { - extends: [ - require.resolve( './jsdoc.js' ), - ], + extends: [ require.resolve( './jsdoc.js' ) ], rules: { 'array-bracket-spacing': [ 'error', 'always' ], 'array-callback-return': 'error', 'brace-style': [ 'error', '1tbs' ], - camelcase: [ 'error', { - properties: 'never', - } ], + camelcase: [ + 'error', + { + properties: 'never', + }, + ], 'comma-dangle': [ 'error', 'always-multiline' ], 'comma-spacing': 'error', 'comma-style': [ 'error', 'last' ], @@ -64,18 +65,24 @@ module.exports = { semi: 'error', 'semi-spacing': 'error', 'space-before-blocks': [ 'error', 'always' ], - 'space-before-function-paren': [ 'error', { - anonymous: 'never', - named: 'never', - asyncArrow: 'always', - } ], + 'space-before-function-paren': [ + 'error', + { + anonymous: 'never', + named: 'never', + asyncArrow: 'always', + }, + ], 'space-in-parens': [ 'error', 'always' ], 'space-infix-ops': 'error', - 'space-unary-ops': [ 'error', { - overrides: { - '!': true, + 'space-unary-ops': [ + 'error', + { + overrides: { + '!': true, + }, }, - } ], + ], 'valid-typeof': 'error', 'vars-on-top': 'error', 'wrap-iife': 'error', diff --git a/packages/eslint-plugin/configs/esnext.js b/packages/eslint-plugin/configs/esnext.js index 8ea39cb8649665..35a758449571fc 100644 --- a/packages/eslint-plugin/configs/esnext.js +++ b/packages/eslint-plugin/configs/esnext.js @@ -2,9 +2,7 @@ module.exports = { env: { es6: true, }, - extends: [ - require.resolve( './es5.js' ), - ], + extends: [ require.resolve( './es5.js' ) ], parserOptions: { sourceType: 'module', }, @@ -25,13 +23,20 @@ module.exports = { 'no-var': 'error', 'object-shorthand': 'error', 'prefer-const': 'error', - quotes: [ 'error', 'single', { allowTemplateLiterals: true, avoidEscape: true } ], - 'space-unary-ops': [ 'error', { - overrides: { - '!': true, - yield: true, + quotes: [ + 'error', + 'single', + { allowTemplateLiterals: true, avoidEscape: true }, + ], + 'space-unary-ops': [ + 'error', + { + overrides: { + '!': true, + yield: true, + }, }, - } ], + ], 'template-curly-spacing': [ 'error', 'always' ], }, }; diff --git a/packages/eslint-plugin/configs/jsdoc.js b/packages/eslint-plugin/configs/jsdoc.js index bcc4cc42fb4070..ce2414a31add63 100644 --- a/packages/eslint-plugin/configs/jsdoc.js +++ b/packages/eslint-plugin/configs/jsdoc.js @@ -31,10 +31,7 @@ const temporaryWordPressInternalTypes = [ * should be removed once the related issues is fixed: * https://github.com/WordPress/gutenberg/issues/18045 */ -const temporaryExternalTypes = [ - 'DOMHighResTimeStamp', - 'espree', -]; +const temporaryExternalTypes = [ 'DOMHighResTimeStamp', 'espree' ]; /** * Helpful utilities that are globally defined and known to the TypeScript compiler. @@ -64,9 +61,7 @@ const typescriptUtilityTypes = [ ]; module.exports = { - extends: [ - 'plugin:jsdoc/recommended', - ], + extends: [ 'plugin:jsdoc/recommended' ], settings: { jsdoc: { preferredTypes: { @@ -79,18 +74,23 @@ module.exports = { }, }, rules: { - 'jsdoc/no-undefined-types': [ 'warn', { - definedTypes: [ - // Required to reference browser types because we don't have the `browser` environment enabled for the project. - // Here we filter out all browser globals that don't begin with an uppercase letter because those - // generally refer to window-level event listeners and are not a valid type to reference (e.g. `onclick`). - ...Object.keys( globals.browser ).filter( ( k ) => /^[A-Z]/.test( k ) ), - ...typescriptUtilityTypes, - ...temporaryWordPressInternalTypes, - ...temporaryExternalTypes, - 'void', - ], - } ], + 'jsdoc/no-undefined-types': [ + 'warn', + { + definedTypes: [ + // Required to reference browser types because we don't have the `browser` environment enabled for the project. + // Here we filter out all browser globals that don't begin with an uppercase letter because those + // generally refer to window-level event listeners and are not a valid type to reference (e.g. `onclick`). + ...Object.keys( globals.browser ).filter( ( k ) => + /^[A-Z]/.test( k ) + ), + ...typescriptUtilityTypes, + ...temporaryWordPressInternalTypes, + ...temporaryExternalTypes, + 'void', + ], + }, + ], 'jsdoc/require-jsdoc': 'off', 'jsdoc/require-param-description': 'off', 'jsdoc/require-returns': 'off', diff --git a/packages/eslint-plugin/configs/jsx-a11y.js b/packages/eslint-plugin/configs/jsx-a11y.js index 38dd0ed2a3bf00..93ed81cd87e7b1 100644 --- a/packages/eslint-plugin/configs/jsx-a11y.js +++ b/packages/eslint-plugin/configs/jsx-a11y.js @@ -1,14 +1,13 @@ module.exports = { - extends: [ - 'plugin:jsx-a11y/recommended', - ], - plugins: [ - 'jsx-a11y', - ], + extends: [ 'plugin:jsx-a11y/recommended' ], + plugins: [ 'jsx-a11y' ], rules: { - 'jsx-a11y/label-has-for': [ 'error', { - required: 'id', - } ], + 'jsx-a11y/label-has-for': [ + 'error', + { + required: 'id', + }, + ], 'jsx-a11y/media-has-caption': 'off', 'jsx-a11y/no-noninteractive-tabindex': 'off', 'jsx-a11y/role-has-required-aria-props': 'off', diff --git a/packages/eslint-plugin/configs/react.js b/packages/eslint-plugin/configs/react.js index 8541cb096cac44..c86f1ec718a1c8 100644 --- a/packages/eslint-plugin/configs/react.js +++ b/packages/eslint-plugin/configs/react.js @@ -1,26 +1,26 @@ module.exports = { - extends: [ - 'plugin:react/recommended', - ], + extends: [ 'plugin:react/recommended' ], parserOptions: { ecmaFeatures: { jsx: true, }, }, - plugins: [ - '@wordpress', - 'react', - 'react-hooks', - ], + plugins: [ '@wordpress', 'react', 'react-hooks' ], rules: { - '@wordpress/no-unused-vars-before-return': [ 'error', { - excludePattern: '^use', - } ], + '@wordpress/no-unused-vars-before-return': [ + 'error', + { + excludePattern: '^use', + }, + ], 'react/display-name': 'off', - 'react/jsx-curly-spacing': [ 'error', { - when: 'always', - children: true, - } ], + 'react/jsx-curly-spacing': [ + 'error', + { + when: 'always', + children: true, + }, + ], 'react/jsx-equals-spacing': 'error', 'react/jsx-indent': [ 'error', 'tab' ], 'react/jsx-indent-props': [ 'error', 'tab' ], diff --git a/packages/eslint-plugin/configs/recommended-with-formatting.js b/packages/eslint-plugin/configs/recommended-with-formatting.js index 71d8625cd961ba..61e08359128075 100644 --- a/packages/eslint-plugin/configs/recommended-with-formatting.js +++ b/packages/eslint-plugin/configs/recommended-with-formatting.js @@ -17,23 +17,13 @@ module.exports = { overrides: [ { // Unit test files and their helpers only. - files: [ - '**/@(test|__tests__)/**/*.js', - '**/?(*.)test.js', - ], - extends: [ - require.resolve( './test-unit.js' ), - ], + files: [ '**/@(test|__tests__)/**/*.js', '**/?(*.)test.js' ], + extends: [ require.resolve( './test-unit.js' ) ], }, { // End-to-end test files and their helpers only. - files: [ - '**/specs/**/*.js', - '**/?(*.)spec.js', - ], - extends: [ - require.resolve( './test-e2e.js' ), - ], + files: [ '**/specs/**/*.js', '**/?(*.)spec.js' ], + extends: [ require.resolve( './test-e2e.js' ) ], }, ], }; diff --git a/packages/eslint-plugin/configs/recommended.js b/packages/eslint-plugin/configs/recommended.js index b2bbe922820aac..9e1659685a85ad 100644 --- a/packages/eslint-plugin/configs/recommended.js +++ b/packages/eslint-plugin/configs/recommended.js @@ -1,3 +1,7 @@ module.exports = { - extends: [ require.resolve( './recommended-with-formatting.js' ), 'eslint-config-prettier' ], + extends: [ + require.resolve( './recommended-with-formatting.js' ), + 'plugin:prettier/recommended', + 'prettier/react', + ], }; diff --git a/packages/eslint-plugin/configs/test-e2e.js b/packages/eslint-plugin/configs/test-e2e.js index 63a52f0f0d0105..fb5f8d60f327d2 100644 --- a/packages/eslint-plugin/configs/test-e2e.js +++ b/packages/eslint-plugin/configs/test-e2e.js @@ -1,7 +1,5 @@ module.exports = { - extends: [ - 'plugin:jest/recommended', - ], + extends: [ 'plugin:jest/recommended' ], env: { browser: true, }, diff --git a/packages/eslint-plugin/configs/test-unit.js b/packages/eslint-plugin/configs/test-unit.js index 048ced2fe38d1f..5d71776c0a778a 100644 --- a/packages/eslint-plugin/configs/test-unit.js +++ b/packages/eslint-plugin/configs/test-unit.js @@ -1,5 +1,3 @@ module.exports = { - extends: [ - 'plugin:jest/recommended', - ], + extends: [ 'plugin:jest/recommended' ], }; diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 476aa97e339908..f2ead7d1ecb7d1 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -29,6 +29,7 @@ "eslint-plugin-jest": "^22.15.1", "eslint-plugin-jsdoc": "^15.8.0", "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-react": "^7.14.3", "eslint-plugin-react-hooks": "^1.6.1", "globals": "^12.0.0", diff --git a/packages/eslint-plugin/rules/__tests__/dependency-group.js b/packages/eslint-plugin/rules/__tests__/dependency-group.js index 2089b9ab3ec021..b81d172f26199e 100644 --- a/packages/eslint-plugin/rules/__tests__/dependency-group.js +++ b/packages/eslint-plugin/rules/__tests__/dependency-group.js @@ -47,9 +47,18 @@ import classnames from 'classnames'; import { Component } from '@wordpress/element'; import edit from './edit';`, errors: [ - { message: 'Expected preceding "External dependencies" comment block' }, - { message: 'Expected preceding "WordPress dependencies" comment block' }, - { message: 'Expected preceding "Internal dependencies" comment block' }, + { + message: + 'Expected preceding "External dependencies" comment block', + }, + { + message: + 'Expected preceding "WordPress dependencies" comment block', + }, + { + message: + 'Expected preceding "Internal dependencies" comment block', + }, ], output: ` /** diff --git a/packages/eslint-plugin/rules/__tests__/gutenberg-phase.js b/packages/eslint-plugin/rules/__tests__/gutenberg-phase.js index 579e127855090d..0504a1f0e55b99 100644 --- a/packages/eslint-plugin/rules/__tests__/gutenberg-phase.js +++ b/packages/eslint-plugin/rules/__tests__/gutenberg-phase.js @@ -14,9 +14,12 @@ const ruleTester = new RuleTester( { }, } ); -const ACCESS_ERROR = 'The `GUTENBERG_PHASE` constant should be accessed using `process.env.GUTENBERG_PHASE`.'; -const EQUALITY_ERROR = 'The `GUTENBERG_PHASE` constant should only be used in a strict equality comparison with a primitive number.'; -const IF_ERROR = 'The `GUTENBERG_PHASE` constant should only be used as part of the condition in an if statement or ternary expression.'; +const ACCESS_ERROR = + 'The `GUTENBERG_PHASE` constant should be accessed using `process.env.GUTENBERG_PHASE`.'; +const EQUALITY_ERROR = + 'The `GUTENBERG_PHASE` constant should only be used in a strict equality comparison with a primitive number.'; +const IF_ERROR = + 'The `GUTENBERG_PHASE` constant should only be used as part of the condition in an if statement or ternary expression.'; ruleTester.run( 'gutenberg-phase', rule, { valid: [ diff --git a/packages/eslint-plugin/rules/__tests__/no-base-control-with-label-without-id.js b/packages/eslint-plugin/rules/__tests__/no-base-control-with-label-without-id.js index a03a3a2296f7b1..e1f346ee5dd070 100644 --- a/packages/eslint-plugin/rules/__tests__/no-base-control-with-label-without-id.js +++ b/packages/eslint-plugin/rules/__tests__/no-base-control-with-label-without-id.js @@ -61,14 +61,24 @@ ruleTester.run( 'no-base-control-with-label-without-id', rule, { > <input id="my-id" /> </BaseControl>`, - errors: [ { message: 'When using BaseControl component if a label property is passed an id property should also be passed.' } ], + errors: [ + { + message: + 'When using BaseControl component if a label property is passed an id property should also be passed.', + }, + ], }, { code: ` <BaseControl label="ok" />`, - errors: [ { message: 'When using BaseControl component if a label property is passed an id property should also be passed.' } ], + errors: [ + { + message: + 'When using BaseControl component if a label property is passed an id property should also be passed.', + }, + ], }, ], } ); diff --git a/packages/eslint-plugin/rules/__tests__/no-unused-vars-before-return.js b/packages/eslint-plugin/rules/__tests__/no-unused-vars-before-return.js index c8fa8c4a216ae4..291559e956f3ec 100644 --- a/packages/eslint-plugin/rules/__tests__/no-unused-vars-before-return.js +++ b/packages/eslint-plugin/rules/__tests__/no-unused-vars-before-return.js @@ -62,7 +62,12 @@ function example( number ) { return number + foo; }`, - errors: [ { message: 'Variables should not be assigned until just prior its first reference. An early return statement may leave this variable unused.' } ], + errors: [ + { + message: + 'Variables should not be assigned until just prior its first reference. An early return statement may leave this variable unused.', + }, + ], }, { code: ` @@ -74,7 +79,12 @@ function example() { return number + foo; }`, - errors: [ { message: 'Variables should not be assigned until just prior its first reference. An early return statement may leave this variable unused.' } ], + errors: [ + { + message: + 'Variables should not be assigned until just prior its first reference. An early return statement may leave this variable unused.', + }, + ], }, { code: ` @@ -87,7 +97,12 @@ function example() { return number + foo; }`, options: [ { excludePattern: '^run' } ], - errors: [ { message: 'Variables should not be assigned until just prior its first reference. An early return statement may leave this variable unused.' } ], + errors: [ + { + message: + 'Variables should not be assigned until just prior its first reference. An early return statement may leave this variable unused.', + }, + ], }, ], } ); diff --git a/packages/eslint-plugin/rules/__tests__/react-no-unsafe-timeout.js b/packages/eslint-plugin/rules/__tests__/react-no-unsafe-timeout.js index 85046a73b56104..4dadadb5da59ed 100644 --- a/packages/eslint-plugin/rules/__tests__/react-no-unsafe-timeout.js +++ b/packages/eslint-plugin/rules/__tests__/react-no-unsafe-timeout.js @@ -56,15 +56,30 @@ function MyComponent() { invalid: [ { code: `function MyComponent() { setTimeout(); }`, - errors: [ { message: 'setTimeout in a component must be cancelled on unmount' } ], + errors: [ + { + message: + 'setTimeout in a component must be cancelled on unmount', + }, + ], }, { code: `class MyComponent extends Component { componentDidMount() { setTimeout(); } }`, - errors: [ { message: 'setTimeout in a component must be cancelled on unmount' } ], + errors: [ + { + message: + 'setTimeout in a component must be cancelled on unmount', + }, + ], }, { code: `class MyComponent extends wp.element.Component { componentDidMount() { setTimeout(); } }`, - errors: [ { message: 'setTimeout in a component must be cancelled on unmount' } ], + errors: [ + { + message: + 'setTimeout in a component must be cancelled on unmount', + }, + ], }, ], } ); diff --git a/packages/eslint-plugin/rules/__tests__/valid-sprintf.js b/packages/eslint-plugin/rules/__tests__/valid-sprintf.js index 06664969d81489..119653220b3c0a 100644 --- a/packages/eslint-plugin/rules/__tests__/valid-sprintf.js +++ b/packages/eslint-plugin/rules/__tests__/valid-sprintf.js @@ -41,35 +41,72 @@ ruleTester.run( 'valid-sprintf', rule, { invalid: [ { code: `sprintf()`, - errors: [ { message: 'sprintf must be called with a format string' } ], + errors: [ + { message: 'sprintf must be called with a format string' }, + ], }, { code: `sprintf( '%s' )`, - errors: [ { message: 'sprintf must be called with placeholder value argument(s)' } ], + errors: [ + { + message: + 'sprintf must be called with placeholder value argument(s)', + }, + ], }, { code: `sprintf( 1, 'substitute' )`, - errors: [ { message: 'sprintf must be called with a valid format string' } ], + errors: [ + { + message: + 'sprintf must be called with a valid format string', + }, + ], }, { code: `sprintf( [], 'substitute' )`, - errors: [ { message: 'sprintf must be called with a valid format string' } ], + errors: [ + { + message: + 'sprintf must be called with a valid format string', + }, + ], }, { code: `sprintf( '%%', 'substitute' )`, - errors: [ { message: 'sprintf format string must contain at least one placeholder' } ], + errors: [ + { + message: + 'sprintf format string must contain at least one placeholder', + }, + ], }, { code: `sprintf( __( '%%' ), 'substitute' )`, - errors: [ { message: 'sprintf format string must contain at least one placeholder' } ], + errors: [ + { + message: + 'sprintf format string must contain at least one placeholder', + }, + ], }, { code: `sprintf( _n( '%s', '' ), 'substitute' )`, - errors: [ { message: 'sprintf format string options must have the same number of placeholders' } ], + errors: [ + { + message: + 'sprintf format string options must have the same number of placeholders', + }, + ], }, { code: `sprintf( _n( '%s', '%s %s' ), 'substitute' )`, - errors: [ { message: 'sprintf format string options must have the same number of placeholders' } ], + errors: [ + { + message: + 'sprintf format string options must have the same number of placeholders', + }, + ], }, ], } ); diff --git a/packages/eslint-plugin/rules/dependency-group.js b/packages/eslint-plugin/rules/dependency-group.js index 995250e53c1871..8881cb28ef6ee2 100644 --- a/packages/eslint-plugin/rules/dependency-group.js +++ b/packages/eslint-plugin/rules/dependency-group.js @@ -3,7 +3,8 @@ module.exports = { type: 'layout', docs: { description: 'Enforce dependencies docblocks formatting', - url: 'https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/dependency-group.md', + url: + 'https://github.com/WordPress/gutenberg/blob/master/packages/eslint-plugin/docs/rules/dependency-group.md', }, schema: [], fixable: true, @@ -83,7 +84,10 @@ module.exports = { locality = '(External|Node)'; } - const pattern = new RegExp( `^\\*?\\n \\* ${ locality } dependencies\\.?\\n $`, 'i' ); + const pattern = new RegExp( + `^\\*?\\n \\* ${ locality } dependencies\\.?\\n $`, + 'i' + ); return pattern.test( value ); } @@ -190,7 +194,10 @@ module.exports = { verified.add( locality ); // Determine whether a correction must be made. - const correction = getDependencyBlockCorrection( child, locality ); + const correction = getDependencyBlockCorrection( + child, + locality + ); if ( ! correction ) { return; } diff --git a/packages/eslint-plugin/rules/gutenberg-phase.js b/packages/eslint-plugin/rules/gutenberg-phase.js index 168ce1ff782d03..3eb0d3fbe62c0f 100644 --- a/packages/eslint-plugin/rules/gutenberg-phase.js +++ b/packages/eslint-plugin/rules/gutenberg-phase.js @@ -43,14 +43,13 @@ function testIsAccessedViaProcessEnv( node, context ) { parent && parent.type === 'MemberExpression' && context.getSource( parent ) === 'process.env.GUTENBERG_PHASE' - ) { return; } context.report( node, - 'The `GUTENBERG_PHASE` constant should be accessed using `process.env.GUTENBERG_PHASE`.', + 'The `GUTENBERG_PHASE` constant should be accessed using `process.env.GUTENBERG_PHASE`.' ); } @@ -72,17 +71,22 @@ function testIsAccessedViaProcessEnv( node, context ) { * @param {Object} context The eslint context object. */ function testIsUsedInStrictBinaryExpression( node, context ) { - const parent = findParent( node, ( candidate ) => candidate.type === 'BinaryExpression' ); + const parent = findParent( + node, + ( candidate ) => candidate.type === 'BinaryExpression' + ); if ( parent ) { - const comparisonNode = node.parent.type === 'MemberExpression' ? node.parent : node; + const comparisonNode = + node.parent.type === 'MemberExpression' ? node.parent : node; // Test for process.env.GUTENBERG_PHASE === <number> or <number> === process.env.GUTENBERG_PHASE const hasCorrectOperator = [ '===', '!==' ].includes( parent.operator ); - const hasCorrectOperands = ( - ( parent.left === comparisonNode && typeof parent.right.value === 'number' ) || - ( parent.right === comparisonNode && typeof parent.left.value === 'number' ) - ); + const hasCorrectOperands = + ( parent.left === comparisonNode && + typeof parent.right.value === 'number' ) || + ( parent.right === comparisonNode && + typeof parent.left.value === 'number' ); if ( hasCorrectOperator && hasCorrectOperands ) { return; @@ -91,7 +95,7 @@ function testIsUsedInStrictBinaryExpression( node, context ) { context.report( node, - 'The `GUTENBERG_PHASE` constant should only be used in a strict equality comparison with a primitive number.', + 'The `GUTENBERG_PHASE` constant should only be used in a strict equality comparison with a primitive number.' ); } @@ -112,13 +116,16 @@ function testIsUsedInStrictBinaryExpression( node, context ) { * @param {Object} context The eslint context object. */ function testIsUsedInIfOrTernary( node, context ) { - const conditionalParent = findParent( + const conditionalParent = findParent( node, ( candidate ) => + [ 'IfStatement', 'ConditionalExpression' ].includes( candidate.type ) + ); + const binaryParent = findParent( node, - ( candidate ) => [ 'IfStatement', 'ConditionalExpression' ].includes( candidate.type ) + ( candidate ) => candidate.type === 'BinaryExpression' ); - const binaryParent = findParent( node, ( candidate ) => candidate.type === 'BinaryExpression' ); - if ( conditionalParent && + if ( + conditionalParent && binaryParent && conditionalParent.test && conditionalParent.test.start === binaryParent.start && @@ -129,7 +136,7 @@ function testIsUsedInIfOrTernary( node, context ) { context.report( node, - 'The `GUTENBERG_PHASE` constant should only be used as part of the condition in an if statement or ternary expression.', + 'The `GUTENBERG_PHASE` constant should only be used as part of the condition in an if statement or ternary expression.' ); } diff --git a/packages/eslint-plugin/rules/no-base-control-with-label-without-id.js b/packages/eslint-plugin/rules/no-base-control-with-label-without-id.js index 8d82e00a171f1c..e31e18a94dc94e 100644 --- a/packages/eslint-plugin/rules/no-base-control-with-label-without-id.js +++ b/packages/eslint-plugin/rules/no-base-control-with-label-without-id.js @@ -5,10 +5,12 @@ module.exports = { }, create( context ) { return { - 'JSXOpeningElement[name.name=\'BaseControl\']': ( node ) => { + "JSXOpeningElement[name.name='BaseControl']": ( node ) => { const containsAttribute = ( attrName ) => { return node.attributes.some( ( attribute ) => { - return attribute.name && attribute.name.name === attrName; + return ( + attribute.name && attribute.name.name === attrName + ); } ); }; if ( @@ -17,7 +19,8 @@ module.exports = { ) { context.report( { node, - message: 'When using BaseControl component if a label property is passed an id property should also be passed.', + message: + 'When using BaseControl component if a label property is passed an id property should also be passed.', } ); } }, diff --git a/packages/eslint-plugin/rules/no-unguarded-get-range-at.js b/packages/eslint-plugin/rules/no-unguarded-get-range-at.js index e74bc775de54f3..70799a71c90ef3 100644 --- a/packages/eslint-plugin/rules/no-unguarded-get-range-at.js +++ b/packages/eslint-plugin/rules/no-unguarded-get-range-at.js @@ -5,7 +5,9 @@ module.exports = { }, create( context ) { return { - 'CallExpression[callee.object.callee.object.name="window"][callee.object.callee.property.name="getSelection"][callee.property.name="getRangeAt"]'( node ) { + 'CallExpression[callee.object.callee.object.name="window"][callee.object.callee.property.name="getSelection"][callee.property.name="getRangeAt"]'( + node + ) { context.report( { node, message: 'Avoid unguarded getRangeAt', diff --git a/packages/eslint-plugin/rules/no-unused-vars-before-return.js b/packages/eslint-plugin/rules/no-unused-vars-before-return.js index fe8f20f4ded761..1a4cbb7d91c151 100644 --- a/packages/eslint-plugin/rules/no-unused-vars-before-return.js +++ b/packages/eslint-plugin/rules/no-unused-vars-before-return.js @@ -29,13 +29,19 @@ module.exports = { * @return {boolean} Whether declarator is emempt from consideration. */ function isExemptObjectDestructureDeclarator( node ) { - return node.id.type === 'ObjectPattern' && node.id.properties.length > 1; + return ( + node.id.type === 'ObjectPattern' && + node.id.properties.length > 1 + ); } return { ReturnStatement( node ) { let functionScope = context.getScope(); - while ( functionScope.type !== 'function' && functionScope.upper ) { + while ( + functionScope.type !== 'function' && + functionScope.upper + ) { functionScope = functionScope.upper; } @@ -64,16 +70,20 @@ module.exports = { if ( excludePattern !== undefined && - new RegExp( excludePattern ).test( declaratorCandidate.node.init.callee.name ) + new RegExp( excludePattern ).test( + declaratorCandidate.node.init.callee.name + ) ) { return; } // The first entry in `references` is the declaration // itself, which can be ignored. - const isUsedBeforeReturn = variable.references.slice( 1 ).some( ( reference ) => { - return reference.identifier.end < node.end; - } ); + const isUsedBeforeReturn = variable.references + .slice( 1 ) + .some( ( reference ) => { + return reference.identifier.end < node.end; + } ); if ( isUsedBeforeReturn ) { continue; @@ -82,7 +92,7 @@ module.exports = { context.report( declaratorCandidate.node, 'Variables should not be assigned until just prior its first reference. ' + - 'An early return statement may leave this variable unused.' + 'An early return statement may leave this variable unused.' ); } }, diff --git a/packages/eslint-plugin/rules/react-no-unsafe-timeout.js b/packages/eslint-plugin/rules/react-no-unsafe-timeout.js index f97a4d7384fe36..8253d0e3c1f03e 100644 --- a/packages/eslint-plugin/rules/react-no-unsafe-timeout.js +++ b/packages/eslint-plugin/rules/react-no-unsafe-timeout.js @@ -46,10 +46,9 @@ module.exports = { 'CallExpression[callee.name="setTimeout"]'( node ) { // If the result of a `setTimeout` call is assigned to a // variable, assume the timer ID is handled by a cancellation. - const hasAssignment = ( + const hasAssignment = node.parent.type === 'AssignmentExpression' || - node.parent.type === 'VariableDeclarator' - ); + node.parent.type === 'VariableDeclarator'; if ( hasAssignment ) { return; } @@ -73,11 +72,12 @@ module.exports = { // by checking references to see if `setTimeout` resolves to a // variable in scope. const { references } = context.getScope(); - const hasResolvedReference = references.some( ( reference ) => ( - reference.identifier.name === 'setTimeout' && - !! reference.resolved && - reference.resolved.scope.type !== 'global' - ) ); + const hasResolvedReference = references.some( + ( reference ) => + reference.identifier.name === 'setTimeout' && + !! reference.resolved && + reference.resolved.scope.type !== 'global' + ); if ( hasResolvedReference ) { return; diff --git a/packages/format-library/src/bold/index.js b/packages/format-library/src/bold/index.js index 467f80878c0a5e..37d920490a8c9a 100644 --- a/packages/format-library/src/bold/index.js +++ b/packages/format-library/src/bold/index.js @@ -3,7 +3,11 @@ */ import { __ } from '@wordpress/i18n'; import { toggleFormat } from '@wordpress/rich-text'; -import { RichTextToolbarButton, RichTextShortcut, __unstableRichTextInputEvent } from '@wordpress/block-editor'; +import { + RichTextToolbarButton, + RichTextShortcut, + __unstableRichTextInputEvent, +} from '@wordpress/block-editor'; const name = 'core/bold'; const title = __( 'Bold' ); diff --git a/packages/format-library/src/default-formats.js b/packages/format-library/src/default-formats.js index f5d57910a7f13a..889998635d3942 100644 --- a/packages/format-library/src/default-formats.js +++ b/packages/format-library/src/default-formats.js @@ -9,12 +9,4 @@ import { link } from './link'; import { strikethrough } from './strikethrough'; import { underline } from './underline'; -export default [ - bold, - code, - image, - italic, - link, - strikethrough, - underline, -]; +export default [ bold, code, image, italic, link, strikethrough, underline ]; diff --git a/packages/format-library/src/default-formats.native.js b/packages/format-library/src/default-formats.native.js index 4c822c0ea649b0..22cc07db98e758 100644 --- a/packages/format-library/src/default-formats.native.js +++ b/packages/format-library/src/default-formats.native.js @@ -6,9 +6,4 @@ import { italic } from './italic'; import { link } from './link'; import { strikethrough } from './strikethrough'; -export default [ - bold, - italic, - link, - strikethrough, -]; +export default [ bold, italic, link, strikethrough ]; diff --git a/packages/format-library/src/image/index.js b/packages/format-library/src/image/index.js index c9bb130f5ef47d..67abdc15911c64 100644 --- a/packages/format-library/src/image/index.js +++ b/packages/format-library/src/image/index.js @@ -5,7 +5,11 @@ import { Path, SVG, TextControl, Popover, Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { insertObject } from '@wordpress/rich-text'; -import { MediaUpload, RichTextToolbarButton, MediaUploadCheck } from '@wordpress/block-editor'; +import { + MediaUpload, + RichTextToolbarButton, + MediaUploadCheck, +} from '@wordpress/block-editor'; import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; const ALLOWED_MEDIA_TYPES = [ 'image' ]; @@ -46,7 +50,9 @@ export const image = { } static getDerivedStateFromProps( props, state ) { - const { activeObjectAttributes: { style } } = props; + const { + activeObjectAttributes: { style }, + } = props; if ( style === state.previousStyle ) { return null; @@ -70,7 +76,11 @@ export const image = { } onKeyDown( event ) { - if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( event.keyCode ) > -1 ) { + if ( + [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( + event.keyCode + ) > -1 + ) { // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. event.stopPropagation(); } @@ -85,45 +95,67 @@ export const image = { } render() { - const { value, onChange, onFocus, isObjectActive, activeObjectAttributes } = this.props; + const { + value, + onChange, + onFocus, + isObjectActive, + activeObjectAttributes, + } = this.props; return ( <MediaUploadCheck> <RichTextToolbarButton - icon={ <SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><Path d="M4 16h10c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v9c0 1.1.9 2 2 2zM4 5h10v9H4V5zm14 9v2h4v-2h-4zM2 20h20v-2H2v2zm6.4-8.8L7 9.4 5 12h8l-2.6-3.4-2 2.6z" /></SVG> } + icon={ + <SVG + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + > + <Path d="M4 16h10c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v9c0 1.1.9 2 2 2zM4 5h10v9H4V5zm14 9v2h4v-2h-4zM2 20h20v-2H2v2zm6.4-8.8L7 9.4 5 12h8l-2.6-3.4-2 2.6z" /> + </SVG> + } title={ title } onClick={ this.openModal } isActive={ isObjectActive } /> - { this.state.modal && <MediaUpload - allowedTypes={ ALLOWED_MEDIA_TYPES } - onSelect={ ( { id, url, alt, width } ) => { - this.closeModal(); - onChange( insertObject( value, { - type: name, - attributes: { - className: `wp-image-${ id }`, - style: `width: ${ Math.min( width, 150 ) }px;`, - url, - alt, - }, - } ) ); - onFocus(); - } } - onClose={ this.closeModal } - render={ ( { open } ) => { - open(); - return null; - } } - /> } - { isObjectActive && + { this.state.modal && ( + <MediaUpload + allowedTypes={ ALLOWED_MEDIA_TYPES } + onSelect={ ( { id, url, alt, width } ) => { + this.closeModal(); + onChange( + insertObject( value, { + type: name, + attributes: { + className: `wp-image-${ id }`, + style: `width: ${ Math.min( + width, + 150 + ) }px;`, + url, + alt, + }, + } ) + ); + onFocus(); + } } + onClose={ this.closeModal } + render={ ( { open } ) => { + open(); + return null; + } } + /> + ) } + { isObjectActive && ( <Popover position="bottom center" focusOnMount={ false } anchorRef={ getRange() } > - { // Disable reason: KeyPress must be suppressed so the block doesn't hide the toolbar - /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ } + { + // Disable reason: KeyPress must be suppressed so the block doesn't hide the toolbar + /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ + } <form className="block-editor-format-toolbar__image-container-content" onKeyPress={ stopKeyPropagation } @@ -155,11 +187,15 @@ export const image = { min={ 1 } onChange={ this.onChange } /> - <Button icon="editor-break" label={ __( 'Apply' ) } type="submit" /> + <Button + icon="editor-break" + label={ __( 'Apply' ) } + type="submit" + /> </form> { /* eslint-enable jsx-a11y/no-noninteractive-element-interactions */ } </Popover> - } + ) } </MediaUploadCheck> ); } diff --git a/packages/format-library/src/index.js b/packages/format-library/src/index.js index d3341b31315ee3..274d9972eae3ea 100644 --- a/packages/format-library/src/index.js +++ b/packages/format-library/src/index.js @@ -1,13 +1,13 @@ /** * WordPress dependencies */ -import { - registerFormatType, -} from '@wordpress/rich-text'; +import { registerFormatType } from '@wordpress/rich-text'; /** * Internal dependencies */ import formats from './default-formats'; -formats.forEach( ( { name, ...settings } ) => registerFormatType( name, settings ) ); +formats.forEach( ( { name, ...settings } ) => + registerFormatType( name, settings ) +); diff --git a/packages/format-library/src/italic/index.js b/packages/format-library/src/italic/index.js index 5d66a7d38520de..54ebc758a88eb3 100644 --- a/packages/format-library/src/italic/index.js +++ b/packages/format-library/src/italic/index.js @@ -3,7 +3,11 @@ */ import { __ } from '@wordpress/i18n'; import { toggleFormat } from '@wordpress/rich-text'; -import { RichTextToolbarButton, RichTextShortcut, __unstableRichTextInputEvent } from '@wordpress/block-editor'; +import { + RichTextToolbarButton, + RichTextShortcut, + __unstableRichTextInputEvent, +} from '@wordpress/block-editor'; const name = 'core/italic'; const title = __( 'Italic' ); diff --git a/packages/format-library/src/link/index.js b/packages/format-library/src/link/index.js index 10dfd1863e972c..1a63d9621e8059 100644 --- a/packages/format-library/src/link/index.js +++ b/packages/format-library/src/link/index.js @@ -12,7 +12,10 @@ import { isCollapsed, } from '@wordpress/rich-text'; import { isURL, isEmail } from '@wordpress/url'; -import { RichTextToolbarButton, RichTextShortcut } from '@wordpress/block-editor'; +import { + RichTextToolbarButton, + RichTextShortcut, +} from '@wordpress/block-editor'; import { decodeEntities } from '@wordpress/html-entities'; /** @@ -37,7 +40,9 @@ export const link = { return value; } - const pastedText = ( html || plainText ).replace( /<[^>]+>/g, '' ).trim(); + const pastedText = ( html || plainText ) + .replace( /<[^>]+>/g, '' ) + .trim(); // A URL was pasted, turn the selection into a link if ( ! isURL( pastedText ) ) { @@ -54,87 +59,109 @@ export const link = { }, } ); }, - edit: withSpokenMessages( class LinkEdit extends Component { - constructor() { - super( ...arguments ); + edit: withSpokenMessages( + class LinkEdit extends Component { + constructor() { + super( ...arguments ); - this.addLink = this.addLink.bind( this ); - this.stopAddingLink = this.stopAddingLink.bind( this ); - this.onRemoveFormat = this.onRemoveFormat.bind( this ); - this.state = { - addingLink: false, - }; - } + this.addLink = this.addLink.bind( this ); + this.stopAddingLink = this.stopAddingLink.bind( this ); + this.onRemoveFormat = this.onRemoveFormat.bind( this ); + this.state = { + addingLink: false, + }; + } - addLink() { - const { value, onChange } = this.props; - const text = getTextContent( slice( value ) ); + addLink() { + const { value, onChange } = this.props; + const text = getTextContent( slice( value ) ); - if ( text && isURL( text ) ) { - onChange( applyFormat( value, { type: name, attributes: { url: text } } ) ); - } else if ( text && isEmail( text ) ) { - onChange( applyFormat( value, { type: name, attributes: { url: `mailto:${ text }` } } ) ); - } else { - this.setState( { addingLink: true } ); + if ( text && isURL( text ) ) { + onChange( + applyFormat( value, { + type: name, + attributes: { url: text }, + } ) + ); + } else if ( text && isEmail( text ) ) { + onChange( + applyFormat( value, { + type: name, + attributes: { url: `mailto:${ text }` }, + } ) + ); + } else { + this.setState( { addingLink: true } ); + } } - } - stopAddingLink() { - this.setState( { addingLink: false } ); - } + stopAddingLink() { + this.setState( { addingLink: false } ); + } - onRemoveFormat() { - const { value, onChange, speak } = this.props; + onRemoveFormat() { + const { value, onChange, speak } = this.props; - onChange( removeFormat( value, name ) ); - speak( __( 'Link removed.' ), 'assertive' ); - } + onChange( removeFormat( value, name ) ); + speak( __( 'Link removed.' ), 'assertive' ); + } - render() { - const { isActive, activeAttributes, value, onChange, onFocus } = this.props; + render() { + const { + isActive, + activeAttributes, + value, + onChange, + onFocus, + } = this.props; - return ( - <> - <RichTextShortcut - type="primary" - character="k" - onUse={ this.addLink } - /> - <RichTextShortcut - type="primaryShift" - character="k" - onUse={ this.onRemoveFormat } - /> - { isActive && <RichTextToolbarButton - name="link" - icon="editor-unlink" - title={ __( 'Unlink' ) } - onClick={ this.onRemoveFormat } - isActive={ isActive } - shortcutType="primaryShift" - shortcutCharacter="k" - /> } - { ! isActive && <RichTextToolbarButton - name="link" - icon="admin-links" - title={ title } - onClick={ this.addLink } - isActive={ isActive } - shortcutType="primary" - shortcutCharacter="k" - /> } - <InlineLinkUI - key={ isActive } // Make sure link UI state resets when switching between links. - addingLink={ this.state.addingLink } - stopAddingLink={ this.stopAddingLink } - isActive={ isActive } - activeAttributes={ activeAttributes } - value={ value } - onChange={ onChange } - onFocus={ onFocus } - /> - </> - ); + return ( + <> + <RichTextShortcut + type="primary" + character="k" + onUse={ this.addLink } + /> + <RichTextShortcut + type="primaryShift" + character="k" + onUse={ this.onRemoveFormat } + /> + { isActive && ( + <RichTextToolbarButton + name="link" + icon="editor-unlink" + title={ __( 'Unlink' ) } + onClick={ this.onRemoveFormat } + isActive={ isActive } + shortcutType="primaryShift" + shortcutCharacter="k" + /> + ) } + { ! isActive && ( + <RichTextToolbarButton + name="link" + icon="admin-links" + title={ title } + onClick={ this.addLink } + isActive={ isActive } + shortcutType="primary" + shortcutCharacter="k" + /> + ) } + <InlineLinkUI + key={ isActive } // Make sure link UI state resets when switching between links. + addingLink={ this.state.addingLink } + stopAddingLink={ this.stopAddingLink } + isActive={ isActive } + activeAttributes={ activeAttributes } + value={ value } + onChange={ onChange } + onFocus={ onFocus } + /> + </> + ); + } } - } ), + ), }; diff --git a/packages/format-library/src/link/index.native.js b/packages/format-library/src/link/index.native.js index 5f0ad03034d2f3..3d205706f892ee 100644 --- a/packages/format-library/src/link/index.native.js +++ b/packages/format-library/src/link/index.native.js @@ -37,121 +37,130 @@ export const link = { url: 'href', target: 'target', }, - edit: withSpokenMessages( class LinkEdit extends Component { - constructor() { - super( ...arguments ); - - this.addLink = this.addLink.bind( this ); - this.stopAddingLink = this.stopAddingLink.bind( this ); - this.onRemoveFormat = this.onRemoveFormat.bind( this ); - this.getURLFromClipboard = this.getURLFromClipboard.bind( this ); - this.state = { - addingLink: false, - }; - } + edit: withSpokenMessages( + class LinkEdit extends Component { + constructor() { + super( ...arguments ); + + this.addLink = this.addLink.bind( this ); + this.stopAddingLink = this.stopAddingLink.bind( this ); + this.onRemoveFormat = this.onRemoveFormat.bind( this ); + this.getURLFromClipboard = this.getURLFromClipboard.bind( + this + ); + this.state = { + addingLink: false, + }; + } - addLink() { - const { value, onChange } = this.props; - const text = getTextContent( slice( value ) ); + addLink() { + const { value, onChange } = this.props; + const text = getTextContent( slice( value ) ); + + if ( text && isURL( text ) ) { + onChange( + applyFormat( value, { + type: name, + attributes: { url: text }, + } ) + ); + } else { + this.setState( { addingLink: true } ); + this.getURLFromClipboard(); + } + } - if ( text && isURL( text ) ) { - onChange( applyFormat( value, { type: name, attributes: { url: text } } ) ); - } else { - this.setState( { addingLink: true } ); - this.getURLFromClipboard(); + stopAddingLink() { + this.setState( { addingLink: false } ); } - } - stopAddingLink() { - this.setState( { addingLink: false } ); - } + getLinkSelection() { + const { value, isActive } = this.props; + const startFormat = getActiveFormat( value, 'core/link' ); - getLinkSelection() { - const { value, isActive } = this.props; - const startFormat = getActiveFormat( value, 'core/link' ); + // if the link isn't selected, get the link manually by looking around the cursor + // TODO: handle partly selected links + if ( startFormat && isCollapsed( value ) && isActive ) { + let startIndex = value.start; + let endIndex = value.end; - // if the link isn't selected, get the link manually by looking around the cursor - // TODO: handle partly selected links - if ( startFormat && isCollapsed( value ) && isActive ) { - let startIndex = value.start; - let endIndex = value.end; + while ( find( value.formats[ startIndex ], startFormat ) ) { + startIndex--; + } - while ( find( value.formats[ startIndex ], startFormat ) ) { - startIndex--; - } + endIndex++; - endIndex++; + while ( find( value.formats[ endIndex ], startFormat ) ) { + endIndex++; + } - while ( find( value.formats[ endIndex ], startFormat ) ) { - endIndex++; + return { + ...value, + start: startIndex + 1, + end: endIndex, + }; } - return { - ...value, - start: startIndex + 1, - end: endIndex, - }; + return value; } - return value; - } - - onRemoveFormat() { - const { onChange, speak, value } = this.props; - const startFormat = getActiveFormat( value, 'core/link' ); - - // Before we try to remove anything we check if there is something at the caret position to remove. - if ( isCollapsed( value ) && startFormat === undefined ) { - return; - } + onRemoveFormat() { + const { onChange, speak, value } = this.props; + const startFormat = getActiveFormat( value, 'core/link' ); - const linkSelection = this.getLinkSelection(); + // Before we try to remove anything we check if there is something at the caret position to remove. + if ( isCollapsed( value ) && startFormat === undefined ) { + return; + } - onChange( removeFormat( linkSelection, name ) ); - speak( __( 'Link removed.' ), 'assertive' ); - } + const linkSelection = this.getLinkSelection(); - async getURLFromClipboard() { - const clipboardText = await Clipboard.getString(); - if ( ! clipboardText ) { - return; + onChange( removeFormat( linkSelection, name ) ); + speak( __( 'Link removed.' ), 'assertive' ); } - // Check if pasted text is URL - if ( ! isURL( clipboardText ) ) { - return; + + async getURLFromClipboard() { + const clipboardText = await Clipboard.getString(); + if ( ! clipboardText ) { + return; + } + // Check if pasted text is URL + if ( ! isURL( clipboardText ) ) { + return; + } + this.setState( { clipboardURL: clipboardText } ); } - this.setState( { clipboardURL: clipboardText } ); - } - render() { - const { isActive, activeAttributes, onChange } = this.props; - const linkSelection = this.getLinkSelection(); - // If no URL is set and we have a clipboard URL let's use it - if ( ! activeAttributes.url && this.state.clipboardURL ) { - activeAttributes.url = this.state.clipboardURL; + render() { + const { isActive, activeAttributes, onChange } = this.props; + const linkSelection = this.getLinkSelection(); + // If no URL is set and we have a clipboard URL let's use it + if ( ! activeAttributes.url && this.state.clipboardURL ) { + activeAttributes.url = this.state.clipboardURL; + } + return ( + <> + <ModalLinkUI + isVisible={ this.state.addingLink } + isActive={ isActive } + activeAttributes={ activeAttributes } + onClose={ this.stopAddingLink } + onChange={ onChange } + onRemove={ this.onRemoveFormat } + value={ linkSelection } + /> + <RichTextToolbarButton + name="link" + icon="admin-links" + title={ __( 'Link' ) } + onClick={ this.addLink } + isActive={ isActive } + shortcutType="primary" + shortcutCharacter="k" + /> + </> + ); } - return ( - <> - <ModalLinkUI - isVisible={ this.state.addingLink } - isActive={ isActive } - activeAttributes={ activeAttributes } - onClose={ this.stopAddingLink } - onChange={ onChange } - onRemove={ this.onRemoveFormat } - value={ linkSelection } - /> - <RichTextToolbarButton - name="link" - icon="admin-links" - title={ __( 'Link' ) } - onClick={ this.addLink } - isActive={ isActive } - shortcutType="primary" - shortcutCharacter="k" - /> - </> - ); } - } ), + ), }; diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index 72e075255aa04b..aaeeb5006caa8e 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -3,10 +3,7 @@ */ import { __ } from '@wordpress/i18n'; import { Component, createRef, useMemo } from '@wordpress/element'; -import { - ToggleControl, - withSpokenMessages, -} from '@wordpress/components'; +import { ToggleControl, withSpokenMessages } from '@wordpress/components'; import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; import { prependHTTP } from '@wordpress/url'; import { @@ -83,7 +80,9 @@ class InlineLinkUI extends Component { } static getDerivedStateFromProps( props, state ) { - const { activeAttributes: { url, target } } = props; + const { + activeAttributes: { url, target }, + } = props; const opensInNewWindow = target === '_blank'; if ( ! isShowingInput( props, state ) ) { @@ -102,7 +101,11 @@ class InlineLinkUI extends Component { } onKeyDown( event ) { - if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( event.keyCode ) > -1 ) { + if ( + [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( + event.keyCode + ) > -1 + ) { // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. event.stopPropagation(); } @@ -113,7 +116,11 @@ class InlineLinkUI extends Component { } setLinkTarget( opensInNewWindow ) { - const { activeAttributes: { url = '' }, value, onChange } = this.props; + const { + activeAttributes: { url = '' }, + value, + onChange, + } = this.props; this.setState( { opensInNewWindow } ); @@ -121,11 +128,16 @@ class InlineLinkUI extends Component { if ( ! isShowingInput( this.props, this.state ) ) { const selectedText = getTextContent( slice( value ) ); - onChange( applyFormat( value, createLinkFormat( { - url, - opensInNewWindow, - text: selectedText, - } ) ) ); + onChange( + applyFormat( + value, + createLinkFormat( { + url, + opensInNewWindow, + text: selectedText, + } ) + ) + ); } } @@ -148,7 +160,12 @@ class InlineLinkUI extends Component { event.preventDefault(); if ( isCollapsed( value ) && ! isActive ) { - const toInsert = applyFormat( create( { text: url } ), format, 0, url.length ); + const toInsert = applyFormat( + create( { text: url } ), + format, + 0, + url.length + ); onChange( insert( value, toInsert ) ); } else { onChange( applyFormat( value, format ) ); @@ -159,7 +176,12 @@ class InlineLinkUI extends Component { this.resetState(); if ( ! isValidHref( url ) ) { - speak( __( 'Warning: the link has been inserted but may have errors. Please test it.' ), 'assertive' ); + speak( + __( + 'Warning: the link has been inserted but may have errors. Please test it.' + ), + 'assertive' + ); } else if ( isActive ) { speak( __( 'Link edited.' ), 'assertive' ); } else { @@ -173,7 +195,10 @@ class InlineLinkUI extends Component { // LinkContainer. Detect clicks on autocomplete suggestions using a ref here, and // return to avoid the popover being closed. const autocompleteElement = this.autocompleteRef.current; - if ( autocompleteElement && autocompleteElement.contains( document.activeElement ) ) { + if ( + autocompleteElement && + autocompleteElement.contains( document.activeElement ) + ) { return; } @@ -186,7 +211,12 @@ class InlineLinkUI extends Component { } render() { - const { isActive, activeAttributes: { url }, addingLink, value } = this.props; + const { + isActive, + activeAttributes: { url }, + addingLink, + value, + } = this.props; if ( ! isActive && ! addingLink ) { return null; @@ -227,7 +257,11 @@ class InlineLinkUI extends Component { onKeyPress={ stopKeyPropagation } url={ url } onEditLinkClick={ this.editLink } - linkClassName={ isValidHref( prependHTTP( url ) ) ? undefined : 'has-invalid-link' } + linkClassName={ + isValidHref( prependHTTP( url ) ) + ? undefined + : 'has-invalid-link' + } /> ) } </URLPopoverAtLink> diff --git a/packages/format-library/src/link/modal.native.js b/packages/format-library/src/link/modal.native.js index e83f78c5255e5f..d39030b4c6c1fc 100644 --- a/packages/format-library/src/link/modal.native.js +++ b/packages/format-library/src/link/modal.native.js @@ -10,10 +10,7 @@ import { Platform } from 'react-native'; import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { prependHTTP } from '@wordpress/url'; -import { - BottomSheet, - withSpokenMessages, -} from '@wordpress/components'; +import { BottomSheet, withSpokenMessages } from '@wordpress/components'; import { create, insert, @@ -37,7 +34,9 @@ class ModalLinkUI extends Component { this.submitLink = this.submitLink.bind( this ); this.onChangeInputValue = this.onChangeInputValue.bind( this ); this.onChangeText = this.onChangeText.bind( this ); - this.onChangeOpensInNewWindow = this.onChangeOpensInNewWindow.bind( this ); + this.onChangeOpensInNewWindow = this.onChangeOpensInNewWindow.bind( + this + ); this.removeLink = this.removeLink.bind( this ); this.onDismiss = this.onDismiss.bind( this ); @@ -53,7 +52,9 @@ class ModalLinkUI extends Component { return; } - const { activeAttributes: { url, target } } = this.props; + const { + activeAttributes: { url, target }, + } = this.props; const opensInNewWindow = target === '_blank'; this.setState( { @@ -86,21 +87,44 @@ class ModalLinkUI extends Component { text: linkText, } ); - if ( isCollapsed( value ) && ! isActive ) { // insert link - const toInsert = applyFormat( create( { text: linkText } ), format, 0, linkText.length ); + if ( isCollapsed( value ) && ! isActive ) { + // insert link + const toInsert = applyFormat( + create( { text: linkText } ), + format, + 0, + linkText.length + ); const newAttributes = insert( value, toInsert ); onChange( { ...newAttributes, needsSelectionUpdate: true } ); - } else if ( text !== getTextContent( slice( value ) ) ) { // edit text in selected link - const toInsert = applyFormat( create( { text } ), format, 0, text.length ); - const newAttributes = insert( value, toInsert, value.start, value.end ); + } else if ( text !== getTextContent( slice( value ) ) ) { + // edit text in selected link + const toInsert = applyFormat( + create( { text } ), + format, + 0, + text.length + ); + const newAttributes = insert( + value, + toInsert, + value.start, + value.end + ); onChange( { ...newAttributes, needsSelectionUpdate: true } ); - } else { // transform selected text into link + } else { + // transform selected text into link const newAttributes = applyFormat( value, format ); onChange( { ...newAttributes, needsSelectionUpdate: true } ); } if ( ! isValidHref( url ) ) { - speak( __( 'Warning: the link has been inserted but may have errors. Please test it.' ), 'assertive' ); + speak( + __( + 'Warning: the link has been inserted but may have errors. Please test it.' + ), + 'assertive' + ); } else if ( isActive ) { speak( __( 'Link edited.' ), 'assertive' ); } else { @@ -133,7 +157,8 @@ class ModalLinkUI extends Component { onClose={ this.onDismiss } hideHeader > - { /* eslint-disable jsx-a11y/no-autofocus */ + { + /* eslint-disable jsx-a11y/no-autofocus */ <BottomSheet.Cell icon={ 'admin-links' } label={ __( 'URL' ) } @@ -145,7 +170,8 @@ class ModalLinkUI extends Component { onChangeValue={ this.onChangeInputValue } autoFocus={ Platform.OS === 'ios' } /> - /* eslint-enable jsx-a11y/no-autofocus */ } + /* eslint-enable jsx-a11y/no-autofocus */ + } <BottomSheet.Cell icon={ 'editor-textcolor' } label={ __( 'Link text' ) } diff --git a/packages/format-library/src/link/test/inline.js b/packages/format-library/src/link/test/inline.js index fa5201992c10b6..cc59087e8c429b 100644 --- a/packages/format-library/src/link/test/inline.js +++ b/packages/format-library/src/link/test/inline.js @@ -9,9 +9,7 @@ import { shallow } from 'enzyme'; describe( 'InlineLinkUI', () => { it( 'InlineLinkUI renders', () => { - const wrapper = shallow( - <InlineLinkUI /> - ); + const wrapper = shallow( <InlineLinkUI /> ); expect( wrapper ).toBeTruthy(); } ); @@ -26,7 +24,10 @@ describe( 'InlineLinkUI', () => { it( 'should set state.opensInNewWindow to true if props.activeAttributes.target is _blank', () => { const givenProps = { addingLink: false, - activeAttributes: { url: 'http://www.google.com', target: '_blank' }, + activeAttributes: { + url: 'http://www.google.com', + target: '_blank', + }, }; const wrapper = shallow( diff --git a/packages/format-library/src/link/test/modal.native.js b/packages/format-library/src/link/test/modal.native.js index 9d84f9f903ffbe..d08f88a9879d39 100644 --- a/packages/format-library/src/link/test/modal.native.js +++ b/packages/format-library/src/link/test/modal.native.js @@ -9,9 +9,7 @@ import { shallow } from 'enzyme'; describe( 'LinksUI', () => { it( 'LinksUI renders', () => { - const wrapper = shallow( - <ModalLinkUI /> - ); + const wrapper = shallow( <ModalLinkUI /> ); expect( wrapper ).toBeTruthy(); } ); @@ -19,11 +17,10 @@ describe( 'LinksUI', () => { // Given const onRemove = jest.fn(); const wrapper = shallow( - <ModalLinkUI - onRemove={ onRemove } - onClose={ jest.fn() } - /> - ).dive().dive(); // -> dive() removes the HOC layer that was blocking access to ModalLinkUI + <ModalLinkUI onRemove={ onRemove } onClose={ jest.fn() } /> + ) + .dive() + .dive(); // -> dive() removes the HOC layer that was blocking access to ModalLinkUI // When @@ -40,10 +37,7 @@ describe( 'LinksUI', () => { // Given const onRemove = jest.fn(); const wrapper = shallow( - <ModalLinkUI - onRemove={ onRemove } - onClose={ jest.fn() } - /> + <ModalLinkUI onRemove={ onRemove } onClose={ jest.fn() } /> ).dive(); // -> dive() removes the HOC layer that was blocking access to ModalLinkUI // Mock `submitLink` for simplicity (we don't want to test submitLink itself here) @@ -52,8 +46,15 @@ describe( 'LinksUI', () => { // When // Simulate user typing on the URL Cell. - const bottomSheet = wrapper.dive().find( 'BottomSheet' ).first(); - const cell = bottomSheet.dive().find( 'WithPreferredColorScheme(BottomSheetCell)' ).first().dive(); + const bottomSheet = wrapper + .dive() + .find( 'BottomSheet' ) + .first(); + const cell = bottomSheet + .dive() + .find( 'WithPreferredColorScheme(BottomSheetCell)' ) + .first() + .dive(); cell.simulate( 'changeValue', 'wordpress.com' ); diff --git a/packages/format-library/src/link/test/utils.js b/packages/format-library/src/link/test/utils.js index aec4af14588885..7ae7c1b3ad10c6 100644 --- a/packages/format-library/src/link/test/utils.js +++ b/packages/format-library/src/link/test/utils.js @@ -1,10 +1,7 @@ - /** * Internal dependencies */ -import { - isValidHref, -} from '../utils'; +import { isValidHref } from '../utils'; describe( 'isValidHref', () => { it( 'returns true if the href cannot be recognised as a url or an anchor link', () => { @@ -21,30 +18,48 @@ describe( 'isValidHref', () => { it( 'returns true for valid URLs', () => { expect( isValidHref( 'tel:+123456789' ) ).toBe( true ); expect( isValidHref( 'mailto:test@somewhere.com' ) ).toBe( true ); - expect( isValidHref( 'file:///c:/WINDOWS/winamp.exe' ) ).toBe( true ); + expect( isValidHref( 'file:///c:/WINDOWS/winamp.exe' ) ).toBe( + true + ); expect( isValidHref( 'http://test.com' ) ).toBe( true ); expect( isValidHref( 'https://test.com' ) ).toBe( true ); expect( isValidHref( 'http://test-with-hyphen.com' ) ).toBe( true ); expect( isValidHref( 'http://test.com/' ) ).toBe( true ); expect( isValidHref( 'http://test.com#fragment' ) ).toBe( true ); - expect( isValidHref( 'http://test.com/path#fragment' ) ).toBe( true ); - expect( isValidHref( 'http://test.com/with/path/separators' ) ).toBe( true ); - expect( isValidHref( 'http://test.com/with?query=string&params' ) ).toBe( true ); + expect( isValidHref( 'http://test.com/path#fragment' ) ).toBe( + true + ); + expect( + isValidHref( 'http://test.com/with/path/separators' ) + ).toBe( true ); + expect( + isValidHref( 'http://test.com/with?query=string&params' ) + ).toBe( true ); } ); it( 'returns false for invalid urls', () => { expect( isValidHref( 'tel:+12 345 6789' ) ).toBe( false ); - expect( isValidHref( 'mailto:test @ somewhere.com' ) ).toBe( false ); + expect( isValidHref( 'mailto:test @ somewhere.com' ) ).toBe( + false + ); expect( isValidHref( 'mailto: test@somewhere.com' ) ).toBe( false ); expect( isValidHref( 'ht#tp://this/is/invalid' ) ).toBe( false ); expect( isValidHref( 'ht#tp://th&is/is/invalid' ) ).toBe( false ); expect( isValidHref( 'http:/test.com' ) ).toBe( false ); expect( isValidHref( 'http://?test.com' ) ).toBe( false ); expect( isValidHref( 'http://#test.com' ) ).toBe( false ); - expect( isValidHref( 'http://test.com?double?params' ) ).toBe( false ); - expect( isValidHref( 'http://test.com#double#anchor' ) ).toBe( false ); - expect( isValidHref( 'http://test.com?path/after/params' ) ).toBe( false ); - expect( isValidHref( 'http://test.com#path/after/fragment' ) ).toBe( false ); + expect( isValidHref( 'http://test.com?double?params' ) ).toBe( + false + ); + expect( isValidHref( 'http://test.com#double#anchor' ) ).toBe( + false + ); + expect( isValidHref( 'http://test.com?path/after/params' ) ).toBe( + false + ); + expect( isValidHref( 'http://test.com#path/after/fragment' ) ).toBe( + false + ); } ); it( 'returns false if the URL has whitespace', () => { @@ -53,8 +68,12 @@ describe( 'isValidHref', () => { expect( isValidHref( 'http:// test.com' ) ).toBe( false ); expect( isValidHref( 'http://test.c om' ) ).toBe( false ); expect( isValidHref( 'http://test.com/ee ee/' ) ).toBe( false ); - expect( isValidHref( 'http://test.com/eeee?qwd qwdw' ) ).toBe( false ); - expect( isValidHref( 'http://test.com/eeee#qwd qwdw' ) ).toBe( false ); + expect( isValidHref( 'http://test.com/eeee?qwd qwdw' ) ).toBe( + false + ); + expect( isValidHref( 'http://test.com/eeee#qwd qwdw' ) ).toBe( + false + ); } ); } ); @@ -76,4 +95,3 @@ describe( 'isValidHref', () => { } ); } ); } ); - diff --git a/packages/format-library/src/link/utils.js b/packages/format-library/src/link/utils.js index 58f64ab71ce41b..ed22d57b24a022 100644 --- a/packages/format-library/src/link/utils.js +++ b/packages/format-library/src/link/utils.js @@ -47,7 +47,10 @@ export function isValidHref( href ) { // Add some extra checks for http(s) URIs, since these are the most common use-case. // This ensures URIs with an http protocol have exactly two forward slashes following the protocol. - if ( startsWith( protocol, 'http' ) && ! /^https?:\/\/[^\/\s]/i.test( trimmedHref ) ) { + if ( + startsWith( protocol, 'http' ) && + ! /^https?:\/\/[^\/\s]/i.test( trimmedHref ) + ) { return false; } diff --git a/packages/format-library/src/underline/index.js b/packages/format-library/src/underline/index.js index d83cc2e3c484c2..217a56bd64d1b7 100644 --- a/packages/format-library/src/underline/index.js +++ b/packages/format-library/src/underline/index.js @@ -3,7 +3,10 @@ */ import { __ } from '@wordpress/i18n'; import { toggleFormat } from '@wordpress/rich-text'; -import { RichTextShortcut, __unstableRichTextInputEvent } from '@wordpress/block-editor'; +import { + RichTextShortcut, + __unstableRichTextInputEvent, +} from '@wordpress/block-editor'; const name = 'core/underline'; @@ -23,7 +26,8 @@ export const underline = { attributes: { style: 'text-decoration: underline;', }, - } ) ); + } ) + ); }; return ( diff --git a/packages/hooks/benchmark/index.js b/packages/hooks/benchmark/index.js index e78b2827bb1ff2..6b54e16035bd54 100644 --- a/packages/hooks/benchmark/index.js +++ b/packages/hooks/benchmark/index.js @@ -1,7 +1,7 @@ const Benchmark = require( 'benchmark' ); const hooks = require( '../' ); -const suite = new Benchmark.Suite; +const suite = new Benchmark.Suite(); const filter = process.argv[ 2 ]; const isInFilter = ( key ) => ! filter || filter === key; diff --git a/packages/hooks/src/createAddHook.js b/packages/hooks/src/createAddHook.js index 79b3d6f0b91d3e..3d545a2e824cfc 100644 --- a/packages/hooks/src/createAddHook.js +++ b/packages/hooks/src/createAddHook.js @@ -39,7 +39,9 @@ function createAddHook( hooks ) { // Validate numeric priority if ( 'number' !== typeof priority ) { // eslint-disable-next-line no-console - console.error( 'If specified, the hook priority must be a number.' ); + console.error( + 'If specified, the hook priority must be a number.' + ); return; } @@ -69,7 +71,10 @@ function createAddHook( hooks ) { // problem; otherwise we need to increase the execution index of // any other runs by 1 to account for the added element. ( hooks.__current || [] ).forEach( ( hookInfo ) => { - if ( hookInfo.name === hookName && hookInfo.currentIndex >= i ) { + if ( + hookInfo.name === hookName && + hookInfo.currentIndex >= i + ) { hookInfo.currentIndex++; } } ); diff --git a/packages/hooks/src/createDidHook.js b/packages/hooks/src/createDidHook.js index b5cf3cf687195f..9075507b3d8653 100644 --- a/packages/hooks/src/createDidHook.js +++ b/packages/hooks/src/createDidHook.js @@ -24,9 +24,9 @@ function createDidHook( hooks ) { return; } - return hooks[ hookName ] && hooks[ hookName ].runs ? - hooks[ hookName ].runs : - 0; + return hooks[ hookName ] && hooks[ hookName ].runs + ? hooks[ hookName ].runs + : 0; }; } diff --git a/packages/hooks/src/createDoingHook.js b/packages/hooks/src/createDoingHook.js index c2bf6ba830e32e..055e99e52cedd9 100644 --- a/packages/hooks/src/createDoingHook.js +++ b/packages/hooks/src/createDoingHook.js @@ -23,9 +23,9 @@ function createDoingHook( hooks ) { } // Return the __current hook. - return hooks.__current[ 0 ] ? - hookName === hooks.__current[ 0 ].name : - false; + return hooks.__current[ 0 ] + ? hookName === hooks.__current[ 0 ].name + : false; }; } diff --git a/packages/hooks/src/createHasHook.js b/packages/hooks/src/createHasHook.js index 7e2474ccf8ae0b..d92f884560b5e6 100644 --- a/packages/hooks/src/createHasHook.js +++ b/packages/hooks/src/createHasHook.js @@ -20,8 +20,12 @@ function createHasHook( hooks ) { return function hasHook( hookName, namespace ) { // Use the namespace if provided. if ( 'undefined' !== typeof namespace ) { - return hookName in hooks && - hooks[ hookName ].handlers.some( ( hook ) => hook.namespace === namespace ); + return ( + hookName in hooks && + hooks[ hookName ].handlers.some( + ( hook ) => hook.namespace === namespace + ) + ); } return hookName in hooks; diff --git a/packages/hooks/src/createRemoveHook.js b/packages/hooks/src/createRemoveHook.js index bbca6a6188e56b..f2cf9456d45f8a 100644 --- a/packages/hooks/src/createRemoveHook.js +++ b/packages/hooks/src/createRemoveHook.js @@ -50,9 +50,7 @@ function createRemoveHook( hooks, removeAll ) { // Try to find the specified callback to remove. const handlers = hooks[ hookName ].handlers; for ( let i = handlers.length - 1; i >= 0; i-- ) { - if ( - handlers[ i ].namespace === namespace - ) { + if ( handlers[ i ].namespace === namespace ) { handlers.splice( i, 1 ); handlersRemoved++; // This callback may also be part of a hook that is @@ -61,7 +59,10 @@ function createRemoveHook( hooks, removeAll ) { // otherwise we need to decrease the execution index of any // other runs by 1 to account for the removed element. ( hooks.__current || [] ).forEach( ( hookInfo ) => { - if ( hookInfo.name === hookName && hookInfo.currentIndex >= i ) { + if ( + hookInfo.name === hookName && + hookInfo.currentIndex >= i + ) { hookInfo.currentIndex--; } } ); diff --git a/packages/hooks/src/createRunHook.js b/packages/hooks/src/createRunHook.js index d9c909b734af7d..311b1cc9052620 100644 --- a/packages/hooks/src/createRunHook.js +++ b/packages/hooks/src/createRunHook.js @@ -39,9 +39,7 @@ function createRunHook( hooks, returnFirstArg ) { } if ( ! handlers || ! handlers.length ) { - return returnFirstArg ? - args[ 0 ] : - undefined; + return returnFirstArg ? args[ 0 ] : undefined; } const hookInfo = { diff --git a/packages/hooks/src/test/index.test.js b/packages/hooks/src/test/index.test.js index 26d04853ede06c..15ab466608b60c 100644 --- a/packages/hooks/src/test/index.test.js +++ b/packages/hooks/src/test/index.test.js @@ -185,7 +185,7 @@ test( 'Can add filters with periods in hookName', () => { } ); test( 'cannot add filters with namespace containing backslash', () => { - addFilter( 'hook_name', 'i\n\v\a\l\i\d\n\a\m\e', () => null ); + addFilter( 'hook_name', 'i\n\valid\name', () => null ); expect( console ).toHaveErroredWith( 'The namespace can only contain numbers, letters, dashes, periods, underscores and slashes.' ); @@ -237,16 +237,24 @@ test( 'filters with the same and different priorities', () => { addFilter( 'test_order', 'my_callback_fn_2b', callbacks.fn_2b, 2 ); addFilter( 'test_order', 'my_callback_fn_2c', callbacks.fn_2c, 2 ); - expect( applyFilters( 'test_order', [] ) ).toEqual( - [ '2a', '2b', '2c', '3a', '3b', '3c' ] - ); + expect( applyFilters( 'test_order', [] ) ).toEqual( [ + '2a', + '2b', + '2c', + '3a', + '3b', + '3c', + ] ); removeFilter( 'test_order', 'my_callback_fn_2b', callbacks.fn_2b ); removeFilter( 'test_order', 'my_callback_fn_3a', callbacks.fn_3a ); - expect( applyFilters( 'test_order', [] ) ).toEqual( - [ '2a', '2c', '3b', '3c' ] - ); + expect( applyFilters( 'test_order', [] ) ).toEqual( [ + '2a', + '2c', + '3b', + '3c', + ] ); addFilter( 'test_order', 'my_callback_fn_4a', callbacks.fn_4a, 4 ); addFilter( 'test_order', 'my_callback_fn_4b', callbacks.fn_4b, 4 ); @@ -261,10 +269,20 @@ test( 'filters with the same and different priorities', () => { expect( applyFilters( 'test_order', [] ) ).toEqual( [ // all except 2b and 3a, which we removed earlier - '1a', '1b', '1c', '1d', - '2a', '2c', '2d', - '3b', '3c', '3d', - '4a', '4b', '4c', '4d', + '1a', + '1b', + '1c', + '1d', + '2a', + '2c', + '2d', + '3b', + '3c', + '3d', + '4a', + '4b', + '4c', + '4d', ] ); } ); @@ -320,28 +338,58 @@ test( 'fire action multiple times', () => { } ); test( 'add a filter before the one currently executing', () => { - addFilter( 'test.filter', 'my_callback', ( outerValue ) => { - addFilter( 'test.filter', 'my_callback', ( innerValue ) => innerValue + 'a', 1 ); - return outerValue + 'b'; - }, 2 ); + addFilter( + 'test.filter', + 'my_callback', + ( outerValue ) => { + addFilter( + 'test.filter', + 'my_callback', + ( innerValue ) => innerValue + 'a', + 1 + ); + return outerValue + 'b'; + }, + 2 + ); expect( applyFilters( 'test.filter', 'test_' ) ).toBe( 'test_b' ); } ); test( 'add a filter after the one currently executing', () => { - addFilter( 'test.filter', 'my_callback', ( outerValue ) => { - addFilter( 'test.filter', 'my_callback', ( innerValue ) => innerValue + 'b', 2 ); - return outerValue + 'a'; - }, 1 ); + addFilter( + 'test.filter', + 'my_callback', + ( outerValue ) => { + addFilter( + 'test.filter', + 'my_callback', + ( innerValue ) => innerValue + 'b', + 2 + ); + return outerValue + 'a'; + }, + 1 + ); expect( applyFilters( 'test.filter', 'test_' ) ).toBe( 'test_ab' ); } ); test( 'add a filter immediately after the one currently executing', () => { - addFilter( 'test.filter', 'my_callback', ( outerValue ) => { - addFilter( 'test.filter', 'my_callback', ( innerValue ) => innerValue + 'b', 1 ); - return outerValue + 'a'; - }, 1 ); + addFilter( + 'test.filter', + 'my_callback', + ( outerValue ) => { + addFilter( + 'test.filter', + 'my_callback', + ( innerValue ) => innerValue + 'b', + 1 + ); + return outerValue + 'a'; + }, + 1 + ); expect( applyFilters( 'test.filter', 'test_' ) ).toBe( 'test_ab' ); } ); @@ -379,7 +427,12 @@ test( 'filter removes a callback that has already executed', () => { addFilter( 'test.filter', 'my_callback_filter_a', filterA, 1 ); addFilter( 'test.filter', 'my_callback_filter_b', filterB, 3 ); addFilter( 'test.filter', 'my_callback_filter_c', filterC, 5 ); - addFilter( 'test.filter', 'my_callback_filter_removes_b', filterRemovesB, 4 ); + addFilter( + 'test.filter', + 'my_callback_filter_removes_b', + filterRemovesB, + 4 + ); expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testabc' ); } ); @@ -387,7 +440,12 @@ test( 'filter removes a callback that has already executed', () => { test( 'filter removes a callback that has already executed (same priority)', () => { addFilter( 'test.filter', 'my_callback_filter_a', filterA, 1 ); addFilter( 'test.filter', 'my_callback_filter_b', filterB, 2 ); - addFilter( 'test.filter', 'my_callback_filter_removes_b', filterRemovesB, 2 ); + addFilter( + 'test.filter', + 'my_callback_filter_removes_b', + filterRemovesB, + 2 + ); addFilter( 'test.filter', 'my_callback_filter_c', filterC, 4 ); expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testabc' ); @@ -395,7 +453,12 @@ test( 'filter removes a callback that has already executed (same priority)', () test( 'filter removes the current callback', () => { addFilter( 'test.filter', 'my_callback_filter_a', filterA, 1 ); - addFilter( 'test.filter', 'my_callback_filter_c_removes_self', filterCRemovesSelf, 3 ); + addFilter( + 'test.filter', + 'my_callback_filter_c_removes_self', + filterCRemovesSelf, + 3 + ); addFilter( 'test.filter', 'my_callback_filter_c', filterC, 5 ); expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testabc' ); @@ -405,7 +468,12 @@ test( 'filter removes a callback that has not yet executed (last)', () => { addFilter( 'test.filter', 'my_callback_filter_a', filterA, 1 ); addFilter( 'test.filter', 'my_callback_filter_b', filterB, 3 ); addFilter( 'test.filter', 'my_callback_filter_c', filterC, 5 ); - addFilter( 'test.filter', 'my_callback_filter_removes_c', filterRemovesC, 4 ); + addFilter( + 'test.filter', + 'my_callback_filter_removes_c', + filterRemovesC, + 4 + ); expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testab' ); } ); @@ -414,14 +482,24 @@ test( 'filter removes a callback that has not yet executed (middle)', () => { addFilter( 'test.filter', 'my_callback_filter_a', filterA, 1 ); addFilter( 'test.filter', 'my_callback_filter_b', filterB, 3 ); addFilter( 'test.filter', 'my_callback_filter_c', filterC, 4 ); - addFilter( 'test.filter', 'my_callback_filter_removes_b', filterRemovesB, 2 ); + addFilter( + 'test.filter', + 'my_callback_filter_removes_b', + filterRemovesB, + 2 + ); expect( applyFilters( 'test.filter', 'test' ) ).toBe( 'testac' ); } ); test( 'filter removes a callback that has not yet executed (same priority)', () => { addFilter( 'test.filter', 'my_callback_filter_a', filterA, 1 ); - addFilter( 'test.filter', 'my_callback_filter_removes_b', filterRemovesB, 2 ); + addFilter( + 'test.filter', + 'my_callback_filter_removes_b', + filterRemovesB, + 2 + ); addFilter( 'test.filter', 'my_callback_filter_b', filterB, 2 ); addFilter( 'test.filter', 'my_callback_filter_c', filterC, 4 ); @@ -553,23 +631,37 @@ test( 'current filter when multiple filters are running', () => { expect( currentFilter() ).toBe( null ); - expect( applyFilters( 'test.filter1', [ 'test' ] ) ).toEqual( - [ 'test', 'test.filter1', 'test.filter2' ] - ); + expect( applyFilters( 'test.filter1', [ 'test' ] ) ).toEqual( [ + 'test', + 'test.filter1', + 'test.filter2', + ] ); expect( currentFilter() ).toBe( null ); } ); test( 'adding and removing filters with recursion', () => { function removeRecurseAndAdd2( val ) { - expect( removeFilter( 'remove_and_add', 'my_callback_recurse' ) ).toBe( 1 ); + expect( removeFilter( 'remove_and_add', 'my_callback_recurse' ) ).toBe( + 1 + ); val += '-' + applyFilters( 'remove_and_add', '' ) + '-'; - addFilter( 'remove_and_add', 'my_callback_recurse', removeRecurseAndAdd2, 10 ); + addFilter( + 'remove_and_add', + 'my_callback_recurse', + removeRecurseAndAdd2, + 10 + ); return val + '2'; } addFilter( 'remove_and_add', 'my_callback', ( val ) => val + '1', 11 ); - addFilter( 'remove_and_add', 'my_callback_recurse', removeRecurseAndAdd2, 12 ); + addFilter( + 'remove_and_add', + 'my_callback_recurse', + removeRecurseAndAdd2, + 12 + ); addFilter( 'remove_and_add', 'my_callback', ( val ) => val + '3', 13 ); addFilter( 'remove_and_add', 'my_callback', ( val ) => val + '4', 14 ); @@ -705,7 +797,7 @@ test( 'add an all filter and run it any hook to trigger it', () => { test( 'add an all action and run it any hook to trigger it', () => { addAction( 'all', 'my_callback', actionA ); - addAction( 'test.action', 'my_callback', actionA );// Doesn't get triggered. + addAction( 'test.action', 'my_callback', actionA ); // Doesn't get triggered. doAction( 'test.action-anything' ); expect( window.actionValue ).toBe( 'a' ); } ); diff --git a/packages/hooks/src/validateHookName.js b/packages/hooks/src/validateHookName.js index 2834dafc974010..de93ecdd306df6 100644 --- a/packages/hooks/src/validateHookName.js +++ b/packages/hooks/src/validateHookName.js @@ -22,7 +22,9 @@ function validateHookName( hookName ) { if ( ! /^[a-zA-Z][a-zA-Z0-9_.-]*$/.test( hookName ) ) { // eslint-disable-next-line no-console - console.error( 'The hook name can only contain numbers, letters, dashes, periods and underscores.' ); + console.error( + 'The hook name can only contain numbers, letters, dashes, periods and underscores.' + ); return false; } diff --git a/packages/hooks/src/validateNamespace.js b/packages/hooks/src/validateNamespace.js index da3d2d95fe732c..bd004482415f8c 100644 --- a/packages/hooks/src/validateNamespace.js +++ b/packages/hooks/src/validateNamespace.js @@ -15,7 +15,9 @@ function validateNamespace( namespace ) { if ( ! /^[a-zA-Z][a-zA-Z0-9_.\-\/]*$/.test( namespace ) ) { // eslint-disable-next-line no-console - console.error( 'The namespace can only contain numbers, letters, dashes, periods, underscores and slashes.' ); + console.error( + 'The namespace can only contain numbers, letters, dashes, periods, underscores and slashes.' + ); return false; } diff --git a/packages/html-entities/src/index.js b/packages/html-entities/src/index.js index 15c04fe51268ad..615087f992e78d 100644 --- a/packages/html-entities/src/index.js +++ b/packages/html-entities/src/index.js @@ -21,8 +21,13 @@ export function decodeEntities( html ) { // create a textarea for decoding entities, that we can reuse if ( undefined === _decodeTextArea ) { - if ( document.implementation && document.implementation.createHTMLDocument ) { - _decodeTextArea = document.implementation.createHTMLDocument( '' ).createElement( 'textarea' ); + if ( + document.implementation && + document.implementation.createHTMLDocument + ) { + _decodeTextArea = document.implementation + .createHTMLDocument( '' ) + .createElement( 'textarea' ); } else { _decodeTextArea = document.createElement( 'textarea' ); } diff --git a/packages/html-entities/src/test/entities.js b/packages/html-entities/src/test/entities.js index e02916011f771a..27c77ed480cdba 100644 --- a/packages/html-entities/src/test/entities.js +++ b/packages/html-entities/src/test/entities.js @@ -21,7 +21,7 @@ describe( 'decodeEntities', () => { } ); it( 'should not care about leading zeros on entity codes', () => { const html = 'Jim&#0039;s mother&#039s post&#39s title.'; - const expected = 'Jim\'s mother\'s post\'s title.'; + const expected = "Jim's mother's post's title."; expect( decodeEntities( html ) ).toEqual( expected ); } ); } ); diff --git a/packages/i18n/benchmark/index.js b/packages/i18n/benchmark/index.js index 54d9bddca8b6eb..f26b59038bf338 100644 --- a/packages/i18n/benchmark/index.js +++ b/packages/i18n/benchmark/index.js @@ -1,7 +1,7 @@ const Benchmark = require( 'benchmark' ); const { __ } = require( '../' ); -const suite = new Benchmark.Suite; +const suite = new Benchmark.Suite(); suite .add( '__', () => { diff --git a/packages/i18n/src/test/index.js b/packages/i18n/src/test/index.js index 19d398daa47efe..b5e68537754df4 100644 --- a/packages/i18n/src/test/index.js +++ b/packages/i18n/src/test/index.js @@ -48,21 +48,29 @@ describe( 'i18n', () => { describe( '_n', () => { it( 'use the plural form', () => { - expect( _n( '%d banana', '%d bananas', 3, 'test_domain' ) ).toBe( '%d bananes' ); + expect( _n( '%d banana', '%d bananas', 3, 'test_domain' ) ).toBe( + '%d bananes' + ); } ); it( 'use the singular form', () => { - expect( _n( '%d banana', '%d bananas', 1, 'test_domain' ) ).toBe( '%d banane' ); + expect( _n( '%d banana', '%d bananas', 1, 'test_domain' ) ).toBe( + '%d banane' + ); } ); } ); describe( '_nx', () => { it( 'use the plural form', () => { - expect( _nx( '%d apple', '%d apples', 3, 'fruit', 'test_domain' ) ).toBe( '%d pommes' ); + expect( + _nx( '%d apple', '%d apples', 3, 'fruit', 'test_domain' ) + ).toBe( '%d pommes' ); } ); it( 'use the singular form', () => { - expect( _nx( '%d apple', '%d apples', 1, 'fruit', 'test_domain' ) ).toBe( '%d pomme' ); + expect( + _nx( '%d apple', '%d apples', 1, 'fruit', 'test_domain' ) + ).toBe( '%d pomme' ); } ); } ); @@ -89,16 +97,21 @@ describe( 'i18n', () => { } ); it( 'supports omitted plural forms expression', () => { - setLocaleData( { - '': { - domain: 'test_domain2', - lang: 'fr', + setLocaleData( + { + '': { + domain: 'test_domain2', + lang: 'fr', + }, + + '%d banana': [ '%d banane', '%d bananes' ], }, + 'test_domain2' + ); - '%d banana': [ '%d banane', '%d bananes' ], - }, 'test_domain2' ); - - expect( _n( '%d banana', '%d bananes', 2, 'test_domain2' ) ).toBe( '%d bananes' ); + expect( _n( '%d banana', '%d bananes', 2, 'test_domain2' ) ).toBe( + '%d bananes' + ); } ); describe( '__', () => { @@ -107,21 +120,29 @@ describe( 'i18n', () => { } ); it( 'new translation available.', () => { - expect( __( 'cheeseburger', 'test_domain' ) ).toBe( 'hamburger au fromage' ); + expect( __( 'cheeseburger', 'test_domain' ) ).toBe( + 'hamburger au fromage' + ); } ); } ); describe( '_n', () => { it( 'existing plural form still works', () => { - expect( _n( '%d banana', '%d bananas', 3, 'test_domain' ) ).toBe( '%d bananes' ); + expect( + _n( '%d banana', '%d bananas', 3, 'test_domain' ) + ).toBe( '%d bananes' ); } ); it( 'new singular form was added', () => { - expect( _n( '%d cat', '%d cats', 1, 'test_domain' ) ).toBe( '%d chat' ); + expect( _n( '%d cat', '%d cats', 1, 'test_domain' ) ).toBe( + '%d chat' + ); } ); it( 'new plural form was added', () => { - expect( _n( '%d cat', '%d cats', 3, 'test_domain' ) ).toBe( '%d chats' ); + expect( _n( '%d cat', '%d cats', 3, 'test_domain' ) ).toBe( + '%d chats' + ); } ); } ); } ); diff --git a/packages/i18n/tools/pot-to-php.js b/packages/i18n/tools/pot-to-php.js index 29aafe3abcf599..b8a1b92819498c 100755 --- a/packages/i18n/tools/pot-to-php.js +++ b/packages/i18n/tools/pot-to-php.js @@ -7,16 +7,17 @@ const fs = require( 'fs' ); const TAB = '\t'; const NEWLINE = '\n'; -const fileHeader = [ - '<?php', - '/* THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY. */', - '$generated_i18n_strings = array(', -].join( NEWLINE ) + NEWLINE; - -const fileFooter = NEWLINE + [ - ');', - '/* THIS IS THE END OF THE GENERATED FILE */', -].join( NEWLINE ) + NEWLINE; +const fileHeader = + [ + '<?php', + '/* THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY. */', + '$generated_i18n_strings = array(', + ].join( NEWLINE ) + NEWLINE; + +const fileFooter = + NEWLINE + + [ ');', '/* THIS IS THE END OF THE GENERATED FILE */' ].join( NEWLINE ) + + NEWLINE; /** * Escapes single quotes. @@ -25,7 +26,7 @@ const fileFooter = NEWLINE + [ * @return {string} The escaped string. */ function escapeSingleQuotes( input ) { - return input.replace( /'/g, '\\\'' ); + return input.replace( /'/g, "\\'" ); } /** @@ -46,7 +47,9 @@ function convertTranslationToPHP( translation, textdomain, context = '' ) { if ( ! isEmpty( comments ) ) { if ( ! isEmpty( comments.reference ) ) { // All references are split by newlines, add a // Reference prefix to make them tidy. - php += TAB + '// Reference: ' + + php += + TAB + + '// Reference: ' + comments.reference .split( NEWLINE ) .join( NEWLINE + TAB + '// Reference: ' ) + @@ -63,7 +66,8 @@ function convertTranslationToPHP( translation, textdomain, context = '' ) { } if ( ! isEmpty( comments.extracted ) ) { - php += TAB + `/* translators: ${ comments.extracted } */${ NEWLINE }`; + php += + TAB + `/* translators: ${ comments.extracted } */${ NEWLINE }`; } } @@ -74,15 +78,21 @@ function convertTranslationToPHP( translation, textdomain, context = '' ) { if ( isEmpty( context ) ) { php += TAB + `__( '${ original }', '${ textdomain }' )`; } else { - php += TAB + `_x( '${ original }', '${ translation.msgctxt }', '${ textdomain }' )`; + php += + TAB + + `_x( '${ original }', '${ translation.msgctxt }', '${ textdomain }' )`; } } else { const plural = escapeSingleQuotes( translation.msgid_plural ); if ( isEmpty( context ) ) { - php += TAB + `_n_noop( '${ original }', '${ plural }', '${ textdomain }' )`; + php += + TAB + + `_n_noop( '${ original }', '${ plural }', '${ textdomain }' )`; } else { - php += TAB + `_nx_noop( '${ original }', '${ plural }', '${ translation.msgctxt }', '${ textdomain }' )`; + php += + TAB + + `_nx_noop( '${ original }', '${ plural }', '${ translation.msgctxt }', '${ textdomain }' )`; } } } @@ -100,23 +110,26 @@ function convertPOTToPHP( potFile, phpFile, options ) { const translations = parsedPO.translations[ context ]; const newOutput = Object.values( translations ) - .map( ( translation ) => convertTranslationToPHP( translation, options.textdomain, context ) ) + .map( ( translation ) => + convertTranslationToPHP( + translation, + options.textdomain, + context + ) + ) .filter( ( php ) => php !== '' ); output = [ ...output, ...newOutput ]; } - const fileOutput = fileHeader + output.join( ',' + NEWLINE + NEWLINE ) + fileFooter; + const fileOutput = + fileHeader + output.join( ',' + NEWLINE + NEWLINE ) + fileFooter; fs.writeFileSync( phpFile, fileOutput ); } const args = process.argv.slice( 2 ); -convertPOTToPHP( - args[ 0 ], - args[ 1 ], - { - textdomain: args[ 2 ], - } -); +convertPOTToPHP( args[ 0 ], args[ 1 ], { + textdomain: args[ 2 ], +} ); diff --git a/packages/icons/src/library/align-center.js b/packages/icons/src/library/align-center.js index 4b95f643d1061c..5e59c0257bffa4 100644 --- a/packages/icons/src/library/align-center.js +++ b/packages/icons/src/library/align-center.js @@ -10,4 +10,3 @@ const alignCenter = ( ); export default alignCenter; - diff --git a/packages/icons/src/library/align-left.js b/packages/icons/src/library/align-left.js index a34551b910bcf6..f6a1bd571bac06 100644 --- a/packages/icons/src/library/align-left.js +++ b/packages/icons/src/library/align-left.js @@ -10,4 +10,3 @@ const alignLeft = ( ); export default alignLeft; - diff --git a/packages/icons/src/library/align-right.js b/packages/icons/src/library/align-right.js index 9398e65c030d98..2c49885ed9cdaf 100644 --- a/packages/icons/src/library/align-right.js +++ b/packages/icons/src/library/align-right.js @@ -10,4 +10,3 @@ const alignRight = ( ); export default alignRight; - diff --git a/packages/icons/src/library/check.js b/packages/icons/src/library/check.js index fd048ca0739449..6b9893d5cf23c6 100644 --- a/packages/icons/src/library/check.js +++ b/packages/icons/src/library/check.js @@ -10,4 +10,3 @@ const check = ( ); export default check; - diff --git a/packages/icons/src/library/group.js b/packages/icons/src/library/group.js index 499805021940f5..b0fcf8e65a89f3 100644 --- a/packages/icons/src/library/group.js +++ b/packages/icons/src/library/group.js @@ -5,8 +5,16 @@ import { Path, SVG } from '@wordpress/primitives'; const group = ( <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> - <Path fillRule="evenodd" clipRule="evenodd" d="M9 8a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-1v3a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h1V8zm2 3h4V9h-4v2zm2 2H9v2h4v-2z" /> - <Path fillRule="evenodd" clipRule="evenodd" d="M2 4.732A2 2 0 1 1 4.732 2h14.536A2 2 0 1 1 22 4.732v14.536A2 2 0 1 1 19.268 22H4.732A2 2 0 1 1 2 19.268V4.732zM4.732 4h14.536c.175.304.428.557.732.732v14.536a2.01 2.01 0 0 0-.732.732H4.732A2.01 2.01 0 0 0 4 19.268V4.732A2.01 2.01 0 0 0 4.732 4z" /> + <Path + fillRule="evenodd" + clipRule="evenodd" + d="M9 8a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-1v3a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h1V8zm2 3h4V9h-4v2zm2 2H9v2h4v-2z" + /> + <Path + fillRule="evenodd" + clipRule="evenodd" + d="M2 4.732A2 2 0 1 1 4.732 2h14.536A2 2 0 1 1 22 4.732v14.536A2 2 0 1 1 19.268 22H4.732A2 2 0 1 1 2 19.268V4.732zM4.732 4h14.536c.175.304.428.557.732.732v14.536a2.01 2.01 0 0 0-.732.732H4.732A2.01 2.01 0 0 0 4 19.268V4.732A2.01 2.01 0 0 0 4.732 4z" + /> </SVG> ); diff --git a/packages/icons/src/library/heading.js b/packages/icons/src/library/heading.js index 8feb2435fa51b4..88c083cc6b714a 100644 --- a/packages/icons/src/library/heading.js +++ b/packages/icons/src/library/heading.js @@ -10,4 +10,3 @@ const heading = ( ); export default heading; - diff --git a/packages/icons/src/library/menu.js b/packages/icons/src/library/menu.js index a0ecd7df433df5..08c85ec221f59d 100644 --- a/packages/icons/src/library/menu.js +++ b/packages/icons/src/library/menu.js @@ -10,4 +10,3 @@ const menu = ( ); export default menu; - diff --git a/packages/icons/src/library/position-center.js b/packages/icons/src/library/position-center.js index 029f1f4a9aacab..acda81b179fcbf 100644 --- a/packages/icons/src/library/position-center.js +++ b/packages/icons/src/library/position-center.js @@ -1,4 +1,3 @@ - /** * WordPress dependencies */ diff --git a/packages/icons/src/library/position-left.js b/packages/icons/src/library/position-left.js index 6a79937e30b7f2..411639e9a6995e 100644 --- a/packages/icons/src/library/position-left.js +++ b/packages/icons/src/library/position-left.js @@ -1,4 +1,3 @@ - /** * WordPress dependencies */ diff --git a/packages/icons/src/library/position-right.js b/packages/icons/src/library/position-right.js index d5918265696923..f4f9cdc2027e0b 100644 --- a/packages/icons/src/library/position-right.js +++ b/packages/icons/src/library/position-right.js @@ -1,4 +1,3 @@ - /** * WordPress dependencies */ diff --git a/packages/icons/src/library/rss.js b/packages/icons/src/library/rss.js index 6a04efa4740b74..c8c0d5ccc17cbe 100644 --- a/packages/icons/src/library/rss.js +++ b/packages/icons/src/library/rss.js @@ -10,4 +10,3 @@ const rss = ( ); export default rss; - diff --git a/packages/icons/src/library/search.js b/packages/icons/src/library/search.js index fedd9d7d2c74dd..6ca14df2b2a7d7 100644 --- a/packages/icons/src/library/search.js +++ b/packages/icons/src/library/search.js @@ -10,4 +10,3 @@ const search = ( ); export default search; - diff --git a/packages/icons/src/library/stretch-full-width.js b/packages/icons/src/library/stretch-full-width.js index e27b0ab2c2eee8..8b25ab02381a45 100644 --- a/packages/icons/src/library/stretch-full-width.js +++ b/packages/icons/src/library/stretch-full-width.js @@ -1,4 +1,3 @@ - /** * WordPress dependencies */ diff --git a/packages/icons/src/library/stretch-wide.js b/packages/icons/src/library/stretch-wide.js index 6bdda30ede996a..2e1d83b91b2b65 100644 --- a/packages/icons/src/library/stretch-wide.js +++ b/packages/icons/src/library/stretch-wide.js @@ -1,4 +1,3 @@ - /** * WordPress dependencies */ diff --git a/packages/icons/src/library/tag.js b/packages/icons/src/library/tag.js index b7eba812e78c5b..771fcf835395ab 100644 --- a/packages/icons/src/library/tag.js +++ b/packages/icons/src/library/tag.js @@ -10,4 +10,3 @@ const tag = ( ); export default tag; - diff --git a/packages/icons/src/library/trash.js b/packages/icons/src/library/trash.js index 56c011563ac356..d3653149f041e5 100644 --- a/packages/icons/src/library/trash.js +++ b/packages/icons/src/library/trash.js @@ -10,4 +10,3 @@ const trash = ( ); export default trash; - diff --git a/packages/icons/src/library/video.js b/packages/icons/src/library/video.js index 38be9ecfef2a4c..c7c028148b6a3c 100644 --- a/packages/icons/src/library/video.js +++ b/packages/icons/src/library/video.js @@ -10,4 +10,3 @@ const video = ( ); export default video; - diff --git a/packages/is-shallow-equal/benchmark/index.js b/packages/is-shallow-equal/benchmark/index.js index f2e363724126c0..5506e53206e129 100644 --- a/packages/is-shallow-equal/benchmark/index.js +++ b/packages/is-shallow-equal/benchmark/index.js @@ -2,7 +2,7 @@ const Benchmark = require( 'benchmark' ); -const suite = new Benchmark.Suite; +const suite = new Benchmark.Suite(); const beforeObject = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 }; const afterObjectSame = beforeObject; @@ -14,38 +14,52 @@ const afterArrayEqual = [ 1, 2, 3, 4, 5, 6, 7 ]; const afterArrayUnequal = [ 1, 2, 3, 4, 5, 'Unequal', 7 ]; [ - [ '@wordpress/is-shallow-equal (type specific)', require( '../objects' ), require( '../arrays' ) ], + [ + '@wordpress/is-shallow-equal (type specific)', + require( '../objects' ), + require( '../arrays' ), + ], [ '@wordpress/is-shallow-equal', require( '../' ) ], [ 'shallowequal', require( 'shallowequal' ) ], - [ 'shallow-equal (type specific)', require( 'shallow-equal/objects' ), require( 'shallow-equal/arrays' ) ], + [ + 'shallow-equal (type specific)', + require( 'shallow-equal/objects' ), + require( 'shallow-equal/arrays' ), + ], [ 'is-equal-shallow', require( 'is-equal-shallow' ) ], [ 'shallow-equals', require( 'shallow-equals' ) ], [ 'fbjs/lib/shallowEqual', require( 'fbjs/lib/shallowEqual' ) ], -].forEach( ( [ name, isShallowEqualObjects, isShallowEqualArrays = isShallowEqualObjects ] ) => { - suite.add( name + ' (object, equal)', () => { - isShallowEqualObjects( beforeObject, afterObjectEqual ); - } ); - - suite.add( name + ' (object, same)', () => { - isShallowEqualObjects( beforeObject, afterObjectSame ); - } ); - - suite.add( name + ' (object, unequal)', () => { - isShallowEqualObjects( beforeObject, afterObjectUnequal ); - } ); - - suite.add( name + ' (array, equal)', () => { - isShallowEqualArrays( beforeArray, afterArrayEqual ); - } ); - - suite.add( name + ' (array, same)', () => { - isShallowEqualArrays( beforeArray, afterArraySame ); - } ); - - suite.add( name + ' (array, unequal)', () => { - isShallowEqualArrays( beforeArray, afterArrayUnequal ); - } ); -} ); +].forEach( + ( [ + name, + isShallowEqualObjects, + isShallowEqualArrays = isShallowEqualObjects, + ] ) => { + suite.add( name + ' (object, equal)', () => { + isShallowEqualObjects( beforeObject, afterObjectEqual ); + } ); + + suite.add( name + ' (object, same)', () => { + isShallowEqualObjects( beforeObject, afterObjectSame ); + } ); + + suite.add( name + ' (object, unequal)', () => { + isShallowEqualObjects( beforeObject, afterObjectUnequal ); + } ); + + suite.add( name + ' (array, equal)', () => { + isShallowEqualArrays( beforeArray, afterArrayEqual ); + } ); + + suite.add( name + ' (array, same)', () => { + isShallowEqualArrays( beforeArray, afterArraySame ); + } ); + + suite.add( name + ' (array, unequal)', () => { + isShallowEqualArrays( beforeArray, afterArrayUnequal ); + } ); + } +); suite // eslint-disable-next-line no-console diff --git a/packages/jest-console/src/index.js b/packages/jest-console/src/index.js index 02212e517586a8..0c10020455dcfe 100644 --- a/packages/jest-console/src/index.js +++ b/packages/jest-console/src/index.js @@ -16,7 +16,9 @@ import supportedMatchers from './supported-matchers'; * @param {string} methodName Name of console method. */ const setConsoleMethodSpy = ( matcherName, methodName ) => { - const spy = jest.spyOn( console, methodName ).mockName( `console.${ methodName }` ); + const spy = jest + .spyOn( console, methodName ) + .mockName( `console.${ methodName }` ); /** * Resets the spy to its initial state. diff --git a/packages/jest-console/src/matchers.js b/packages/jest-console/src/matchers.js index fa2d6487340b8c..28032203ac296a 100644 --- a/packages/jest-console/src/matchers.js +++ b/packages/jest-console/src/matchers.js @@ -9,68 +9,79 @@ import { isEqual, reduce, some } from 'lodash'; */ import supportedMatchers from './supported-matchers'; -const createToHaveBeenCalledMatcher = ( matcherName, methodName ) => - ( received ) => { - const spy = received[ methodName ]; - const calls = spy.mock.calls; - const pass = calls.length > 0; - const message = pass ? - () => +const createToHaveBeenCalledMatcher = ( matcherName, methodName ) => ( + received +) => { + const spy = received[ methodName ]; + const calls = spy.mock.calls; + const pass = calls.length > 0; + const message = pass + ? () => matcherHint( `.not${ matcherName }`, spy.getMockName() ) + '\n\n' + 'Expected mock function not to be called but it was called with:\n' + - calls.map( printReceived ) : - () => + calls.map( printReceived ) + : () => matcherHint( matcherName, spy.getMockName() ) + '\n\n' + 'Expected mock function to be called.'; - spy.assertionsNumber += 1; + spy.assertionsNumber += 1; - return { - message, - pass, - }; + return { + message, + pass, }; +}; -const createToHaveBeenCalledWith = ( matcherName, methodName ) => - ( received, ...expected ) => { - const spy = received[ methodName ]; - const calls = spy.mock.calls; - const pass = some( - calls, - ( objects ) => isEqual( objects, expected ) - ); - const message = pass ? - () => +const createToHaveBeenCalledWith = ( matcherName, methodName ) => ( + received, + ...expected +) => { + const spy = received[ methodName ]; + const calls = spy.mock.calls; + const pass = some( calls, ( objects ) => isEqual( objects, expected ) ); + const message = pass + ? () => matcherHint( `.not${ matcherName }`, spy.getMockName() ) + '\n\n' + 'Expected mock function not to be called with:\n' + - printExpected( expected ) : - () => + printExpected( expected ) + : () => matcherHint( matcherName, spy.getMockName() ) + '\n\n' + 'Expected mock function to be called with:\n' + - printExpected( expected ) + '\n' + + printExpected( expected ) + + '\n' + 'but it was called with:\n' + calls.map( printReceived ); - spy.assertionsNumber += 1; + spy.assertionsNumber += 1; - return { - message, - pass, - }; + return { + message, + pass, }; +}; expect.extend( - reduce( supportedMatchers, ( result, matcherName, methodName ) => { - const matcherNameWith = `${ matcherName }With`; + reduce( + supportedMatchers, + ( result, matcherName, methodName ) => { + const matcherNameWith = `${ matcherName }With`; - return { - ...result, - [ matcherName ]: createToHaveBeenCalledMatcher( `.${ matcherName }`, methodName ), - [ matcherNameWith ]: createToHaveBeenCalledWith( `.${ matcherNameWith }`, methodName ), - }; - }, {} ) + return { + ...result, + [ matcherName ]: createToHaveBeenCalledMatcher( + `.${ matcherName }`, + methodName + ), + [ matcherNameWith ]: createToHaveBeenCalledWith( + `.${ matcherNameWith }`, + methodName + ), + }; + }, + {} + ) ); diff --git a/packages/jest-console/src/test/index.test.js b/packages/jest-console/src/test/index.test.js index c626a1c2bc94ca..490e3242070c17 100644 --- a/packages/jest-console/src/test/index.test.js +++ b/packages/jest-console/src/test/index.test.js @@ -6,75 +6,83 @@ import '../matchers'; describe( 'jest-console', () => { - describe.each( [ [ 'error', 'toHaveErrored' ], [ 'info', 'toHaveInformed' ], [ 'log', 'toHaveLogged' ], [ 'warn', 'toHaveWarned' ] ] )( - 'console.%s', - ( methodName, matcherName ) => { - const matcherNameWith = `${ matcherName }With`; - const message = `This is ${ methodName }!`; - - test( `${ matcherName } works`, () => { - console[ methodName ]( message ); - - expect( console )[ matcherName ](); - } ); - - test( `${ matcherName } works when not called`, () => { - expect( console ).not[ matcherName ](); - expect( - () => expect( console )[ matcherName ]() - ).toThrow( 'Expected mock function to be called.' ); - } ); - - test( `${ matcherNameWith } works with arguments that match`, () => { - console[ methodName ]( message ); - - expect( console )[ matcherNameWith ]( message ); - } ); - - test( `${ matcherNameWith } works when not called`, () => { - expect( console ).not[ matcherNameWith ]( message ); - expect( - () => expect( console )[ matcherNameWith ]( message ) - ).toThrow( /Expected mock function to be called with:.*but it was called with:/s ); - } ); - - test( `${ matcherNameWith } works with many arguments that do not match`, () => { - console[ methodName ]( 'Unknown message.' ); - console[ methodName ]( message, 'Unknown param.' ); - - expect( console ).not[ matcherNameWith ]( message ); + describe.each( [ + [ 'error', 'toHaveErrored' ], + [ 'info', 'toHaveInformed' ], + [ 'log', 'toHaveLogged' ], + [ 'warn', 'toHaveWarned' ], + ] )( 'console.%s', ( methodName, matcherName ) => { + const matcherNameWith = `${ matcherName }With`; + const message = `This is ${ methodName }!`; + + test( `${ matcherName } works`, () => { + console[ methodName ]( message ); + + expect( console )[ matcherName ](); + } ); + + test( `${ matcherName } works when not called`, () => { + expect( console ).not[ matcherName ](); + expect( () => expect( console )[ matcherName ]() ).toThrow( + 'Expected mock function to be called.' + ); + } ); + + test( `${ matcherNameWith } works with arguments that match`, () => { + console[ methodName ]( message ); + + expect( console )[ matcherNameWith ]( message ); + } ); + + test( `${ matcherNameWith } works when not called`, () => { + expect( console ).not[ matcherNameWith ]( message ); + expect( () => + expect( console )[ matcherNameWith ]( message ) + ).toThrow( + /Expected mock function to be called with:.*but it was called with:/s + ); + } ); + + test( `${ matcherNameWith } works with many arguments that do not match`, () => { + console[ methodName ]( 'Unknown message.' ); + console[ methodName ]( message, 'Unknown param.' ); + + expect( console ).not[ matcherNameWith ]( message ); + expect( () => + expect( console )[ matcherNameWith ]( message ) + ).toThrow( + /Expected mock function to be called with:.*but it was called with:.*Unknown param./s + ); + } ); + + test( 'assertions number gets incremented after every matcher call', () => { + const spy = console[ methodName ]; + + expect( spy.assertionsNumber ).toBe( 0 ); + + console[ methodName ]( message ); + + expect( console )[ matcherName ](); + expect( spy.assertionsNumber ).toBe( 1 ); + + expect( console )[ matcherNameWith ]( message ); + expect( spy.assertionsNumber ).toBe( 2 ); + } ); + + describe( 'lifecycle', () => { + beforeAll( () => { + // This is a difficult one to test, since the matcher's + // own lifecycle is defined to run before ours. Infer + // that we're being watched by testing the console + // method as being a spy. expect( - () => expect( console )[ matcherNameWith ]( message ) - ).toThrow( /Expected mock function to be called with:.*but it was called with:.*Unknown param./s ); + console[ methodName ].assertionsNumber + ).toBeGreaterThanOrEqual( 0 ); } ); - test( 'assertions number gets incremented after every matcher call', () => { - const spy = console[ methodName ]; - - expect( spy.assertionsNumber ).toBe( 0 ); - - console[ methodName ]( message ); - - expect( console )[ matcherName ](); - expect( spy.assertionsNumber ).toBe( 1 ); - - expect( console )[ matcherNameWith ]( message ); - expect( spy.assertionsNumber ).toBe( 2 ); - } ); - - describe( 'lifecycle', () => { - beforeAll( () => { - // This is a difficult one to test, since the matcher's - // own lifecycle is defined to run before ours. Infer - // that we're being watched by testing the console - // method as being a spy. - expect( console[ methodName ].assertionsNumber ).toBeGreaterThanOrEqual( 0 ); - } ); - - it( 'captures logging in lifecycle', () => {} ); - } ); - } - ); + it( 'captures logging in lifecycle', () => {} ); + } ); + } ); } ); /* eslint-enable no-console */ diff --git a/packages/jest-preset-default/jest-preset.js b/packages/jest-preset-default/jest-preset.js index 0e5d04f5f6e53b..fed6f89aa9c42d 100644 --- a/packages/jest-preset-default/jest-preset.js +++ b/packages/jest-preset-default/jest-preset.js @@ -1,14 +1,26 @@ module.exports = { moduleNameMapper: { - '\\.(scss|css)$': require.resolve( '@wordpress/jest-preset-default/scripts/style-mock.js' ), + '\\.(scss|css)$': require.resolve( + '@wordpress/jest-preset-default/scripts/style-mock.js' + ), }, modulePaths: [ '<rootDir>' ], - setupFiles: [ require.resolve( '@wordpress/jest-preset-default/scripts/setup-globals.js' ) ], + setupFiles: [ + require.resolve( + '@wordpress/jest-preset-default/scripts/setup-globals.js' + ), + ], setupFilesAfterEnv: [ - require.resolve( '@wordpress/jest-preset-default/scripts/setup-test-framework.js' ), + require.resolve( + '@wordpress/jest-preset-default/scripts/setup-test-framework.js' + ), ], snapshotSerializers: [ require.resolve( 'enzyme-to-json/serializer.js' ) ], - testMatch: [ '**/__tests__/**/*.[jt]s', '**/test/*.[jt]s', '**/?(*.)test.[jt]s' ], + testMatch: [ + '**/__tests__/**/*.[jt]s', + '**/test/*.[jt]s', + '**/?(*.)test.[jt]s', + ], testPathIgnorePatterns: [ '/node_modules/', '/wordpress/' ], timers: 'fake', transform: { diff --git a/packages/jest-preset-default/scripts/setup-globals.js b/packages/jest-preset-default/scripts/setup-globals.js index e03f036a0029b7..8e1ea1d90d5039 100644 --- a/packages/jest-preset-default/scripts/setup-globals.js +++ b/packages/jest-preset-default/scripts/setup-globals.js @@ -16,8 +16,8 @@ global.window.matchMedia = () => ( { // Setup fake localStorage const storage = {}; global.window.localStorage = { - getItem: ( key ) => key in storage ? storage[ key ] : null, - setItem: ( key, value ) => storage[ key ] = value, + getItem: ( key ) => ( key in storage ? storage[ key ] : null ), + setItem: ( key, value ) => ( storage[ key ] = value ), }; // UserSettings global diff --git a/packages/jest-preset-default/scripts/travis-fold-passes-reporter.js b/packages/jest-preset-default/scripts/travis-fold-passes-reporter.js index a621f81c916ab5..611ed1ba03d912 100644 --- a/packages/jest-preset-default/scripts/travis-fold-passes-reporter.js +++ b/packages/jest-preset-default/scripts/travis-fold-passes-reporter.js @@ -20,7 +20,9 @@ module.exports = class TravisFoldPassesReporter extends VerboseReporter { this.foldedTestResults.length === 1 ? '' : 's' }.` ); - this.foldedTestResults.forEach( ( args ) => super.onTestResult( ...args ) ); + this.foldedTestResults.forEach( ( args ) => + super.onTestResult( ...args ) + ); this.log( 'travis_fold:end:TravisFoldPassesReporter' ); this.foldedTestResults = []; } diff --git a/packages/jest-puppeteer-axe/src/index.js b/packages/jest-puppeteer-axe/src/index.js index 8ad73875e2ad69..4846c198dd0cf7 100644 --- a/packages/jest-puppeteer-axe/src/index.js +++ b/packages/jest-puppeteer-axe/src/index.js @@ -17,38 +17,41 @@ import AxePuppeteer from 'axe-puppeteer'; * @return {string} The user friendly message to display when the matcher fails. */ function formatViolations( violations ) { - return violations.map( ( { help, helpUrl, id, nodes } ) => { - let output = `Rule: "${ id }" (${ help })\n` + - `Help: ${ helpUrl }\n` + - 'Affected Nodes:\n'; - - nodes.forEach( ( node ) => { - if ( node.any.length ) { - output += ` ${ node.target }\n`; - output += ' Fix ANY of the following:\n'; - node.any.forEach( ( item ) => { - output += ` - ${ item.message }\n`; - } ); - } - - if ( node.all.length ) { - output += ` ${ node.target }\n`; - output += ' Fix ALL of the following:\n'; - node.all.forEach( ( item ) => { - output += ` - ${ item.message }.\n`; - } ); - } - - if ( node.none.length ) { - output += ` ${ node.target }\n`; - output += ' Fix ALL of the following:\n'; - node.none.forEach( ( item ) => { - output += ` - ${ item.message }.\n`; - } ); - } - } ); - return output; - } ).join( '\n' ); + return violations + .map( ( { help, helpUrl, id, nodes } ) => { + let output = + `Rule: "${ id }" (${ help })\n` + + `Help: ${ helpUrl }\n` + + 'Affected Nodes:\n'; + + nodes.forEach( ( node ) => { + if ( node.any.length ) { + output += ` ${ node.target }\n`; + output += ' Fix ANY of the following:\n'; + node.any.forEach( ( item ) => { + output += ` - ${ item.message }\n`; + } ); + } + + if ( node.all.length ) { + output += ` ${ node.target }\n`; + output += ' Fix ALL of the following:\n'; + node.all.forEach( ( item ) => { + output += ` - ${ item.message }.\n`; + } ); + } + + if ( node.none.length ) { + output += ` ${ node.target }\n`; + output += ' Fix ALL of the following:\n'; + node.none.forEach( ( item ) => { + output += ` - ${ item.message }.\n`; + } ); + } + } ); + return output; + } ) + .join( '\n' ); } /** @@ -73,7 +76,10 @@ function formatViolations( violations ) { * * @return {Object} A matcher object with two keys `pass` and `message`. */ -async function toPassAxeTests( page, { include, exclude, disabledRules, options, config } = {} ) { +async function toPassAxeTests( + page, + { include, exclude, disabledRules, options, config } = {} +) { const axe = new AxePuppeteer( page ); if ( include ) { @@ -99,22 +105,24 @@ async function toPassAxeTests( page, { include, exclude, disabledRules, options, const { violations } = await axe.analyze(); const pass = violations.length === 0; - const message = pass ? - () => { - return this.utils.matcherHint( '.not.toPassAxeTests' ) + - '\n\n' + - 'Expected page to contain accessibility check violations.\n' + - 'No violations found.'; - } : - () => { - return this.utils.matcherHint( '.toPassAxeTests' ) + - '\n\n' + - 'Expected page to pass Axe accessibility tests.\n' + - 'Violations found:\n' + - this.utils.RECEIVED_COLOR( - formatViolations( violations ) + const message = pass + ? () => { + return ( + this.utils.matcherHint( '.not.toPassAxeTests' ) + + '\n\n' + + 'Expected page to contain accessibility check violations.\n' + + 'No violations found.' ); - }; + } + : () => { + return ( + this.utils.matcherHint( '.toPassAxeTests' ) + + '\n\n' + + 'Expected page to pass Axe accessibility tests.\n' + + 'Violations found:\n' + + this.utils.RECEIVED_COLOR( formatViolations( violations ) ) + ); + }; return { message, diff --git a/packages/keyboard-shortcuts/src/hooks/use-shortcut.js b/packages/keyboard-shortcuts/src/hooks/use-shortcut.js index 88eeabdac0375b..f6b0f4e584b90f 100644 --- a/packages/keyboard-shortcuts/src/hooks/use-shortcut.js +++ b/packages/keyboard-shortcuts/src/hooks/use-shortcut.js @@ -12,9 +12,14 @@ import { useKeyboardShortcut } from '@wordpress/compose'; * @param {Object} options Shortcut options. */ function useShortcut( name, callback, options ) { - const shortcuts = useSelect( ( select ) => { - return select( 'core/keyboard-shortcuts' ).getAllShortcutRawKeyCombinations( name ); - }, [ name ] ); + const shortcuts = useSelect( + ( select ) => { + return select( + 'core/keyboard-shortcuts' + ).getAllShortcutRawKeyCombinations( name ); + }, + [ name ] + ); useKeyboardShortcut( shortcuts, callback, options ); } diff --git a/packages/keyboard-shortcuts/src/store/actions.js b/packages/keyboard-shortcuts/src/store/actions.js index e8e9678362b1af..b4cb625287d8fb 100644 --- a/packages/keyboard-shortcuts/src/store/actions.js +++ b/packages/keyboard-shortcuts/src/store/actions.js @@ -28,7 +28,13 @@ * * @return {Object} action. */ -export function registerShortcut( { name, category, description, keyCombination, aliases } ) { +export function registerShortcut( { + name, + category, + description, + keyCombination, + aliases, +} ) { return { type: 'REGISTER_SHORTCUT', name, diff --git a/packages/keyboard-shortcuts/src/store/selectors.js b/packages/keyboard-shortcuts/src/store/selectors.js index 0b6216e9f5f348..b14c2f43c56a60 100644 --- a/packages/keyboard-shortcuts/src/store/selectors.js +++ b/packages/keyboard-shortcuts/src/store/selectors.js @@ -7,7 +7,11 @@ import { compact } from 'lodash'; /** * WordPress dependencies */ -import { displayShortcut, shortcutAriaLabel, rawShortcut } from '@wordpress/keycodes'; +import { + displayShortcut, + shortcutAriaLabel, + rawShortcut, +} from '@wordpress/keycodes'; /** @typedef {import('./actions').WPShortcutKeyCombination} WPShortcutKeyCombination */ @@ -48,9 +52,11 @@ function getKeyCombinationRepresentation( shortcut, representation ) { return null; } - return shortcut.modifier ? - FORMATTING_METHODS[ representation ][ shortcut.modifier ]( shortcut.character ) : - shortcut.character; + return shortcut.modifier + ? FORMATTING_METHODS[ representation ][ shortcut.modifier ]( + shortcut.character + ) + : shortcut.character; } /** @@ -75,7 +81,11 @@ export function getShortcutKeyCombination( state, name ) { * * @return {string?} Shortcut representation. */ -export function getShortcutRepresentation( state, name, representation = 'display' ) { +export function getShortcutRepresentation( + state, + name, + representation = 'display' +) { const shortcut = getShortcutKeyCombination( state, name ); return getKeyCombinationRepresentation( shortcut, representation ); } @@ -101,9 +111,9 @@ export function getShortcutDescription( state, name ) { * @return {WPShortcutKeyCombination[]} Key combinations. */ export function getShortcutAliases( state, name ) { - return state[ name ] && state[ name ].aliases ? - state[ name ].aliases : - EMPTY_ARRAY; + return state[ name ] && state[ name ].aliases + ? state[ name ].aliases + : EMPTY_ARRAY; } /** @@ -118,10 +128,11 @@ export const getAllShortcutRawKeyCombinations = createSelector( ( state, name ) => { return compact( [ getKeyCombinationRepresentation( - getShortcutKeyCombination( state, name ), 'raw' + getShortcutKeyCombination( state, name ), + 'raw' ), - ...getShortcutAliases( state, name ).map( - ( combination ) => getKeyCombinationRepresentation( combination, 'raw' ) + ...getShortcutAliases( state, name ).map( ( combination ) => + getKeyCombinationRepresentation( combination, 'raw' ) ), ] ); }, diff --git a/packages/keycodes/src/index.js b/packages/keycodes/src/index.js index 3615ca06149316..998200bd21f31b 100644 --- a/packages/keycodes/src/index.js +++ b/packages/keycodes/src/index.js @@ -112,11 +112,14 @@ export const SHIFT = 'shift'; * - `shiftAlt` */ export const modifiers = { - primary: ( _isApple ) => _isApple() ? [ COMMAND ] : [ CTRL ], - primaryShift: ( _isApple ) => _isApple() ? [ SHIFT, COMMAND ] : [ CTRL, SHIFT ], - primaryAlt: ( _isApple ) => _isApple() ? [ ALT, COMMAND ] : [ CTRL, ALT ], - secondary: ( _isApple ) => _isApple() ? [ SHIFT, ALT, COMMAND ] : [ CTRL, SHIFT, ALT ], - access: ( _isApple ) => _isApple() ? [ CTRL, ALT ] : [ SHIFT, ALT ], + primary: ( _isApple ) => ( _isApple() ? [ COMMAND ] : [ CTRL ] ), + primaryShift: ( _isApple ) => + _isApple() ? [ SHIFT, COMMAND ] : [ CTRL, SHIFT ], + primaryAlt: ( _isApple ) => + _isApple() ? [ ALT, COMMAND ] : [ CTRL, ALT ], + secondary: ( _isApple ) => + _isApple() ? [ SHIFT, ALT, COMMAND ] : [ CTRL, SHIFT, ALT ], + access: ( _isApple ) => ( _isApple() ? [ CTRL, ALT ] : [ SHIFT, ALT ] ), ctrl: () => [ CTRL ], alt: () => [ ALT ], ctrlShift: () => [ CTRL, SHIFT ], @@ -154,15 +157,18 @@ export const displayShortcutList = mapValues( modifiers, ( modifier ) => { [ SHIFT ]: isApple ? '⇧' : 'Shift', }; - const modifierKeys = modifier( _isApple ).reduce( ( accumulator, key ) => { - const replacementKey = get( replacementKeyMap, key, key ); - // If on the Mac, adhere to platform convention and don't show plus between keys. - if ( isApple ) { - return [ ...accumulator, replacementKey ]; - } + const modifierKeys = modifier( _isApple ).reduce( + ( accumulator, key ) => { + const replacementKey = get( replacementKeyMap, key, key ); + // If on the Mac, adhere to platform convention and don't show plus between keys. + if ( isApple ) { + return [ ...accumulator, replacementKey ]; + } - return [ ...accumulator, replacementKey, '+' ]; - }, [] ); + return [ ...accumulator, replacementKey, '+' ]; + }, + [] + ); const capitalizedCharacter = capitalize( character ); return [ ...modifierKeys, capitalizedCharacter ]; @@ -176,9 +182,13 @@ export const displayShortcutList = mapValues( modifiers, ( modifier ) => { * @type {WPKeycodeHandlerByModifier} Keyed map of functions to display * shortcuts. */ -export const displayShortcut = mapValues( displayShortcutList, ( shortcutList ) => { - return ( character, _isApple = isAppleOS ) => shortcutList( character, _isApple ).join( '' ); -} ); +export const displayShortcut = mapValues( + displayShortcutList, + ( shortcutList ) => { + return ( character, _isApple = isAppleOS ) => + shortcutList( character, _isApple ).join( '' ); + } +); /** * An object that contains functions to return an aria label for a keyboard shortcut. diff --git a/packages/keycodes/src/platform.js b/packages/keycodes/src/platform.js index 386f0d7fb173f9..19295d47b1f4f8 100644 --- a/packages/keycodes/src/platform.js +++ b/packages/keycodes/src/platform.js @@ -13,6 +13,8 @@ import { includes } from 'lodash'; export function isAppleOS( _window = window ) { const { platform } = _window.navigator; - return platform.indexOf( 'Mac' ) !== -1 || - includes( [ 'iPad', 'iPhone' ], platform ); + return ( + platform.indexOf( 'Mac' ) !== -1 || + includes( [ 'iPad', 'iPhone' ], platform ) + ); } diff --git a/packages/keycodes/src/test/index.js b/packages/keycodes/src/test/index.js index 2d7d83249ec574..dccd99884ac2b3 100644 --- a/packages/keycodes/src/test/index.js +++ b/packages/keycodes/src/test/index.js @@ -25,36 +25,62 @@ describe( 'displayShortcutList', () => { } ); it( 'outputs [ ⌘, Del ] on MacOS (works for multiple character keys)', () => { - const shortcut = displayShortcutList.primary( 'del', isAppleOSTrue ); + const shortcut = displayShortcutList.primary( + 'del', + isAppleOSTrue + ); expect( shortcut ).toEqual( [ '⌘', 'Del' ] ); } ); } ); describe( 'primaryShift', () => { it( 'should output [ Ctrl, +, Shift, +, M ] on Windows', () => { - const shortcut = displayShortcutList.primaryShift( 'm', isAppleOSFalse ); + const shortcut = displayShortcutList.primaryShift( + 'm', + isAppleOSFalse + ); expect( shortcut ).toEqual( [ 'Ctrl', '+', 'Shift', '+', 'M' ] ); } ); it( 'should output [ ⇧, ⌘, M ] on MacOS', () => { - const shortcut = displayShortcutList.primaryShift( 'm', isAppleOSTrue ); + const shortcut = displayShortcutList.primaryShift( + 'm', + isAppleOSTrue + ); expect( shortcut ).toEqual( [ '⇧', '⌘', 'M' ] ); } ); it( 'outputs [ ⇧, ⌘, Del ] on MacOS (works for multiple character keys)', () => { - const shortcut = displayShortcutList.primaryShift( 'del', isAppleOSTrue ); + const shortcut = displayShortcutList.primaryShift( + 'del', + isAppleOSTrue + ); expect( shortcut ).toEqual( [ '⇧', '⌘', 'Del' ] ); } ); } ); describe( 'secondary', () => { it( 'should output [ Ctrl, +, Shift, +, Alt ] text on Windows', () => { - const shortcut = displayShortcutList.secondary( 'm', isAppleOSFalse ); - expect( shortcut ).toEqual( [ 'Ctrl', '+', 'Shift', '+', 'Alt', '+', 'M' ] ); + const shortcut = displayShortcutList.secondary( + 'm', + isAppleOSFalse + ); + expect( shortcut ).toEqual( [ + 'Ctrl', + '+', + 'Shift', + '+', + 'Alt', + '+', + 'M', + ] ); } ); it( 'should output [ ⇧, ⌥, ⌘, M ] on MacOS', () => { - const shortcut = displayShortcutList.secondary( 'm', isAppleOSTrue ); + const shortcut = displayShortcutList.secondary( + 'm', + isAppleOSTrue + ); expect( shortcut ).toEqual( [ '⇧', '⌥', '⌘', 'M' ] ); } ); } ); @@ -92,7 +118,10 @@ describe( 'displayShortcut', () => { describe( 'primaryShift', () => { it( 'should output Ctrl+Shift text on Windows', () => { - const shortcut = displayShortcut.primaryShift( 'm', isAppleOSFalse ); + const shortcut = displayShortcut.primaryShift( + 'm', + isAppleOSFalse + ); expect( shortcut ).toEqual( 'Ctrl+Shift+M' ); } ); @@ -102,7 +131,10 @@ describe( 'displayShortcut', () => { } ); it( 'outputs ⇧⌘Del on MacOS (works for multiple character keys)', () => { - const shortcut = displayShortcut.primaryShift( 'del', isAppleOSTrue ); + const shortcut = displayShortcut.primaryShift( + 'del', + isAppleOSTrue + ); expect( shortcut ).toEqual( '⇧⌘Del' ); } ); } ); @@ -147,12 +179,18 @@ describe( 'shortcutAriaLabel', () => { describe( 'primaryShift', () => { it( 'should output "Control + Shift + Period" on Windows', () => { - const shortcut = shortcutAriaLabel.primaryShift( '.', isAppleOSFalse ); + const shortcut = shortcutAriaLabel.primaryShift( + '.', + isAppleOSFalse + ); expect( shortcut ).toEqual( 'Control + Shift + Period' ); } ); it( 'should output "Shift Command Period" on MacOS', () => { - const shortcut = shortcutAriaLabel.primaryShift( '.', isAppleOSTrue ); + const shortcut = shortcutAriaLabel.primaryShift( + '.', + isAppleOSTrue + ); expect( shortcut ).toEqual( 'Shift Command Period' ); } ); } ); @@ -262,7 +300,9 @@ describe( 'isKeyboardEvent', () => { it( 'should identify modifier key when Ctrl is pressed', () => { expect.assertions( 3 ); const attachNode = attachEventListeners( ( event ) => { - expect( isKeyboardEvent.primary( event, undefined, isAppleOSFalse ) ).toBe( true ); + expect( + isKeyboardEvent.primary( event, undefined, isAppleOSFalse ) + ).toBe( true ); } ); keyPress( attachNode, { @@ -274,7 +314,9 @@ describe( 'isKeyboardEvent', () => { it( 'should identify modifier key when ⌘ is pressed', () => { expect.assertions( 3 ); const attachNode = attachEventListeners( ( event ) => { - expect( isKeyboardEvent.primary( event, undefined, isAppleOSTrue ) ).toBe( true ); + expect( + isKeyboardEvent.primary( event, undefined, isAppleOSTrue ) + ).toBe( true ); } ); keyPress( attachNode, { @@ -286,7 +328,9 @@ describe( 'isKeyboardEvent', () => { it( 'should identify modifier key when Ctrl + M is pressed', () => { expect.assertions( 3 ); const attachNode = attachEventListeners( ( event ) => { - expect( isKeyboardEvent.primary( event, 'm', isAppleOSFalse ) ).toBe( true ); + expect( + isKeyboardEvent.primary( event, 'm', isAppleOSFalse ) + ).toBe( true ); } ); keyPress( attachNode, { @@ -298,7 +342,9 @@ describe( 'isKeyboardEvent', () => { it( 'should identify modifier key when ⌘M is pressed', () => { expect.assertions( 3 ); const attachNode = attachEventListeners( ( event ) => { - expect( isKeyboardEvent.primary( event, 'm', isAppleOSTrue ) ).toBe( true ); + expect( + isKeyboardEvent.primary( event, 'm', isAppleOSTrue ) + ).toBe( true ); } ); keyPress( attachNode, { @@ -312,7 +358,9 @@ describe( 'isKeyboardEvent', () => { it( 'should identify modifier key when Shift + Ctrl is pressed', () => { expect.assertions( 3 ); const attachNode = attachEventListeners( ( event ) => { - expect( isKeyboardEvent.primary( event, undefined, isAppleOSFalse ) ).toBe( true ); + expect( + isKeyboardEvent.primary( event, undefined, isAppleOSFalse ) + ).toBe( true ); } ); keyPress( attachNode, { @@ -325,7 +373,9 @@ describe( 'isKeyboardEvent', () => { it( 'should identify modifier key when ⇧⌘ is pressed', () => { expect.assertions( 3 ); const attachNode = attachEventListeners( ( event ) => { - expect( isKeyboardEvent.primary( event, undefined, isAppleOSTrue ) ).toBe( true ); + expect( + isKeyboardEvent.primary( event, undefined, isAppleOSTrue ) + ).toBe( true ); } ); keyPress( attachNode, { @@ -338,7 +388,9 @@ describe( 'isKeyboardEvent', () => { it( 'should identify modifier key when Shift + Ctrl + M is pressed', () => { expect.assertions( 3 ); const attachNode = attachEventListeners( ( event ) => { - expect( isKeyboardEvent.primary( event, 'm', isAppleOSFalse ) ).toBe( true ); + expect( + isKeyboardEvent.primary( event, 'm', isAppleOSFalse ) + ).toBe( true ); } ); keyPress( attachNode, { @@ -351,7 +403,9 @@ describe( 'isKeyboardEvent', () => { it( 'should identify modifier key when ⇧⌘M is pressed', () => { expect.assertions( 3 ); const attachNode = attachEventListeners( ( event ) => { - expect( isKeyboardEvent.primary( event, 'm', isAppleOSTrue ) ).toBe( true ); + expect( + isKeyboardEvent.primary( event, 'm', isAppleOSTrue ) + ).toBe( true ); } ); keyPress( attachNode, { @@ -366,7 +420,9 @@ describe( 'isKeyboardEvent', () => { it( 'should identify modifier key when Shift + Alt + Ctrl is pressed', () => { expect.assertions( 3 ); const attachNode = attachEventListeners( ( event ) => { - expect( isKeyboardEvent.primary( event, undefined, isAppleOSFalse ) ).toBe( true ); + expect( + isKeyboardEvent.primary( event, undefined, isAppleOSFalse ) + ).toBe( true ); } ); keyPress( attachNode, { @@ -380,7 +436,9 @@ describe( 'isKeyboardEvent', () => { it( 'should identify modifier key when ⇧⌥⌘ is pressed', () => { expect.assertions( 3 ); const attachNode = attachEventListeners( ( event ) => { - expect( isKeyboardEvent.primary( event, undefined, isAppleOSTrue ) ).toBe( true ); + expect( + isKeyboardEvent.primary( event, undefined, isAppleOSTrue ) + ).toBe( true ); } ); keyPress( attachNode, { @@ -394,7 +452,9 @@ describe( 'isKeyboardEvent', () => { it( 'should identify modifier key when Shift + Ctrl + ALt + M is pressed', () => { expect.assertions( 3 ); const attachNode = attachEventListeners( ( event ) => { - expect( isKeyboardEvent.primary( event, 'm', isAppleOSFalse ) ).toBe( true ); + expect( + isKeyboardEvent.primary( event, 'm', isAppleOSFalse ) + ).toBe( true ); } ); keyPress( attachNode, { @@ -408,7 +468,9 @@ describe( 'isKeyboardEvent', () => { it( 'should identify modifier key when ⇧⌥⌘M is pressed', () => { expect.assertions( 3 ); const attachNode = attachEventListeners( ( event ) => { - expect( isKeyboardEvent.primary( event, 'm', isAppleOSTrue ) ).toBe( true ); + expect( + isKeyboardEvent.primary( event, 'm', isAppleOSTrue ) + ).toBe( true ); } ); keyPress( attachNode, { @@ -424,7 +486,9 @@ describe( 'isKeyboardEvent', () => { it( 'should identify modifier key when Alt + Ctrl is pressed', () => { expect.assertions( 3 ); const attachNode = attachEventListeners( ( event ) => { - expect( isKeyboardEvent.primary( event, undefined, isAppleOSFalse ) ).toBe( true ); + expect( + isKeyboardEvent.primary( event, undefined, isAppleOSFalse ) + ).toBe( true ); } ); keyPress( attachNode, { @@ -437,7 +501,9 @@ describe( 'isKeyboardEvent', () => { it( 'should identify modifier key when ⌥⌘ is pressed', () => { expect.assertions( 3 ); const attachNode = attachEventListeners( ( event ) => { - expect( isKeyboardEvent.primary( event, undefined, isAppleOSTrue ) ).toBe( true ); + expect( + isKeyboardEvent.primary( event, undefined, isAppleOSTrue ) + ).toBe( true ); } ); keyPress( attachNode, { @@ -450,7 +516,9 @@ describe( 'isKeyboardEvent', () => { it( 'should identify modifier key when Ctrl + ALt + M is pressed', () => { expect.assertions( 3 ); const attachNode = attachEventListeners( ( event ) => { - expect( isKeyboardEvent.primary( event, 'm', isAppleOSFalse ) ).toBe( true ); + expect( + isKeyboardEvent.primary( event, 'm', isAppleOSFalse ) + ).toBe( true ); } ); keyPress( attachNode, { @@ -463,7 +531,9 @@ describe( 'isKeyboardEvent', () => { it( 'should identify modifier key when ⌥⌘M is pressed', () => { expect.assertions( 3 ); const attachNode = attachEventListeners( ( event ) => { - expect( isKeyboardEvent.primary( event, 'm', isAppleOSTrue ) ).toBe( true ); + expect( + isKeyboardEvent.primary( event, 'm', isAppleOSTrue ) + ).toBe( true ); } ); keyPress( attachNode, { diff --git a/packages/keycodes/src/test/platform.js b/packages/keycodes/src/test/platform.js index 00f8f86074cf12..63cd2628f983e2 100644 --- a/packages/keycodes/src/test/platform.js +++ b/packages/keycodes/src/test/platform.js @@ -5,30 +5,50 @@ import { isAppleOS } from '../platform'; describe( 'isAppleOS helper', () => { it( 'should identify anything with "Mac" in it as Apple OS', () => { - expect( isAppleOS( { navigator: { platform: 'Mac' } } ) ).toEqual( true ); - expect( isAppleOS( { navigator: { platform: 'MacIntel' } } ) ).toEqual( true ); + expect( isAppleOS( { navigator: { platform: 'Mac' } } ) ).toEqual( + true + ); + expect( isAppleOS( { navigator: { platform: 'MacIntel' } } ) ).toEqual( + true + ); } ); it( 'should identify "iPad" as Apple OS', () => { - expect( isAppleOS( { navigator: { platform: 'iPad' } } ) ).toEqual( true ); + expect( isAppleOS( { navigator: { platform: 'iPad' } } ) ).toEqual( + true + ); } ); it( 'should identify "iPhone" as Apple OS', () => { - expect( isAppleOS( { navigator: { platform: 'iPhone' } } ) ).toEqual( true ); + expect( isAppleOS( { navigator: { platform: 'iPhone' } } ) ).toEqual( + true + ); } ); it( 'should not identify Windows as MacOS', () => { - expect( isAppleOS( { navigator: { platform: 'Windows' } } ) ).toEqual( false ); - expect( isAppleOS( { navigator: { platform: 'Win' } } ) ).toEqual( false ); + expect( isAppleOS( { navigator: { platform: 'Windows' } } ) ).toEqual( + false + ); + expect( isAppleOS( { navigator: { platform: 'Win' } } ) ).toEqual( + false + ); } ); it( 'should not identify *NIX as MacOS', () => { - expect( isAppleOS( { navigator: { platform: 'Linux' } } ) ).toEqual( false ); - expect( isAppleOS( { navigator: { platform: 'Unix' } } ) ).toEqual( false ); + expect( isAppleOS( { navigator: { platform: 'Linux' } } ) ).toEqual( + false + ); + expect( isAppleOS( { navigator: { platform: 'Unix' } } ) ).toEqual( + false + ); } ); it( 'should not identify other cases as MacOS', () => { - expect( isAppleOS( { navigator: { platform: 'MAC' } } ) ).toEqual( false ); - expect( isAppleOS( { navigator: { platform: 'mac' } } ) ).toEqual( false ); + expect( isAppleOS( { navigator: { platform: 'MAC' } } ) ).toEqual( + false + ); + expect( isAppleOS( { navigator: { platform: 'mac' } } ) ).toEqual( + false + ); } ); } ); diff --git a/packages/library-export-default-webpack-plugin/index.js b/packages/library-export-default-webpack-plugin/index.js index 16b9dfb3eef7e7..2105077d0ad480 100644 --- a/packages/library-export-default-webpack-plugin/index.js +++ b/packages/library-export-default-webpack-plugin/index.js @@ -10,27 +10,33 @@ module.exports = class LibraryExportDefaultPlugin { } apply( compiler ) { - compiler.hooks.compilation.tap( 'LibraryExportDefaultPlugin', ( compilation ) => { - const { mainTemplate, chunkTemplate } = compilation; + compiler.hooks.compilation.tap( + 'LibraryExportDefaultPlugin', + ( compilation ) => { + const { mainTemplate, chunkTemplate } = compilation; - const onRenderWithEntry = ( source, chunk ) => { - if ( ! includes( this.entryPointNames, chunk.name ) ) { - return source; + const onRenderWithEntry = ( source, chunk ) => { + if ( ! includes( this.entryPointNames, chunk.name ) ) { + return source; + } + return new ConcatSource( source, '["default"]' ); + }; + + for ( const template of [ mainTemplate, chunkTemplate ] ) { + template.hooks.renderWithEntry.tap( + 'LibraryExportDefaultPlugin', + onRenderWithEntry + ); } - return new ConcatSource( source, '["default"]' ); - }; - for ( const template of [ mainTemplate, chunkTemplate ] ) { - template.hooks.renderWithEntry.tap( + mainTemplate.hooks.hash.tap( 'LibraryExportDefaultPlugin', - onRenderWithEntry + ( hash ) => { + hash.update( 'export property' ); + hash.update( 'default' ); + } ); } - - mainTemplate.hooks.hash.tap( 'LibraryExportDefaultPlugin', ( hash ) => { - hash.update( 'export property' ); - hash.update( 'default' ); - } ); - } ); + ); } }; diff --git a/packages/library-export-default-webpack-plugin/test/fixtures/webpack.config.js b/packages/library-export-default-webpack-plugin/test/fixtures/webpack.config.js index 3db7ce708e50ef..df1810cc331e16 100644 --- a/packages/library-export-default-webpack-plugin/test/fixtures/webpack.config.js +++ b/packages/library-export-default-webpack-plugin/test/fixtures/webpack.config.js @@ -16,7 +16,5 @@ module.exports = { library: [ 'wp', '[name]' ], libraryTarget: 'global', }, - plugins: [ - new LibraryExportDefaultPlugin( [ 'boo' ] ), - ], + plugins: [ new LibraryExportDefaultPlugin( [ 'boo' ] ) ], }; diff --git a/packages/list-reusable-blocks/src/components/import-dropdown/index.js b/packages/list-reusable-blocks/src/components/import-dropdown/index.js index f3a12cd90a7932..712c8cb7a6317a 100644 --- a/packages/list-reusable-blocks/src/components/import-dropdown/index.js +++ b/packages/list-reusable-blocks/src/components/import-dropdown/index.js @@ -20,11 +20,7 @@ function ImportDropdown( { onUpload } ) { position="bottom right" contentClassName="list-reusable-blocks-import-dropdown__content" renderToggle={ ( { isOpen, onToggle } ) => ( - <Button - aria-expanded={ isOpen } - onClick={ onToggle } - isPrimary - > + <Button aria-expanded={ isOpen } onClick={ onToggle } isPrimary> { __( 'Import from JSON' ) } </Button> ) } diff --git a/packages/list-reusable-blocks/src/components/import-form/index.js b/packages/list-reusable-blocks/src/components/import-form/index.js index dd80120e0f33ab..eb25a57e2e231f 100644 --- a/packages/list-reusable-blocks/src/components/import-form/index.js +++ b/packages/list-reusable-blocks/src/components/import-form/index.js @@ -80,11 +80,7 @@ class ImportForm extends Component { className="list-reusable-blocks-import-form" onSubmit={ this.onSubmit } > - { error && ( - <Notice status="error"> - { error } - </Notice> - ) } + { error && <Notice status="error">{ error }</Notice> } <label htmlFor={ inputId } className="list-reusable-blocks-import-form__label" diff --git a/packages/list-reusable-blocks/src/index.js b/packages/list-reusable-blocks/src/index.js index 76be468bdbd2ce..8a9871eed014be 100644 --- a/packages/list-reusable-blocks/src/index.js +++ b/packages/list-reusable-blocks/src/index.js @@ -12,7 +12,9 @@ import ImportDropdown from './components/import-dropdown'; // Setup Export Links document.body.addEventListener( 'click', ( event ) => { - if ( ! event.target.classList.contains( 'wp-list-reusable-blocks__export' ) ) { + if ( + ! event.target.classList.contains( 'wp-list-reusable-blocks__export' ) + ) { return; } event.preventDefault(); @@ -29,7 +31,9 @@ document.addEventListener( 'DOMContentLoaded', () => { const showNotice = () => { const notice = document.createElement( 'div' ); notice.className = 'notice notice-success is-dismissible'; - notice.innerHTML = `<p>${ __( 'Reusable block imported successfully!' ) }</p>`; + notice.innerHTML = `<p>${ __( + 'Reusable block imported successfully!' + ) }</p>`; const headerEnd = document.querySelector( '.wp-header-end' ); if ( ! headerEnd ) { diff --git a/packages/list-reusable-blocks/src/utils/export.js b/packages/list-reusable-blocks/src/utils/export.js index a0d75bd427c1e7..a8456262779622 100644 --- a/packages/list-reusable-blocks/src/utils/export.js +++ b/packages/list-reusable-blocks/src/utils/export.js @@ -20,14 +20,20 @@ import { download } from './file'; */ async function exportReusableBlock( id ) { const postType = await apiFetch( { path: `/wp/v2/types/wp_block` } ); - const post = await apiFetch( { path: `/wp/v2/${ postType.rest_base }/${ id }?context=edit` } ); + const post = await apiFetch( { + path: `/wp/v2/${ postType.rest_base }/${ id }?context=edit`, + } ); const title = post.title.raw; const content = post.content.raw; - const fileContent = JSON.stringify( { - __file: 'wp_block', - title, - content, - }, null, 2 ); + const fileContent = JSON.stringify( + { + __file: 'wp_block', + title, + content, + }, + null, + 2 + ); const fileName = kebabCase( title ) + '.json'; download( fileName, fileContent, 'application/json' ); diff --git a/packages/media-utils/src/components/media-upload/index.js b/packages/media-utils/src/components/media-upload/index.js index eac379f9ebb68d..160e2707ea4519 100644 --- a/packages/media-utils/src/components/media-upload/index.js +++ b/packages/media-utils/src/components/media-upload/index.js @@ -32,10 +32,12 @@ const getFeaturedImageMediaFrame = () => { * @return {void} */ createStates: function createStates() { - this.on( 'toolbar:create:featured-image', this.featuredImageToolbar, this ); - this.states.add( [ - new wp.media.controller.FeaturedImage(), - ] ); + this.on( + 'toolbar:create:featured-image', + this.featuredImageToolbar, + this + ); + this.states.add( [ new wp.media.controller.FeaturedImage() ] ); }, } ); }; @@ -50,7 +52,6 @@ const getGalleryDetailsMediaFrame = () => { * @class */ return wp.media.view.MediaFrame.Post.extend( { - /** * Create the default states. * @@ -67,9 +68,14 @@ const getGalleryDetailsMediaFrame = () => { multiple: 'add', editable: false, - library: wp.media.query( defaults( { - type: 'image', - }, this.options.library ) ), + library: wp.media.query( + defaults( + { + type: 'image', + }, + this.options.library + ) + ), } ), new wp.media.controller.GalleryEdit( { @@ -89,7 +95,17 @@ const getGalleryDetailsMediaFrame = () => { // the media library image object contains numerous attributes // we only need this set to display the image in the library const slimImageObject = ( img ) => { - const attrSet = [ 'sizes', 'mime', 'type', 'subtype', 'id', 'url', 'alt', 'link', 'caption' ]; + const attrSet = [ + 'sizes', + 'mime', + 'type', + 'subtype', + 'id', + 'url', + 'alt', + 'link', + 'caption', + ]; return pick( img, attrSet ); }; @@ -192,7 +208,7 @@ class MediaUpload extends Component { state: currentState, multiple, selection, - editing: ( value ) ? true : false, + editing: value ? true : false, } ); wp.media.frame = this.frame; this.initializeListeners(); @@ -209,7 +225,7 @@ class MediaUpload extends Component { state: 'featured-image', multiple: this.props.multiple, selection, - editing: ( this.props.value ) ? true : false, + editing: this.props.value ? true : false, } ); wp.media.frame = this.frame; } @@ -228,21 +244,24 @@ class MediaUpload extends Component { } if ( multiple ) { - onSelect( selectedImages.models.map( ( model ) => slimImageObject( model.toJSON() ) ) ); + onSelect( + selectedImages.models.map( ( model ) => + slimImageObject( model.toJSON() ) + ) + ); } else { - onSelect( slimImageObject( ( selectedImages.models[ 0 ].toJSON() ) ) ); + onSelect( slimImageObject( selectedImages.models[ 0 ].toJSON() ) ); } } onSelect() { const { onSelect, multiple = false } = this.props; // Get media attachment details from the frame state - const attachment = this.frame.state().get( 'selection' ).toJSON(); - onSelect( - multiple ? - attachment : - attachment[ 0 ] - ); + const attachment = this.frame + .state() + .get( 'selection' ) + .toJSON(); + onSelect( multiple ? attachment : attachment[ 0 ] ); } onOpen() { @@ -276,7 +295,9 @@ class MediaUpload extends Component { const collection = frameContent.collection; // clean all attachments we have in memory. - collection.toArray().forEach( ( model ) => model.trigger( 'destroy', model ) ); + collection + .toArray() + .forEach( ( model ) => model.trigger( 'destroy', model ) ); // reset has more flag, if library had small amount of items all items may have been loaded before. collection.mirroring._hasMore = true; @@ -303,4 +324,3 @@ class MediaUpload extends Component { } export default MediaUpload; - diff --git a/packages/media-utils/src/utils/test/upload-media.test.js b/packages/media-utils/src/utils/test/upload-media.test.js index 857c139625c027..53c08f9128a25f 100644 --- a/packages/media-utils/src/utils/test/upload-media.test.js +++ b/packages/media-utils/src/utils/test/upload-media.test.js @@ -15,8 +15,12 @@ jest.mock( '@wordpress/blob', () => ( { } ) ); jest.mock( '@wordpress/api-fetch', () => jest.fn() ); -const xmlFile = new window.File( [ 'fake_file' ], 'test.xml', { type: 'text/xml' } ); -const imageFile = new window.File( [ 'fake_file' ], 'test.jpeg', { type: 'image/jpeg' } ); +const xmlFile = new window.File( [ 'fake_file' ], 'test.xml', { + type: 'text/xml', +} ); +const imageFile = new window.File( [ 'fake_file' ], 'test.jpeg', { + type: 'image/jpeg', +} ); describe( 'uploadMedia', () => { it( 'should do nothing on no files', async () => { @@ -42,9 +46,11 @@ describe( 'uploadMedia', () => { onFileChange, } ); - expect( onError ).toHaveBeenCalledWith( expect.objectContaining( { - code: 'MIME_TYPE_NOT_SUPPORTED', - } ) ); + expect( onError ).toHaveBeenCalledWith( + expect.objectContaining( { + code: 'MIME_TYPE_NOT_SUPPORTED', + } ) + ); expect( onFileChange ).not.toHaveBeenCalled(); } ); @@ -58,9 +64,11 @@ describe( 'uploadMedia', () => { onFileChange, } ); - expect( onError ).toHaveBeenCalledWith( expect.objectContaining( { - code: 'MIME_TYPE_NOT_SUPPORTED', - } ) ); + expect( onError ).toHaveBeenCalledWith( + expect.objectContaining( { + code: 'MIME_TYPE_NOT_SUPPORTED', + } ) + ); expect( onFileChange ).not.toHaveBeenCalled(); } ); @@ -91,9 +99,11 @@ describe( 'uploadMedia', () => { onFileChange, } ); - expect( onError ).toHaveBeenCalledWith( expect.objectContaining( { - code: 'MIME_TYPE_NOT_SUPPORTED', - } ) ); + expect( onError ).toHaveBeenCalledWith( + expect.objectContaining( { + code: 'MIME_TYPE_NOT_SUPPORTED', + } ) + ); expect( onFileChange ).not.toHaveBeenCalled(); } ); @@ -127,10 +137,12 @@ describe( 'uploadMedia', () => { onFileChange, } ); - expect( onError ).toHaveBeenCalledWith( expect.objectContaining( { - code: 'MIME_TYPE_NOT_SUPPORTED', - file: xmlFile, - } ) ); + expect( onError ).toHaveBeenCalledWith( + expect.objectContaining( { + code: 'MIME_TYPE_NOT_SUPPORTED', + file: xmlFile, + } ) + ); expect( onFileChange ).toHaveBeenCalledTimes( 2 ); } ); @@ -145,9 +157,11 @@ describe( 'uploadMedia', () => { onFileChange, } ); - expect( onError ).toHaveBeenCalledWith( expect.objectContaining( { - code: 'SIZE_ABOVE_LIMIT', - } ) ); + expect( onError ).toHaveBeenCalledWith( + expect.objectContaining( { + code: 'SIZE_ABOVE_LIMIT', + } ) + ); expect( onFileChange ).not.toHaveBeenCalled(); } ); @@ -161,9 +175,11 @@ describe( 'uploadMedia', () => { wpAllowedMimeTypes: { aac: 'audio/aac' }, } ); - expect( onError ).toHaveBeenCalledWith( expect.objectContaining( { - code: 'MIME_TYPE_NOT_ALLOWED_FOR_USER', - } ) ); + expect( onError ).toHaveBeenCalledWith( + expect.objectContaining( { + code: 'MIME_TYPE_NOT_ALLOWED_FOR_USER', + } ) + ); expect( onFileChange ).not.toHaveBeenCalled(); } ); } ); @@ -179,27 +195,23 @@ describe( 'getMimeTypesArray', () => { } ); it( 'should return the type plus a new mime type with type and subtype with the extension if a type is passed', () => { - expect( - getMimeTypesArray( { ext: 'chicken' } ) - ).toEqual( - [ 'chicken', 'chicken/ext' ] - ); + expect( getMimeTypesArray( { ext: 'chicken' } ) ).toEqual( [ + 'chicken', + 'chicken/ext', + ] ); } ); it( 'should return the mime type passed and a new mime type with type and the extension as subtype', () => { - expect( - getMimeTypesArray( { ext: 'chicken/ribs' } ) - ).toEqual( - [ 'chicken/ribs', 'chicken/ext' ] - ); + expect( getMimeTypesArray( { ext: 'chicken/ribs' } ) ).toEqual( [ + 'chicken/ribs', + 'chicken/ext', + ] ); } ); it( 'should return the mime type passed and an additional mime type per extension supported', () => { expect( getMimeTypesArray( { 'jpg|jpeg|jpe': 'image/jpeg' } ) - ).toEqual( - [ 'image/jpeg', 'image/jpg', 'image/jpeg', 'image/jpe' ] - ); + ).toEqual( [ 'image/jpeg', 'image/jpg', 'image/jpeg', 'image/jpe' ] ); } ); it( 'should handle multiple mime types', () => { diff --git a/packages/media-utils/src/utils/upload-media.js b/packages/media-utils/src/utils/upload-media.js index 553584d010ace3..fb31af14cf4965 100644 --- a/packages/media-utils/src/utils/upload-media.js +++ b/packages/media-utils/src/utils/upload-media.js @@ -42,7 +42,10 @@ export function getMimeTypesArray( wpMimeTypesObject ) { return flatMap( wpMimeTypesObject, ( mime, extensionsString ) => { const [ type ] = mime.split( '/' ); const extensions = extensionsString.split( '|' ); - return [ mime, ...map( extensions, ( extension ) => `${ type }/${ extension }` ) ]; + return [ + mime, + ...map( extensions, ( extension ) => `${ type }/${ extension }` ), + ]; } ); } @@ -85,17 +88,14 @@ export async function uploadMedia( { if ( ! allowedTypes ) { return true; } - return some( - allowedTypes, - ( allowedType ) => { - // If a complete mimetype is specified verify if it matches exactly the mime type of the file. - if ( includes( allowedType, '/' ) ) { - return allowedType === fileType; - } - // Otherwise a general mime type is used and we should verify if the file mimetype starts with it. - return startsWith( fileType, `${ allowedType }/` ); + return some( allowedTypes, ( allowedType ) => { + // If a complete mimetype is specified verify if it matches exactly the mime type of the file. + if ( includes( allowedType, '/' ) ) { + return allowedType === fileType; } - ); + // Otherwise a general mime type is used and we should verify if the file mimetype starts with it. + return startsWith( fileType, `${ allowedType }/` ); + } ); }; // Allowed types for the current WP_User @@ -119,10 +119,15 @@ export async function uploadMedia( { for ( const mediaFile of files ) { // verify if user is allowed to upload this mime type - if ( allowedMimeTypesForUser && ! isAllowedMimeTypeForUser( mediaFile.type ) ) { + if ( + allowedMimeTypesForUser && + ! isAllowedMimeTypeForUser( mediaFile.type ) + ) { triggerError( { code: 'MIME_TYPE_NOT_ALLOWED_FOR_USER', - message: __( 'Sorry, this file type is not permitted for security reasons.' ), + message: __( + 'Sorry, this file type is not permitted for security reasons.' + ), file: mediaFile, } ); continue; @@ -142,7 +147,9 @@ export async function uploadMedia( { if ( maxUploadFileSize && mediaFile.size > maxUploadFileSize ) { triggerError( { code: 'SIZE_ABOVE_LIMIT', - message: __( 'This file exceeds the maximum upload size for this site.' ), + message: __( + 'This file exceeds the maximum upload size for this site.' + ), file: mediaFile, } ); continue; @@ -169,7 +176,10 @@ export async function uploadMedia( { for ( let idx = 0; idx < validFiles.length; ++idx ) { const mediaFile = validFiles[ idx ]; try { - const savedMedia = await createMediaFromFile( mediaFile, additionalData ); + const savedMedia = await createMediaFromFile( + mediaFile, + additionalData + ); const mediaObject = { ...omit( savedMedia, [ 'alt_text', 'source_url' ] ), alt: savedMedia.alt_text, @@ -210,7 +220,7 @@ function createMediaFromFile( file, additionalData ) { // Create upload payload const data = new window.FormData(); data.append( 'file', file, file.name || file.type.replace( '/', '.' ) ); - forEach( additionalData, ( ( value, key ) => data.append( key, value ) ) ); + forEach( additionalData, ( value, key ) => data.append( key, value ) ); return apiFetch( { path: '/wp/v2/media', body: data, diff --git a/packages/notices/src/store/actions.js b/packages/notices/src/store/actions.js index 0fed89a60e0f31..ec665cf359ac40 100644 --- a/packages/notices/src/store/actions.js +++ b/packages/notices/src/store/actions.js @@ -41,7 +41,11 @@ import { DEFAULT_CONTEXT, DEFAULT_STATUS } from './constants'; * @param {Array<WPNoticeAction>} [options.actions] User actions to be * presented with notice. */ -export function* createNotice( status = DEFAULT_STATUS, content, options = {} ) { +export function* createNotice( + status = DEFAULT_STATUS, + content, + options = {} +) { const { speak = true, isDismissible = true, diff --git a/packages/notices/src/store/test/reducer.js b/packages/notices/src/store/test/reducer.js index 77869efe8d51b2..1a845d58b158fb 100644 --- a/packages/notices/src/store/test/reducer.js +++ b/packages/notices/src/store/test/reducer.js @@ -12,10 +12,8 @@ import { createNotice, removeNotice } from '../actions'; import { getNotices } from '../selectors'; import { DEFAULT_CONTEXT } from '../constants'; -const getYieldedOfType = ( generatorAction, type ) => find( - Array.from( generatorAction ), - matchesProperty( [ 'type' ], type ) -); +const getYieldedOfType = ( generatorAction, type ) => + find( Array.from( generatorAction ), matchesProperty( [ 'type' ], type ) ); describe( 'reducer', () => { it( 'should default to an empty object', () => { @@ -25,7 +23,10 @@ describe( 'reducer', () => { } ); it( 'should track a notice', () => { - const action = getYieldedOfType( createNotice( 'error', 'save error' ), 'CREATE_NOTICE' ); + const action = getYieldedOfType( + createNotice( 'error', 'save error' ), + 'CREATE_NOTICE' + ); const state = reducer( undefined, action ); expect( state ).toEqual( { @@ -43,7 +44,10 @@ describe( 'reducer', () => { } ); it( 'should track a notice by context', () => { - const action = getYieldedOfType( createNotice( 'error', 'save error', { context: 'foo' } ), 'CREATE_NOTICE' ); + const action = getYieldedOfType( + createNotice( 'error', 'save error', { context: 'foo' } ), + 'CREATE_NOTICE' + ); const state = reducer( undefined, action ); expect( state ).toEqual( { @@ -61,10 +65,16 @@ describe( 'reducer', () => { } ); it( 'should track notices, respecting order by which they were created', () => { - let action = getYieldedOfType( createNotice( 'error', 'save error' ), 'CREATE_NOTICE' ); + let action = getYieldedOfType( + createNotice( 'error', 'save error' ), + 'CREATE_NOTICE' + ); const original = deepFreeze( reducer( undefined, action ) ); - action = getYieldedOfType( createNotice( 'success', 'successfully saved' ), 'CREATE_NOTICE' ); + action = getYieldedOfType( + createNotice( 'success', 'successfully saved' ), + 'CREATE_NOTICE' + ); const state = reducer( original, action ); expect( state ).toEqual( { @@ -90,7 +100,10 @@ describe( 'reducer', () => { } ); it( 'should omit a removed notice', () => { - const action = getYieldedOfType( createNotice( 'error', 'save error' ), 'CREATE_NOTICE' ); + const action = getYieldedOfType( + createNotice( 'error', 'save error' ), + 'CREATE_NOTICE' + ); const original = deepFreeze( reducer( undefined, action ) ); const id = getNotices( original )[ 0 ].id; @@ -102,7 +115,10 @@ describe( 'reducer', () => { } ); it( 'should omit a removed notice by context', () => { - const action = getYieldedOfType( createNotice( 'error', 'save error', { context: 'foo' } ), 'CREATE_NOTICE' ); + const action = getYieldedOfType( + createNotice( 'error', 'save error', { context: 'foo' } ), + 'CREATE_NOTICE' + ); const original = deepFreeze( reducer( undefined, action ) ); const id = getNotices( original, 'foo' )[ 0 ].id; @@ -114,7 +130,10 @@ describe( 'reducer', () => { } ); it( 'should omit a removed notice across contexts', () => { - const action = getYieldedOfType( createNotice( 'error', 'save error' ), 'CREATE_NOTICE' ); + const action = getYieldedOfType( + createNotice( 'error', 'save error' ), + 'CREATE_NOTICE' + ); const original = deepFreeze( reducer( undefined, action ) ); const id = getNotices( original )[ 0 ].id; @@ -124,10 +143,16 @@ describe( 'reducer', () => { } ); it( 'should dedupe distinct ids, preferring new', () => { - let action = getYieldedOfType( createNotice( 'error', 'save error (1)', { id: 'error-message' } ), 'CREATE_NOTICE' ); + let action = getYieldedOfType( + createNotice( 'error', 'save error (1)', { id: 'error-message' } ), + 'CREATE_NOTICE' + ); const original = deepFreeze( reducer( undefined, action ) ); - action = getYieldedOfType( createNotice( 'error', 'save error (2)', { id: 'error-message' } ), 'CREATE_NOTICE' ); + action = getYieldedOfType( + createNotice( 'error', 'save error (2)', { id: 'error-message' } ), + 'CREATE_NOTICE' + ); const state = reducer( original, action ); expect( state ).toEqual( { diff --git a/packages/notices/src/store/test/selectors.js b/packages/notices/src/store/test/selectors.js index d15879ab3b4a9d..b470282d05c08e 100644 --- a/packages/notices/src/store/test/selectors.js +++ b/packages/notices/src/store/test/selectors.js @@ -29,9 +29,7 @@ describe( 'selectors', () => { it( 'should return the notices array for a given context', () => { const state = { - foo: [ - { id: 'c', content: 'message 3' }, - ], + foo: [ { id: 'c', content: 'message 3' } ], }; expect( getNotices( state, 'foo' ) ).toEqual( [ diff --git a/packages/notices/src/store/utils/on-sub-key.js b/packages/notices/src/store/utils/on-sub-key.js index 0c525da246f42f..24adf06b773ebc 100644 --- a/packages/notices/src/store/utils/on-sub-key.js +++ b/packages/notices/src/store/utils/on-sub-key.js @@ -6,7 +6,10 @@ * * @return {Function} Higher-order reducer. */ -export const onSubKey = ( actionProperty ) => ( reducer ) => ( state = {}, action ) => { +export const onSubKey = ( actionProperty ) => ( reducer ) => ( + state = {}, + action +) => { // Retrieve subkey from action. Do not track if undefined; useful for cases // where reducer is scoped by action shape. const key = action[ actionProperty ]; diff --git a/packages/npm-package-json-lint-config/index.js b/packages/npm-package-json-lint-config/index.js index d6a93751371b16..e8fbd3eebdcec2 100644 --- a/packages/npm-package-json-lint-config/index.js +++ b/packages/npm-package-json-lint-config/index.js @@ -102,9 +102,7 @@ const defaultConfig = { 'require-version': 'error', 'scripts-type': 'error', 'valid-values-author': 'off', - 'valid-values-license': [ 'error', [ - 'GPL-2.0-or-later', - ] ], + 'valid-values-license': [ 'error', [ 'GPL-2.0-or-later' ] ], 'valid-values-private': 'off', 'version-format': 'error', 'version-type': 'error', diff --git a/packages/nux/src/components/dot-tip/index.js b/packages/nux/src/components/dot-tip/index.js index 6c2a5fe33d17d4..121e9e47cd84e2 100644 --- a/packages/nux/src/components/dot-tip/index.js +++ b/packages/nux/src/components/dot-tip/index.js @@ -85,5 +85,5 @@ export default compose( disableTips(); }, }; - } ), + } ) )( DotTip ); diff --git a/packages/nux/src/components/dot-tip/test/index.js b/packages/nux/src/components/dot-tip/test/index.js index 485ac9a8083b4d..b1c6b206d5ec63 100644 --- a/packages/nux/src/components/dot-tip/test/index.js +++ b/packages/nux/src/components/dot-tip/test/index.js @@ -35,7 +35,10 @@ describe( 'DotTip', () => { It looks like you’re writing a letter. Would you like help? </DotTip> ); - wrapper.find( 'ForwardRef(Button)[children="Got it"]' ).first().simulate( 'click' ); + wrapper + .find( 'ForwardRef(Button)[children="Got it"]' ) + .first() + .simulate( 'click' ); expect( onDismiss ).toHaveBeenCalled(); } ); @@ -46,7 +49,10 @@ describe( 'DotTip', () => { It looks like you’re writing a letter. Would you like help? </DotTip> ); - wrapper.find( 'ForwardRef(Button)[label="Disable tips"]' ).first().simulate( 'click' ); + wrapper + .find( 'ForwardRef(Button)[label="Disable tips"]' ) + .first() + .simulate( 'click' ); expect( onDisable ).toHaveBeenCalled(); } ); } ); diff --git a/packages/nux/src/store/reducer.js b/packages/nux/src/store/reducer.js index 5ebf8ea394e70b..07635d66189dba 100644 --- a/packages/nux/src/store/reducer.js +++ b/packages/nux/src/store/reducer.js @@ -15,10 +15,7 @@ import { combineReducers } from '@wordpress/data'; export function guides( state = [], action ) { switch ( action.type ) { case 'TRIGGER_GUIDE': - return [ - ...state, - action.tipIds, - ]; + return [ ...state, action.tipIds ]; } return state; diff --git a/packages/nux/src/store/selectors.js b/packages/nux/src/store/selectors.js index 58decf89455191..7efd84cb10043b 100644 --- a/packages/nux/src/store/selectors.js +++ b/packages/nux/src/store/selectors.js @@ -26,18 +26,21 @@ export const getAssociatedGuide = createSelector( ( state, tipId ) => { for ( const tipIds of state.guides ) { if ( includes( tipIds, tipId ) ) { - const nonDismissedTips = difference( tipIds, keys( state.preferences.dismissedTips ) ); - const [ currentTipId = null, nextTipId = null ] = nonDismissedTips; + const nonDismissedTips = difference( + tipIds, + keys( state.preferences.dismissedTips ) + ); + const [ + currentTipId = null, + nextTipId = null, + ] = nonDismissedTips; return { tipIds, currentTipId, nextTipId }; } } return null; }, - ( state ) => [ - state.guides, - state.preferences.dismissedTips, - ], + ( state ) => [ state.guides, state.preferences.dismissedTips ] ); /** diff --git a/packages/nux/src/store/test/reducer.js b/packages/nux/src/store/test/reducer.js index d2982a87d70501..49172442d8f379 100644 --- a/packages/nux/src/store/test/reducer.js +++ b/packages/nux/src/store/test/reducer.js @@ -14,9 +14,7 @@ describe( 'reducer', () => { type: 'TRIGGER_GUIDE', tipIds: [ 'test/tip-1', 'test/tip-2' ], } ); - expect( state ).toEqual( [ - [ 'test/tip-1', 'test/tip-2' ], - ] ); + expect( state ).toEqual( [ [ 'test/tip-1', 'test/tip-2' ] ] ); } ); } ); @@ -46,10 +44,13 @@ describe( 'reducer', () => { } ); it( 'should mark tips as dismissed', () => { - const state = dismissedTips( {}, { - type: 'DISMISS_TIP', - id: 'test/tip', - } ); + const state = dismissedTips( + {}, + { + type: 'DISMISS_TIP', + id: 'test/tip', + } + ); expect( state ).toEqual( { 'test/tip': true, } ); diff --git a/packages/nux/src/store/test/selectors.js b/packages/nux/src/store/test/selectors.js index 546d052958dd5e..e2a06c74e08b68 100644 --- a/packages/nux/src/store/test/selectors.js +++ b/packages/nux/src/store/test/selectors.js @@ -110,9 +110,7 @@ describe( 'selectors', () => { it( 'should return false if the tip is in a guide and it is not the current tip', () => { const state = { - guides: [ - [ 'test/tip-1', 'test/tip-2', 'test/tip-3' ], - ], + guides: [ [ 'test/tip-1', 'test/tip-2', 'test/tip-3' ] ], preferences: { areTipsEnabled: true, dismissedTips: {}, diff --git a/packages/plugins/src/api/index.js b/packages/plugins/src/api/index.js index 5e8fe1d19c851d..30d51258df07e8 100644 --- a/packages/plugins/src/api/index.js +++ b/packages/plugins/src/api/index.js @@ -108,15 +108,11 @@ const plugins = {}; */ export function registerPlugin( name, settings ) { if ( typeof settings !== 'object' ) { - console.error( - 'No settings object provided!' - ); + console.error( 'No settings object provided!' ); return null; } if ( typeof name !== 'string' ) { - console.error( - 'Plugin names must be strings.' - ); + console.error( 'Plugin names must be strings.' ); return null; } if ( ! /^[a-z][a-z0-9-]*$/.test( name ) ) { @@ -126,9 +122,7 @@ export function registerPlugin( name, settings ) { return null; } if ( plugins[ name ] ) { - console.error( - `Plugin "${ name }" is already registered.` - ); + console.error( `Plugin "${ name }" is already registered.` ); } settings = applyFilters( 'plugins.registerPlugin', settings, name ); @@ -177,9 +171,7 @@ export function registerPlugin( name, settings ) { */ export function unregisterPlugin( name ) { if ( ! plugins[ name ] ) { - console.error( - 'Plugin "' + name + '" is not registered.' - ); + console.error( 'Plugin "' + name + '" is not registered.' ); return; } const oldPlugin = plugins[ name ]; diff --git a/packages/plugins/src/api/test/index.js b/packages/plugins/src/api/test/index.js index fb11c32204da39..83f1c320ee9a65 100644 --- a/packages/plugins/src/api/test/index.js +++ b/packages/plugins/src/api/test/index.js @@ -1,12 +1,7 @@ /** * Internal dependencies */ -import { - registerPlugin, - unregisterPlugin, - getPlugin, - getPlugins, -} from '../'; +import { registerPlugin, unregisterPlugin, getPlugin, getPlugins } from '../'; describe( 'registerPlugin', () => { afterEach( () => { @@ -41,19 +36,26 @@ describe( 'registerPlugin', () => { registerPlugin( 'plugin/with/special/characters', { render: () => {}, } ); - expect( console ).toHaveErroredWith( 'Plugin names must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-plugin".' ); + expect( console ).toHaveErroredWith( + 'Plugin names must include only lowercase alphanumeric characters or dashes, and start with a letter. Example: "my-plugin".' + ); } ); it( 'fails to register a plugin with a non-string name', () => { - registerPlugin( {}, { - render: () => {}, - } ); + registerPlugin( + {}, + { + render: () => {}, + } + ); expect( console ).toHaveErroredWith( 'Plugin names must be strings.' ); } ); it( 'fails to register a plugin without a render function', () => { registerPlugin( 'another-plugin', {} ); - expect( console ).toHaveErroredWith( 'The "render" property must be specified and must be a valid function.' ); + expect( console ).toHaveErroredWith( + 'The "render" property must be specified and must be a valid function.' + ); } ); it( 'fails to register a plugin that was already been registered', () => { @@ -63,6 +65,8 @@ describe( 'registerPlugin', () => { registerPlugin( 'plugin', { render: () => 'plugin content', } ); - expect( console ).toHaveErroredWith( 'Plugin "plugin" is already registered.' ); + expect( console ).toHaveErroredWith( + 'Plugin "plugin" is already registered.' + ); } ); } ); diff --git a/packages/plugins/src/components/plugin-area/index.js b/packages/plugins/src/components/plugin-area/index.js index e03e5458d50ef4..b60ddbfb875667 100644 --- a/packages/plugins/src/components/plugin-area/index.js +++ b/packages/plugins/src/components/plugin-area/index.js @@ -72,13 +72,27 @@ class PluginArea extends Component { } componentDidMount() { - addAction( 'plugins.pluginRegistered', 'core/plugins/plugin-area/plugins-registered', this.setPlugins ); - addAction( 'plugins.pluginUnregistered', 'core/plugins/plugin-area/plugins-unregistered', this.setPlugins ); + addAction( + 'plugins.pluginRegistered', + 'core/plugins/plugin-area/plugins-registered', + this.setPlugins + ); + addAction( + 'plugins.pluginUnregistered', + 'core/plugins/plugin-area/plugins-unregistered', + this.setPlugins + ); } componentWillUnmount() { - removeAction( 'plugins.pluginRegistered', 'core/plugins/plugin-area/plugins-registered' ); - removeAction( 'plugins.pluginUnregistered', 'core/plugins/plugin-area/plugins-unregistered' ); + removeAction( + 'plugins.pluginRegistered', + 'core/plugins/plugin-area/plugins-registered' + ); + removeAction( + 'plugins.pluginUnregistered', + 'core/plugins/plugin-area/plugins-unregistered' + ); } setPlugins() { diff --git a/packages/plugins/src/components/plugin-context/index.js b/packages/plugins/src/components/plugin-context/index.js index 586891c67eb4ee..d3a73ca0a0fe1b 100644 --- a/packages/plugins/src/components/plugin-context/index.js +++ b/packages/plugins/src/components/plugin-context/index.js @@ -21,15 +21,16 @@ export { Provider as PluginContextProvider }; * * @return {WPComponent} Enhanced component with injected context as props. */ -export const withPluginContext = ( mapContextToProps ) => createHigherOrderComponent( ( OriginalComponent ) => { - return ( props ) => ( - <Consumer> - { ( context ) => ( - <OriginalComponent - { ...props } - { ...mapContextToProps( context, props ) } - /> - ) } - </Consumer> - ); -}, 'withPluginContext' ); +export const withPluginContext = ( mapContextToProps ) => + createHigherOrderComponent( ( OriginalComponent ) => { + return ( props ) => ( + <Consumer> + { ( context ) => ( + <OriginalComponent + { ...props } + { ...mapContextToProps( context, props ) } + /> + ) } + </Consumer> + ); + }, 'withPluginContext' ); diff --git a/packages/postcss-themes/index.js b/packages/postcss-themes/index.js index 9c7dd80c645013..395877b1742266 100644 --- a/packages/postcss-themes/index.js +++ b/packages/postcss-themes/index.js @@ -26,10 +26,17 @@ module.exports = postcss.plugin( 'postcss-themes', function( options ) { const defaultColor = options.defaults[ colorKey ]; value = value.replace( whole, defaultColor ); - Object.entries( options.themes ).forEach( ( [ key, colors ] ) => { - const previousValue = themeValues[ key ] ? themeValues[ key ] : decl.value; - themeValues[ key ] = previousValue.replace( whole, colors[ colorKey ] ); - } ); + Object.entries( options.themes ).forEach( + ( [ key, colors ] ) => { + const previousValue = themeValues[ key ] + ? themeValues[ key ] + : decl.value; + themeValues[ key ] = previousValue.replace( + whole, + colors[ colorKey ] + ); + } + ); } hasThemeDecls = true; @@ -47,11 +54,17 @@ module.exports = postcss.plugin( 'postcss-themes', function( options ) { if ( hasThemeDecls ) { Object.keys( options.themes ).forEach( ( key ) => { const newRule = postcss.rule( { - selector: rule.selector.split( ',' ).map( - ( subselector ) => 'body.' + key + ' ' + subselector.trim() - ).join( ', ' ), + selector: rule.selector + .split( ',' ) + .map( + ( subselector ) => + 'body.' + key + ' ' + subselector.trim() + ) + .join( ', ' ), } ); - themeDecls[ key ].forEach( ( decl ) => newRule.append( decl ) ); + themeDecls[ key ].forEach( ( decl ) => + newRule.append( decl ) + ); rule.parent.insertAfter( rule, newRule ); } ); } diff --git a/packages/postcss-themes/test/index.js b/packages/postcss-themes/test/index.js index 45a058d6e2a31a..982a5c38785e1c 100644 --- a/packages/postcss-themes/test/index.js +++ b/packages/postcss-themes/test/index.js @@ -42,14 +42,18 @@ describe( 'postcss-themes', () => { } ); it( 'replaces multiple rules in the same declaration', () => { - return run( 'a{ background: linear-gradient( -45deg, theme(main) 50%, theme(highlight) 50% ) }' ).then( ( result ) => { + return run( + 'a{ background: linear-gradient( -45deg, theme(main) 50%, theme(highlight) 50% ) }' + ).then( ( result ) => { expect( result.css ).toMatchSnapshot(); expect( result.warnings() ).toHaveLength( 0 ); } ); } ); it( 'gather several declaration in a unique rule', () => { - return run( 'a{ color: theme( main ); background: theme(highlight); }' ).then( ( result ) => { + return run( + 'a{ color: theme( main ); background: theme(highlight); }' + ).then( ( result ) => { expect( result.css ).toMatchSnapshot(); expect( result.warnings() ).toHaveLength( 0 ); } ); diff --git a/packages/primitives/src/block-quotation/index.native.js b/packages/primitives/src/block-quotation/index.native.js index abbafa09cae949..6d34fdc0b5aee2 100644 --- a/packages/primitives/src/block-quotation/index.native.js +++ b/packages/primitives/src/block-quotation/index.native.js @@ -14,16 +14,14 @@ import styles from './style.scss'; export const BlockQuotation = ( props ) => { const newChildren = Children.map( props.children, ( child ) => { if ( child && child.props.identifier === 'citation' ) { - return cloneElement( child, { style: styles.wpBlockQuoteCitation } ); + return cloneElement( child, { + style: styles.wpBlockQuoteCitation, + } ); } if ( child && child.props.identifier === 'value' ) { return cloneElement( child, { tagsToEliminate: [ 'div' ] } ); } return child; } ); - return ( - <View style={ styles.wpBlockQuote } > - { newChildren } - </View> - ); + return <View style={ styles.wpBlockQuote }>{ newChildren }</View>; }; diff --git a/packages/primitives/src/horizontal-rule/index.native.js b/packages/primitives/src/horizontal-rule/index.native.js index 7c5382762c4c5b..315fc3d94f38d7 100644 --- a/packages/primitives/src/horizontal-rule/index.native.js +++ b/packages/primitives/src/horizontal-rule/index.native.js @@ -10,7 +10,5 @@ export const HorizontalRule = ( props ) => { ...props.lineStyle, }; - return ( - <Hr { ...props } lineStyle={ lineStyle } /> - ); + return <Hr { ...props } lineStyle={ lineStyle } />; }; diff --git a/packages/primitives/src/svg/index.js b/packages/primitives/src/svg/index.js index efebe49b2896b0..33307deac052ff 100644 --- a/packages/primitives/src/svg/index.js +++ b/packages/primitives/src/svg/index.js @@ -17,7 +17,8 @@ export const Rect = ( props ) => createElement( 'rect', props ); export const SVG = ( { className, isPressed, ...props } ) => { const appliedProps = { ...props, - className: classnames( className, { 'is-pressed': isPressed } ) || undefined, + className: + classnames( className, { 'is-pressed': isPressed } ) || undefined, role: 'img', 'aria-hidden': 'true', focusable: 'false', diff --git a/packages/primitives/src/svg/index.native.js b/packages/primitives/src/svg/index.native.js index ec084ff5222abd..82f79f2899f10e 100644 --- a/packages/primitives/src/svg/index.native.js +++ b/packages/primitives/src/svg/index.native.js @@ -8,19 +8,23 @@ import { Svg } from 'react-native-svg'; */ import styles from './style.scss'; -export { - Circle, - G, - Path, - Polygon, - Rect, -} from 'react-native-svg'; +export { Circle, G, Path, Polygon, Rect } from 'react-native-svg'; export const SVG = ( { className = '', isPressed, ...props } ) => { const colorScheme = props.colorScheme || 'light'; - const stylesFromClasses = className.split( ' ' ).map( ( element ) => styles[ element ] ).filter( Boolean ); - const defaultStyle = isPressed ? styles[ 'is-pressed' ] : styles[ 'components-toolbar__control-' + colorScheme ]; - const styleValues = Object.assign( {}, defaultStyle, props.style, ...stylesFromClasses ); + const stylesFromClasses = className + .split( ' ' ) + .map( ( element ) => styles[ element ] ) + .filter( Boolean ); + const defaultStyle = isPressed + ? styles[ 'is-pressed' ] + : styles[ 'components-toolbar__control-' + colorScheme ]; + const styleValues = Object.assign( + {}, + defaultStyle, + props.style, + ...stylesFromClasses + ); const appliedProps = { ...props, style: styleValues }; @@ -34,4 +38,3 @@ export const SVG = ( { className = '', isPressed, ...props } ) => { /> ); }; - diff --git a/packages/priority-queue/src/index.js b/packages/priority-queue/src/index.js index ebcb92bedc9e49..e1933cbf11a47a 100644 --- a/packages/priority-queue/src/index.js +++ b/packages/priority-queue/src/index.js @@ -76,9 +76,10 @@ export const createQueue = () => { * animation frame timestamp. */ const runWaitingList = ( deadline ) => { - const hasTimeRemaining = typeof deadline === 'number' ? - () => false : - () => deadline.timeRemaining() > 0; + const hasTimeRemaining = + typeof deadline === 'number' + ? () => false + : () => deadline.timeRemaining() > 0; do { if ( waitingList.length === 0 ) { @@ -87,7 +88,9 @@ export const createQueue = () => { } const nextElement = /** @type {WPPriorityQueueContext} */ ( waitingList.shift() ); - const callback = /** @type {WPPriorityQueueCallback} */ ( elementsMap.get( nextElement ) ); + const callback = /** @type {WPPriorityQueueCallback} */ ( elementsMap.get( + nextElement + ) ); callback(); elementsMap.delete( nextElement ); } while ( hasTimeRemaining() ); @@ -131,7 +134,9 @@ export const createQueue = () => { const index = waitingList.indexOf( element ); waitingList.splice( index, 1 ); - const callback = /** @type {WPPriorityQueueCallback} */ ( elementsMap.get( element ) ); + const callback = /** @type {WPPriorityQueueCallback} */ ( elementsMap.get( + element + ) ); elementsMap.delete( element ); callback(); diff --git a/packages/priority-queue/src/test/index.js b/packages/priority-queue/src/test/index.js index 8b407feabd9b42..e057fb3c19e9a2 100644 --- a/packages/priority-queue/src/test/index.js +++ b/packages/priority-queue/src/test/index.js @@ -8,8 +8,11 @@ jest.mock( '../request-idle-callback', () => { const emitter = new ( require.requireActual( 'events' ).EventEmitter )(); return Object.assign( - ( callback ) => emitter.once( 'tick', ( deadline = Date.now() ) => callback( deadline ) ), - { tick: ( deadline ) => emitter.emit( 'tick', deadline ) }, + ( callback ) => + emitter.once( 'tick', ( deadline = Date.now() ) => + callback( deadline ) + ), + { tick: ( deadline ) => emitter.emit( 'tick', deadline ) } ); } ); @@ -84,7 +87,8 @@ describe( 'createQueue', () => { // Mock implementation such that with the first call, it reports as // having some time remaining, but no time remaining on the second. - const timeRemaining = jest.fn() + const timeRemaining = jest + .fn() .mockImplementationOnce( () => 100 ) .mockImplementationOnce( () => 0 ); diff --git a/packages/project-management-automation/lib/add-first-time-contributor-label.js b/packages/project-management-automation/lib/add-first-time-contributor-label.js index 81f42ccc0dcb0a..237b7ed3b63d21 100644 --- a/packages/project-management-automation/lib/add-first-time-contributor-label.js +++ b/packages/project-management-automation/lib/add-first-time-contributor-label.js @@ -15,18 +15,26 @@ async function addFirstTimeContributorLabel( payload, octokit ) { const repo = payload.repository.name; const author = payload.pull_request.user.login; - debug( `add-first-time-contributor-label: Searching for commits in ${ owner }/${ repo } by @${ author }` ); + debug( + `add-first-time-contributor-label: Searching for commits in ${ owner }/${ repo } by @${ author }` + ); - const { data: { total_count: totalCount } } = await octokit.search.commits( { + const { + data: { total_count: totalCount }, + } = await octokit.search.commits( { q: `repo:${ owner }/${ repo }+author:${ author }`, } ); if ( totalCount !== 0 ) { - debug( `add-first-time-contributor-label: ${ totalCount } commits found. Aborting` ); + debug( + `add-first-time-contributor-label: ${ totalCount } commits found. Aborting` + ); return; } - debug( `add-first-time-contributor-label: Adding 'First Time Contributor' label to issue #${ payload.pull_request.number }` ); + debug( + `add-first-time-contributor-label: Adding 'First Time Contributor' label to issue #${ payload.pull_request.number }` + ); await octokit.issues.addLabels( { owner, diff --git a/packages/project-management-automation/lib/add-milestone.js b/packages/project-management-automation/lib/add-milestone.js index dccbe08869525f..456503a978953e 100644 --- a/packages/project-management-automation/lib/add-milestone.js +++ b/packages/project-management-automation/lib/add-milestone.js @@ -25,32 +25,42 @@ async function addMilestone( payload, octokit ) { } if ( payload.pull_request.base.ref !== 'master' ) { - debug( 'add-milestone: Pull request is not based on `master`. Aborting' ); + debug( + 'add-milestone: Pull request is not based on `master`. Aborting' + ); return; } debug( 'add-milestone: Fetching current milestone' ); - const { data: { milestone } } = await octokit.issues.get( { + const { + data: { milestone }, + } = await octokit.issues.get( { owner: payload.repository.owner.login, repo: payload.repository.name, issue_number: payload.pull_request.number, } ); if ( milestone ) { - debug( 'add-milestone: Pull request already has a milestone. Aborting' ); + debug( + 'add-milestone: Pull request already has a milestone. Aborting' + ); return; } debug( 'add-milestone: Fetching `package.json` contents' ); - const { data: { content, encoding } } = await octokit.repos.getContents( { + const { + data: { content, encoding }, + } = await octokit.repos.getContents( { owner: payload.repository.owner.login, repo: payload.repository.name, path: 'package.json', } ); - const { version } = JSON.parse( Buffer.from( content, encoding ).toString() ); + const { version } = JSON.parse( + Buffer.from( content, encoding ).toString() + ); let [ major, minor ] = version.split( '.' ).map( Number ); @@ -63,14 +73,17 @@ async function addMilestone( payload, octokit ) { minor += 1; } - const numVersionsElapsed = ( ( major - REFERENCE_MAJOR ) * 10 ) + ( minor - REFERENCE_MINOR ); + const numVersionsElapsed = + ( major - REFERENCE_MAJOR ) * 10 + ( minor - REFERENCE_MINOR ); const numDaysElapsed = numVersionsElapsed * DAYS_PER_RELEASE; // Using UTC for the calculation ensures it's not affected by daylight savings. const dueDate = new Date( REFERENCE_DATE ); dueDate.setUTCDate( dueDate.getUTCDate() + numDaysElapsed ); - debug( `add-milestone: Creating 'Gutenberg ${ major }.${ minor }' milestone, due on ${ dueDate.toISOString() }` ); + debug( + `add-milestone: Creating 'Gutenberg ${ major }.${ minor }' milestone, due on ${ dueDate.toISOString() }` + ); await octokit.issues.createMilestone( { owner: payload.repository.owner.login, @@ -90,7 +103,9 @@ async function addMilestone( payload, octokit ) { ( { title } ) => title === `Gutenberg ${ major }.${ minor }` ); - debug( `add-milestone: Adding issue #${ payload.pull_request.number } to milestone #${ number }` ); + debug( + `add-milestone: Adding issue #${ payload.pull_request.number } to milestone #${ number }` + ); await octokit.issues.update( { owner: payload.repository.owner.login, diff --git a/packages/project-management-automation/lib/assign-fixed-issues.js b/packages/project-management-automation/lib/assign-fixed-issues.js index c0780256a41022..c649b2189e06d1 100644 --- a/packages/project-management-automation/lib/assign-fixed-issues.js +++ b/packages/project-management-automation/lib/assign-fixed-issues.js @@ -16,7 +16,9 @@ async function assignFixedIssues( payload, octokit ) { while ( ( match = regex.exec( payload.pull_request.body ) ) ) { const [ , issue ] = match; - debug( `assign-fixed-issues: Assigning issue #${ issue } to @${ payload.pull_request.user.login }` ); + debug( + `assign-fixed-issues: Assigning issue #${ issue } to @${ payload.pull_request.user.login }` + ); await octokit.issues.addAssignees( { owner: payload.repository.owner.login, @@ -25,7 +27,9 @@ async function assignFixedIssues( payload, octokit ) { assignees: [ payload.pull_request.user.login ], } ); - debug( `assign-fixed-issues: Applying '[Status] In Progress' label to issue #${ issue }` ); + debug( + `assign-fixed-issues: Applying '[Status] In Progress' label to issue #${ issue }` + ); await octokit.issues.addLabels( { owner: payload.repository.owner.login, diff --git a/packages/project-management-automation/lib/index.js b/packages/project-management-automation/lib/index.js index b752af33433c76..00b0cf3c8d2a0c 100644 --- a/packages/project-management-automation/lib/index.js +++ b/packages/project-management-automation/lib/index.js @@ -39,18 +39,25 @@ const automations = [ const octokit = new GitHub( token ); - debug( `main: Received event = '${ context.eventName }', action = '${ context.payload.action }'` ); + debug( + `main: Received event = '${ context.eventName }', action = '${ context.payload.action }'` + ); for ( const { event, action, task } of automations ) { - if ( event === context.eventName && action === context.payload.action ) { + if ( + event === context.eventName && + action === context.payload.action + ) { try { debug( `main: Starting task ${ task.name }` ); await task( context.payload, octokit ); } catch ( error ) { - debug( `main: Task ${ task.name } failed with error: ${ error }` ); + debug( + `main: Task ${ task.name } failed with error: ${ error }` + ); } } } debug( 'main: All done!' ); -}() ); +} )(); diff --git a/packages/project-management-automation/lib/test/add-first-time-contributor-label.js b/packages/project-management-automation/lib/test/add-first-time-contributor-label.js index ef7f65bf0909fe..ac8e5d59f26a0c 100644 --- a/packages/project-management-automation/lib/test/add-first-time-contributor-label.js +++ b/packages/project-management-automation/lib/test/add-first-time-contributor-label.js @@ -22,11 +22,13 @@ describe( 'addFirstTimeContributorLabel', () => { it( 'does nothing if the user has commits', async () => { const octokit = { search: { - commits: jest.fn( () => Promise.resolve( { - data: { - total_count: 100, - }, - } ) ), + commits: jest.fn( () => + Promise.resolve( { + data: { + total_count: 100, + }, + } ) + ), }, issues: { addLabels: jest.fn(), @@ -44,11 +46,13 @@ describe( 'addFirstTimeContributorLabel', () => { it( 'adds the label if the user does not have commits', async () => { const octokit = { search: { - commits: jest.fn( () => Promise.resolve( { - data: { - total_count: 0, - }, - } ) ), + commits: jest.fn( () => + Promise.resolve( { + data: { + total_count: 0, + }, + } ) + ), }, issues: { addLabels: jest.fn(), diff --git a/packages/project-management-automation/lib/test/add-milestone.js b/packages/project-management-automation/lib/test/add-milestone.js index 41466382046720..ae6cdb7b932c95 100644 --- a/packages/project-management-automation/lib/test/add-milestone.js +++ b/packages/project-management-automation/lib/test/add-milestone.js @@ -79,11 +79,13 @@ describe( 'addFirstTimeContributorLabel', () => { }; const octokit = { issues: { - get: jest.fn( () => Promise.resolve( { - data: { - milestone: 'Gutenberg 6.4', - }, - } ) ), + get: jest.fn( () => + Promise.resolve( { + data: { + milestone: 'Gutenberg 6.4', + }, + } ) + ), createMilestone: jest.fn(), listMilestonesForRepo: jest.fn(), update: jest.fn(), @@ -124,30 +126,38 @@ describe( 'addFirstTimeContributorLabel', () => { }; const octokit = { issues: { - get: jest.fn( () => Promise.resolve( { - data: { - milestone: null, - }, - } ) ), + get: jest.fn( () => + Promise.resolve( { + data: { + milestone: null, + }, + } ) + ), createMilestone: jest.fn(), - listMilestonesForRepo: jest.fn( () => Promise.resolve( { - data: [ - { title: 'Gutenberg 6.2', number: 10 }, - { title: 'Gutenberg 6.3', number: 11 }, - { title: 'Gutenberg 6.4', number: 12 }, - ], - } ) ), + listMilestonesForRepo: jest.fn( () => + Promise.resolve( { + data: [ + { title: 'Gutenberg 6.2', number: 10 }, + { title: 'Gutenberg 6.3', number: 11 }, + { title: 'Gutenberg 6.4', number: 12 }, + ], + } ) + ), update: jest.fn(), }, repos: { - getContents: jest.fn( () => Promise.resolve( { - data: { - content: Buffer.from( JSON.stringify( { - version: '6.3.0', - } ) ).toString( 'base64' ), - encoding: 'base64', - }, - } ) ), + getContents: jest.fn( () => + Promise.resolve( { + data: { + content: Buffer.from( + JSON.stringify( { + version: '6.3.0', + } ) + ).toString( 'base64' ), + encoding: 'base64', + }, + } ) + ), }, }; @@ -199,30 +209,38 @@ describe( 'addFirstTimeContributorLabel', () => { }; const octokit = { issues: { - get: jest.fn( () => Promise.resolve( { - data: { - milestone: null, - }, - } ) ), + get: jest.fn( () => + Promise.resolve( { + data: { + milestone: null, + }, + } ) + ), createMilestone: jest.fn(), - listMilestonesForRepo: jest.fn( () => Promise.resolve( { - data: [ - { title: 'Gutenberg 6.8', number: 10 }, - { title: 'Gutenberg 6.9', number: 11 }, - { title: 'Gutenberg 7.0', number: 12 }, - ], - } ) ), + listMilestonesForRepo: jest.fn( () => + Promise.resolve( { + data: [ + { title: 'Gutenberg 6.8', number: 10 }, + { title: 'Gutenberg 6.9', number: 11 }, + { title: 'Gutenberg 7.0', number: 12 }, + ], + } ) + ), update: jest.fn(), }, repos: { - getContents: jest.fn( () => Promise.resolve( { - data: { - content: Buffer.from( JSON.stringify( { - version: '6.9.0', - } ) ).toString( 'base64' ), - encoding: 'base64', - }, - } ) ), + getContents: jest.fn( () => + Promise.resolve( { + data: { + content: Buffer.from( + JSON.stringify( { + version: '6.9.0', + } ) + ).toString( 'base64' ), + encoding: 'base64', + }, + } ) + ), }, }; diff --git a/packages/project-management-automation/lib/test/assign-fixed-issues.js b/packages/project-management-automation/lib/test/assign-fixed-issues.js index 3af702875e464b..32356463626b9f 100644 --- a/packages/project-management-automation/lib/test/assign-fixed-issues.js +++ b/packages/project-management-automation/lib/test/assign-fixed-issues.js @@ -7,7 +7,8 @@ describe( 'assignFixedIssues', () => { it( 'does nothing if there are no fixed issues', async () => { const payload = { pull_request: { - body: 'This pull request seeks to make Gutenberg better than ever.', + body: + 'This pull request seeks to make Gutenberg better than ever.', }, }; const octokit = { @@ -26,7 +27,8 @@ describe( 'assignFixedIssues', () => { it( 'assigns and labels fixed issues', async () => { const payload = { pull_request: { - body: 'This pull request seeks to close #123 and also fixes https://github.com/WordPress/gutenberg/issues/456.', + body: + 'This pull request seeks to close #123 and also fixes https://github.com/WordPress/gutenberg/issues/456.', user: { login: 'matt', }, diff --git a/packages/redux-routine/src/runtime.js b/packages/redux-routine/src/runtime.js index 204f4c10eb7ffd..d534baae4efc01 100644 --- a/packages/redux-routine/src/runtime.js +++ b/packages/redux-routine/src/runtime.js @@ -19,19 +19,28 @@ import { isActionOfType, isAction } from './is-action'; * @return {Function} co-routine runtime */ export default function createRuntime( controls = {}, dispatch ) { - const rungenControls = map( controls, ( control, actionType ) => ( value, next, iterate, yieldNext, yieldError ) => { - if ( ! isActionOfType( value, actionType ) ) { - return false; - } - const routine = control( value ); - if ( isPromise( routine ) ) { - // Async control routine awaits resolution. - routine.then( yieldNext, yieldError ); - } else { - yieldNext( routine ); + const rungenControls = map( + controls, + ( control, actionType ) => ( + value, + next, + iterate, + yieldNext, + yieldError + ) => { + if ( ! isActionOfType( value, actionType ) ) { + return false; + } + const routine = control( value ); + if ( isPromise( routine ) ) { + // Async control routine awaits resolution. + routine.then( yieldNext, yieldError ); + } else { + yieldNext( routine ); + } + return true; } - return true; - } ); + ); const unhandledActionControl = ( value, next ) => { if ( ! isAction( value ) ) { @@ -45,12 +54,17 @@ export default function createRuntime( controls = {}, dispatch ) { const rungenRuntime = create( rungenControls ); - return ( action ) => new Promise( ( resolve, reject ) => - rungenRuntime( action, ( result ) => { - if ( isAction( result ) ) { - dispatch( result ); - } - resolve( result ); - }, reject ) - ); + return ( action ) => + new Promise( ( resolve, reject ) => + rungenRuntime( + action, + ( result ) => { + if ( isAction( result ) ) { + dispatch( result ); + } + resolve( result ); + }, + reject + ) + ); } diff --git a/packages/redux-routine/src/test/index.js b/packages/redux-routine/src/test/index.js index 4a25b4771580fd..6c2888e00a7514 100644 --- a/packages/redux-routine/src/test/index.js +++ b/packages/redux-routine/src/test/index.js @@ -52,9 +52,8 @@ describe( 'createMiddleware', () => { it( 'should throw if promise rejects', async () => { expect.hasAssertions(); const middleware = createMiddleware( { - WAIT_FAIL: () => new Promise( ( resolve, reject ) => - reject( 'Message' ) - ), + WAIT_FAIL: () => + new Promise( ( resolve, reject ) => reject( 'Message' ) ), } ); const store = createStoreWithMiddleware( middleware ); function* createAction() { @@ -71,9 +70,10 @@ describe( 'createMiddleware', () => { it( 'should throw if promise throws', () => { expect.hasAssertions(); const middleware = createMiddleware( { - WAIT_FAIL: () => new Promise( () => { - throw new Error( 'Message' ); - } ), + WAIT_FAIL: () => + new Promise( () => { + throw new Error( 'Message' ); + } ), } ); const store = createStoreWithMiddleware( middleware ); function* createAction() { @@ -96,9 +96,10 @@ describe( 'createMiddleware', () => { it( 'should handle a null returned from a caught promise error', () => { expect.hasAssertions(); const middleware = createMiddleware( { - WAIT_FAIL: () => new Promise( () => { - throw new Error( 'Message' ); - } ), + WAIT_FAIL: () => + new Promise( () => { + throw new Error( 'Message' ); + } ), } ); const store = createStoreWithMiddleware( middleware ); function* createAction() { @@ -129,9 +130,10 @@ describe( 'createMiddleware', () => { it( 'assigns async controlled return value into yield assignment', async () => { const middleware = createMiddleware( { - WAIT: ( action ) => new Promise( ( resolve ) => { - resolve( action.value ); - } ), + WAIT: ( action ) => + new Promise( ( resolve ) => { + resolve( action.value ); + } ), } ); const store = createStoreWithMiddleware( middleware ); function* createAction() { @@ -144,20 +146,23 @@ describe( 'createMiddleware', () => { expect( store.getState() ).toBe( 2 ); } ); - it( 'does not recurse when action like object returns from a sync ' + - 'control', () => { - const post = { type: 'post' }; - const middleware = createMiddleware( { - UPDATE: () => post, - } ); - const store = createStoreWithMiddleware( middleware ); - function* getPostAction() { - const nextState = yield { type: 'UPDATE' }; - return { type: 'CHANGE', nextState }; - } + it( + 'does not recurse when action like object returns from a sync ' + + 'control', + () => { + const post = { type: 'post' }; + const middleware = createMiddleware( { + UPDATE: () => post, + } ); + const store = createStoreWithMiddleware( middleware ); + function* getPostAction() { + const nextState = yield { type: 'UPDATE' }; + return { type: 'CHANGE', nextState }; + } - store.dispatch( getPostAction() ); + store.dispatch( getPostAction() ); - expect( store.getState() ).toEqual( post ); - } ); + expect( store.getState() ).toEqual( post ); + } + ); } ); diff --git a/packages/redux-routine/src/test/is-action.js b/packages/redux-routine/src/test/is-action.js index 2972c6b53d5ec4..322471f62c234a 100644 --- a/packages/redux-routine/src/test/is-action.js +++ b/packages/redux-routine/src/test/is-action.js @@ -3,14 +3,7 @@ */ import { isAction, isActionOfType } from '../is-action'; -const nonActions = [ - null, - [], - 42, - 'foo', - function() {}, - { foo: 'bar' }, -]; +const nonActions = [ null, [], 42, 'foo', function() {}, { foo: 'bar' } ]; const validAction = { type: 'winner' }; describe( 'isAction()', () => { diff --git a/packages/redux-routine/src/test/is-generator.js b/packages/redux-routine/src/test/is-generator.js index bfd210db0575c2..d3b52e529596c2 100644 --- a/packages/redux-routine/src/test/is-generator.js +++ b/packages/redux-routine/src/test/is-generator.js @@ -23,13 +23,13 @@ describe( 'isGenerator', () => { } ); it( 'should return false if an async generator', () => { - const value = ( async function*() {}() ); + const value = ( async function*() {} )(); expect( isGenerator( value ) ).toBe( false ); } ); it( 'should return true if a generator', () => { - const value = ( function*() {}() ); + const value = ( function*() {} )(); expect( isGenerator( value ) ).toBe( true ); } ); diff --git a/packages/rich-text/src/apply-format.js b/packages/rich-text/src/apply-format.js index 03a9ddc9f94d5c..cab61c5b8fd090 100644 --- a/packages/rich-text/src/apply-format.js +++ b/packages/rich-text/src/apply-format.js @@ -39,24 +39,38 @@ export function applyFormat( // The selection is collapsed. if ( startIndex === endIndex ) { - const startFormat = find( newFormats[ startIndex ], { type: format.type } ); + const startFormat = find( newFormats[ startIndex ], { + type: format.type, + } ); // If the caret is at a format of the same type, expand start and end to // the edges of the format. This is useful to apply new attributes. if ( startFormat ) { const index = newFormats[ startIndex ].indexOf( startFormat ); - while ( newFormats[ startIndex ] && newFormats[ startIndex ][ index ] === startFormat ) { - newFormats[ startIndex ] = - replace( newFormats[ startIndex ], index, format ); + while ( + newFormats[ startIndex ] && + newFormats[ startIndex ][ index ] === startFormat + ) { + newFormats[ startIndex ] = replace( + newFormats[ startIndex ], + index, + format + ); startIndex--; } endIndex++; - while ( newFormats[ endIndex ] && newFormats[ endIndex ][ index ] === startFormat ) { - newFormats[ endIndex ] = - replace( newFormats[ endIndex ], index, format ); + while ( + newFormats[ endIndex ] && + newFormats[ endIndex ][ index ] === startFormat + ) { + newFormats[ endIndex ] = replace( + newFormats[ endIndex ], + index, + format + ); endIndex++; } } @@ -66,8 +80,9 @@ export function applyFormat( for ( let index = startIndex; index < endIndex; index++ ) { if ( newFormats[ index ] ) { - newFormats[ index ] = newFormats[ index ] - .filter( ( { type } ) => type !== format.type ); + newFormats[ index ] = newFormats[ index ].filter( + ( { type } ) => type !== format.type + ); const length = newFormats[ index ].length; diff --git a/packages/rich-text/src/change-list-type.js b/packages/rich-text/src/change-list-type.js index e429b91c6cbca6..a7724e81d3f8c2 100644 --- a/packages/rich-text/src/change-list-type.js +++ b/packages/rich-text/src/change-list-type.js @@ -44,9 +44,11 @@ export function changeListType( value, newFormat ) { } changed = true; - newReplacements[ index ] = newReplacements[ index ].map( ( format, i ) => { - return i < startCount || i > endCount ? format : newFormat; - } ); + newReplacements[ index ] = newReplacements[ index ].map( + ( format, i ) => { + return i < startCount || i > endCount ? format : newFormat; + } + ); } if ( ! changed ) { diff --git a/packages/rich-text/src/component/format-edit.js b/packages/rich-text/src/component/format-edit.js index 13aa91606ecc45..753e6a02739b57 100644 --- a/packages/rich-text/src/component/format-edit.js +++ b/packages/rich-text/src/component/format-edit.js @@ -31,11 +31,7 @@ export default function FormatEdit( { allowedFormats, withoutInteractiveFormatting, } ) { - return formatTypes.map( ( { - name, - edit: Edit, - tagName, - } ) => { + return formatTypes.map( ( { name, edit: Edit, tagName } ) => { if ( ! Edit ) { return null; } @@ -54,7 +50,8 @@ export default function FormatEdit( { const activeFormat = getActiveFormat( value, name ); const isActive = activeFormat !== undefined; const activeObject = getActiveObject( value ); - const isObjectActive = activeObject !== undefined && activeObject.type === name; + const isObjectActive = + activeObject !== undefined && activeObject.type === name; return ( <Edit diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js index 163d2ac59f84be..5a97df2d1f565d 100644 --- a/packages/rich-text/src/component/index.js +++ b/packages/rich-text/src/component/index.js @@ -2,18 +2,21 @@ * External dependencies */ import classnames from 'classnames'; -import { - find, - isNil, - pickBy, - startsWith, -} from 'lodash'; +import { find, isNil, pickBy, startsWith } from 'lodash'; /** * WordPress dependencies */ import { Component, forwardRef } from '@wordpress/element'; -import { BACKSPACE, DELETE, ENTER, LEFT, RIGHT, SPACE, ESCAPE } from '@wordpress/keycodes'; +import { + BACKSPACE, + DELETE, + ENTER, + LEFT, + RIGHT, + SPACE, + ESCAPE, +} from '@wordpress/keycodes'; import { withSafeTimeout, compose } from '@wordpress/compose'; import isShallowEqual from '@wordpress/is-shallow-equal'; import deprecated from '@wordpress/deprecated'; @@ -101,9 +104,10 @@ function createPrepareEditableTree( props, prefix ) { return accumulator; }, [] ); - return ( value ) => fns.reduce( ( accumulator, fn ) => { - return fn( accumulator, value.text ); - }, value.formats ); + return ( value ) => + fns.reduce( ( accumulator, fn ) => { + return fn( accumulator, value.text ); + }, value.formats ); } /** @@ -135,11 +139,7 @@ function fixPlaceholderSelection() { * See export statement below. */ class RichText extends Component { - constructor( { - value, - selectionStart, - selectionEnd, - } ) { + constructor( { value, selectionStart, selectionEnd } ) { super( ...arguments ); this.onFocus = this.onFocus.bind( this ); @@ -148,7 +148,9 @@ class RichText extends Component { this.handleDelete = this.handleDelete.bind( this ); this.handleEnter = this.handleEnter.bind( this ); this.handleSpace = this.handleSpace.bind( this ); - this.handleHorizontalNavigation = this.handleHorizontalNavigation.bind( this ); + this.handleHorizontalNavigation = this.handleHorizontalNavigation.bind( + this + ); this.onPaste = this.onPaste.bind( this ); this.onCreateUndoLevel = this.onCreateUndoLevel.bind( this ); this.onInput = this.onInput.bind( this ); @@ -184,7 +186,10 @@ class RichText extends Component { } componentWillUnmount() { - document.removeEventListener( 'selectionchange', this.onSelectionChange ); + document.removeEventListener( + 'selectionchange', + this.onSelectionChange + ); window.cancelAnimationFrame( this.rafId ); } @@ -199,27 +204,36 @@ class RichText extends Component { preserveWhiteSpace, } = this.props; const selection = getSelection(); - const range = selection.rangeCount > 0 ? selection.getRangeAt( 0 ) : null; + const range = + selection.rangeCount > 0 ? selection.getRangeAt( 0 ) : null; return create( { element: forwardedRef.current, range, multilineTag, - multilineWrapperTags: multilineTag === 'li' ? [ 'ul', 'ol' ] : undefined, + multilineWrapperTags: + multilineTag === 'li' ? [ 'ul', 'ol' ] : undefined, __unstableIsEditableTree: true, preserveWhiteSpace, } ); } applyRecord( record, { domOnly } = {} ) { - const { __unstableMultilineTag: multilineTag, forwardedRef } = this.props; + const { + __unstableMultilineTag: multilineTag, + forwardedRef, + } = this.props; apply( { value: record, current: forwardedRef.current, multilineTag, - multilineWrapperTags: multilineTag === 'li' ? [ 'ul', 'ol' ] : undefined, - prepareEditableTree: createPrepareEditableTree( this.props, 'format_prepare_functions' ), + multilineWrapperTags: + multilineTag === 'li' ? [ 'ul', 'ol' ] : undefined, + prepareEditableTree: createPrepareEditableTree( + this.props, + 'format_prepare_functions' + ), __unstableDomOnly: domOnly, placeholder: this.props.placeholder, } ); @@ -280,14 +294,20 @@ class RichText extends Component { window.console.log( 'Received plain text:\n\n', plainText ); const record = this.record; - const transformed = formatTypes.reduce( ( accumlator, { __unstablePasteRule } ) => { - // Only allow one transform. - if ( __unstablePasteRule && accumlator === record ) { - accumlator = __unstablePasteRule( record, { html, plainText } ); - } + const transformed = formatTypes.reduce( + ( accumlator, { __unstablePasteRule } ) => { + // Only allow one transform. + if ( __unstablePasteRule && accumlator === record ) { + accumlator = __unstablePasteRule( record, { + html, + plainText, + } ); + } - return accumlator; - }, record ); + return accumlator; + }, + record + ); if ( transformed !== record ) { this.onChange( transformed ); @@ -367,10 +387,13 @@ class RichText extends Component { } else { this.props.onSelectionChange( this.record.start, this.record.end ); this.setState( { - activeFormats: getActiveFormats( { - ...this.record, - activeFormats: undefined, - }, EMPTY_ACTIVE_FORMATS ), + activeFormats: getActiveFormats( + { + ...this.record, + activeFormats: undefined, + }, + EMPTY_ACTIVE_FORMATS + ), } ); } @@ -391,7 +414,10 @@ class RichText extends Component { } onBlur() { - document.removeEventListener( 'selectionchange', this.onSelectionChange ); + document.removeEventListener( + 'selectionchange', + this.onSelectionChange + ); } /** @@ -421,10 +447,11 @@ class RichText extends Component { // The browser formatted something or tried to insert HTML. // Overwrite it. It will be handled later by the format library if // needed. - if ( inputType && ( - inputType.indexOf( 'format' ) === 0 || - INSERTION_INPUT_TYPES_TO_IGNORE.has( inputType ) - ) ) { + if ( + inputType && + ( inputType.indexOf( 'format' ) === 0 || + INSERTION_INPUT_TYPES_TO_IGNORE.has( inputType ) ) + ) { this.applyRecord( this.record ); return; } @@ -468,13 +495,16 @@ class RichText extends Component { inputRule( change, this.valueToFormat ); } - const transformed = formatTypes.reduce( ( accumlator, { __unstableInputRule } ) => { - if ( __unstableInputRule ) { - accumlator = __unstableInputRule( accumlator ); - } + const transformed = formatTypes.reduce( + ( accumlator, { __unstableInputRule } ) => { + if ( __unstableInputRule ) { + accumlator = __unstableInputRule( accumlator ); + } - return accumlator; - }, change ); + return accumlator; + }, + change + ); if ( transformed !== change ) { this.onCreateUndoLevel(); @@ -488,7 +518,10 @@ class RichText extends Component { // Do not update the selection when characters are being composed as // this rerenders the component and might distroy internal browser // editing state. - document.removeEventListener( 'selectionchange', this.onSelectionChange ); + document.removeEventListener( + 'selectionchange', + this.onSelectionChange + ); } onCompositionEnd() { @@ -559,7 +592,10 @@ class RichText extends Component { activeFormats: undefined, }; - const activeFormats = getActiveFormats( newValue, EMPTY_ACTIVE_FORMATS ); + const activeFormats = getActiveFormats( + newValue, + EMPTY_ACTIVE_FORMATS + ); // Update the value with the new active formats. newValue.activeFormats = activeFormats; @@ -633,7 +669,11 @@ class RichText extends Component { handleDelete( event ) { const { keyCode } = event; - if ( keyCode !== DELETE && keyCode !== BACKSPACE && keyCode !== ESCAPE ) { + if ( + keyCode !== DELETE && + keyCode !== BACKSPACE && + keyCode !== ESCAPE + ) { return; } @@ -664,7 +704,12 @@ class RichText extends Component { let newValue; // Check to see if we should remove the first item if empty. - if ( isReverse && value.start === 0 && value.end === 0 && isEmptyLine( value ) ) { + if ( + isReverse && + value.start === 0 && + value.end === 0 && + isEmptyLine( value ) + ) { newValue = removeLineSeparator( value, ! isReverse ); } else { newValue = removeLineSeparator( value, isReverse ); @@ -728,8 +773,12 @@ class RichText extends Component { if ( // Only override when no modifiers are pressed. - shiftKey || altKey || metaKey || ctrlKey || - keyCode !== SPACE || multilineTag !== 'li' + shiftKey || + altKey || + metaKey || + ctrlKey || + keyCode !== SPACE || + multilineTag !== 'li' ) { return; } @@ -764,7 +813,10 @@ class RichText extends Component { if ( // Only override left and right keys without modifiers pressed. - shiftKey || altKey || metaKey || ctrlKey || + shiftKey || + altKey || + metaKey || + ctrlKey || ( keyCode !== LEFT && keyCode !== RIGHT ) ) { return; @@ -774,7 +826,9 @@ class RichText extends Component { const { text, formats, start, end, activeFormats = [] } = value; const collapsed = isCollapsed( value ); // To do: ideally, we should look at visual position instead. - const { direction } = getComputedStyle( this.props.forwardedRef.current ); + const { direction } = getComputedStyle( + this.props.forwardedRef.current + ); const reverseKey = direction === 'rtl' ? RIGHT : LEFT; const isReverse = event.keyCode === reverseKey; @@ -866,7 +920,10 @@ class RichText extends Component { const { target } = event; // If the child element has no text content, it must be an object. - if ( target === this.props.forwardedRef.current || target.textContent ) { + if ( + target === this.props.forwardedRef.current || + target.textContent + ) { return; } @@ -896,23 +953,20 @@ class RichText extends Component { let shouldReapply = tagName !== prevProps.tagName; // Check if the content changed. - shouldReapply = shouldReapply || ( - value !== prevProps.value && - value !== this.value - ); + shouldReapply = + shouldReapply || + ( value !== prevProps.value && value !== this.value ); - const selectionChanged = ( - selectionStart !== prevProps.selectionStart && - selectionStart !== this.record.start - ) || ( - selectionEnd !== prevProps.selectionEnd && - selectionEnd !== this.record.end - ); + const selectionChanged = + ( selectionStart !== prevProps.selectionStart && + selectionStart !== this.record.start ) || + ( selectionEnd !== prevProps.selectionEnd && + selectionEnd !== this.record.end ); // Check if the selection changed. - shouldReapply = shouldReapply || ( - isSelected && ! prevProps.isSelected && selectionChanged - ); + shouldReapply = + shouldReapply || + ( isSelected && ! prevProps.isSelected && selectionChanged ); const prefix = 'format_prepare_props_'; const predicate = ( v, key ) => key.startsWith( prefix ); @@ -920,12 +974,11 @@ class RichText extends Component { const prevPrepareProps = pickBy( prevProps, predicate ); // Check if any format props changed. - shouldReapply = shouldReapply || - ! isShallowEqual( prepareProps, prevPrepareProps ); + shouldReapply = + shouldReapply || ! isShallowEqual( prepareProps, prevPrepareProps ); // Rerender if the placeholder changed. - shouldReapply = shouldReapply || - placeholder !== prevProps.placeholder; + shouldReapply = shouldReapply || placeholder !== prevProps.placeholder; if ( shouldReapply ) { this.value = value; @@ -959,12 +1012,16 @@ class RichText extends Component { return value; } - const prepare = createPrepareEditableTree( this.props, 'format_value_functions' ); + const prepare = createPrepareEditableTree( + this.props, + 'format_value_functions' + ); value = create( { html: value, multilineTag, - multilineWrapperTags: multilineTag === 'li' ? [ 'ul', 'ol' ] : undefined, + multilineWrapperTags: + multilineTag === 'li' ? [ 'ul', 'ol' ] : undefined, preserveWhiteSpace, } ); value.formats = prepare( value ); @@ -985,7 +1042,12 @@ class RichText extends Component { this.props.formatTypes.forEach( ( formatType ) => { // Remove formats created by prepareEditableTree, because they are editor only. if ( formatType.__experimentalCreatePrepareEditableTree ) { - value = removeFormat( value, formatType.name, 0, value.text.length ); + value = removeFormat( + value, + formatType.name, + 0, + value.text.length + ); } } ); @@ -1024,7 +1086,8 @@ class RichText extends Component { disabled, } = this.props; const ariaProps = pickBy( this.props, ( value, key ) => - startsWith( key, 'aria-' ) ); + startsWith( key, 'aria-' ) + ); return ( <TagName @@ -1041,10 +1104,14 @@ class RichText extends Component { onInput={ this.onInput } onCompositionStart={ this.onCompositionStart } onCompositionEnd={ this.onCompositionEnd } - onKeyDown={ props.onKeyDown ? ( event ) => { - props.onKeyDown( event ); - this.onKeyDown( event ); - } : this.onKeyDown } + onKeyDown={ + props.onKeyDown + ? ( event ) => { + props.onKeyDown( event ); + this.onKeyDown( event ); + } + : this.onKeyDown + } onFocus={ this.onFocus } onBlur={ this.onBlur } onMouseDown={ this.onPointerDown } @@ -1086,21 +1153,26 @@ class RichText extends Component { forwardedRef={ forwardedRef } /> <InlineWarning forwardedRef={ forwardedRef } /> - { isSelected && <FormatEdit - allowedFormats={ allowedFormats } - withoutInteractiveFormatting={ withoutInteractiveFormatting } - value={ this.record } - onChange={ this.onChange } - onFocus={ onFocus } - formatTypes={ formatTypes } - /> } - { children && children( { - isSelected, - value: this.record, - onChange: this.onChange, - onFocus, - Editable: this.Editable, - } ) } + { isSelected && ( + <FormatEdit + allowedFormats={ allowedFormats } + withoutInteractiveFormatting={ + withoutInteractiveFormatting + } + value={ this.record } + onChange={ this.onChange } + onFocus={ onFocus } + formatTypes={ formatTypes } + /> + ) } + { children && + children( { + isSelected, + value: this.record, + onChange: this.onChange, + onFocus, + Editable: this.Editable, + } ) } { ! children && <this.Editable /> } </> ); @@ -1112,10 +1184,9 @@ RichText.defaultProps = { value: '', }; -const RichTextWrapper = compose( [ - withSafeTimeout, - withFormatTypes, -] )( RichText ); +const RichTextWrapper = compose( [ withSafeTimeout, withFormatTypes ] )( + RichText +); /** * Renders a rich content input, providing users with the option to format the diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js index 0743f167528746..ae606cd7c248eb 100644 --- a/packages/rich-text/src/component/index.native.js +++ b/packages/rich-text/src/component/index.native.js @@ -5,9 +5,7 @@ */ import RCTAztecView from 'react-native-aztec'; import { View, Platform } from 'react-native'; -import { - pickBy, -} from 'lodash'; +import { pickBy } from 'lodash'; import memize from 'memize'; /** @@ -47,7 +45,12 @@ const gutenbergFormatNamesToAztec = { }; export class RichText extends Component { - constructor( { value, selectionStart, selectionEnd, __unstableMultilineTag: multiline } ) { + constructor( { + value, + selectionStart, + selectionEnd, + __unstableMultilineTag: multiline, + } ) { super( ...arguments ); this.isMultiline = false; @@ -71,14 +74,15 @@ export class RichText extends Component { this.onTextUpdate = this.onTextUpdate.bind( this ); this.onContentSizeChange = this.onContentSizeChange.bind( this ); this.onFormatChange = this.onFormatChange.bind( this ); - this.formatToValue = memize( - this.formatToValue.bind( this ), - { maxSize: 1 } - ); + this.formatToValue = memize( this.formatToValue.bind( this ), { + maxSize: 1, + } ); // This prevents a bug in Aztec which triggers onSelectionChange twice on format change this.onSelectionChange = this.onSelectionChange.bind( this ); - this.onSelectionChangeFromAztec = this.onSelectionChangeFromAztec.bind( this ); + this.onSelectionChangeFromAztec = this.onSelectionChangeFromAztec.bind( + this + ); this.valueToFormat = this.valueToFormat.bind( this ); this.willTrimSpaces = this.willTrimSpaces.bind( this ); this.getHtmlToRender = this.getHtmlToRender.bind( this ); @@ -129,9 +133,7 @@ export class RichText extends Component { * @return {Object} A RichText value with formats and selection. */ createRecord() { - const { - preserveWhiteSpace, - } = this.props; + const { preserveWhiteSpace } = this.props; const value = { start: this.selectionStart, end: this.selectionEnd, @@ -150,20 +152,24 @@ export class RichText extends Component { valueToFormat( value ) { // remove the outer root tags - return this.removeRootTagsProduceByAztec( toHTMLString( { - value, - multilineTag: this.multilineTag, - } ) ); + return this.removeRootTagsProduceByAztec( + toHTMLString( { + value, + multilineTag: this.multilineTag, + } ) + ); } getActiveFormatNames( record ) { - const { - formatTypes, - } = this.props; - - return formatTypes.map( ( { name } ) => name ).filter( ( name ) => { - return getActiveFormat( record, name ) !== undefined; - } ).map( ( name ) => gutenbergFormatNamesToAztec[ name ] ).filter( Boolean ); + const { formatTypes } = this.props; + + return formatTypes + .map( ( { name } ) => name ) + .filter( ( name ) => { + return getActiveFormat( record, name ) !== undefined; + } ) + .map( ( name ) => gutenbergFormatNamesToAztec[ name ] ) + .filter( Boolean ); } onFormatChange( record ) { @@ -224,19 +230,25 @@ export class RichText extends Component { removeRootTag( tag, html ) { const openingTagRegexp = RegExp( '^<' + tag + '[^>]*>', 'gim' ); const closingTagRegexp = RegExp( '</' + tag + '>$', 'gim' ); - return html.replace( openingTagRegexp, '' ).replace( closingTagRegexp, '' ); + return html + .replace( openingTagRegexp, '' ) + .replace( closingTagRegexp, '' ); } removeTag( tag, html ) { const openingTagRegexp = RegExp( '<' + tag + '>', 'gim' ); const closingTagRegexp = RegExp( '</' + tag + '>', 'gim' ); - return html.replace( openingTagRegexp, '' ).replace( closingTagRegexp, '' ); + return html + .replace( openingTagRegexp, '' ) + .replace( closingTagRegexp, '' ); } /* * Handles any case where the content of the AztecRN instance has changed */ onChange( event ) { - const contentWithoutRootTag = this.removeRootTagsProduceByAztec( unescapeSpaces( event.nativeEvent.text ) ); + const contentWithoutRootTag = this.removeRootTagsProduceByAztec( + unescapeSpaces( event.nativeEvent.text ) + ); // On iOS, onChange can be triggered after selection changes, even though there are no content changes. if ( contentWithoutRootTag === this.value ) { return; @@ -249,7 +261,9 @@ export class RichText extends Component { } onTextUpdate( event ) { - const contentWithoutRootTag = this.removeRootTagsProduceByAztec( unescapeSpaces( event.nativeEvent.text ) ); + const contentWithoutRootTag = this.removeRootTagsProduceByAztec( + unescapeSpaces( event.nativeEvent.text ) + ); const refresh = this.value !== contentWithoutRootTag; this.value = contentWithoutRootTag; @@ -306,7 +320,12 @@ export class RichText extends Component { } if ( multilineTag ) { - if ( isReverse && value.start === 0 && value.end === 0 && isEmptyLine( value ) ) { + if ( + isReverse && + value.start === 0 && + value.end === 0 && + isEmptyLine( value ) + ) { newValue = removeLineSeparator( value, ! isReverse ); } else { newValue = removeLineSeparator( value, isReverse ); @@ -341,10 +360,7 @@ export class RichText extends Component { * @param {Object} event The paste event which wraps `nativeEvent`. */ onPaste( event ) { - const { - onPaste, - onChange, - } = this.props; + const { onPaste, onChange } = this.props; const { activeFormats = [] } = this.state; const { pastedText, pastedHtml, files } = event.nativeEvent; @@ -354,7 +370,8 @@ export class RichText extends Component { // There is a selection, check if a URL is pasted. if ( ! isCollapsed( currentRecord ) ) { - const trimmedText = ( pastedHtml || pastedText ).replace( /<[^>]+>/g, '' ) + const trimmedText = ( pastedHtml || pastedText ) + .replace( /<[^>]+>/g, '' ) .trim(); // A URL was pasted, turn the selection into a link @@ -390,10 +407,7 @@ export class RichText extends Component { onFocus() { this.isTouched = true; - const { - unstableOnFocus, - onSelectionChange, - } = this.props; + const { unstableOnFocus, onSelectionChange } = this.props; if ( unstableOnFocus ) { unstableOnFocus(); @@ -412,7 +426,10 @@ export class RichText extends Component { this.isTouched = false; // Check if value is up to date with latest state of native AztecView - if ( event.nativeEvent.text && event.nativeEvent.text !== this.props.value ) { + if ( + event.nativeEvent.text && + event.nativeEvent.text !== this.props.value + ) { this.onTextUpdate( event ); } @@ -424,7 +441,8 @@ export class RichText extends Component { } onSelectionChange( start, end ) { - const hasChanged = this.selectionStart !== start || this.selectionEnd !== end; + const hasChanged = + this.selectionStart !== start || this.selectionEnd !== end; this.selectionStart = start; this.selectionEnd = end; @@ -433,7 +451,9 @@ export class RichText extends Component { // and we did not just trigger a text update // `onChange` could be the last event and could have been triggered a long time ago so // this approach is not perfectly reliable - const isManual = this.lastAztecEventType !== 'input' && this.props.value === this.value; + const isManual = + this.lastAztecEventType !== 'input' && + this.props.value === this.value; if ( hasChanged && isManual ) { const value = this.createRecord(); const activeFormats = getActiveFormats( value ); @@ -450,8 +470,14 @@ export class RichText extends Component { const realEnd = Math.max( start, end ); // check and dicsard stray event, where the text and selection is equal to the ones already cached - const contentWithoutRootTag = this.removeRootTagsProduceByAztec( unescapeSpaces( event.nativeEvent.text ) ); - if ( contentWithoutRootTag === this.value && realStart === this.selectionStart && realEnd === this.selectionEnd ) { + const contentWithoutRootTag = this.removeRootTagsProduceByAztec( + unescapeSpaces( event.nativeEvent.text ) + ); + if ( + contentWithoutRootTag === this.value && + realStart === this.selectionStart && + realEnd === this.selectionEnd + ) { return; } @@ -483,9 +509,7 @@ export class RichText extends Component { } formatToValue( value ) { - const { - preserveWhiteSpace, - } = this.props; + const { preserveWhiteSpace } = this.props; // Handle deprecated `children` and `node` sources. if ( Array.isArray( value ) ) { return create( { @@ -515,9 +539,11 @@ export class RichText extends Component { } shouldComponentUpdate( nextProps ) { - if ( nextProps.tagName !== this.props.tagName || + if ( + nextProps.tagName !== this.props.tagName || nextProps.reversed !== this.props.reversed || - nextProps.start !== this.props.start ) { + nextProps.start !== this.props.start + ) { this.lastEventCount = undefined; this.value = undefined; return true; @@ -533,13 +559,18 @@ export class RichText extends Component { // with only some of the text, while the virtual keyboard's suggestion system does its magic. // ** compare with this.lastContent for optimizing performance by not forcing Aztec with text it already has // , but compare with props.value to not lose "half word" text because of Android virtual keyb autosuggestion behavior - if ( ( typeof nextProps.value !== 'undefined' ) && - ( typeof this.props.value !== 'undefined' ) && - ( ! this.comesFromAztec || ! this.firedAfterTextChanged ) && - nextProps.value !== this.props.value ) { + if ( + typeof nextProps.value !== 'undefined' && + typeof this.props.value !== 'undefined' && + ( ! this.comesFromAztec || ! this.firedAfterTextChanged ) && + nextProps.value !== this.props.value + ) { // Gutenberg seems to try to mirror the caret state even on events that only change the content so, // let's force caret update if state has selection set. - if ( typeof nextProps.selectionStart !== 'undefined' && typeof nextProps.selectionEnd !== 'undefined' ) { + if ( + typeof nextProps.selectionStart !== 'undefined' && + typeof nextProps.selectionEnd !== 'undefined' + ) { this.needsSelectionUpdate = true; } @@ -547,11 +578,13 @@ export class RichText extends Component { } if ( ! this.comesFromAztec ) { - if ( ( typeof nextProps.selectionStart !== 'undefined' ) && - ( typeof nextProps.selectionEnd !== 'undefined' ) && - nextProps.selectionStart !== this.props.selectionStart && - nextProps.selectionStart !== this.selectionStart && - nextProps.__unstableIsSelected ) { + if ( + typeof nextProps.selectionStart !== 'undefined' && + typeof nextProps.selectionEnd !== 'undefined' && + nextProps.selectionStart !== this.props.selectionStart && + nextProps.selectionStart !== this.selectionStart && + nextProps.__unstableIsSelected + ) { this.needsSelectionUpdate = true; this.lastEventCount = undefined; // force a refresh on the native side } @@ -565,9 +598,15 @@ export class RichText extends Component { // is trying to implement the web-side counterpart of BlockList's `focusTabbable` where the BlockList is focusing an // inputbox by searching the DOM. We don't have the DOM in RN so, using the combination of blockIsSelected and __unstableMobileNoFocusOnMount // to determine if we should focus the RichText. - if ( this.props.blockIsSelected && ! this.props.__unstableMobileNoFocusOnMount ) { + if ( + this.props.blockIsSelected && + ! this.props.__unstableMobileNoFocusOnMount + ) { this._editor.focus(); - this.onSelectionChange( this.props.selectionStart || 0, this.props.selectionEnd || 0 ); + this.onSelectionChange( + this.props.selectionStart || 0, + this.props.selectionEnd || 0 + ); } } @@ -582,28 +621,25 @@ export class RichText extends Component { this.value = this.props.value; this.lastEventCount = undefined; } - const { - __unstableIsSelected: isSelected, - } = this.props; + const { __unstableIsSelected: isSelected } = this.props; - const { - __unstableIsSelected: prevIsSelected, - } = prevProps; + const { __unstableIsSelected: prevIsSelected } = prevProps; if ( isSelected && ! prevIsSelected ) { this._editor.focus(); // Update selection props explicitly when component is selected as Aztec won't call onSelectionChange // if its internal value hasn't change. When created, default value is 0, 0 - this.onSelectionChange( this.props.selectionStart || 0, this.props.selectionEnd || 0 ); + this.onSelectionChange( + this.props.selectionStart || 0, + this.props.selectionEnd || 0 + ); } else if ( ! isSelected && prevIsSelected ) { this._editor.blur(); } } willTrimSpaces( html ) { - const { - tagName, - } = this.props; + const { tagName } = this.props; // aztec won't trim spaces in a case of <pre> block, so we are excluding it if ( tagName === 'pre' ) { @@ -611,8 +647,12 @@ export class RichText extends Component { } // regex for detecting spaces around block element html tags - const blockHtmlElements = '(div|br|blockquote|ul|ol|li|p|pre|h1|h2|h3|h4|h5|h6|iframe|hr)'; - const leadingOrTrailingSpaces = new RegExp( `(\\s+)<\/?${ blockHtmlElements }>|<\/?${ blockHtmlElements }>(\\s+)`, 'g' ); + const blockHtmlElements = + '(div|br|blockquote|ul|ol|li|p|pre|h1|h2|h3|h4|h5|h6|iframe|hr)'; + const leadingOrTrailingSpaces = new RegExp( + `(\\s+)<\/?${ blockHtmlElements }>|<\/?${ blockHtmlElements }>(\\s+)`, + 'g' + ); const matches = html.match( leadingOrTrailingSpaces ); if ( matches && matches.length > 0 ) { return true; @@ -662,11 +702,12 @@ export class RichText extends Component { const record = this.getRecord(); const html = this.getHtmlToRender( record, tagName ); - const placeholderStyle = getStylesFromColorScheme( styles.richTextPlaceholder, styles.richTextPlaceholderDark ); + const placeholderStyle = getStylesFromColorScheme( + styles.richTextPlaceholder, + styles.richTextPlaceholderDark + ); - const { - color: defaultPlaceholderTextColor, - } = placeholderStyle; + const { color: defaultPlaceholderTextColor } = placeholderStyle; const { color: defaultColor, @@ -677,7 +718,10 @@ export class RichText extends Component { let selection = null; if ( this.needsSelectionUpdate ) { this.needsSelectionUpdate = false; - selection = { start: this.props.selectionStart, end: this.props.selectionEnd }; + selection = { + start: this.props.selectionStart, + end: this.props.selectionEnd, + }; // On AztecAndroid, setting the caret to an out-of-bounds position will crash the editor so, let's check for some cases. if ( ! this.isIOS ) { @@ -687,28 +731,44 @@ export class RichText extends Component { if ( this.willTrimSpaces( html ) ) { // the html will get trimmed by the cleaning up functions in Aztec and caret position will get out-of-sync. // So, skip forcing it, let Aztec just do its best and just log the fact. - console.warn( 'RichText value will be trimmed for spaces! Avoiding setting the caret position manually.' ); + console.warn( + 'RichText value will be trimmed for spaces! Avoiding setting the caret position manually.' + ); selection = null; - } else if ( this.props.selectionStart > record.text.length || this.props.selectionEnd > record.text.length ) { - console.warn( 'Oops, selection will land outside the text, skipping setting it...' ); + } else if ( + this.props.selectionStart > record.text.length || + this.props.selectionEnd > record.text.length + ) { + console.warn( + 'Oops, selection will land outside the text, skipping setting it...' + ); selection = null; } else { // The following regular expression is used in Aztec here: // https://github.com/wordpress-mobile/AztecEditor-Android/blob/b1fad439d56fa6d4aa0b78526fef355c59d00dd3/aztec/src/main/kotlin/org/wordpress/aztec/AztecParser.kt#L656 const brBeforeParaMatches = html.match( /(<br>)+<\/p>$/g ); if ( brBeforeParaMatches ) { - console.warn( 'Oops, BR tag(s) at the end of content. Aztec will remove them, adapting the selection...' ); - const count = ( brBeforeParaMatches[ 0 ].match( /br/g ) || [] ).length; + console.warn( + 'Oops, BR tag(s) at the end of content. Aztec will remove them, adapting the selection...' + ); + const count = ( + brBeforeParaMatches[ 0 ].match( /br/g ) || [] + ).length; if ( count > 0 ) { - let newSelectionStart = this.props.selectionStart - count; + let newSelectionStart = + this.props.selectionStart - count; if ( newSelectionStart < 0 ) { newSelectionStart = 0; } - let newSelectionEnd = this.props.selectionEnd - count; + let newSelectionEnd = + this.props.selectionEnd - count; if ( newSelectionEnd < 0 ) { newSelectionEnd = 0; } - selection = { start: newSelectionStart, end: newSelectionEnd }; + selection = { + start: newSelectionStart, + end: newSelectionEnd, + }; } } } @@ -722,12 +782,13 @@ export class RichText extends Component { return ( <View> - { children && children( { - isSelected, - value: record, - onChange: this.onFormatChange, - onFocus: () => {}, - } ) } + { children && + children( { + isSelected, + value: record, + onChange: this.onFormatChange, + onFocus: () => {}, + } ) } <RCTAztecView ref={ ( ref ) => { this._editor = ref; @@ -740,9 +801,16 @@ export class RichText extends Component { ...style, minHeight: this.state.height, } } - text={ { text: html, eventCount: this.lastEventCount, selection } } + text={ { + text: html, + eventCount: this.lastEventCount, + selection, + } } placeholder={ this.props.placeholder } - placeholderTextColor={ this.props.placeholderTextColor || defaultPlaceholderTextColor } + placeholderTextColor={ + this.props.placeholderTextColor || + defaultPlaceholderTextColor + } deleteEnter={ this.props.deleteEnter } onChange={ this.onChange } onFocus={ this.onFocus } @@ -752,14 +820,18 @@ export class RichText extends Component { onPaste={ this.onPaste } activeFormats={ this.getActiveFormatNames( record ) } onContentSizeChange={ this.onContentSizeChange } - onCaretVerticalPositionChange={ this.props.onCaretVerticalPositionChange } + onCaretVerticalPositionChange={ + this.props.onCaretVerticalPositionChange + } onSelectionChange={ this.onSelectionChangeFromAztec } blockType={ { tag: tagName } } color={ ( style && style.color ) || defaultColor } linkTextColor={ defaultTextDecorationColor } maxImagesWidth={ 200 } fontFamily={ this.props.fontFamily || defaultFontFamily } - fontSize={ this.props.fontSize || ( style && style.fontSize ) } + fontSize={ + this.props.fontSize || ( style && style.fontSize ) + } fontWeight={ this.props.fontWeight } fontStyle={ this.props.fontStyle } disableEditingMenu={ this.props.disableEditingMenu } @@ -767,12 +839,14 @@ export class RichText extends Component { textAlign={ this.props.textAlign } selectionColor={ this.props.selectionColor } /> - { isSelected && <FormatEdit - formatTypes={ formatTypes } - value={ record } - onChange={ this.onFormatChange } - onFocus={ () => {} } - /> } + { isSelected && ( + <FormatEdit + formatTypes={ formatTypes } + value={ record } + onChange={ this.onFormatChange } + onFocus={ () => {} } + /> + ) } </View> ); } diff --git a/packages/rich-text/src/component/inline-warning.js b/packages/rich-text/src/component/inline-warning.js index 301c4d898dc476..d74858dd912b66 100644 --- a/packages/rich-text/src/component/inline-warning.js +++ b/packages/rich-text/src/component/inline-warning.js @@ -6,11 +6,15 @@ import { useEffect } from '@wordpress/element'; export function InlineWarning( { forwardedRef } ) { useEffect( () => { if ( process.env.NODE_ENV === 'development' ) { - const computedStyle = window.getComputedStyle( forwardedRef.current ); + const computedStyle = window.getComputedStyle( + forwardedRef.current + ); if ( computedStyle.display === 'inline' ) { // eslint-disable-next-line no-console - console.warn( 'RichText cannot be used with an inline container. Please use a different tagName.' ); + console.warn( + 'RichText cannot be used with an inline container. Please use a different tagName.' + ); } } }, [] ); diff --git a/packages/rich-text/src/component/test/index.native.js b/packages/rich-text/src/component/test/index.native.js index f3d35bff0501c7..c78999c58e73ea 100644 --- a/packages/rich-text/src/component/test/index.native.js +++ b/packages/rich-text/src/component/test/index.native.js @@ -20,7 +20,8 @@ describe( 'RichText Native', () => { } ); it( 'reports false for styled text with no outer spaces', () => { - const html = '<p><b>Hello</b> <strong>Hello</strong> WorldWorld!</p>'; + const html = + '<p><b>Hello</b> <strong>Hello</strong> WorldWorld!</p>'; expect( richText.willTrimSpaces( html ) ).toBe( false ); } ); diff --git a/packages/rich-text/src/component/with-format-types.js b/packages/rich-text/src/component/with-format-types.js index 5844c7a1e853de..9a5c78a52609fa 100644 --- a/packages/rich-text/src/component/with-format-types.js +++ b/packages/rich-text/src/component/with-format-types.js @@ -25,44 +25,60 @@ 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 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 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 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 ) { @@ -81,41 +97,43 @@ export default function withFormatTypes( RichText ) { 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 ]; - } + 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 ]; - } + if ( key.startsWith( dispatchPrefix ) ) { + accumulator[ key.slice( dispatchPrefix.length ) ] = + combined[ key ]; + } - return accumulator; - }, {} ); + return accumulator; + }, + {} + ); if ( settings.__experimentalCreateOnChangeEditableValue ) { return { ...acc, - [ `format_value_functions_(${ name })` ]: - settings.__experimentalCreatePrepareEditableTree( - propsByPrefix, - args - ), - [ `format_on_change_functions_(${ name })` ]: - settings.__experimentalCreateOnChangeEditableValue( - propsByPrefix, - args - ), + [ `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 - ), + [ `format_prepare_functions_(${ name })` ]: settings.__experimentalCreatePrepareEditableTree( + propsByPrefix, + args + ), }; }, {} ); }, [ formatTypes, clientId, identifier, selectProps, dispatchProps ] ); diff --git a/packages/rich-text/src/create.js b/packages/rich-text/src/create.js index dd46423d0982f0..ca25ef8e09be10 100644 --- a/packages/rich-text/src/create.js +++ b/packages/rich-text/src/create.js @@ -42,11 +42,15 @@ function toFormat( { type, attributes } ) { let formatType; if ( attributes && attributes.class ) { - formatType = select( 'core/rich-text' ).getFormatTypeForClassName( attributes.class ); + formatType = select( 'core/rich-text' ).getFormatTypeForClassName( + attributes.class + ); if ( formatType ) { // Preserve any additional classes. - attributes.class = ` ${ attributes.class } `.replace( ` ${ formatType.className } `, ' ' ).trim(); + attributes.class = ` ${ attributes.class } ` + .replace( ` ${ formatType.className } `, ' ' ) + .trim(); if ( ! attributes.class ) { delete attributes.class; @@ -55,7 +59,9 @@ function toFormat( { type, attributes } ) { } if ( ! formatType ) { - formatType = select( 'core/rich-text' ).getFormatTypeForBareElement( type ); + formatType = select( 'core/rich-text' ).getFormatTypeForBareElement( + type + ); } if ( ! formatType ) { @@ -201,22 +207,22 @@ function accumulateSelection( accumulator, node, range, value ) { // Selection can be extracted from value. if ( value.start !== undefined ) { accumulator.start = currentLength + value.start; - // Range indicates that the current node has selection. + // Range indicates that the current node has selection. } else if ( node === startContainer && node.nodeType === TEXT_NODE ) { accumulator.start = currentLength + startOffset; - // Range indicates that the current node is selected. + // Range indicates that the current node is selected. } else if ( parentNode === startContainer && node === startContainer.childNodes[ startOffset ] ) { accumulator.start = currentLength; - // Range indicates that the selection is after the current node. + // Range indicates that the selection is after the current node. } else if ( parentNode === startContainer && node === startContainer.childNodes[ startOffset - 1 ] ) { accumulator.start = currentLength + value.text.length; - // Fallback if no child inside handled the selection. + // Fallback if no child inside handled the selection. } else if ( node === startContainer ) { accumulator.start = currentLength; } @@ -224,22 +230,22 @@ function accumulateSelection( accumulator, node, range, value ) { // Selection can be extracted from value. if ( value.end !== undefined ) { accumulator.end = currentLength + value.end; - // Range indicates that the current node has selection. + // Range indicates that the current node has selection. } else if ( node === endContainer && node.nodeType === TEXT_NODE ) { accumulator.end = currentLength + endOffset; - // Range indicates that the current node is selected. + // Range indicates that the current node is selected. } else if ( parentNode === endContainer && node === endContainer.childNodes[ endOffset - 1 ] ) { accumulator.end = currentLength + value.text.length; - // Range indicates that the selection is before the current node. + // Range indicates that the selection is before the current node. } else if ( parentNode === endContainer && node === endContainer.childNodes[ endOffset ] ) { accumulator.end = currentLength; - // Fallback if no child inside handled the selection. + // Fallback if no child inside handled the selection. } else if ( node === endContainer ) { accumulator.end = currentLength + endOffset; } @@ -359,12 +365,14 @@ function createFromElement( { continue; } - if ( isEditableTree && ( + if ( + isEditableTree && // Ignore any placeholders. - node.getAttribute( 'data-rich-text-placeholder' ) || - // Ignore any line breaks that are not inserted by us. - ( type === 'br' && ! node.getAttribute( 'data-rich-text-line-break' ) ) - ) ) { + ( node.getAttribute( 'data-rich-text-placeholder' ) || + // Ignore any line breaks that are not inserted by us. + ( type === 'br' && + ! node.getAttribute( 'data-rich-text-line-break' ) ) ) + ) { accumulateSelection( accumulator, node, range, createEmptyValue() ); continue; } @@ -375,15 +383,21 @@ function createFromElement( { continue; } - const lastFormats = accumulator.formats[ accumulator.formats.length - 1 ]; + const lastFormats = + accumulator.formats[ accumulator.formats.length - 1 ]; const lastFormat = lastFormats && lastFormats[ lastFormats.length - 1 ]; const newFormat = toFormat( { type, attributes: getAttributes( { element: node } ), } ); - const format = isFormatEqual( newFormat, lastFormat ) ? lastFormat : newFormat; - - if ( multilineWrapperTags && multilineWrapperTags.indexOf( type ) !== -1 ) { + const format = isFormatEqual( newFormat, lastFormat ) + ? lastFormat + : newFormat; + + if ( + multilineWrapperTags && + multilineWrapperTags.indexOf( type ) !== -1 + ) { const value = createFromMultilineElement( { element: node, range, @@ -428,7 +442,9 @@ function createFromElement( { return mergeFormats.newFormats; } - const newFormats = formats ? [ format, ...formats ] : [ format ]; + const newFormats = formats + ? [ format, ...formats ] + : [ format ]; mergeFormats.formats = formats; mergeFormats.newFormats = newFormats; @@ -507,7 +523,10 @@ function createFromMultilineElement( { if ( index !== 0 || currentWrapperTags.length > 0 ) { mergePair( accumulator, { formats: [ , ], - replacements: currentWrapperTags.length > 0 ? [ currentWrapperTags ] : [ , ], + replacements: + currentWrapperTags.length > 0 + ? [ currentWrapperTags ] + : [ , ], text: LINE_SEPARATOR, } ); } diff --git a/packages/rich-text/src/get-active-formats.js b/packages/rich-text/src/get-active-formats.js index a097fab4cf23f9..34bca40ecaddeb 100644 --- a/packages/rich-text/src/get-active-formats.js +++ b/packages/rich-text/src/get-active-formats.js @@ -7,12 +7,10 @@ * * @return {?Object} Active format objects. */ -export function getActiveFormats( { - formats, - start, - end, - activeFormats, -}, EMPTY_ACTIVE_FORMATS = [] ) { +export function getActiveFormats( + { formats, start, end, activeFormats }, + EMPTY_ACTIVE_FORMATS = [] +) { if ( start === undefined ) { return EMPTY_ACTIVE_FORMATS; } diff --git a/packages/rich-text/src/indent-list-items.js b/packages/rich-text/src/indent-list-items.js index bee5d96f1baca2..b444cfbf448bc5 100644 --- a/packages/rich-text/src/indent-list-items.js +++ b/packages/rich-text/src/indent-list-items.js @@ -69,7 +69,8 @@ export function indentListItems( value, rootFormat ) { ); } else { const targetFormats = replacements[ previousLineIndex ] || []; - const lastformat = targetFormats[ targetFormats.length - 1 ] || rootFormat; + const lastformat = + targetFormats[ targetFormats.length - 1 ] || rootFormat; newFormats[ index ] = targetFormats.concat( [ lastformat ], diff --git a/packages/rich-text/src/insert-line-separator.js b/packages/rich-text/src/insert-line-separator.js index 77434a25ac012a..8ba1c98ab948cd 100644 --- a/packages/rich-text/src/insert-line-separator.js +++ b/packages/rich-text/src/insert-line-separator.js @@ -20,11 +20,12 @@ import { LINE_SEPARATOR } from './special-characters'; export function insertLineSeparator( value, startIndex = value.start, - endIndex = value.end, + endIndex = value.end ) { const beforeText = getTextContent( value ).slice( 0, startIndex ); const previousLineSeparatorIndex = beforeText.lastIndexOf( LINE_SEPARATOR ); - const previousLineSeparatorFormats = value.replacements[ previousLineSeparatorIndex ]; + const previousLineSeparatorFormats = + value.replacements[ previousLineSeparatorIndex ]; let replacements = [ , ]; if ( previousLineSeparatorFormats ) { diff --git a/packages/rich-text/src/insert-object.js b/packages/rich-text/src/insert-object.js index 7495d6082cbcb1..7f9b61ad8488c4 100644 --- a/packages/rich-text/src/insert-object.js +++ b/packages/rich-text/src/insert-object.js @@ -18,12 +18,7 @@ const OBJECT_REPLACEMENT_CHARACTER = '\ufffc'; * * @return {Object} A new value with the object inserted. */ -export function insertObject( - value, - formatToInsert, - startIndex, - endIndex -) { +export function insertObject( value, formatToInsert, startIndex, endIndex ) { const valueToInsert = { formats: [ , ], replacements: [ formatToInsert ], diff --git a/packages/rich-text/src/insert.js b/packages/rich-text/src/insert.js index 4d2353eaba4cb9..d49d7be1daa5b6 100644 --- a/packages/rich-text/src/insert.js +++ b/packages/rich-text/src/insert.js @@ -33,9 +33,19 @@ export function insert( const index = startIndex + valueToInsert.text.length; return normaliseFormats( { - formats: formats.slice( 0, startIndex ).concat( valueToInsert.formats, formats.slice( endIndex ) ), - replacements: replacements.slice( 0, startIndex ).concat( valueToInsert.replacements, replacements.slice( endIndex ) ), - text: text.slice( 0, startIndex ) + valueToInsert.text + text.slice( endIndex ), + formats: formats + .slice( 0, startIndex ) + .concat( valueToInsert.formats, formats.slice( endIndex ) ), + replacements: replacements + .slice( 0, startIndex ) + .concat( + valueToInsert.replacements, + replacements.slice( endIndex ) + ), + text: + text.slice( 0, startIndex ) + + valueToInsert.text + + text.slice( endIndex ), start: index, end: index, } ); diff --git a/packages/rich-text/src/is-empty.js b/packages/rich-text/src/is-empty.js index aa7bad7407615f..14fbbdbe763c2d 100644 --- a/packages/rich-text/src/is-empty.js +++ b/packages/rich-text/src/is-empty.js @@ -40,5 +40,8 @@ export function isEmptyLine( { text, start, end } ) { return true; } - return text.slice( start - 1, end + 1 ) === `${ LINE_SEPARATOR }${ LINE_SEPARATOR }`; + return ( + text.slice( start - 1, end + 1 ) === + `${ LINE_SEPARATOR }${ LINE_SEPARATOR }` + ); } diff --git a/packages/rich-text/src/join.js b/packages/rich-text/src/join.js index cabfc827560164..e8b5dc5e74f725 100644 --- a/packages/rich-text/src/join.js +++ b/packages/rich-text/src/join.js @@ -20,9 +20,14 @@ export function join( values, separator = '' ) { separator = create( { text: separator } ); } - return normaliseFormats( values.reduce( ( accumlator, { formats, replacements, text } ) => ( { - formats: accumlator.formats.concat( separator.formats, formats ), - replacements: accumlator.replacements.concat( separator.replacements, replacements ), - text: accumlator.text + separator.text + text, - } ) ) ); + return normaliseFormats( + values.reduce( ( accumlator, { formats, replacements, text } ) => ( { + formats: accumlator.formats.concat( separator.formats, formats ), + replacements: accumlator.replacements.concat( + separator.replacements, + replacements + ), + text: accumlator.text + separator.text + text, + } ) ) + ); } diff --git a/packages/rich-text/src/outdent-list-items.js b/packages/rich-text/src/outdent-list-items.js index 99cae6c12944f3..41a1cc3c863459 100644 --- a/packages/rich-text/src/outdent-list-items.js +++ b/packages/rich-text/src/outdent-list-items.js @@ -23,7 +23,8 @@ export function outdentListItems( value ) { const { text, replacements, start, end } = value; const startingLineIndex = getLineIndex( value, start ); const newFormats = replacements.slice( 0 ); - const parentFormats = replacements[ getParentLineIndex( value, startingLineIndex ) ] || []; + const parentFormats = + replacements[ getParentLineIndex( value, startingLineIndex ) ] || []; const endingLineIndex = getLineIndex( value, end ); const lastChildIndex = getLastChildIndex( value, endingLineIndex ); diff --git a/packages/rich-text/src/register-format-type.js b/packages/rich-text/src/register-format-type.js index 0f8f98fd739ff8..7bf90967169d54 100644 --- a/packages/rich-text/src/register-format-type.js +++ b/packages/rich-text/src/register-format-type.js @@ -33,9 +33,7 @@ export function registerFormatType( name, settings ) { }; if ( typeof settings.name !== 'string' ) { - window.console.error( - 'Format names must be strings.' - ); + window.console.error( 'Format names must be strings.' ); return; } @@ -53,18 +51,14 @@ export function registerFormatType( name, settings ) { return; } - if ( - typeof settings.tagName !== 'string' || - settings.tagName === '' - ) { - window.console.error( - 'Format tag names must be a string.' - ); + if ( typeof settings.tagName !== 'string' || settings.tagName === '' ) { + window.console.error( 'Format tag names must be a string.' ); return; } if ( - ( typeof settings.className !== 'string' || settings.className === '' ) && + ( typeof settings.className !== 'string' || + settings.className === '' ) && settings.className !== null ) { window.console.error( @@ -81,8 +75,9 @@ export function registerFormatType( name, settings ) { } if ( settings.className === null ) { - const formatTypeForBareElement = select( 'core/rich-text' ) - .getFormatTypeForBareElement( settings.tagName ); + const formatTypeForBareElement = select( + 'core/rich-text' + ).getFormatTypeForBareElement( settings.tagName ); if ( formatTypeForBareElement ) { window.console.error( @@ -91,8 +86,9 @@ export function registerFormatType( name, settings ) { return; } } else { - const formatTypeForClassName = select( 'core/rich-text' ) - .getFormatTypeForClassName( settings.className ); + const formatTypeForClassName = select( + 'core/rich-text' + ).getFormatTypeForClassName( settings.className ); if ( formatTypeForClassName ) { window.console.error( @@ -111,15 +107,15 @@ export function registerFormatType( name, settings ) { if ( 'keywords' in settings && settings.keywords.length > 3 ) { window.console.error( - 'The format "' + settings.name + '" can have a maximum of 3 keywords.' + 'The format "' + + settings.name + + '" can have a maximum of 3 keywords.' ); return; } if ( typeof settings.title !== 'string' ) { - window.console.error( - 'Format titles must be strings.' - ); + window.console.error( 'Format titles must be strings.' ); return; } diff --git a/packages/rich-text/src/remove-format.js b/packages/rich-text/src/remove-format.js index 5ce2fcd7ce8333..294e741461eef4 100644 --- a/packages/rich-text/src/remove-format.js +++ b/packages/rich-text/src/remove-format.js @@ -65,7 +65,9 @@ export function removeFormat( } function filterFormats( formats, index, formatType ) { - const newFormats = formats[ index ].filter( ( { type } ) => type !== formatType ); + const newFormats = formats[ index ].filter( + ( { type } ) => type !== formatType + ); if ( newFormats.length ) { formats[ index ] = newFormats; diff --git a/packages/rich-text/src/remove-line-separator.js b/packages/rich-text/src/remove-line-separator.js index f9e6dfb157952e..a30b150b99027a 100644 --- a/packages/rich-text/src/remove-line-separator.js +++ b/packages/rich-text/src/remove-line-separator.js @@ -15,10 +15,7 @@ import { remove } from './remove'; * * @return {Object|undefined} A new value with the line separator removed. Or undefined if no line separator is found on the position. */ -export function removeLineSeparator( - value, - backward = true, -) { +export function removeLineSeparator( value, backward = true ) { const { replacements, text, start, end } = value; const collapsed = isCollapsed( value ); let index = start - 1; @@ -46,11 +43,7 @@ export function removeLineSeparator( replacements: newReplacements, }; } else { - newValue = remove( - value, - removeStart, - removeEnd - ); + newValue = remove( value, removeStart, removeEnd ); } return newValue; } diff --git a/packages/rich-text/src/replace.js b/packages/rich-text/src/replace.js index 0cb26cb7431bd0..597fcfec59f090 100644 --- a/packages/rich-text/src/replace.js +++ b/packages/rich-text/src/replace.js @@ -20,7 +20,11 @@ import { normaliseFormats } from './normalise-formats'; * * @return {Object} A new value with replacements applied. */ -export function replace( { formats, replacements, text, start, end }, pattern, replacement ) { +export function replace( + { formats, replacements, text, start, end }, + pattern, + replacement +) { text = text.replace( pattern, ( match, ...rest ) => { const offset = rest[ rest.length - 2 ]; let newText = replacement; @@ -44,8 +48,15 @@ export function replace( { formats, replacements, text, start, end }, pattern, r } } - formats = formats.slice( 0, offset ).concat( newFormats, formats.slice( offset + match.length ) ); - replacements = replacements.slice( 0, offset ).concat( newReplacements, replacements.slice( offset + match.length ) ); + formats = formats + .slice( 0, offset ) + .concat( newFormats, formats.slice( offset + match.length ) ); + replacements = replacements + .slice( 0, offset ) + .concat( + newReplacements, + replacements.slice( offset + match.length ) + ); if ( start ) { start = end = offset + newText.length; diff --git a/packages/rich-text/src/slice.js b/packages/rich-text/src/slice.js index b535a445d45f5c..d815a4615eae76 100644 --- a/packages/rich-text/src/slice.js +++ b/packages/rich-text/src/slice.js @@ -9,11 +9,7 @@ * * @return {Object} A new extracted value. */ -export function slice( - value, - startIndex = value.start, - endIndex = value.end -) { +export function slice( value, startIndex = value.start, endIndex = value.end ) { const { formats, replacements, text } = value; if ( startIndex === undefined || endIndex === undefined ) { diff --git a/packages/rich-text/src/split.js b/packages/rich-text/src/split.js index 05ef87fbe0c4f5..1f5ebbb5b84166 100644 --- a/packages/rich-text/src/split.js +++ b/packages/rich-text/src/split.js @@ -30,7 +30,10 @@ export function split( { formats, replacements, text, start, end }, string ) { const startIndex = nextStart; const value = { formats: formats.slice( startIndex, startIndex + substring.length ), - replacements: replacements.slice( startIndex, startIndex + substring.length ), + replacements: replacements.slice( + startIndex, + startIndex + substring.length + ), text: substring, }; diff --git a/packages/rich-text/src/store/selectors.js b/packages/rich-text/src/store/selectors.js index c5f970c6a956eb..c76ad76d64c66d 100644 --- a/packages/rich-text/src/store/selectors.js +++ b/packages/rich-text/src/store/selectors.js @@ -13,9 +13,7 @@ import { find } from 'lodash'; */ export const getFormatTypes = createSelector( ( state ) => Object.values( state.formatTypes ), - ( state ) => [ - state.formatTypes, - ] + ( state ) => [ state.formatTypes ] ); /** diff --git a/packages/rich-text/src/store/test/selectors.js b/packages/rich-text/src/store/test/selectors.js index 745f077c5f3f7c..fa451162249c0a 100644 --- a/packages/rich-text/src/store/test/selectors.js +++ b/packages/rich-text/src/store/test/selectors.js @@ -60,7 +60,10 @@ describe( 'selectors', () => { describe( 'getFormatTypeForBareElement', () => { it( 'should get a format type', () => { - const result = getFormatTypeForBareElement( defaultState, 'strong' ); + const result = getFormatTypeForBareElement( + defaultState, + 'strong' + ); expect( result ).toEqual( formatTypeBareTag ); } ); @@ -68,7 +71,10 @@ describe( 'selectors', () => { describe( 'getFormatTypeForClassName', () => { it( 'should get a format type', () => { - const result = getFormatTypeForClassName( defaultState, 'class-name' ); + const result = getFormatTypeForClassName( + defaultState, + 'class-name' + ); expect( result ).toEqual( formatTypeClassName ); } ); diff --git a/packages/rich-text/src/test/apply-format.js b/packages/rich-text/src/test/apply-format.js index 2c74bc1d9aa269..9a2ae2166ab240 100644 --- a/packages/rich-text/src/test/apply-format.js +++ b/packages/rich-text/src/test/apply-format.js @@ -41,7 +41,12 @@ describe( 'applyFormat', () => { const expected = { ...record, activeFormats: [ em ], - formats: [ [ strong, em ], [ strong, em ], [ strong, em ], [ strong, em ] ], + formats: [ + [ strong, em ], + [ strong, em ], + [ strong, em ], + [ strong, em ], + ], }; const result = applyFormat( deepFreeze( record ), em, 0, 4 ); @@ -58,7 +63,12 @@ describe( 'applyFormat', () => { const expected = { ...record, activeFormats: [ em ], - formats: [ [ strong, em ], [ strong, em ], [ strong, em ], [ strong, em ] ], + formats: [ + [ strong, em ], + [ strong, em ], + [ strong, em ], + [ strong, em ], + ], }; const result = applyFormat( deepFreeze( record ), em, 0, 4 ); @@ -142,7 +152,21 @@ describe( 'applyFormat', () => { }; const expected = { activeFormats: [ strong ], - formats: [ , , , [ strong ], [ strong, em ], [ strong, em ], [ em ], , , , , , , ], + formats: [ + , + , + , + [ strong ], + [ strong, em ], + [ strong, em ], + [ em ], + , + , + , + , + , + , + ], text: 'one two three', }; const result = applyFormat( deepFreeze( record ), strong, 3, 6 ); @@ -161,7 +185,21 @@ describe( 'applyFormat', () => { }; const expected = { activeFormats: [ strong ], - formats: [ , , , [ strong ], [ strong, em ], [ strong, em ], [ em ], , , , , , , ], + formats: [ + , + , + , + [ strong ], + [ strong, em ], + [ strong, em ], + [ em ], + , + , + , + , + , + , + ], text: 'one two three', start: 3, end: 6, diff --git a/packages/rich-text/src/test/change-list-type.js b/packages/rich-text/src/test/change-list-type.js index 6294368c6427d2..b5c949e37508f0 100644 --- a/packages/rich-text/src/test/change-list-type.js +++ b/packages/rich-text/src/test/change-list-type.js @@ -52,14 +52,46 @@ describe( 'changeListType', () => { const text = `a${ LINE_SEPARATOR }1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }i${ LINE_SEPARATOR }3${ LINE_SEPARATOR }4${ LINE_SEPARATOR }b`; const record = { - replacements: [ , [ ul ], , [ ul ], , [ ul, ul ], , [ ul ], , [ ul ], , , , [ ul ], , ], + replacements: [ + , + [ ul ], + , + [ ul ], + , + [ ul, ul ], + , + [ ul ], + , + [ ul ], + , + , + , + [ ul ], + , + ], text, start: 4, end: 9, }; const expected = { ...record, - replacements: [ , [ ol ], , [ ol ], , [ ol, ul ], , [ ol ], , [ ol ], , , , [ ul ], , ], + replacements: [ + , + [ ol ], + , + [ ol ], + , + [ ol, ul ], + , + [ ol ], + , + [ ol ], + , + , + , + [ ul ], + , + ], }; const result = changeListType( deepFreeze( record ), ol ); diff --git a/packages/rich-text/src/test/create.js b/packages/rich-text/src/test/create.js index b77cbb06120f5b..786e8dc732b28b 100644 --- a/packages/rich-text/src/test/create.js +++ b/packages/rich-text/src/test/create.js @@ -25,56 +25,62 @@ describe( 'create', () => { require( '../store' ); } ); - spec.forEach( ( { - description, - multilineTag, - multilineWrapperTags, - html, - createRange, - record, - } ) => { - if ( html === undefined ) { - return; - } - - it( description, () => { - const element = createElement( document, html ); - const range = createRange( element ); - const createdRecord = create( { - element, - range, - multilineTag, - multilineWrapperTags, - } ); - const formatsLength = getSparseArrayLength( record.formats ); - const createdFormatsLength = getSparseArrayLength( createdRecord.formats ); - - expect( createdRecord ).toEqual( record ); - expect( createdFormatsLength ).toEqual( formatsLength ); - } ); - } ); - - specWithRegistration.forEach( ( { - description, - formatName, - formatType, - html, - value: expectedValue, - } ) => { - it( description, () => { - if ( formatName ) { - registerFormatType( formatName, formatType ); - } - - const result = create( { html } ); - - if ( formatName ) { - unregisterFormatType( formatName ); + spec.forEach( + ( { + description, + multilineTag, + multilineWrapperTags, + html, + createRange, + record, + } ) => { + if ( html === undefined ) { + return; } - expect( result ).toEqual( expectedValue ); - } ); - } ); + it( description, () => { + const element = createElement( document, html ); + const range = createRange( element ); + const createdRecord = create( { + element, + range, + multilineTag, + multilineWrapperTags, + } ); + const formatsLength = getSparseArrayLength( record.formats ); + const createdFormatsLength = getSparseArrayLength( + createdRecord.formats + ); + + expect( createdRecord ).toEqual( record ); + expect( createdFormatsLength ).toEqual( formatsLength ); + } ); + } + ); + + specWithRegistration.forEach( + ( { + description, + formatName, + formatType, + html, + value: expectedValue, + } ) => { + it( description, () => { + if ( formatName ) { + registerFormatType( formatName, formatType ); + } + + const result = create( { html } ); + + if ( formatName ) { + unregisterFormatType( formatName ); + } + + expect( result ).toEqual( expectedValue ); + } ); + } + ); it( 'should reference formats', () => { const value = create( { html: '<em>te<strong>st</strong></em>' } ); diff --git a/packages/rich-text/src/test/get-format-types.js b/packages/rich-text/src/test/get-format-types.js index 1f7939198c8251..918234ceaeb3ca 100644 --- a/packages/rich-text/src/test/get-format-types.js +++ b/packages/rich-text/src/test/get-format-types.js @@ -42,7 +42,10 @@ describe( 'getFormatTypes', () => { formatTestSetting: 'settingTestValue', }; registerFormatType( 'core/test-format', testFormat ); - registerFormatType( 'core/test-format-with-settings', testFormatWithSettings ); + registerFormatType( + 'core/test-format-with-settings', + testFormatWithSettings + ); expect( getFormatTypes() ).toEqual( [ { name: 'core/test-format', diff --git a/packages/rich-text/src/test/get-last-child-index.js b/packages/rich-text/src/test/get-last-child-index.js index f59cec9eeeab1f..f87345a08b6bbf 100644 --- a/packages/rich-text/src/test/get-last-child-index.js +++ b/packages/rich-text/src/test/get-last-child-index.js @@ -14,37 +14,62 @@ describe( 'getLastChildIndex', () => { const ul = { type: 'ul' }; it( 'should return undefined if there is only one line', () => { - expect( getLastChildIndex( deepFreeze( { - replacements: [ , ], - text: '1', - } ), undefined ) ).toBe( undefined ); + expect( + getLastChildIndex( + deepFreeze( { + replacements: [ , ], + text: '1', + } ), + undefined + ) + ).toBe( undefined ); } ); it( 'should return the last line if no line is indented', () => { - expect( getLastChildIndex( deepFreeze( { - replacements: [ , ], - text: `1${ LINE_SEPARATOR }`, - } ), undefined ) ).toBe( 1 ); + expect( + getLastChildIndex( + deepFreeze( { + replacements: [ , ], + text: `1${ LINE_SEPARATOR }`, + } ), + undefined + ) + ).toBe( 1 ); } ); it( 'should return the last child index', () => { - expect( getLastChildIndex( deepFreeze( { - replacements: [ , [ ul ], , [ ul ], , ], - text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`, - } ), undefined ) ).toBe( 3 ); + expect( + getLastChildIndex( + deepFreeze( { + replacements: [ , [ ul ], , [ ul ], , ], + text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`, + } ), + undefined + ) + ).toBe( 3 ); } ); it( 'should return the last child index by sibling', () => { - expect( getLastChildIndex( deepFreeze( { - replacements: [ , [ ul ], , [ ul ], , ], - text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`, - } ), 1 ) ).toBe( 3 ); + expect( + getLastChildIndex( + deepFreeze( { + replacements: [ , [ ul ], , [ ul ], , ], + text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`, + } ), + 1 + ) + ).toBe( 3 ); } ); it( 'should return the last child index (with further lower indented items)', () => { - expect( getLastChildIndex( deepFreeze( { - replacements: [ , [ ul ], , , , ], - text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`, - } ), 1 ) ).toBe( 1 ); + expect( + getLastChildIndex( + deepFreeze( { + replacements: [ , [ ul ], , , , ], + text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`, + } ), + 1 + ) + ).toBe( 1 ); } ); } ); diff --git a/packages/rich-text/src/test/get-parent-line-index.js b/packages/rich-text/src/test/get-parent-line-index.js index 832ee4412dacc7..6873d09381d89d 100644 --- a/packages/rich-text/src/test/get-parent-line-index.js +++ b/packages/rich-text/src/test/get-parent-line-index.js @@ -14,30 +14,50 @@ describe( 'getParentLineIndex', () => { const ul = { type: 'ul' }; it( 'should return undefined if there is only one line', () => { - expect( getParentLineIndex( deepFreeze( { - replacements: [ , ], - text: '1', - } ), undefined ) ).toBe( undefined ); + expect( + getParentLineIndex( + deepFreeze( { + replacements: [ , ], + text: '1', + } ), + undefined + ) + ).toBe( undefined ); } ); it( 'should return undefined if the list is part of the first root list child', () => { - expect( getParentLineIndex( deepFreeze( { - replacements: [ , ], - text: `1${ LINE_SEPARATOR }2`, - } ), 2 ) ).toBe( undefined ); + expect( + getParentLineIndex( + deepFreeze( { + replacements: [ , ], + text: `1${ LINE_SEPARATOR }2`, + } ), + 2 + ) + ).toBe( undefined ); } ); it( 'should return the line index of the parent list (1)', () => { - expect( getParentLineIndex( deepFreeze( { - replacements: [ , , , [ ul ], , ], - text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`, - } ), 3 ) ).toBe( 1 ); + expect( + getParentLineIndex( + deepFreeze( { + replacements: [ , , , [ ul ], , ], + text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3`, + } ), + 3 + ) + ).toBe( 1 ); } ); it( 'should return the line index of the parent list (2)', () => { - expect( getParentLineIndex( deepFreeze( { - replacements: [ , [ ul ], , [ ul, ul ], , [ ul ], , ], - text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3${ LINE_SEPARATOR }4`, - } ), 5 ) ).toBe( undefined ); + expect( + getParentLineIndex( + deepFreeze( { + replacements: [ , [ ul ], , [ ul, ul ], , [ ul ], , ], + text: `1${ LINE_SEPARATOR }2${ LINE_SEPARATOR }3${ LINE_SEPARATOR }4`, + } ), + 5 + ) + ).toBe( undefined ); } ); } ); diff --git a/packages/rich-text/src/test/helpers/index.js b/packages/rich-text/src/test/helpers/index.js index 45206a6d3e9a09..bd81af8caf52da 100644 --- a/packages/rich-text/src/test/helpers/index.js +++ b/packages/rich-text/src/test/helpers/index.js @@ -181,7 +181,12 @@ export const spec = [ record: { start: 0, end: 4, - formats: [ [ em, strong ], [ em, strong ], [ em, strong ], [ em, strong ] ], + formats: [ + [ em, strong ], + [ em, strong ], + [ em, strong ], + [ em, strong ], + ], replacements: [ , , , , ], text: 'test', }, @@ -439,7 +444,8 @@ export const spec = [ description: 'should handle multiline list value', multilineTag: 'li', multilineWrapperTags: [ 'ul', 'ol' ], - html: '<li>one<ul><li>a</li><li>b<ol><li>1</li><li>2</li></ol></li></ul></li><li>three</li>', + html: + '<li>one<ul><li>a</li><li>b<ol><li>1</li><li>2</li></ol></li></ul></li><li>three</li>', createRange: ( element ) => ( { startOffset: 0, startContainer: element, @@ -452,7 +458,25 @@ export const spec = [ start: 0, end: 9, formats: [ , , , , , , , , , , , , , , , , , ], - replacements: [ , , , [ ul ], , [ ul ], , [ ul, ol ], , [ ul, ol ], , , , , , , , ], + replacements: [ + , + , + , + [ ul ], + , + [ ul ], + , + [ ul, ol ], + , + [ ul, ol ], + , + , + , + , + , + , + , + ], text: 'one\u2028a\u2028b\u20281\u20282\u2028three', }, }, @@ -623,7 +647,10 @@ export const spec = [ record: { start: 1, end: 1, - formats: [ [ a, em ], [ a, strong ] ], + formats: [ + [ a, em ], + [ a, strong ], + ], replacements: [ , , ], text: '12', }, @@ -642,7 +669,10 @@ export const spec = [ record: { start: 1, end: 1, - formats: [ [ em, a ], [ strong, a ] ], + formats: [ + [ em, a ], + [ strong, a ], + ], replacements: [ , , ], text: '12', }, @@ -661,11 +691,15 @@ export const specWithRegistration = [ }, html: '<a class="custom-format">a</a>', value: { - formats: [ [ { - type: 'my-plugin/link', - attributes: {}, - unregisteredAttributes: {}, - } ] ], + formats: [ + [ + { + type: 'my-plugin/link', + attributes: {}, + unregisteredAttributes: {}, + }, + ], + ], replacements: [ , ], text: 'a', }, @@ -681,13 +715,17 @@ export const specWithRegistration = [ }, html: '<a class="custom-format test">a</a>', value: { - formats: [ [ { - type: 'my-plugin/link', - attributes: {}, - unregisteredAttributes: { - class: 'test', - }, - } ] ], + formats: [ + [ + { + type: 'my-plugin/link', + attributes: {}, + unregisteredAttributes: { + class: 'test', + }, + }, + ], + ], replacements: [ , ], text: 'a', }, @@ -703,13 +741,17 @@ export const specWithRegistration = [ }, html: '<a class="custom-format">a</a>', value: { - formats: [ [ { - type: 'core/link', - attributes: {}, - unregisteredAttributes: { - class: 'custom-format', - }, - } ] ], + formats: [ + [ + { + type: 'core/link', + attributes: {}, + unregisteredAttributes: { + class: 'custom-format', + }, + }, + ], + ], replacements: [ , ], text: 'a', }, @@ -718,12 +760,16 @@ export const specWithRegistration = [ description: 'should create fallback format', html: '<a class="custom-format">a</a>', value: { - formats: [ [ { - type: 'a', - attributes: { - class: 'custom-format', - }, - } ] ], + formats: [ + [ + { + type: 'a', + attributes: { + class: 'custom-format', + }, + }, + ], + ], replacements: [ , ], text: 'a', }, @@ -747,7 +793,8 @@ export const specWithRegistration = [ noToHTMLString: true, }, { - description: 'should create format if editable tree only but changes need to be recorded', + description: + 'should create format if editable tree only but changes need to be recorded', formatName: 'my-plugin/link', formatType: { title: 'Custom Link', @@ -759,11 +806,15 @@ export const specWithRegistration = [ }, html: '<a class="custom-format">a</a>', value: { - formats: [ [ { - type: 'my-plugin/link', - attributes: {}, - unregisteredAttributes: {}, - } ] ], + formats: [ + [ + { + type: 'my-plugin/link', + attributes: {}, + unregisteredAttributes: {}, + }, + ], + ], replacements: [ , ], text: 'a', }, diff --git a/packages/rich-text/src/test/is-format-equal.js b/packages/rich-text/src/test/is-format-equal.js index baad9a435c6180..8925dcf6e7d1bf 100644 --- a/packages/rich-text/src/test/is-format-equal.js +++ b/packages/rich-text/src/test/is-format-equal.js @@ -52,13 +52,15 @@ describe( 'isFormatEqual', () => { format1: { type: 'bold', attributes: { a: '1' } }, format2: { type: 'bold', attributes: { a: '1', b: '1' } }, isEqual: false, - description: 'should return false if one has a different amount of attributes', + description: + 'should return false if one has a different amount of attributes', }, { format1: { type: 'bold', attributes: { b: '1', a: '1' } }, format2: { type: 'bold', attributes: { a: '1', b: '1' } }, isEqual: true, - description: 'should return true both have same attributes but different order', + description: + 'should return true both have same attributes but different order', }, ]; diff --git a/packages/rich-text/src/test/join.js b/packages/rich-text/src/test/join.js index 84801125cc2b0a..38eb10fc5283c0 100644 --- a/packages/rich-text/src/test/join.js +++ b/packages/rich-text/src/test/join.js @@ -38,7 +38,10 @@ describe( 'join', () => { replacements: [ , , , , , , , ], text: 'one two', }; - const result = join( [ deepFreeze( one ), deepFreeze( two ) ], separator ); + const result = join( + [ deepFreeze( one ), deepFreeze( two ) ], + separator + ); expect( result ).not.toBe( one ); expect( result ).not.toBe( two ); diff --git a/packages/rich-text/src/test/normalise-formats.js b/packages/rich-text/src/test/normalise-formats.js index c71b8a35ed634d..0124f94190baa7 100644 --- a/packages/rich-text/src/test/normalise-formats.js +++ b/packages/rich-text/src/test/normalise-formats.js @@ -16,7 +16,14 @@ describe( 'normaliseFormats', () => { it( 'should normalise formats', () => { const record = { - formats: [ , [ em ], [ { ...em }, { ...strong } ], [ em, strong ], , [ { ...em } ] ], + formats: [ + , + [ em ], + [ { ...em }, { ...strong } ], + [ em, strong ], + , + [ { ...em } ], + ], text: 'one two three', }; const result = normaliseFormats( deepFreeze( record ) ); diff --git a/packages/rich-text/src/test/register-format-type.js b/packages/rich-text/src/test/register-format-type.js index 5fc78630be2bab..9983c48f85f39d 100644 --- a/packages/rich-text/src/test/register-format-type.js +++ b/packages/rich-text/src/test/register-format-type.js @@ -17,9 +17,11 @@ describe( 'registerFormatType', () => { } ); afterEach( () => { - select( 'core/rich-text' ).getFormatTypes().forEach( ( { name } ) => { - unregisterFormatType( name ); - } ); + select( 'core/rich-text' ) + .getFormatTypes() + .forEach( ( { name } ) => { + unregisterFormatType( name ); + } ); } ); const validName = 'plugin/test'; @@ -44,31 +46,41 @@ describe( 'registerFormatType', () => { it( 'should reject format types without a namespace', () => { const format = registerFormatType( 'doing-it-wrong' ); - expect( console ).toHaveErroredWith( 'Format names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-format' ); + expect( console ).toHaveErroredWith( + 'Format names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-format' + ); expect( format ).toBeUndefined(); } ); it( 'should reject format types with too many namespaces', () => { const format = registerFormatType( 'doing/it/wrong' ); - expect( console ).toHaveErroredWith( 'Format names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-format' ); + expect( console ).toHaveErroredWith( + 'Format names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-format' + ); expect( format ).toBeUndefined(); } ); it( 'should reject format types with invalid characters', () => { const format = registerFormatType( 'still/_doing_it_wrong' ); - expect( console ).toHaveErroredWith( 'Format names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-format' ); + expect( console ).toHaveErroredWith( + 'Format names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-format' + ); expect( format ).toBeUndefined(); } ); it( 'should reject format types with uppercase characters', () => { const format = registerFormatType( 'Core/Bold' ); - expect( console ).toHaveErroredWith( 'Format names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-format' ); + expect( console ).toHaveErroredWith( + 'Format names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-format' + ); expect( format ).toBeUndefined(); } ); it( 'should reject format types not starting with a letter', () => { const format = registerFormatType( 'my-plugin/4-fancy-format' ); - expect( console ).toHaveErroredWith( 'Format names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-format' ); + expect( console ).toHaveErroredWith( + 'Format names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-format' + ); expect( format ).toBeUndefined(); } ); @@ -79,7 +91,10 @@ describe( 'registerFormatType', () => { } ); it( 'should accept valid format names', () => { - const format = registerFormatType( 'my-plugin/fancy-format-4', validSettings ); + const format = registerFormatType( + 'my-plugin/fancy-format-4', + validSettings + ); expect( console ).not.toHaveErrored(); expect( format ).toEqual( { name: 'my-plugin/fancy-format-4', @@ -90,7 +105,9 @@ describe( 'registerFormatType', () => { it( 'should error on already registered name', () => { registerFormatType( validName, validSettings ); const duplicateFormat = registerFormatType( validName, validSettings ); - expect( console ).toHaveErroredWith( 'Format "plugin/test" is already registered.' ); + expect( console ).toHaveErroredWith( + 'Format "plugin/test" is already registered.' + ); expect( duplicateFormat ).toBeUndefined(); } ); @@ -98,7 +115,9 @@ describe( 'registerFormatType', () => { const settings = { ...validSettings }; delete settings.tagName; const format = registerFormatType( validName, settings ); - expect( console ).toHaveErroredWith( 'Format tag names must be a string.' ); + expect( console ).toHaveErroredWith( + 'Format tag names must be a string.' + ); expect( format ).toBeUndefined(); } ); @@ -107,7 +126,9 @@ describe( 'registerFormatType', () => { ...validSettings, tagName: '', } ); - expect( console ).toHaveErroredWith( 'Format tag names must be a string.' ); + expect( console ).toHaveErroredWith( + 'Format tag names must be a string.' + ); expect( format ).toBeUndefined(); } ); @@ -115,7 +136,9 @@ describe( 'registerFormatType', () => { const settings = { ...validSettings }; delete settings.className; const format = registerFormatType( validName, settings ); - expect( console ).toHaveErroredWith( 'Format class names must be a string, or null to handle bare elements.' ); + expect( console ).toHaveErroredWith( + 'Format class names must be a string, or null to handle bare elements.' + ); expect( format ).toBeUndefined(); } ); @@ -124,7 +147,9 @@ describe( 'registerFormatType', () => { ...validSettings, className: '', } ); - expect( console ).toHaveErroredWith( 'Format class names must be a string, or null to handle bare elements.' ); + expect( console ).toHaveErroredWith( + 'Format class names must be a string, or null to handle bare elements.' + ); expect( format ).toBeUndefined(); } ); @@ -133,14 +158,21 @@ describe( 'registerFormatType', () => { ...validSettings, className: 'invalid class name', } ); - expect( console ).toHaveErroredWith( 'A class name must begin with a letter, followed by any number of hyphens, letters, or numbers.' ); + expect( console ).toHaveErroredWith( + 'A class name must begin with a letter, followed by any number of hyphens, letters, or numbers.' + ); expect( format ).toBeUndefined(); } ); it( 'should error on already registered tagName', () => { registerFormatType( validName, validSettings ); - const duplicateTagNameFormat = registerFormatType( 'plugin/second', validSettings ); - expect( console ).toHaveErroredWith( 'Format "plugin/test" is already registered to handle bare tag name "test".' ); + const duplicateTagNameFormat = registerFormatType( + 'plugin/second', + validSettings + ); + expect( console ).toHaveErroredWith( + 'Format "plugin/test" is already registered to handle bare tag name "test".' + ); expect( duplicateTagNameFormat ).toBeUndefined(); } ); @@ -153,7 +185,9 @@ describe( 'registerFormatType', () => { ...validSettings, className: 'test', } ); - expect( console ).toHaveErroredWith( 'Format "plugin/test" is already registered to handle class name "test".' ); + expect( console ).toHaveErroredWith( + 'Format "plugin/test" is already registered to handle class name "test".' + ); expect( duplicateClassNameFormat ).toBeUndefined(); } ); @@ -161,7 +195,9 @@ describe( 'registerFormatType', () => { const settings = { ...validSettings }; delete settings.title; const format = registerFormatType( validName, settings ); - expect( console ).toHaveErroredWith( `The format "${ validName }" must have a title.` ); + expect( console ).toHaveErroredWith( + `The format "${ validName }" must have a title.` + ); expect( format ).toBeUndefined(); } ); @@ -170,7 +206,9 @@ describe( 'registerFormatType', () => { ...validSettings, title: '', } ); - expect( console ).toHaveErroredWith( 'The format "plugin/test" must have a title.' ); + expect( console ).toHaveErroredWith( + 'The format "plugin/test" must have a title.' + ); expect( format ).toBeUndefined(); } ); diff --git a/packages/rich-text/src/test/remove-format.js b/packages/rich-text/src/test/remove-format.js index 343eb8c47e011f..bf3dd19179f703 100644 --- a/packages/rich-text/src/test/remove-format.js +++ b/packages/rich-text/src/test/remove-format.js @@ -16,7 +16,21 @@ describe( 'removeFormat', () => { it( 'should remove format', () => { const record = { - formats: [ , , , [ strong ], [ em, strong ], [ em, strong ], [ em ], , , , , , , ], + formats: [ + , + , + , + [ strong ], + [ em, strong ], + [ em, strong ], + [ em ], + , + , + , + , + , + , + ], text: 'one two three', }; const expected = { @@ -33,7 +47,21 @@ describe( 'removeFormat', () => { it( 'should remove format for collased selection', () => { const record = { - formats: [ , , , [ strong ], [ em, strong ], [ em, strong ], [ em ], , , , , , , ], + formats: [ + , + , + , + [ strong ], + [ em, strong ], + [ em, strong ], + [ em ], + , + , + , + , + , + , + ], text: 'one two three', }; const expected = { diff --git a/packages/rich-text/src/test/replace.js b/packages/rich-text/src/test/replace.js index 6cfd4dcc9e48b7..d2481cce755b9e 100644 --- a/packages/rich-text/src/test/replace.js +++ b/packages/rich-text/src/test/replace.js @@ -78,9 +78,13 @@ describe( 'replace', () => { end: 18, }; // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace - const result = replace( deepFreeze( record ), /([^\d]*)(\d*)([^\w]*)/, ( match, p1, p2, p3 ) => { - return [ p1, p2, p3 ].join( ' - ' ); - } ); + const result = replace( + deepFreeze( record ), + /([^\d]*)(\d*)([^\w]*)/, + ( match, p1, p2, p3 ) => { + return [ p1, p2, p3 ].join( ' - ' ); + } + ); expect( result ).toEqual( expected ); expect( result ).not.toBe( record ); diff --git a/packages/rich-text/src/test/split.js b/packages/rich-text/src/test/split.js index 8eef998e488d6e..d0a33354416259 100644 --- a/packages/rich-text/src/test/split.js +++ b/packages/rich-text/src/test/split.js @@ -40,8 +40,9 @@ describe( 'split', () => { expect( result ).toEqual( expected ); result.forEach( ( item, index ) => { expect( item ).not.toBe( record ); - expect( getSparseArrayLength( item.formats ) ) - .toBe( getSparseArrayLength( expected[ index ].formats ) ); + expect( getSparseArrayLength( item.formats ) ).toBe( + getSparseArrayLength( expected[ index ].formats ) + ); } ); } ); @@ -72,8 +73,9 @@ describe( 'split', () => { expect( result ).toEqual( expected ); result.forEach( ( item, index ) => { expect( item ).not.toBe( record ); - expect( getSparseArrayLength( item.formats ) ) - .toBe( getSparseArrayLength( expected[ index ].formats ) ); + expect( getSparseArrayLength( item.formats ) ).toBe( + getSparseArrayLength( expected[ index ].formats ) + ); } ); } ); @@ -104,8 +106,9 @@ describe( 'split', () => { expect( result ).toEqual( expected ); result.forEach( ( item, index ) => { expect( item ).not.toBe( record ); - expect( getSparseArrayLength( item.formats ) ) - .toBe( getSparseArrayLength( expected[ index ].formats ) ); + expect( getSparseArrayLength( item.formats ) ).toBe( + getSparseArrayLength( expected[ index ].formats ) + ); } ); } ); @@ -136,8 +139,9 @@ describe( 'split', () => { expect( result ).toEqual( expected ); result.forEach( ( item, index ) => { expect( item ).not.toBe( record ); - expect( getSparseArrayLength( item.formats ) ) - .toBe( getSparseArrayLength( expected[ index ].formats ) ); + expect( getSparseArrayLength( item.formats ) ).toBe( + getSparseArrayLength( expected[ index ].formats ) + ); } ); } ); @@ -145,7 +149,31 @@ describe( 'split', () => { const record = { start: 6, end: 16, - formats: [ , , , , [ em ], [ em ], [ em ], , , , , , , , , , , , , , , , , ], + formats: [ + , + , + , + , + [ em ], + [ em ], + [ em ], + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ], replacements: [ , , , , , , , , , , , , , , , , , , , , , , , ], text: 'one two three four five', }; @@ -187,8 +215,9 @@ describe( 'split', () => { expect( result ).toEqual( expected ); result.forEach( ( item, index ) => { expect( item ).not.toBe( record ); - expect( getSparseArrayLength( item.formats ) ) - .toBe( getSparseArrayLength( expected[ index ].formats ) ); + expect( getSparseArrayLength( item.formats ) ).toBe( + getSparseArrayLength( expected[ index ].formats ) + ); } ); } ); @@ -224,8 +253,9 @@ describe( 'split', () => { expect( result ).toEqual( expected ); result.forEach( ( item, index ) => { expect( item ).not.toBe( record ); - expect( getSparseArrayLength( item.formats ) ) - .toBe( getSparseArrayLength( expected[ index ].formats ) ); + expect( getSparseArrayLength( item.formats ) ).toBe( + getSparseArrayLength( expected[ index ].formats ) + ); } ); } ); } ); diff --git a/packages/rich-text/src/test/to-dom.js b/packages/rich-text/src/test/to-dom.js index 2036ba24b80f7a..d021c479b47f6a 100644 --- a/packages/rich-text/src/test/to-dom.js +++ b/packages/rich-text/src/test/to-dom.js @@ -21,22 +21,18 @@ describe( 'recordToDom', () => { require( '../store' ); } ); - spec.forEach( ( { - description, - multilineTag, - record, - startPath, - endPath, - } ) => { - it( description, () => { - const { body, selection } = toDom( { - value: record, - multilineTag, + spec.forEach( + ( { description, multilineTag, record, startPath, endPath } ) => { + it( description, () => { + const { body, selection } = toDom( { + value: record, + multilineTag, + } ); + expect( body ).toMatchSnapshot(); + expect( selection ).toEqual( { startPath, endPath } ); } ); - expect( body ).toMatchSnapshot(); - expect( selection ).toEqual( { startPath, endPath } ); - } ); - } ); + } + ); } ); describe( 'applyValue', () => { @@ -100,7 +96,9 @@ describe( 'applyValue', () => { cases.forEach( ( { current, future, description, movedCount } ) => { it( description, () => { const body = createElement( document, current ).cloneNode( true ); - const futureBody = createElement( document, future ).cloneNode( true ); + const futureBody = createElement( document, future ).cloneNode( + true + ); const childNodes = Array.from( futureBody.childNodes ); applyValue( futureBody, body ); const count = childNodes.reduce( ( acc, { parentNode } ) => { diff --git a/packages/rich-text/src/test/to-html-string.js b/packages/rich-text/src/test/to-html-string.js index bd8e846cb13ef6..a355f0773f768c 100644 --- a/packages/rich-text/src/test/to-html-string.js +++ b/packages/rich-text/src/test/to-html-string.js @@ -29,66 +29,81 @@ describe( 'toHTMLString', () => { require( '../store' ); } ); - specWithRegistration.forEach( ( { - description, - formatName, - formatType, - html, - value, - noToHTMLString, - } ) => { - if ( noToHTMLString ) { - return; - } - - it( description, () => { - if ( formatName ) { - registerFormatType( formatName, formatType ); + specWithRegistration.forEach( + ( { + description, + formatName, + formatType, + html, + value, + noToHTMLString, + } ) => { + if ( noToHTMLString ) { + return; } - const result = toHTMLString( { value } ); + it( description, () => { + if ( formatName ) { + registerFormatType( formatName, formatType ); + } - if ( formatName ) { - unregisterFormatType( formatName ); - } + const result = toHTMLString( { value } ); - expect( result ).toEqual( html ); - } ); - } ); + if ( formatName ) { + unregisterFormatType( formatName ); + } + + expect( result ).toEqual( html ); + } ); + } + ); it( 'should extract recreate HTML 1', () => { - const HTML = 'one <em>two 🍒</em> <a href="#"><img src=""><strong>three</strong></a><img src="">'; + const HTML = + 'one <em>two 🍒</em> <a href="#"><img src=""><strong>three</strong></a><img src="">'; const element = createNode( `<p>${ HTML }</p>` ); - expect( toHTMLString( { value: create( { element } ) } ) ).toEqual( HTML ); + expect( toHTMLString( { value: create( { element } ) } ) ).toEqual( + HTML + ); } ); it( 'should extract recreate HTML 2', () => { - const HTML = 'one <em>two 🍒</em> <a href="#">test <img src=""><strong>three</strong></a><img src="">'; + const HTML = + 'one <em>two 🍒</em> <a href="#">test <img src=""><strong>three</strong></a><img src="">'; const element = createNode( `<p>${ HTML }</p>` ); - expect( toHTMLString( { value: create( { element } ) } ) ).toEqual( HTML ); + expect( toHTMLString( { value: create( { element } ) } ) ).toEqual( + HTML + ); } ); it( 'should extract recreate HTML 3', () => { const HTML = '<img src="">'; const element = createNode( `<p>${ HTML }</p>` ); - expect( toHTMLString( { value: create( { element } ) } ) ).toEqual( HTML ); + expect( toHTMLString( { value: create( { element } ) } ) ).toEqual( + HTML + ); } ); it( 'should extract recreate HTML 4', () => { const HTML = '<em>two 🍒</em>'; const element = createNode( `<p>${ HTML }</p>` ); - expect( toHTMLString( { value: create( { element } ) } ) ).toEqual( HTML ); + expect( toHTMLString( { value: create( { element } ) } ) ).toEqual( + HTML + ); } ); it( 'should extract recreate HTML 5', () => { - const HTML = '<em>If you want to learn more about how to build additional blocks, or if you are interested in helping with the project, head over to the <a href="https://github.com/WordPress/gutenberg">GitHub repository</a>.</em>'; + const HTML = + '<em>If you want to learn more about how to build additional blocks, or if you are interested in helping with the project, head over to the <a href="https://github.com/WordPress/gutenberg">GitHub repository</a>.</em>'; const element = createNode( `<p>${ HTML }</p>` ); - expect( toHTMLString( { value: create( { element } ) } ) ).toEqual( HTML ); + expect( toHTMLString( { value: create( { element } ) } ) ).toEqual( + HTML + ); } ); it( 'should extract recreate HTML 6', () => { @@ -97,7 +112,11 @@ describe( 'toHTMLString', () => { const multilineTag = 'li'; const multilineWrapperTags = [ 'ul', 'ol' ]; const value = create( { element, multilineTag, multilineWrapperTags } ); - const result = toHTMLString( { value, multilineTag, multilineWrapperTags } ); + const result = toHTMLString( { + value, + multilineTag, + multilineWrapperTags, + } ); expect( result ).toEqual( HTML ); } ); @@ -106,7 +125,9 @@ describe( 'toHTMLString', () => { const HTML = '<a href="a">a</a><a href="b">a</a>'; const element = createNode( `<p>${ HTML }</p>` ); - expect( toHTMLString( { value: create( { element } ) } ) ).toEqual( HTML ); + expect( toHTMLString( { value: create( { element } ) } ) ).toEqual( + HTML + ); } ); it( 'should serialize neighbouring same formats', () => { @@ -114,6 +135,8 @@ describe( 'toHTMLString', () => { const element = createNode( `<p>${ HTML }</p>` ); const expectedHTML = '<a href="a">aa</a>'; - expect( toHTMLString( { value: create( { element } ) } ) ).toEqual( expectedHTML ); + expect( toHTMLString( { value: create( { element } ) } ) ).toEqual( + expectedHTML + ); } ); } ); diff --git a/packages/rich-text/src/test/toggle-format.js b/packages/rich-text/src/test/toggle-format.js index 9613e970d40a4e..6a71412c413fb2 100644 --- a/packages/rich-text/src/test/toggle-format.js +++ b/packages/rich-text/src/test/toggle-format.js @@ -16,7 +16,21 @@ describe( 'toggleFormat', () => { it( 'should remove format if it exists at start of selection', () => { const record = { - formats: [ , , , [ strong ], [ em, strong ], [ em ], [ em ], , , , , , , ], + formats: [ + , + , + , + [ strong ], + [ em, strong ], + [ em ], + [ em ], + , + , + , + , + , + , + ], text: 'one two three', start: 3, end: 6, @@ -35,7 +49,7 @@ describe( 'toggleFormat', () => { expect( getSparseArrayLength( result.formats ) ).toBe( 3 ); } ); - it( 'should apply format if it doesn\'t exist at start of selection', () => { + it( "should apply format if it doesn't exist at start of selection", () => { const record = { formats: [ , , , , [ em, strong ], [ em ], [ em ], , , , , , , ], text: 'one two three', @@ -43,7 +57,21 @@ describe( 'toggleFormat', () => { end: 6, }; const expected = { - formats: [ , , , [ strong ], [ strong, em ], [ strong, em ], [ em ], , , , , , , ], + formats: [ + , + , + , + [ strong ], + [ strong, em ], + [ strong, em ], + [ em ], + , + , + , + , + , + , + ], activeFormats: [ strong ], text: 'one two three', start: 3, diff --git a/packages/rich-text/src/test/unregister-format-type.js b/packages/rich-text/src/test/unregister-format-type.js index 82774f5395d9d7..0da7b8c481ee32 100644 --- a/packages/rich-text/src/test/unregister-format-type.js +++ b/packages/rich-text/src/test/unregister-format-type.js @@ -32,7 +32,9 @@ describe( 'unregisterFormatType', () => { it( 'should fail if the format is not registered', () => { const oldFormat = unregisterFormatType( 'core/test-format' ); - expect( console ).toHaveErroredWith( 'Format core/test-format is not registered.' ); + expect( console ).toHaveErroredWith( + 'Format core/test-format is not registered.' + ); expect( oldFormat ).toBeUndefined(); } ); diff --git a/packages/rich-text/src/to-dom.js b/packages/rich-text/src/to-dom.js index 318d99f472719e..2e5c54e769b1ea 100644 --- a/packages/rich-text/src/to-dom.js +++ b/packages/rich-text/src/to-dom.js @@ -142,10 +142,14 @@ export function toDom( { remove, appendText, onStartIndex( body, pointer ) { - startPath = createPathToNode( pointer, body, [ pointer.nodeValue.length ] ); + startPath = createPathToNode( pointer, body, [ + pointer.nodeValue.length, + ] ); }, onEndIndex( body, pointer ) { - endPath = createPathToNode( pointer, body, [ pointer.nodeValue.length ] ); + endPath = createPathToNode( pointer, body, [ + pointer.nodeValue.length, + ] ); }, isEditableTree, placeholder, @@ -203,7 +207,8 @@ export function applyValue( future, current ) { } else if ( ! currentChild.isEqualNode( futureChild ) ) { if ( currentChild.nodeName !== futureChild.nodeName || - ( currentChild.nodeType === TEXT_NODE && currentChild.data !== futureChild.data ) + ( currentChild.nodeType === TEXT_NODE && + currentChild.data !== futureChild.data ) ) { current.replaceChild( futureChild, currentChild ); } else { @@ -269,8 +274,14 @@ function isRangeEqual( a, b ) { } export function applySelection( { startPath, endPath }, current ) { - const { node: startContainer, offset: startOffset } = getNodeByPath( current, startPath ); - const { node: endContainer, offset: endOffset } = getNodeByPath( current, endPath ); + const { node: startContainer, offset: startOffset } = getNodeByPath( + current, + startPath + ); + const { node: endContainer, offset: endOffset } = getNodeByPath( + current, + endPath + ); const selection = window.getSelection(); const { ownerDocument } = current; const range = ownerDocument.createRange(); diff --git a/packages/rich-text/src/to-html-string.js b/packages/rich-text/src/to-html-string.js index bf18f3346cc604..37407dca5027a3 100644 --- a/packages/rich-text/src/to-html-string.js +++ b/packages/rich-text/src/to-html-string.js @@ -97,18 +97,26 @@ function createElementHTML( { type, attributes, object, children } ) { continue; } - attributeString += ` ${ key }="${ escapeAttribute( attributes[ key ] ) }"`; + attributeString += ` ${ key }="${ escapeAttribute( + attributes[ key ] + ) }"`; } if ( object ) { return `<${ type }${ attributeString }>`; } - return `<${ type }${ attributeString }>${ createChildrenHTML( children ) }</${ type }>`; + return `<${ type }${ attributeString }>${ createChildrenHTML( + children + ) }</${ type }>`; } function createChildrenHTML( children = [] ) { - return children.map( ( child ) => { - return child.text === undefined ? createElementHTML( child ) : escapeEditableHTML( child.text ); - } ).join( '' ); + return children + .map( ( child ) => { + return child.text === undefined + ? createElementHTML( child ) + : escapeEditableHTML( child.text ); + } ) + .join( '' ); } diff --git a/packages/rich-text/src/to-tree.js b/packages/rich-text/src/to-tree.js index dc352bd23f02b3..2cc90818e36945 100644 --- a/packages/rich-text/src/to-tree.js +++ b/packages/rich-text/src/to-tree.js @@ -26,7 +26,13 @@ import { * @return {Object} Information to be used for * element creation. */ -function fromFormat( { type, attributes, unregisteredAttributes, object, boundaryClass } ) { +function fromFormat( { + type, + attributes, + unregisteredAttributes, + object, + boundaryClass, +} ) { const formatType = getFormatType( type ); let elementAttributes = {}; @@ -46,7 +52,9 @@ function fromFormat( { type, attributes, unregisteredAttributes, object, boundar elementAttributes = { ...unregisteredAttributes, ...elementAttributes }; for ( const name in attributes ) { - const key = formatType.attributes ? formatType.attributes[ name ] : false; + const key = formatType.attributes + ? formatType.attributes[ name ] + : false; if ( key ) { elementAttributes[ key ] = attributes[ name ]; @@ -125,26 +133,34 @@ export function toTree( { for ( let i = 0; i < formatsLength; i++ ) { const character = text.charAt( i ); - const shouldInsertPadding = isEditableTree && ( + const shouldInsertPadding = + isEditableTree && // Pad the line if the line is empty. - ! lastCharacter || - lastCharacter === LINE_SEPARATOR || - // Pad the line if the previous character is a line break, otherwise - // the line break won't be visible. - lastCharacter === '\n' - ); + ( ! lastCharacter || + lastCharacter === LINE_SEPARATOR || + // Pad the line if the previous character is a line break, otherwise + // the line break won't be visible. + lastCharacter === '\n' ); let characterFormats = formats[ i ]; // Set multiline tags in queue for building the tree. if ( multilineTag ) { if ( character === LINE_SEPARATOR ) { - characterFormats = lastSeparatorFormats = ( replacements[ i ] || [] ).reduce( ( accumulator, format ) => { - accumulator.push( format, multilineFormat ); - return accumulator; - }, [ multilineFormat ] ); + characterFormats = lastSeparatorFormats = ( + replacements[ i ] || [] + ).reduce( + ( accumulator, format ) => { + accumulator.push( format, multilineFormat ); + return accumulator; + }, + [ multilineFormat ] + ); } else { - characterFormats = [ ...lastSeparatorFormats, ...( characterFormats || [] ) ]; + characterFormats = [ + ...lastSeparatorFormats, + ...( characterFormats || [] ), + ]; } } @@ -183,7 +199,11 @@ export function toTree( { pointer && lastCharacterFormats && // Reuse the last element if all formats remain the same. - isEqualUntil( characterFormats, lastCharacterFormats, formatIndex ) && + isEqualUntil( + characterFormats, + lastCharacterFormats, + formatIndex + ) && // Do not reuse the last element if the character is a // line separator. ( character !== LINE_SEPARATOR || @@ -195,19 +215,21 @@ export function toTree( { const { type, attributes, unregisteredAttributes } = format; - const boundaryClass = ( + const boundaryClass = isEditableTree && character !== LINE_SEPARATOR && - format === deepestActiveFormat - ); + format === deepestActiveFormat; const parent = getParent( pointer ); - const newNode = append( parent, fromFormat( { - type, - attributes, - unregisteredAttributes, - boundaryClass, - } ) ); + const newNode = append( + parent, + fromFormat( { + type, + attributes, + unregisteredAttributes, + boundaryClass, + } ) + ); if ( isText( pointer ) && getText( pointer ).length === 0 ) { remove( pointer ); @@ -236,18 +258,23 @@ export function toTree( { } if ( character === OBJECT_REPLACEMENT_CHARACTER ) { - pointer = append( getParent( pointer ), fromFormat( { - ...replacements[ i ], - object: true, - } ) ); + pointer = append( + getParent( pointer ), + fromFormat( { + ...replacements[ i ], + object: true, + } ) + ); // Ensure pointer is text node. pointer = append( getParent( pointer ), '' ); } else if ( ! preserveWhiteSpace && character === '\n' ) { pointer = append( getParent( pointer ), { type: 'br', - attributes: isEditableTree ? { - 'data-rich-text-line-break': 'true', - } : undefined, + attributes: isEditableTree + ? { + 'data-rich-text-line-break': 'true', + } + : undefined, object: true, } ); // Ensure pointer is text node. diff --git a/packages/rich-text/src/unregister-format-type.js b/packages/rich-text/src/unregister-format-type.js index 5e60349e327cf5..4590937dc6ca9e 100644 --- a/packages/rich-text/src/unregister-format-type.js +++ b/packages/rich-text/src/unregister-format-type.js @@ -17,9 +17,7 @@ export function unregisterFormatType( name ) { const oldFormat = select( 'core/rich-text' ).getFormatType( name ); if ( ! oldFormat ) { - window.console.error( - `Format ${ name } is not registered.` - ); + window.console.error( `Format ${ name } is not registered.` ); return; } diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 6cf2ebd32f4d00..f22a8d37f8fe61 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -14,6 +14,9 @@ - Add `lint-md-docs` script to lint the markup of markdown files, uses the `markdownlint` module ([#19855](https://github.com/WordPress/gutenberg/pull/19855)). - Add `packages-update` script to update WordPress packages to the latest version automatically ([#19448](https://github.com/WordPress/gutenberg/pull/19448)). +### Bug Fixes +- Fixes and updates valid _rc_ configuration filenames for Babel, ESLint, markdownlint, npmpackagejsonlint, stylelint, and Prettier ([#19994](https://github.com/WordPress/gutenberg/pull/19994)). + ## 6.1.1 (2020-01-01) ### Bug Fixes diff --git a/packages/scripts/config/.eslintrc-md.js b/packages/scripts/config/.eslintrc-md.js index 66f9f0f4c66e9a..dae9d2d3486a44 100644 --- a/packages/scripts/config/.eslintrc-md.js +++ b/packages/scripts/config/.eslintrc-md.js @@ -6,14 +6,10 @@ // that are assumed to be defined. module.exports = { root: true, - plugins: [ - 'markdown', - ], - extends: [ - 'plugin:@wordpress/eslint-plugin/recommended', - ], - "rules": { - "no-undef": "off", - "no-unused-vars": "off" + plugins: [ 'markdown' ], + extends: [ 'plugin:@wordpress/eslint-plugin/recommended' ], + rules: { + 'no-undef': 'off', + 'no-unused-vars': 'off', }, }; diff --git a/packages/scripts/config/.prettierrc.js b/packages/scripts/config/.prettierrc.js index 61b185ac2c203f..17308856dce9d9 100644 --- a/packages/scripts/config/.prettierrc.js +++ b/packages/scripts/config/.prettierrc.js @@ -1,7 +1,7 @@ module.exports = { useTabs: true, - tabWidth: 2, - printWidth: 100, + tabWidth: 4, + printWidth: 80, singleQuote: true, trailingComma: 'es5', bracketSpacing: true, diff --git a/packages/scripts/config/jest-e2e.config.js b/packages/scripts/config/jest-e2e.config.js index e5243e1af32c2a..317cf3bc9bae02 100644 --- a/packages/scripts/config/jest-e2e.config.js +++ b/packages/scripts/config/jest-e2e.config.js @@ -13,11 +13,11 @@ const jestE2EConfig = { testMatch: [ '**/specs/**/*.[jt]s', '**/?(*.)spec.[jt]s' ], testPathIgnorePatterns: [ '/node_modules/', '/wordpress/' ], reporters: - 'TRAVIS' in process.env && 'CI' in process.env ? - [ - '@wordpress/jest-preset-default/scripts/travis-fold-passes-reporter.js', - ] : - undefined, + 'TRAVIS' in process.env && 'CI' in process.env + ? [ + '@wordpress/jest-preset-default/scripts/travis-fold-passes-reporter.js', + ] + : undefined, }; if ( ! hasBabelConfig() ) { diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index ee2d4620f59d87..4c227696a7da35 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -45,14 +45,19 @@ const config = { // Babel uses a directory within local node_modules // by default. Use the environment variable option // to enable more persistent caching. - cacheDirectory: process.env.BABEL_CACHE_DIRECTORY || true, + cacheDirectory: + process.env.BABEL_CACHE_DIRECTORY || true, // Provide a fallback configuration if there's not // one explicitly available in the project. ...( ! hasBabelConfig() && { babelrc: false, configFile: false, - presets: [ require.resolve( '@wordpress/babel-preset-default' ) ], + presets: [ + require.resolve( + '@wordpress/babel-preset-default' + ), + ], } ), }, }, @@ -70,7 +75,10 @@ const config = { process.env.WP_BUNDLE_ANALYZER && new BundleAnalyzerPlugin(), // WP_LIVE_RELOAD_PORT global variable changes port on which live reload works // when running watch mode. - ! isProduction && new LiveReloadPlugin( { port: process.env.WP_LIVE_RELOAD_PORT || 35729 } ), + ! isProduction && + new LiveReloadPlugin( { + port: process.env.WP_LIVE_RELOAD_PORT || 35729, + } ), new DependencyExtractionWebpackPlugin( { injectPolyfill: true } ), ].filter( Boolean ), stats: { diff --git a/packages/scripts/scripts/build.js b/packages/scripts/scripts/build.js index 2c3b9318afd4d5..89686e30e4ee1a 100644 --- a/packages/scripts/scripts/build.js +++ b/packages/scripts/scripts/build.js @@ -10,9 +10,7 @@ const { sync: resolveBin } = require( 'resolve-bin' ); const { getWebpackArgs } = require( '../utils' ); process.env.NODE_ENV = process.env.NODE_ENV || 'production'; -const { status } = spawn( - resolveBin( 'webpack' ), - getWebpackArgs(), - { stdio: 'inherit' } -); +const { status } = spawn( resolveBin( 'webpack' ), getWebpackArgs(), { + stdio: 'inherit', +} ); process.exit( status ); diff --git a/packages/scripts/scripts/check-engines.js b/packages/scripts/scripts/check-engines.js index bead8796a6936c..38dac752c0ee21 100644 --- a/packages/scripts/scripts/check-engines.js +++ b/packages/scripts/scripts/check-engines.js @@ -7,28 +7,23 @@ const { sync: resolveBin } = require( 'resolve-bin' ); /** * Internal dependencies */ -const { - getArgsFromCLI, - hasArgInCLI, -} = require( '../utils' ); +const { getArgsFromCLI, hasArgInCLI } = require( '../utils' ); const args = getArgsFromCLI(); -const hasConfig = hasArgInCLI( '--package' ) || +const hasConfig = + hasArgInCLI( '--package' ) || hasArgInCLI( '--node' ) || hasArgInCLI( '--npm' ) || hasArgInCLI( '--yarn' ); -const config = ! hasConfig ? - [ - '--node', '>=10.0.0', - '--npm', '>=6.9.0', - ] : - []; +const config = ! hasConfig ? [ '--node', '>=10.0.0', '--npm', '>=6.9.0' ] : []; const result = spawn( resolveBin( 'check-node-version' ), [ ...config, ...args ], - { stdio: 'inherit' } + { + stdio: 'inherit', + } ); process.exit( result.status ); diff --git a/packages/scripts/scripts/check-licenses.js b/packages/scripts/scripts/check-licenses.js index f0ed3b98ff3e4d..acf098cffe63d5 100644 --- a/packages/scripts/scripts/check-licenses.js +++ b/packages/scripts/scripts/check-licenses.js @@ -25,13 +25,13 @@ const ERROR = chalk.reset.inverse.bold.red( ' ERROR ' ); const prod = hasArgInCLI( '--prod' ) || hasArgInCLI( '--production' ); const dev = hasArgInCLI( '--dev' ) || hasArgInCLI( '--development' ); const gpl2 = hasArgInCLI( '--gpl2' ); -const ignored = hasArgInCLI( '--ignore' ) ? - getArgFromCLI( '--ignore' ) - // "--ignore=a, b" -> "[ 'a', ' b' ]" - .split( ',' ) - // "[ 'a', ' b' ]" -> "[ 'a', 'b' ]" - .map( ( moduleName ) => moduleName.trim() ) : - []; +const ignored = hasArgInCLI( '--ignore' ) + ? getArgFromCLI( '--ignore' ) + // "--ignore=a, b" -> "[ 'a', ' b' ]" + .split( ',' ) + // "[ 'a', ' b' ]" -> "[ 'a', 'b' ]" + .map( ( moduleName ) => moduleName.trim() ) + : []; /* * A list of license strings that we've found to be GPL2 compatible. @@ -108,15 +108,11 @@ const licenseFiles = [ * and their matching license. */ const licenseFileStrings = { - 'Apache-2.0': [ - 'Licensed under the Apache License, Version 2.0', - ], + 'Apache-2.0': [ 'Licensed under the Apache License, Version 2.0' ], BSD: [ 'Redistributions in binary form must reproduce the above copyright notice,', ], - 'BSD-3-Clause-W3C': [ - 'W3C 3-clause BSD License', - ], + 'BSD-3-Clause-W3C': [ 'W3C 3-clause BSD License' ], MIT: [ 'Permission is hereby granted, free of charge,', '## License\n\nMIT', @@ -166,7 +162,12 @@ const checkLicense = ( allowedLicense, licenseType ) => { .map( ( e ) => e.trim() ); // We can then check our array of licenses against the allowedLicense. - return undefined !== subLicenseTypes.find( ( subLicenseType ) => checkLicense( allowedLicense, subLicenseType ) ); + return ( + undefined !== + subLicenseTypes.find( ( subLicenseType ) => + checkLicense( allowedLicense, subLicenseType ) + ) + ); }; /** @@ -177,14 +178,13 @@ const checkLicense = ( allowedLicense, licenseType ) => { * * @return {boolean} Whether module path is not to be ignored. */ -const isNotIgnoredModule = ( moduleName ) => ( - ! ignored.some( ( ignoredItem ) => ( +const isNotIgnoredModule = ( moduleName ) => + ! ignored.some( ( ignoredItem ) => // `moduleName` is a file path to the module directory. Assume CLI arg // is passed as basename of package (directory(s) after node_modules). // Prefix with sep to avoid false-positives on prefixing variations. moduleName.endsWith( sep + ignoredItem ) - ) ) -); + ); // Use `npm ls` to grab a list of all the packages. const child = spawn.sync( 'npm', [ @@ -218,17 +218,19 @@ modules.forEach( ( path ) => { * - { licenses: [ { type: 'MIT' }, { type: 'Zlib' } ] } */ const packageInfo = require( filename ); - const license = packageInfo.license || - ( - packageInfo.licenses && - packageInfo.licenses - .map( ( l ) => l.type || l ) - .join( ' OR ' ) - ); + const license = + packageInfo.license || + ( packageInfo.licenses && + packageInfo.licenses.map( ( l ) => l.type || l ).join( ' OR ' ) ); let licenseType = typeof license === 'object' ? license.type : license; // Check if the license we've detected is telling us to look in the license file, instead. - if ( licenseType && licenseFiles.find( ( licenseFile ) => licenseType.includes( licenseFile ) ) ) { + if ( + licenseType && + licenseFiles.find( ( licenseFile ) => + licenseType.includes( licenseFile ) + ) + ) { licenseType = undefined; } @@ -248,16 +250,23 @@ modules.forEach( ( path ) => { const licenseText = readFileSync( licensePath ).toString(); // Check if the file contains any of the strings in licenseFileStrings - return Object.keys( licenseFileStrings ).reduce( ( stringDetectedType, licenseStringType ) => { - const licenseFileString = licenseFileStrings[ licenseStringType ]; + return Object.keys( licenseFileStrings ).reduce( + ( stringDetectedType, licenseStringType ) => { + const licenseFileString = + licenseFileStrings[ licenseStringType ]; - return licenseFileString.reduce( ( currentDetectedType, fileString ) => { - if ( licenseText.includes( fileString ) ) { - return licenseStringType; - } - return currentDetectedType; - }, stringDetectedType ); - }, detectedType ); + return licenseFileString.reduce( + ( currentDetectedType, fileString ) => { + if ( licenseText.includes( fileString ) ) { + return licenseStringType; + } + return currentDetectedType; + }, + stringDetectedType + ); + }, + detectedType + ); } return detectedType; }, false ); @@ -268,10 +277,14 @@ modules.forEach( ( path ) => { } // Now that we finally have a license to check, see if any of the allowed licenses match. - const allowed = licenses.find( ( allowedLicense ) => checkLicense( allowedLicense, licenseType ) ); + const allowed = licenses.find( ( allowedLicense ) => + checkLicense( allowedLicense, licenseType ) + ); if ( ! allowed ) { process.exitCode = 1; - process.stdout.write( `${ ERROR } Module ${ packageInfo.name } has an incompatible license '${ licenseType }'.\n` ); + process.stdout.write( + `${ ERROR } Module ${ packageInfo.name } has an incompatible license '${ licenseType }'.\n` + ); } } ); diff --git a/packages/scripts/scripts/env.js b/packages/scripts/scripts/env.js index 000a4fca2e270f..95f3597239817c 100644 --- a/packages/scripts/scripts/env.js +++ b/packages/scripts/scripts/env.js @@ -13,35 +13,52 @@ const { existsSync } = require( 'fs' ); /** * Internal dependencies */ -const { - getArgsFromCLI, - spawnScript, -} = require( '../utils' ); +const { getArgsFromCLI, spawnScript } = require( '../utils' ); const args = getArgsFromCLI(); if ( ! args.length ) { const scripts = { - install: "If you don't have a check out of the WordPress repository that you want to work with, this will automatically download, configure, and connect to WordPress.", - connect: 'If you have a WordPress respository already, define the ' + chalk.magenta( 'WP_DEVELOP_DIR' ) + ' environment variable with the path to your repository, then run this command to add this plugin to it.', - start: "This will start a container that you've already installed and connected to.", + install: + "If you don't have a check out of the WordPress repository that you want to work with, this will automatically download, configure, and connect to WordPress.", + connect: + 'If you have a WordPress respository already, define the ' + + chalk.magenta( 'WP_DEVELOP_DIR' ) + + ' environment variable with the path to your repository, then run this command to add this plugin to it.', + start: + "This will start a container that you've already installed and connected to.", stop: "And this will stop it when you're done!", - update: 'If you used ' + chalk.blue( 'npm run env install' ) + ' to setup WordPress, run this command to update it.', + update: + 'If you used ' + + chalk.blue( 'npm run env install' ) + + ' to setup WordPress, run this command to update it.', reinstall: 'Resets the database and re-configures WordPress again.', cli: 'Run WP-CLI commands against the WordPress install.', 'lint-php': 'Run PHPCS linting on the plugin.', 'test-php': "Run the plugin's PHPUnit tests.", - 'docker-run': 'For more advanced debugging, you may sometimes need to run commands in the Docker containers. This is the equivalent of running ' + chalk.blue( 'docker-compose run' ) + '.', + 'docker-run': + 'For more advanced debugging, you may sometimes need to run commands in the Docker containers. This is the equivalent of running ' + + chalk.blue( 'docker-compose run' ) + + '.', }; - stdout.write( chalk.white( 'Welcome to the WordPress Local Environment! There are several commands available to help you get up and running. Each of these commands should be run after ' ) ); + stdout.write( + chalk.white( + 'Welcome to the WordPress Local Environment! There are several commands available to help you get up and running. Each of these commands should be run after ' + ) + ); stdout.write( chalk.blue( 'npm run env' ) ); stdout.write( chalk.white( '. For example, ' ) ); stdout.write( chalk.blue( 'npm run env install' ) ); stdout.write( chalk.white( '.\n\n' ) ); Object.keys( scripts ).forEach( ( script ) => { - stdout.write( chalk.green( script ) + '\n ' + chalk.white( scripts[ script ] ) + '\n\n' ); + stdout.write( + chalk.green( script ) + + '\n ' + + chalk.white( scripts[ script ] ) + + '\n\n' + ); } ); exit( 0 ); } @@ -49,12 +66,22 @@ if ( ! args.length ) { const command = args.shift(); if ( ! env.WP_DEVELOP_DIR && command !== 'install' ) { - if ( existsSync( normalize( cwd() + '/wordpress/wp-config-sample.php' ) ) ) { + if ( + existsSync( normalize( cwd() + '/wordpress/wp-config-sample.php' ) ) + ) { env.WP_DEVELOP_DIR = normalize( cwd() + '/wordpress' ); env.MANAGED_WP = true; } else { - stdout.write( chalk.white( 'Please ensure the WP_DEVELOP_DIR environment variable is set to your WordPress Development directory before running this script.\n\n' ) ); - stdout.write( chalk.white( "If you don't have a WordPress Development directory to use, run `npm run env install` to automatically configure one!\n" ) ); + stdout.write( + chalk.white( + 'Please ensure the WP_DEVELOP_DIR environment variable is set to your WordPress Development directory before running this script.\n\n' + ) + ); + stdout.write( + chalk.white( + "If you don't have a WordPress Development directory to use, run `npm run env install` to automatically configure one!\n" + ) + ); exit( 1 ); } } diff --git a/packages/scripts/scripts/env/cli.js b/packages/scripts/scripts/env/cli.js index ec1141a710965e..6fa336da96ac62 100644 --- a/packages/scripts/scripts/env/cli.js +++ b/packages/scripts/scripts/env/cli.js @@ -14,4 +14,10 @@ const args = getArgsFromCLI(); const localDir = env.LOCAL_DIR || 'src'; // Run WP-CLI with the working directory set correctly. -execSync( `npm run env:cli -- --path=/var/www/${ localDir } ` + args.join( ' ' ), { cwd: env.WP_DEVELOP_DIR, stdio: 'inherit' } ); +execSync( + `npm run env:cli -- --path=/var/www/${ localDir } ` + args.join( ' ' ), + { + cwd: env.WP_DEVELOP_DIR, + stdio: 'inherit', + } +); diff --git a/packages/scripts/scripts/env/connect.js b/packages/scripts/scripts/env/connect.js index a18bdf5c323740..9cee6d799eb020 100644 --- a/packages/scripts/scripts/env/connect.js +++ b/packages/scripts/scripts/env/connect.js @@ -14,26 +14,31 @@ const { execSync } = require( 'child_process' ); /** * Internal dependencies */ -const { - fromConfigRoot, - mergeYAMLConfigs, -} = require( '../../utils' ); +const { fromConfigRoot, mergeYAMLConfigs } = require( '../../utils' ); -const composeFile = normalize( `${ env.WP_DEVELOP_DIR }/docker-compose.override.yml` ); +const composeFile = normalize( + `${ env.WP_DEVELOP_DIR }/docker-compose.override.yml` +); let compose = {}; if ( existsSync( composeFile ) ) { try { compose = yaml.safeLoad( readFileSync( composeFile, 'utf8' ) ); } catch ( error ) { - stdout.write( 'There was an error loading your docker-compose.override.yml file. Please fix or delete it, and try again.\n' ); + stdout.write( + 'There was an error loading your docker-compose.override.yml file. Please fix or delete it, and try again.\n' + ); stdout.write( error.toString() ); exit( 1 ); } } -const coreComposeFile = normalize( `${ env.WP_DEVELOP_DIR }/docker-compose.yml` ); +const coreComposeFile = normalize( + `${ env.WP_DEVELOP_DIR }/docker-compose.yml` +); if ( ! existsSync( coreComposeFile ) ) { - stdout.write( "docker-compose.yml doesn't seem to exist. Are you sure WP_DEVELOP_DIR is a WordPress source directory?\n" ); + stdout.write( + "docker-compose.yml doesn't seem to exist. Are you sure WP_DEVELOP_DIR is a WordPress source directory?\n" + ); exit( 1 ); } @@ -41,14 +46,18 @@ let coreCompose = {}; try { coreCompose = yaml.safeLoad( readFileSync( coreComposeFile, 'utf8' ) ); } catch ( error ) { - stdout.write( 'There was an error loading your docker-compose.yml in your WordPress directory. Please revert any changes to it, and try again.\n' ); + stdout.write( + 'There was an error loading your docker-compose.yml in your WordPress directory. Please revert any changes to it, and try again.\n' + ); stdout.write( error.toString() ); exit( 1 ); } const pluginMountDir = cwd(); -const composeTemplateFile = env.npm_package_wp_env_docker_template ? normalize( cwd() + `/${ env.npm_package_wp_env_docker_template }` ) : fromConfigRoot( 'docker-compose.override.yml.template' ); +const composeTemplateFile = env.npm_package_wp_env_docker_template + ? normalize( cwd() + `/${ env.npm_package_wp_env_docker_template }` ) + : fromConfigRoot( 'docker-compose.override.yml.template' ); const composeTemplate = readFileSync( composeTemplateFile, 'utf8' ) .replace( /%PLUGIN_MOUNT_DIR%/gi, pluginMountDir ) @@ -58,7 +67,9 @@ let pluginCompose = {}; try { pluginCompose = yaml.safeLoad( composeTemplate ); } catch ( error ) { - stdout.write( 'There was an error loading your docker-compose.override.yml.template file. Please revert any changes to it, and try again.\n' ); + stdout.write( + 'There was an error loading your docker-compose.override.yml.template file. Please revert any changes to it, and try again.\n' + ); stdout.write( error.toString() ); exit( 1 ); } @@ -67,7 +78,11 @@ stdout.write( 'Updating docker-compose.override.yml...\n' ); compose.version = coreCompose.version; -const mergedCompose = mergeYAMLConfigs( compose, pluginCompose, pluginMountDir ); +const mergedCompose = mergeYAMLConfigs( + compose, + pluginCompose, + pluginMountDir +); writeFileSync( composeFile, yaml.safeDump( mergedCompose, { lineWidth: -1 } ) ); diff --git a/packages/scripts/scripts/env/docker-run.js b/packages/scripts/scripts/env/docker-run.js index bde99a4dadfdc3..26faa06d18a3c6 100644 --- a/packages/scripts/scripts/env/docker-run.js +++ b/packages/scripts/scripts/env/docker-run.js @@ -14,4 +14,8 @@ const args = getArgsFromCLI(); const localDir = env.LOCAL_DIR || 'src'; // Execute any docker-compose command passed to this script, in the WordPress Docker environment. -execSync( `docker-compose run -w /var/www/${ localDir }/wp-content/plugins/${ env.npm_package_wp_env_plugin_dir } --rm ` + args.join( ' ' ), { cwd: env.WP_DEVELOP_DIR, stdio: 'inherit' } ); +execSync( + `docker-compose run -w /var/www/${ localDir }/wp-content/plugins/${ env.npm_package_wp_env_plugin_dir } --rm ` + + args.join( ' ' ), + { cwd: env.WP_DEVELOP_DIR, stdio: 'inherit' } +); diff --git a/packages/scripts/scripts/env/install.js b/packages/scripts/scripts/env/install.js index 7eee086a05d84e..b9f5ba8a4cee56 100644 --- a/packages/scripts/scripts/env/install.js +++ b/packages/scripts/scripts/env/install.js @@ -14,10 +14,7 @@ const { existsSync } = require( 'fs' ); /** * Internal dependencies */ -const { - buildWordPress, - downloadWordPressZip, -} = require( '../../utils' ); +const { buildWordPress, downloadWordPressZip } = require( '../../utils' ); const { hasArgInCLI } = require( '../../utils' ); env.WP_DEVELOP_DIR = normalize( cwd() + '/wordpress' ); @@ -28,17 +25,25 @@ if ( hasArgInCLI( '--fast' ) ) { } if ( existsSync( normalize( cwd() + '/wordpress/wp-config-sample.php' ) ) ) { - stdout.write( 'It looks like WordPress is already installed, please delete the `wordpress` directory for a fresh install, or run `npm run env start` to start the existing environment.\n' ); + stdout.write( + 'It looks like WordPress is already installed, please delete the `wordpress` directory for a fresh install, or run `npm run env start` to start the existing environment.\n' + ); exit( 1 ); } if ( commandExistsSync( 'git' ) ) { - execSync( 'git clone --depth=1 git://develop.git.wordpress.org/ wordpress', { stdio: 'inherit' } ); + execSync( + 'git clone --depth=1 git://develop.git.wordpress.org/ wordpress', + { + stdio: 'inherit', + } + ); buildWordPress( true, false ); } else { - stdout.write( "Git isn't available. Switching to downloading a zip version.\n" ); - downloadWordPressZip() - .then( () => { - buildWordPress( true, false ); - } ); + stdout.write( + "Git isn't available. Switching to downloading a zip version.\n" + ); + downloadWordPressZip().then( () => { + buildWordPress( true, false ); + } ); } diff --git a/packages/scripts/scripts/env/lint-php.js b/packages/scripts/scripts/env/lint-php.js index 7c93519e933e93..125114a515bff8 100644 --- a/packages/scripts/scripts/env/lint-php.js +++ b/packages/scripts/scripts/env/lint-php.js @@ -14,4 +14,8 @@ const args = getArgsFromCLI(); const localDir = env.LOCAL_DIR || 'src'; // Run PHPUnit with the working directory set correctly. -execSync( `docker-compose run --rm -w /var/www/${ localDir }/wp-content/plugins/${ env.npm_package_wp_env_plugin_dir } php composer lint ` + args.join( ' ' ), { cwd: env.WP_DEVELOP_DIR, stdio: 'inherit' } ); +execSync( + `docker-compose run --rm -w /var/www/${ localDir }/wp-content/plugins/${ env.npm_package_wp_env_plugin_dir } php composer lint ` + + args.join( ' ' ), + { cwd: env.WP_DEVELOP_DIR, stdio: 'inherit' } +); diff --git a/packages/scripts/scripts/env/start.js b/packages/scripts/scripts/env/start.js index b563b897ba0c81..2af2775eaab6c2 100644 --- a/packages/scripts/scripts/env/start.js +++ b/packages/scripts/scripts/env/start.js @@ -17,12 +17,16 @@ if ( env.MANAGED_WP ) { if ( false && commandExistsSync( 'git' ) ) { stat = statSync( normalize( env.WP_DEVELOP_DIR + '/.git' ) ); } else { - stat = statSync( normalize( env.WP_DEVELOP_DIR + '/wp-config-sample.php' ) ); + stat = statSync( + normalize( env.WP_DEVELOP_DIR + '/wp-config-sample.php' ) + ); } const lastUpdated = new Date( stat.mtimeMs ); if ( Date.now() - lastUpdated.getTime() > 7 * 24 * 60 * 60 * 1000 ) { - stdout.write( "\nIt's been a while since you updated WordPress. Run `npm run env update` to update it.\n\n" ); + stdout.write( + "\nIt's been a while since you updated WordPress. Run `npm run env update` to update it.\n\n" + ); } } diff --git a/packages/scripts/scripts/env/test-php.js b/packages/scripts/scripts/env/test-php.js index da3529bd7ec6b6..5c9a10cac75ce9 100644 --- a/packages/scripts/scripts/env/test-php.js +++ b/packages/scripts/scripts/env/test-php.js @@ -14,4 +14,8 @@ const args = getArgsFromCLI(); const localDir = env.LOCAL_DIR || 'src'; // Run PHPUnit with the working directory set correctly. -execSync( `npm run test:php -- -c /var/www/${ localDir }/wp-content/plugins/${ env.npm_package_wp_env_plugin_dir }/phpunit.xml.dist ` + args.join( ' ' ), { cwd: env.WP_DEVELOP_DIR, stdio: 'inherit' } ); +execSync( + `npm run test:php -- -c /var/www/${ localDir }/wp-content/plugins/${ env.npm_package_wp_env_plugin_dir }/phpunit.xml.dist ` + + args.join( ' ' ), + { cwd: env.WP_DEVELOP_DIR, stdio: 'inherit' } +); diff --git a/packages/scripts/scripts/env/update.js b/packages/scripts/scripts/env/update.js index f912992f0b115f..5e02f2afa2be38 100644 --- a/packages/scripts/scripts/env/update.js +++ b/packages/scripts/scripts/env/update.js @@ -13,10 +13,7 @@ const { normalize } = require( 'path' ); /** * Internal dependencies */ -const { - buildWordPress, - downloadWordPressZip, -} = require( '../../utils' ); +const { buildWordPress, downloadWordPressZip } = require( '../../utils' ); env.WP_DEVELOP_DIR = normalize( cwd() + '/wordpress' ); @@ -24,9 +21,10 @@ if ( commandExistsSync( 'git' ) ) { execSync( 'git pull', { cwd: env.WP_DEVELOP_DIR, stdio: 'inherit' } ); buildWordPress( false, false ); } else { - stdout.write( "Git isn't available. Switching to downloading a zip version.\n" ); - downloadWordPressZip() - .then( () => { - buildWordPress( false, false ); - } ); + stdout.write( + "Git isn't available. Switching to downloading a zip version.\n" + ); + downloadWordPressZip().then( () => { + buildWordPress( false, false ); + } ); } diff --git a/packages/scripts/scripts/format-js.js b/packages/scripts/scripts/format-js.js index 046a9c8b70532b..112978a3794718 100644 --- a/packages/scripts/scripts/format-js.js +++ b/packages/scripts/scripts/format-js.js @@ -32,11 +32,17 @@ function checkPrettier() { const prettierPackageJson = readPkgUp( { cwd: prettierResolvePath } ); const prettierPackageName = prettierPackageJson.pkg.name; - if ( ! [ 'wp-prettier', '@wordpress/prettier' ].includes( prettierPackageName ) ) { + if ( + ! [ 'wp-prettier', '@wordpress/prettier' ].includes( + prettierPackageName + ) + ) { return { success: false, message: - chalk.red( 'Incompatible version of Prettier was found in your project\n' ) + + chalk.red( + 'Incompatible version of Prettier was found in your project\n' + ) + "You need to install the 'wp-prettier' package to get " + 'code formatting compliant with the WordPress coding standards.\n\n', }; @@ -45,7 +51,9 @@ function checkPrettier() { return { success: false, message: - chalk.red( "The 'prettier' package was not found in your project\n" ) + + chalk.red( + "The 'prettier' package was not found in your project\n" + ) + "You need to install the 'wp-prettier' package under an alias to get " + 'code formatting compliant with the WordPress coding standards.\n\n', }; @@ -53,20 +61,22 @@ function checkPrettier() { // See: https://prettier.io/docs/en/configuration.html const hasProjectPrettierConfig = - hasProjectFile( '.prettierrc' ) || + hasProjectFile( '.prettierrc.js' ) || hasProjectFile( '.prettierrc.json' ) || + hasProjectFile( '.prettierrc.toml' ) || hasProjectFile( '.prettierrc.yaml' ) || hasProjectFile( '.prettierrc.yml' ) || - hasProjectFile( '.prettierrc.js' ) || - hasProjectFile( '.prettierrc.config.js' ) || - hasProjectFile( '.prettierrc.toml' ) || + hasProjectFile( 'prettier.config.js' ) || + hasProjectFile( '.prettierrc' ) || hasPackageProp( 'prettier' ); if ( ! hasProjectPrettierConfig ) { return { success: false, message: - chalk.red( 'The Prettier config file was not found in your project\n' ) + + chalk.red( + 'The Prettier config file was not found in your project\n' + ) + 'You need to create a top-level Prettier config file in your project to get ' + 'automatic code formatting that works with IDE and editor integrations.\n\n', }; @@ -94,7 +104,9 @@ const ignoreArgs = [ '--ignore-path', ignorePath ]; // forward the --require-pragma option that formats only files that already have the @format // pragma in the first docblock. -const pragmaArgs = hasArgInCLI( '--require-pragma' ) ? [ '--require-pragma' ] : []; +const pragmaArgs = hasArgInCLI( '--require-pragma' ) + ? [ '--require-pragma' ] + : []; // Get the files and directories to format and convert them to globs let fileArgs = getFileArgsFromCLI(); diff --git a/packages/scripts/scripts/lint-js.js b/packages/scripts/scripts/lint-js.js index dc55a358c3e158..317a05e9e08991 100644 --- a/packages/scripts/scripts/lint-js.js +++ b/packages/scripts/scripts/lint-js.js @@ -21,33 +21,40 @@ const args = getArgsFromCLI(); const defaultFilesArgs = hasFileArgInCLI() ? [] : [ '.' ]; // See: https://eslint.org/docs/user-guide/configuring#using-configuration-files-1. -const hasLintConfig = hasArgInCLI( '-c' ) || +const hasLintConfig = + hasArgInCLI( '-c' ) || hasArgInCLI( '--config' ) || hasProjectFile( '.eslintrc.js' ) || + hasProjectFile( '.eslintrc.json' ) || hasProjectFile( '.eslintrc.yaml' ) || hasProjectFile( '.eslintrc.yml' ) || - hasProjectFile( '.eslintrc.json' ) || + hasProjectFile( 'eslintrc.config.js' ) || hasProjectFile( '.eslintrc' ) || hasPackageProp( 'eslintConfig' ); // When a configuration is not provided by the project, use from the default // provided with the scripts module. Instruct ESLint to avoid discovering via // the `--no-eslintrc` flag, as otherwise it will still merge with inherited. -const defaultConfigArgs = ! hasLintConfig ? - [ '--no-eslintrc', '--config', fromConfigRoot( '.eslintrc.js' ) ] : - []; +const defaultConfigArgs = ! hasLintConfig + ? [ '--no-eslintrc', '--config', fromConfigRoot( '.eslintrc.js' ) ] + : []; // See: https://eslint.org/docs/user-guide/configuring#ignoring-files-and-directories. -const hasIgnoredFiles = hasArgInCLI( '--ignore-path' ) || - hasProjectFile( '.eslintignore' ); +const hasIgnoredFiles = + hasArgInCLI( '--ignore-path' ) || hasProjectFile( '.eslintignore' ); -const defaultIgnoreArgs = ! hasIgnoredFiles ? - [ '--ignore-path', fromConfigRoot( '.eslintignore' ) ] : - []; +const defaultIgnoreArgs = ! hasIgnoredFiles + ? [ '--ignore-path', fromConfigRoot( '.eslintignore' ) ] + : []; const result = spawn( resolveBin( 'eslint' ), - [ ...defaultConfigArgs, ...defaultIgnoreArgs, ...args, ...defaultFilesArgs ], + [ + ...defaultConfigArgs, + ...defaultIgnoreArgs, + ...args, + ...defaultFilesArgs, + ], { stdio: 'inherit' } ); diff --git a/packages/scripts/scripts/lint-md-docs.js b/packages/scripts/scripts/lint-md-docs.js index c2949985cb35ca..740d3e5af7304a 100644 --- a/packages/scripts/scripts/lint-md-docs.js +++ b/packages/scripts/scripts/lint-md-docs.js @@ -21,20 +21,25 @@ const defaultFilesArgs = hasFileArgInCLI() ? [] : [ '**/*.md' ]; // See: https://github.com/igorshubovych/markdownlint-cli#configuration // Check if specified on command-line or project file for config -const hasLintConfig = hasArgInCLI( '-c' ) || hasArgInCLI( '--config' ) || +const hasLintConfig = + hasArgInCLI( '-c' ) || + hasArgInCLI( '--config' ) || hasProjectFile( '.markdownlint.json' ) || hasProjectFile( '.markdownlint.yaml' ) || - hasProjectFile( '.markdownlint.yml' ); + hasProjectFile( '.markdownlint.yml' ) || + hasProjectFile( '.markdownlintrc' ); // When a configuration is not provided by the project, use from the default // provided with the scripts module. -const defaultConfigArgs = ! hasLintConfig ? - [ '--config', fromConfigRoot( '.markdownlint.json' ) ] : - []; +const defaultConfigArgs = ! hasLintConfig + ? [ '--config', fromConfigRoot( '.markdownlint.json' ) ] + : []; // See: https://github.com/igorshubovych/markdownlint-cli#ignoring-files // Check if ignore specified on command-line or project file -const hasIgnoredFiles = hasArgInCLI( '--ignore' ) || hasArgInCLI( '-i' ) || +const hasIgnoredFiles = + hasArgInCLI( '--ignore' ) || + hasArgInCLI( '-i' ) || hasProjectFile( '.markdownlintignore' ); // Default ignore [ build, node_modules ] directories @@ -42,13 +47,18 @@ const hasIgnoredFiles = hasArgInCLI( '--ignore' ) || hasArgInCLI( '-i' ) || // we can switch this to specify an ignore file on the command-line. By default, // markdownlint looks for .markdownlintignore in project direcotry, but how our // scripts work we store the configs in the scripts/config directory -const defaultIgnoreArgs = ! hasIgnoredFiles ? - [ '--ignore', '**/build/**', '--ignore', '**/node_modules/**' ] : - []; +const defaultIgnoreArgs = ! hasIgnoredFiles + ? [ '--ignore', '**/build/**', '--ignore', '**/node_modules/**' ] + : []; const result = spawn( resolveBin( 'markdownlint-cli', { executable: 'markdownlint' } ), - [ ...defaultConfigArgs, ...defaultIgnoreArgs, ...args, ...defaultFilesArgs ], + [ + ...defaultConfigArgs, + ...defaultIgnoreArgs, + ...args, + ...defaultFilesArgs, + ], { stdio: 'inherit' } ); diff --git a/packages/scripts/scripts/lint-md-js.js b/packages/scripts/scripts/lint-md-js.js index 165fed326fae39..b8f50bce4a9deb 100644 --- a/packages/scripts/scripts/lint-md-js.js +++ b/packages/scripts/scripts/lint-md-js.js @@ -25,21 +25,26 @@ const hasLintConfig = hasArgInCLI( '-c' ) || hasArgInCLI( '--config' ); // When a configuration is not provided by the project, use from the default // provided with the scripts module. Instruct ESLint to avoid discovering via // the `--no-eslintrc` flag, as otherwise it will still merge with inherited. -const defaultConfigArgs = ! hasLintConfig ? - [ '--no-eslintrc', '--config', fromConfigRoot( '.eslintrc-md.js' ) ] : - []; +const defaultConfigArgs = ! hasLintConfig + ? [ '--no-eslintrc', '--config', fromConfigRoot( '.eslintrc-md.js' ) ] + : []; // See: https://eslint.org/docs/user-guide/configuring#ignoring-files-and-directories. -const hasIgnoredFiles = hasArgInCLI( '--ignore-path' ) || - hasProjectFile( '.eslintignore' ); +const hasIgnoredFiles = + hasArgInCLI( '--ignore-path' ) || hasProjectFile( '.eslintignore' ); -const defaultIgnoreArgs = ! hasIgnoredFiles ? - [ '--ignore-path', fromConfigRoot( '.eslintignore' ) ] : - []; +const defaultIgnoreArgs = ! hasIgnoredFiles + ? [ '--ignore-path', fromConfigRoot( '.eslintignore' ) ] + : []; const result = spawn( resolveBin( 'eslint' ), - [ ...defaultConfigArgs, ...defaultIgnoreArgs, ...args, ...defaultFilesArgs ], + [ + ...defaultConfigArgs, + ...defaultIgnoreArgs, + ...args, + ...defaultFilesArgs, + ], { stdio: 'inherit' } ); diff --git a/packages/scripts/scripts/lint-pkg-json.js b/packages/scripts/scripts/lint-pkg-json.js index 53e9c42b366437..44c9126829ba52 100644 --- a/packages/scripts/scripts/lint-pkg-json.js +++ b/packages/scripts/scripts/lint-pkg-json.js @@ -20,30 +20,41 @@ const args = getArgsFromCLI(); const defaultFilesArgs = hasFileArgInCLI() ? [] : [ '.' ]; -// See: https://github.com/tclindner/npm-package-json-lint/wiki/configuration#configuration. -const hasLintConfig = hasArgInCLI( '-c' ) || +// See: https://npmpackagejsonlint.org/docs/en/configuration +const hasLintConfig = + hasArgInCLI( '-c' ) || hasArgInCLI( '--configFile' ) || + hasProjectFile( '.npmpackagejsonlintrc.js' ) || hasProjectFile( '.npmpackagejsonlintrc.json' ) || + hasProjectFile( '.npmpackagejsonlintrc.yaml' ) || + hasProjectFile( '.npmpackagejsonlintrc.yml' ) || hasProjectFile( 'npmpackagejsonlint.config.js' ) || + hasProjectFile( '.npmpackagejsonlintrc' ) || hasPackageProp( 'npmpackagejsonlint' ) || // npm-package-json-lint v3.x used a different prop name. hasPackageProp( 'npmPackageJsonLintConfig' ); -const defaultConfigArgs = ! hasLintConfig ? - [ '--configFile', fromConfigRoot( 'npmpackagejsonlint.json' ) ] : - []; +const defaultConfigArgs = ! hasLintConfig + ? [ '--configFile', fromConfigRoot( 'npmpackagejsonlint.json' ) ] + : []; // See: https://github.com/tclindner/npm-package-json-lint/#cli-commands-and-configuration. -const hasIgnoredFiles = hasArgInCLI( '--ignorePath' ) || +const hasIgnoredFiles = + hasArgInCLI( '--ignorePath' ) || hasProjectFile( '.npmpackagejsonlintignore' ); -const defaultIgnoreArgs = ! hasIgnoredFiles ? - [ '--ignorePath', fromConfigRoot( '.npmpackagejsonlintignore' ) ] : - []; +const defaultIgnoreArgs = ! hasIgnoredFiles + ? [ '--ignorePath', fromConfigRoot( '.npmpackagejsonlintignore' ) ] + : []; const result = spawn( resolveBin( 'npm-package-json-lint', { executable: 'npmPkgJsonLint' } ), - [ ...defaultConfigArgs, ...defaultIgnoreArgs, ...args, ...defaultFilesArgs ], + [ + ...defaultConfigArgs, + ...defaultIgnoreArgs, + ...args, + ...defaultFilesArgs, + ], { stdio: 'inherit' } ); diff --git a/packages/scripts/scripts/lint-style.js b/packages/scripts/scripts/lint-style.js index 3f7e86fc43277f..43bbfaa6e5524f 100644 --- a/packages/scripts/scripts/lint-style.js +++ b/packages/scripts/scripts/lint-style.js @@ -20,31 +20,37 @@ const args = getArgsFromCLI(); const defaultFilesArgs = hasFileArgInCLI() ? [] : [ '**/*.{css,scss}' ]; -// See: https://github.com/stylelint/stylelint/blob/master/docs/user-guide/configuration.md#loading-the-configuration-object. -const hasLintConfig = hasArgInCLI( '--config' ) || - hasProjectFile( '.stylelintrc' ) || +// See: https://stylelint.io/user-guide/configuration +const hasLintConfig = + hasArgInCLI( '--config' ) || hasProjectFile( '.stylelintrc.js' ) || hasProjectFile( '.stylelintrc.json' ) || hasProjectFile( '.stylelintrc.yaml' ) || hasProjectFile( '.stylelintrc.yml' ) || - hasProjectFile( '.stylelint.config.js' ) || + hasProjectFile( 'stylelint.config.js' ) || + hasProjectFile( '.stylelintrc' ) || hasPackageProp( 'stylelint' ); -const defaultConfigArgs = ! hasLintConfig ? - [ '--config', fromConfigRoot( '.stylelintrc.json' ) ] : - []; +const defaultConfigArgs = ! hasLintConfig + ? [ '--config', fromConfigRoot( '.stylelintrc.json' ) ] + : []; // See: https://github.com/stylelint/stylelint/blob/master/docs/user-guide/configuration.md#stylelintignore. -const hasIgnoredFiles = hasArgInCLI( '--ignore-path' ) || - hasProjectFile( '.stylelintignore' ); +const hasIgnoredFiles = + hasArgInCLI( '--ignore-path' ) || hasProjectFile( '.stylelintignore' ); -const defaultIgnoreArgs = ! hasIgnoredFiles ? - [ '--ignore-path', fromConfigRoot( '.stylelintignore' ) ] : - []; +const defaultIgnoreArgs = ! hasIgnoredFiles + ? [ '--ignore-path', fromConfigRoot( '.stylelintignore' ) ] + : []; const result = spawn( resolveBin( 'stylelint' ), - [ ...defaultConfigArgs, ...defaultIgnoreArgs, ...args, ...defaultFilesArgs ], + [ + ...defaultConfigArgs, + ...defaultIgnoreArgs, + ...args, + ...defaultFilesArgs, + ], { stdio: 'inherit' } ); diff --git a/packages/scripts/scripts/packages-update.js b/packages/scripts/scripts/packages-update.js index 41a7aa5ece54f6..6ebde42cc7b618 100644 --- a/packages/scripts/scripts/packages-update.js +++ b/packages/scripts/scripts/packages-update.js @@ -16,9 +16,11 @@ function readJSONFile( fileName ) { } function getWordPressPackages( packageJSON ) { - return Object.keys( packageJSON.dependencies ). - concat( Object.keys( packageJSON.devDependencies ) ). - filter( ( packageName ) => ( packageName.startsWith( WORDPRESS_PACKAGES_PREFIX ) ) ); + return Object.keys( packageJSON.dependencies ) + .concat( Object.keys( packageJSON.devDependencies ) ) + .filter( ( packageName ) => + packageName.startsWith( WORDPRESS_PACKAGES_PREFIX ) + ); } function getPackageVersionDiff( initialPackageJSON, finalPackageJSON ) { @@ -26,8 +28,10 @@ function getPackageVersionDiff( initialPackageJSON, finalPackageJSON ) { ( result, keyPackageJSON ) => { return Object.keys( finalPackageJSON[ keyPackageJSON ] ).reduce( ( _result, dependency ) => { - const initial = initialPackageJSON[ keyPackageJSON ][ dependency ]; - const final = finalPackageJSON[ keyPackageJSON ][ dependency ]; + const initial = + initialPackageJSON[ keyPackageJSON ][ dependency ]; + const final = + finalPackageJSON[ keyPackageJSON ][ dependency ]; if ( initial !== final ) { _result.push( { dependency, initial, final } ); } @@ -43,22 +47,22 @@ function getPackageVersionDiff( initialPackageJSON, finalPackageJSON ) { function updatePackagesToLatestVersion( packages ) { const packagesWithLatest = packages.map( - ( packageName ) => ( `${ packageName }@latest` ) + ( packageName ) => `${ packageName }@latest` ); - return spawn.sync( 'npm', [ - 'install', - ...packagesWithLatest, - '--save', - ], { stdio: 'inherit' } ); + return spawn.sync( 'npm', [ 'install', ...packagesWithLatest, '--save' ], { + stdio: 'inherit', + } ); } function outputPackageDiffReport( packageDiff ) { - console.log( [ - 'The following package versions were changed:', - ...packageDiff.map( ( { dependency, initial, final } ) => { - return `${ dependency }: ${ initial } -> ${ final }`; - } ), - ].join( '\n' ) ); + console.log( + [ + 'The following package versions were changed:', + ...packageDiff.map( ( { dependency, initial, final } ) => { + return `${ dependency }: ${ initial } -> ${ final }`; + } ), + ].join( '\n' ) + ); } function updatePackageJSON() { @@ -66,7 +70,9 @@ function updatePackageJSON() { const packages = getWordPressPackages( initialPackageJSON ); const result = updatePackagesToLatestVersion( packages ); const finalPackageJSON = readJSONFile( 'package.json' ); - outputPackageDiffReport( getPackageVersionDiff( initialPackageJSON, finalPackageJSON ) ); + outputPackageDiffReport( + getPackageVersionDiff( initialPackageJSON, finalPackageJSON ) + ); process.exit( result.status ); } diff --git a/packages/scripts/scripts/start.js b/packages/scripts/scripts/start.js index d27fa3a5b0ec48..48b1497c5c1074 100644 --- a/packages/scripts/scripts/start.js +++ b/packages/scripts/scripts/start.js @@ -12,6 +12,8 @@ const { getWebpackArgs } = require( '../utils' ); const { status } = spawn( resolveBin( 'webpack' ), getWebpackArgs( [ '--watch' ] ), - { stdio: 'inherit' } + { + stdio: 'inherit', + } ); process.exit( status ); diff --git a/packages/scripts/scripts/test-e2e.js b/packages/scripts/scripts/test-e2e.js index c82a92f442f883..2cc42f09c4d588 100644 --- a/packages/scripts/scripts/test-e2e.js +++ b/packages/scripts/scripts/test-e2e.js @@ -30,19 +30,22 @@ const { // Provides a default config path for Puppeteer when jest-puppeteer.config.js // wasn't found at the root of the project or a custom path wasn't defined // using JEST_PUPPETEER_CONFIG environment variable. -if ( ! hasProjectFile( 'jest-puppeteer.config.js' ) && ! process.env.JEST_PUPPETEER_CONFIG ) { +if ( + ! hasProjectFile( 'jest-puppeteer.config.js' ) && + ! process.env.JEST_PUPPETEER_CONFIG +) { process.env.JEST_PUPPETEER_CONFIG = fromConfigRoot( 'puppeteer.config.js' ); } -const config = ! hasJestConfig() ? - [ '--config', JSON.stringify( require( fromConfigRoot( 'jest-e2e.config.js' ) ) ) ] : - []; +const config = ! hasJestConfig() + ? [ + '--config', + JSON.stringify( require( fromConfigRoot( 'jest-e2e.config.js' ) ) ), + ] + : []; -const hasRunInBand = hasArgInCLI( '--runInBand' ) || - hasArgInCLI( '-i' ); -const runInBand = ! hasRunInBand ? - [ '--runInBand' ] : - []; +const hasRunInBand = hasArgInCLI( '--runInBand' ) || hasArgInCLI( '-i' ); +const runInBand = ! hasRunInBand ? [ '--runInBand' ] : []; if ( hasArgInCLI( '--puppeteer-interactive' ) ) { process.env.PUPPETEER_HEADLESS = 'false'; diff --git a/packages/scripts/scripts/test-unit-jest.js b/packages/scripts/scripts/test-unit-jest.js index 706addf5d6f7c4..69ea51c6a0a900 100644 --- a/packages/scripts/scripts/test-unit-jest.js +++ b/packages/scripts/scripts/test-unit-jest.js @@ -18,14 +18,15 @@ const jest = require( 'jest' ); /** * Internal dependencies */ -const { - fromConfigRoot, - getArgsFromCLI, - hasJestConfig, -} = require( '../utils' ); +const { fromConfigRoot, getArgsFromCLI, hasJestConfig } = require( '../utils' ); -const config = ! hasJestConfig() ? - [ '--config', JSON.stringify( require( fromConfigRoot( 'jest-unit.config.js' ) ) ) ] : - []; +const config = ! hasJestConfig() + ? [ + '--config', + JSON.stringify( + require( fromConfigRoot( 'jest-unit.config.js' ) ) + ), + ] + : []; jest.run( [ ...config, ...getArgsFromCLI() ] ); diff --git a/packages/scripts/utils/cli.js b/packages/scripts/utils/cli.js index 9b3bbd0dd1ca3d..c1572ece8a23c1 100644 --- a/packages/scripts/utils/cli.js +++ b/packages/scripts/utils/cli.js @@ -7,14 +7,8 @@ const spawn = require( 'cross-spawn' ); /** * Internal dependencies */ -const { - fromScriptsRoot, - hasScriptFile, -} = require( './file' ); -const { - exit, - getArgsFromCLI, -} = require( './process' ); +const { fromScriptsRoot, hasScriptFile } = require( './file' ); +const { exit, getArgsFromCLI } = require( './process' ); const getArgFromCLI = ( arg ) => { for ( const cliArg of getArgsFromCLI() ) { @@ -36,15 +30,15 @@ const handleSignal = ( signal ) => { // eslint-disable-next-line no-console console.log( 'The script failed because the process exited too early. ' + - 'This probably means the system ran out of memory or someone called ' + - '`kill -9` on the process.' + 'This probably means the system ran out of memory or someone called ' + + '`kill -9` on the process.' ); } else if ( signal === 'SIGTERM' ) { // eslint-disable-next-line no-console console.log( 'The script failed because the process exited too early. ' + - 'Someone might have called `kill` or `killall`, or the system could ' + - 'be shutting down.' + 'Someone might have called `kill` or `killall`, or the system could ' + + 'be shutting down.' ); } exit( 1 ); @@ -60,19 +54,20 @@ const spawnScript = ( scriptName, args = [] ) => { if ( ! hasScriptFile( scriptName ) ) { // eslint-disable-next-line no-console console.log( - 'Unknown script "' + scriptName + '". ' + - 'Perhaps you need to update @wordpress/scripts?' + 'Unknown script "' + + scriptName + + '". ' + + 'Perhaps you need to update @wordpress/scripts?' ); exit( 1 ); } const { signal, status } = spawn.sync( 'node', - [ - fromScriptsRoot( scriptName ), - ...args, - ], - { stdio: 'inherit' }, + [ fromScriptsRoot( scriptName ), ...args ], + { + stdio: 'inherit', + } ); if ( signal ) { diff --git a/packages/scripts/utils/config.js b/packages/scripts/utils/config.js index a34895230af704..2524f7d17168aa 100644 --- a/packages/scripts/utils/config.js +++ b/packages/scripts/utils/config.js @@ -6,14 +6,22 @@ const { basename } = require( 'path' ); /** * Internal dependencies */ -const { getArgsFromCLI, getFileArgsFromCLI, hasArgInCLI, hasFileArgInCLI } = require( './cli' ); +const { + getArgsFromCLI, + getFileArgsFromCLI, + hasArgInCLI, + hasFileArgInCLI, +} = require( './cli' ); const { fromConfigRoot, hasProjectFile } = require( './file' ); const { hasPackageProp } = require( './package' ); +// See https://babeljs.io/docs/en/config-files#configuration-file-types const hasBabelConfig = () => - hasProjectFile( '.babelrc' ) || hasProjectFile( '.babelrc.js' ) || + hasProjectFile( '.babelrc.json' ) || hasProjectFile( 'babel.config.js' ) || + hasProjectFile( 'babel.config.json' ) || + hasProjectFile( '.babelrc' ) || hasPackageProp( 'babel' ); const hasJestConfig = () => @@ -23,7 +31,8 @@ const hasJestConfig = () => hasProjectFile( 'jest.config.json' ) || hasPackageProp( 'jest' ); -const hasWebpackConfig = () => hasArgInCLI( '--config' ) || +const hasWebpackConfig = () => + hasArgInCLI( '--config' ) || hasProjectFile( 'webpack.config.js' ) || hasProjectFile( 'webpack.config.babel.js' ); @@ -40,7 +49,8 @@ const hasWebpackConfig = () => hasArgInCLI( '--config' ) || const getWebpackArgs = ( additionalArgs = [] ) => { let webpackArgs = getArgsFromCLI(); - const hasWebpackOutputOption = hasArgInCLI( '-o' ) || hasArgInCLI( '--output' ); + const hasWebpackOutputOption = + hasArgInCLI( '-o' ) || hasArgInCLI( '--output' ); if ( hasFileArgInCLI() && ! hasWebpackOutputOption ) { /** * Converts a path to the entry format supported by webpack, e.g.: @@ -64,7 +74,10 @@ const getWebpackArgs = ( additionalArgs = [] ) => { // The following handles the support for multiple entry points in webpack, e.g.: // `wp-scripts build one.js custom=./two.js` -> `webpack one=./one.js custom=./two.js` webpackArgs = webpackArgs.map( ( cliArg ) => { - if ( getFileArgsFromCLI().includes( cliArg ) && ! cliArg.includes( '=' ) ) { + if ( + getFileArgsFromCLI().includes( cliArg ) && + ! cliArg.includes( '=' ) + ) { return pathToEntry( cliArg ); } diff --git a/packages/scripts/utils/env.js b/packages/scripts/utils/env.js index 7b2f6d46d6ec8a..372ef3479192dc 100644 --- a/packages/scripts/utils/env.js +++ b/packages/scripts/utils/env.js @@ -48,7 +48,11 @@ function mergeYAMLConfigs( originalConfig, newConfig, baseDir ) { originalConfig[ key ] = [ ...cleanOriginal, ...newConfig[ key ] ]; } else if ( isPlainObject( newConfig[ key ] ) ) { // If the newConfig element is an object, we need to recursively merge it. - originalConfig[ key ] = mergeYAMLConfigs( originalConfig[ key ], newConfig[ key ], baseDir ); + originalConfig[ key ] = mergeYAMLConfigs( + originalConfig[ key ], + newConfig[ key ], + baseDir + ); } else { // Any other data types are overwritten by the newConfig. originalConfig[ key ] = newConfig[ key ]; @@ -92,7 +96,9 @@ function downloadWordPressZip() { stdout.write( 'Downloading...\n' ); // Download the archive. request - .get( 'https://github.com/WordPress/wordpress-develop/archive/master.zip' ) + .get( + 'https://github.com/WordPress/wordpress-develop/archive/master.zip' + ) .on( 'error', ( error ) => { stdout.write( "ERROR: The zip file couldn't be downloaded.\n" ); stdout.write( error.toString() ); @@ -110,30 +116,62 @@ function downloadWordPressZip() { */ function buildWordPress( newInstall, fastInstall ) { if ( ! fastInstall ) { - execSync( 'npm install', { cwd: env.WP_DEVELOP_DIR, stdio: 'inherit' } ); - execSync( 'npm run env:start', { cwd: env.WP_DEVELOP_DIR, stdio: 'inherit' } ); + execSync( 'npm install', { + cwd: env.WP_DEVELOP_DIR, + stdio: 'inherit', + } ); + execSync( 'npm run env:start', { + cwd: env.WP_DEVELOP_DIR, + stdio: 'inherit', + } ); if ( env.LOCAL_DIR === 'build' ) { - execSync( 'npm run build', { cwd: env.WP_DEVELOP_DIR, stdio: 'inherit' } ); + execSync( 'npm run build', { + cwd: env.WP_DEVELOP_DIR, + stdio: 'inherit', + } ); } else { - execSync( 'npm run build:dev', { cwd: env.WP_DEVELOP_DIR, stdio: 'inherit' } ); + execSync( 'npm run build:dev', { + cwd: env.WP_DEVELOP_DIR, + stdio: 'inherit', + } ); } } if ( newInstall ) { - execSync( 'npm run env:install', { cwd: env.WP_DEVELOP_DIR, stdio: 'inherit' } ); + execSync( 'npm run env:install', { + cwd: env.WP_DEVELOP_DIR, + stdio: 'inherit', + } ); } // Mount the plugin into the WordPress install. execSync( 'npm run env connect', { stdio: 'inherit' } ); if ( newInstall ) { - execSync( `npm run env cli plugin activate ${ env.npm_package_wp_env_plugin_dir }`, { stdio: 'inherit' } ); + execSync( + `npm run env cli plugin activate ${ env.npm_package_wp_env_plugin_dir }`, + { + stdio: 'inherit', + } + ); - const currentUrl = execSync( 'npm run --silent env cli option get siteurl' ).toString().trim(); + const currentUrl = execSync( + 'npm run --silent env cli option get siteurl' + ) + .toString() + .trim(); stdout.write( chalk.white( '\nWelcome to...\n' ) ); - for ( let ii = 0; env[ `npm_package_wp_env_welcome_logo_${ ii }` ]; ii++ ) { - stdout.write( chalk.green( env[ `npm_package_wp_env_welcome_logo_${ ii }` ] ) + '\n' ); + for ( + let ii = 0; + env[ `npm_package_wp_env_welcome_logo_${ ii }` ]; + ii++ + ) { + stdout.write( + chalk.green( + env[ `npm_package_wp_env_welcome_logo_${ ii }` ] + ) + '\n' + ); } if ( env.npm_package_wp_env_welcome_build_command ) { @@ -146,7 +184,11 @@ function buildWordPress( newInstall, fastInstall ) { stdout.write( chalk.white( nextStep ) ); } - stdout.write( chalk.white( '\nAccess the above install using the following credentials:\n' ) ); + stdout.write( + chalk.white( + '\nAccess the above install using the following credentials:\n' + ) + ); const access = sprintf( 'Default username: %s, password: %s\n', diff --git a/packages/scripts/utils/index.js b/packages/scripts/utils/index.js index ea826a4b4ca128..0ddf62d395b90e 100644 --- a/packages/scripts/utils/index.js +++ b/packages/scripts/utils/index.js @@ -9,27 +9,15 @@ const { hasFileArgInCLI, spawnScript, } = require( './cli' ); -const { - getWebpackArgs, - hasBabelConfig, - hasJestConfig, -} = require( './config' ); +const { getWebpackArgs, hasBabelConfig, hasJestConfig } = require( './config' ); const { buildWordPress, downloadWordPressZip, mergeYAMLConfigs, } = require( './env' ); -const { - fromProjectRoot, - fromConfigRoot, - hasProjectFile, -} = require( './file' ); -const { - hasPackageProp, -} = require( './package' ); -const { - camelCaseDash, -} = require( './string' ); +const { fromProjectRoot, fromConfigRoot, hasProjectFile } = require( './file' ); +const { hasPackageProp } = require( './package' ); +const { camelCaseDash } = require( './string' ); module.exports = { buildWordPress, diff --git a/packages/scripts/utils/process.js b/packages/scripts/utils/process.js index 4996190d21e539..de07d36a8b59c4 100644 --- a/packages/scripts/utils/process.js +++ b/packages/scripts/utils/process.js @@ -2,7 +2,9 @@ const getArgsFromCLI = ( excludePrefixes ) => { const args = process.argv.slice( 2 ); if ( excludePrefixes ) { return args.filter( ( arg ) => { - return ! excludePrefixes.some( ( prefix ) => arg.startsWith( prefix ) ); + return ! excludePrefixes.some( ( prefix ) => + arg.startsWith( prefix ) + ); } ); } return args; diff --git a/packages/scripts/utils/string.js b/packages/scripts/utils/string.js index ea6ea3889c1ad8..f44be223dec807 100644 --- a/packages/scripts/utils/string.js +++ b/packages/scripts/utils/string.js @@ -9,9 +9,8 @@ * @return {string} Camel-cased string. */ function camelCaseDash( string ) { - return string.replace( - /-([a-z])/g, - ( match, letter ) => letter.toUpperCase() + return string.replace( /-([a-z])/g, ( match, letter ) => + letter.toUpperCase() ); } diff --git a/packages/scripts/utils/test/index.js b/packages/scripts/utils/test/index.js index 778c51dedb6e9f..45c2e27339e356 100644 --- a/packages/scripts/utils/test/index.js +++ b/packages/scripts/utils/test/index.js @@ -6,14 +6,8 @@ import crossSpawn from 'cross-spawn'; /** * Internal dependencies */ -import { - hasArgInCLI, - hasProjectFile, - spawnScript, -} from '../'; -import { - getPackagePath as getPackagePathMock, -} from '../package'; +import { hasArgInCLI, hasProjectFile, spawnScript } from '../'; +import { getPackagePath as getPackagePathMock } from '../package'; import { exit as exitMock, getArgsFromCLI as getArgsFromCLIMock, @@ -40,7 +34,11 @@ describe( 'utils', () => { describe( 'hasArgInCLI', () => { beforeAll( () => { - getArgsFromCLIMock.mockReturnValue( [ '-a', '--b', '--config=test' ] ); + getArgsFromCLIMock.mockReturnValue( [ + '-a', + '--b', + '--config=test', + ] ); } ); afterAll( () => { @@ -97,7 +95,9 @@ describe( 'utils', () => { } ); test( 'should exit when an unknown script name provided', () => { - expect( () => spawnScript( 'unknown-script' ) ).toThrow( 'Exit code: 1.' ); + expect( () => spawnScript( 'unknown-script' ) ).toThrow( + 'Exit code: 1.' + ); expect( console ).toHaveLoggedWith( 'Unknown script "unknown-script". Perhaps you need to update @wordpress/scripts?' ); @@ -106,23 +106,31 @@ describe( 'utils', () => { test( 'should exit when the script failed because of SIGKILL signal', () => { crossSpawnMock.mockReturnValueOnce( { signal: 'SIGKILL' } ); - expect( () => spawnScript( scriptName ) ).toThrow( 'Exit code: 1.' ); + expect( () => spawnScript( scriptName ) ).toThrow( + 'Exit code: 1.' + ); expect( console ).toHaveLogged(); } ); test( 'should exit when the script failed because of SIGTERM signal', () => { crossSpawnMock.mockReturnValueOnce( { signal: 'SIGTERM' } ); - expect( () => spawnScript( scriptName ) ).toThrow( 'Exit code: 1.' ); + expect( () => spawnScript( scriptName ) ).toThrow( + 'Exit code: 1.' + ); expect( console ).toHaveLogged(); } ); test( 'should finish successfully when the script properly executed', () => { crossSpawnMock.mockReturnValueOnce( { status: 0 } ); - expect( () => spawnScript( scriptName ) ).toThrow( 'Exit code: 0.' ); + expect( () => spawnScript( scriptName ) ).toThrow( + 'Exit code: 0.' + ); expect( crossSpawnMock ).toHaveBeenCalledWith( - 'node', [ expect.stringContaining( scriptName ) ], { stdio: 'inherit' } + 'node', + [ expect.stringContaining( scriptName ) ], + { stdio: 'inherit' } ); } ); @@ -130,9 +138,13 @@ describe( 'utils', () => { crossSpawnMock.mockReturnValueOnce( { status: 0 } ); const args = [ '-a', '--bbb', '-c=ccccc' ]; - expect( () => spawnScript( scriptName, args ) ).toThrow( 'Exit code: 0.' ); + expect( () => spawnScript( scriptName, args ) ).toThrow( + 'Exit code: 0.' + ); expect( crossSpawnMock ).toHaveBeenCalledWith( - 'node', [ expect.stringContaining( scriptName ), ...args ], { stdio: 'inherit' } + 'node', + [ expect.stringContaining( scriptName ), ...args ], + { stdio: 'inherit' } ); } ); } ); diff --git a/packages/scripts/utils/test/string.js b/packages/scripts/utils/test/string.js index ca81bdda5df540..aa0f441186fa11 100644 --- a/packages/scripts/utils/test/string.js +++ b/packages/scripts/utils/test/string.js @@ -17,7 +17,9 @@ describe( 'string', () => { test( 'converts dashes into camel case', () => { expect( camelCaseDash( 'api-fetch' ) ).toBe( 'apiFetch' ); - expect( camelCaseDash( 'list-reusable-blocks' ) ).toBe( 'listReusableBlocks' ); + expect( camelCaseDash( 'list-reusable-blocks' ) ).toBe( + 'listReusableBlocks' + ); } ); } ); } ); diff --git a/packages/server-side-render/src/index.js b/packages/server-side-render/src/index.js index d4881bcecdd219..cec07d826d1c77 100644 --- a/packages/server-side-render/src/index.js +++ b/packages/server-side-render/src/index.js @@ -15,45 +15,37 @@ import ServerSideRender from './server-side-render'; */ const EMPTY_OBJECT = {}; -const ExportedServerSideRender = withSelect( - ( select ) => { - const coreEditorSelect = select( 'core/editor' ); - if ( coreEditorSelect ) { - const currentPostId = coreEditorSelect.getCurrentPostId(); - if ( currentPostId ) { - return { - currentPostId, - }; - } - } - return EMPTY_OBJECT; - } -)( - ( { urlQueryArgs = EMPTY_OBJECT, currentPostId, ...props } ) => { - const newUrlQueryArgs = useMemo( () => { - if ( ! currentPostId ) { - return urlQueryArgs; - } +const ExportedServerSideRender = withSelect( ( select ) => { + const coreEditorSelect = select( 'core/editor' ); + if ( coreEditorSelect ) { + const currentPostId = coreEditorSelect.getCurrentPostId(); + if ( currentPostId ) { return { - post_id: currentPostId, - ...urlQueryArgs, + currentPostId, }; - }, [ currentPostId, urlQueryArgs ] ); - - return ( - <ServerSideRender urlQueryArgs={ newUrlQueryArgs } { ...props } /> - ); + } } -); + return EMPTY_OBJECT; +} )( ( { urlQueryArgs = EMPTY_OBJECT, currentPostId, ...props } ) => { + const newUrlQueryArgs = useMemo( () => { + if ( ! currentPostId ) { + return urlQueryArgs; + } + return { + post_id: currentPostId, + ...urlQueryArgs, + }; + }, [ currentPostId, urlQueryArgs ] ); + + return <ServerSideRender urlQueryArgs={ newUrlQueryArgs } { ...props } />; +} ); if ( window && window.wp && window.wp.components ) { window.wp.components.ServerSideRender = forwardRef( ( props, ref ) => { deprecated( 'wp.components.ServerSideRender', { alternative: 'wp.serverSideRender', } ); - return ( - <ExportedServerSideRender { ...props } ref={ ref } /> - ); + return <ExportedServerSideRender { ...props } ref={ ref } />; } ); } diff --git a/packages/server-side-render/src/server-side-render.js b/packages/server-side-render/src/server-side-render.js index 03f5690102a771..8e2f93446095d4 100644 --- a/packages/server-side-render/src/server-side-render.js +++ b/packages/server-side-render/src/server-side-render.js @@ -6,17 +6,11 @@ import { isEqual, debounce } from 'lodash'; /** * WordPress dependencies */ -import { - Component, - RawHTML, -} from '@wordpress/element'; +import { Component, RawHTML } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; -import { - Placeholder, - Spinner, -} from '@wordpress/components'; +import { Placeholder, Spinner } from '@wordpress/components'; export function rendererPath( block, attributes = null, urlQueryArgs = {} ) { return addQueryArgs( `/wp/v2/block-renderer/${ block }`, { @@ -64,46 +58,66 @@ export class ServerSideRender extends Component { const path = rendererPath( block, attributes, urlQueryArgs ); // Store the latest fetch request so that when we process it, we can // check if it is the current request, to avoid race conditions on slow networks. - const fetchRequest = this.currentFetchRequest = apiFetch( { path } ) + const fetchRequest = ( this.currentFetchRequest = apiFetch( { path } ) .then( ( response ) => { - if ( this.isStillMounted && fetchRequest === this.currentFetchRequest && response ) { + if ( + this.isStillMounted && + fetchRequest === this.currentFetchRequest && + response + ) { this.setState( { response: response.rendered } ); } } ) .catch( ( error ) => { - if ( this.isStillMounted && fetchRequest === this.currentFetchRequest ) { - this.setState( { response: { - error: true, - errorMsg: error.message, - } } ); + if ( + this.isStillMounted && + fetchRequest === this.currentFetchRequest + ) { + this.setState( { + response: { + error: true, + errorMsg: error.message, + }, + } ); } - } ); + } ) ); return fetchRequest; } render() { const response = this.state.response; - const { className, EmptyResponsePlaceholder, ErrorResponsePlaceholder, LoadingResponsePlaceholder } = this.props; + const { + className, + EmptyResponsePlaceholder, + ErrorResponsePlaceholder, + LoadingResponsePlaceholder, + } = this.props; if ( response === '' ) { return ( - <EmptyResponsePlaceholder response={ response } { ...this.props } /> + <EmptyResponsePlaceholder + response={ response } + { ...this.props } + /> ); } else if ( ! response ) { return ( - <LoadingResponsePlaceholder response={ response } { ...this.props } /> + <LoadingResponsePlaceholder + response={ response } + { ...this.props } + /> ); } else if ( response.error ) { return ( - <ErrorResponsePlaceholder response={ response } { ...this.props } /> + <ErrorResponsePlaceholder + response={ response } + { ...this.props } + /> ); } return ( - <RawHTML - key="html" - className={ className } - > + <RawHTML key="html" className={ className }> { response } </RawHTML> ); @@ -112,28 +126,23 @@ export class ServerSideRender extends Component { ServerSideRender.defaultProps = { EmptyResponsePlaceholder: ( { className } ) => ( - <Placeholder - className={ className } - > + <Placeholder className={ className }> { __( 'Block rendered as empty.' ) } </Placeholder> ), ErrorResponsePlaceholder: ( { response, className } ) => { // translators: %s: error message describing the problem - const errorMessage = sprintf( __( 'Error loading block: %s' ), response.errorMsg ); + const errorMessage = sprintf( + __( 'Error loading block: %s' ), + response.errorMsg + ); return ( - <Placeholder - className={ className } - > - { errorMessage } - </Placeholder> + <Placeholder className={ className }>{ errorMessage }</Placeholder> ); }, LoadingResponsePlaceholder: ( { className } ) => { return ( - <Placeholder - className={ className } - > + <Placeholder className={ className }> <Spinner /> </Placeholder> ); diff --git a/packages/server-side-render/src/test/index.js b/packages/server-side-render/src/test/index.js index b97a80aa91b2b8..508e58a0d70202 100644 --- a/packages/server-side-render/src/test/index.js +++ b/packages/server-side-render/src/test/index.js @@ -5,8 +5,12 @@ import { rendererPath } from '../server-side-render'; describe( 'rendererPath', function() { test( 'should return an base path for empty input', function() { - expect( rendererPath( 'core/test-block', null ) ).toBe( '/wp/v2/block-renderer/core/test-block?context=edit' ); - expect( rendererPath( 'core/test-block' ) ).toBe( '/wp/v2/block-renderer/core/test-block?context=edit' ); + expect( rendererPath( 'core/test-block', null ) ).toBe( + '/wp/v2/block-renderer/core/test-block?context=edit' + ); + expect( rendererPath( 'core/test-block' ) ).toBe( + '/wp/v2/block-renderer/core/test-block?context=edit' + ); } ); test( 'should format basic url params ', function() { @@ -18,7 +22,7 @@ describe( 'rendererPath', function() { numberArg: 123, } ) ).toBe( - '/wp/v2/block-renderer/core/test-block?context=edit&attributes%5BstringArg%5D=test&attributes%5BnullArg%5D=&attributes%5BemptyArg%5D=&attributes%5BnumberArg%5D=123', + '/wp/v2/block-renderer/core/test-block?context=edit&attributes%5BstringArg%5D=test&attributes%5BnullArg%5D=&attributes%5BemptyArg%5D=&attributes%5BnumberArg%5D=123' ); } ); @@ -31,7 +35,7 @@ describe( 'rendererPath', function() { }, } ) ).toBe( - '/wp/v2/block-renderer/core/test-block?context=edit&attributes%5BobjectArg%5D%5BstringProp%5D=test&attributes%5BobjectArg%5D%5BnumberProp%5D=123', + '/wp/v2/block-renderer/core/test-block?context=edit&attributes%5BobjectArg%5D%5BstringProp%5D=test&attributes%5BobjectArg%5D%5BnumberProp%5D=123' ); } ); @@ -52,14 +56,15 @@ describe( 'rendererPath', function() { ], } ) ).toBe( - '/wp/v2/block-renderer/core/test-block?context=edit&attributes%5Bchildren%5D%5B0%5D%5Bname%5D=bobby&attributes%5Bchildren%5D%5B0%5D%5Bage%5D=12&attributes%5Bchildren%5D%5B0%5D%5Bsex%5D=M&attributes%5Bchildren%5D%5B1%5D%5Bname%5D=sally&attributes%5Bchildren%5D%5B1%5D%5Bage%5D=8&attributes%5Bchildren%5D%5B1%5D%5Bsex%5D=F', + '/wp/v2/block-renderer/core/test-block?context=edit&attributes%5Bchildren%5D%5B0%5D%5Bname%5D=bobby&attributes%5Bchildren%5D%5B0%5D%5Bage%5D=12&attributes%5Bchildren%5D%5B0%5D%5Bsex%5D=M&attributes%5Bchildren%5D%5B1%5D%5Bname%5D=sally&attributes%5Bchildren%5D%5B1%5D%5Bage%5D=8&attributes%5Bchildren%5D%5B1%5D%5Bsex%5D=F' ); } ); test( 'should include urlQueryArgs', function() { expect( rendererPath( - 'core/test-block', { + 'core/test-block', + { stringArg: 'test', }, { @@ -67,7 +72,7 @@ describe( 'rendererPath', function() { } ) ).toBe( - '/wp/v2/block-renderer/core/test-block?context=edit&attributes%5BstringArg%5D=test&id=1234', + '/wp/v2/block-renderer/core/test-block?context=edit&attributes%5BstringArg%5D=test&id=1234' ); } ); } ); diff --git a/packages/shortcode/src/index.js b/packages/shortcode/src/index.js index ef609e4a3d170d..6c12810fce70de 100644 --- a/packages/shortcode/src/index.js +++ b/packages/shortcode/src/index.js @@ -90,7 +90,16 @@ export function next( tag, text, index = 0 ) { * @return {string} Text with shortcodes replaced. */ export function replace( tag, text, callback ) { - return text.replace( regexp( tag ), function( match, left, $3, attrs, slash, content, closing, right ) { + return text.replace( regexp( tag ), function( + match, + left, + $3, + attrs, + slash, + content, + closing, + right + ) { // If both extra brackets exist, the shortcode has been properly // escaped. if ( left === '[' && right === ']' ) { @@ -144,7 +153,12 @@ export function string( options ) { * @return {RegExp} Shortcode RegExp. */ export function regexp( tag ) { - return new RegExp( '\\[(\\[?)(' + tag + ')(?![\\w-])([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)(\\[\\/\\2\\]))?)(\\]?)', 'g' ); + return new RegExp( + '\\[(\\[?)(' + + tag + + ')(?![\\w-])([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)(\\[\\/\\2\\]))?)(\\]?)', + 'g' + ); } /** @@ -251,44 +265,51 @@ export function fromMatch( match ) { * * @return {WPShortcode} Shortcode instance. */ -const shortcode = extend( function( options ) { - extend( this, pick( options || {}, 'tag', 'attrs', 'type', 'content' ) ); - - const attributes = this.attrs; - - // Ensure we have a correctly formatted `attrs` object. - this.attrs = { - named: {}, - numeric: [], - }; - - if ( ! attributes ) { - return; - } +const shortcode = extend( + function( options ) { + extend( + this, + pick( options || {}, 'tag', 'attrs', 'type', 'content' ) + ); + + const attributes = this.attrs; + + // Ensure we have a correctly formatted `attrs` object. + this.attrs = { + named: {}, + numeric: [], + }; + + if ( ! attributes ) { + return; + } - // Parse a string of attributes. - if ( isString( attributes ) ) { - this.attrs = attrs( attributes ); - // Identify a correctly formatted `attrs` object. - } else if ( isEqual( Object.keys( attributes ), [ 'named', 'numeric' ] ) ) { - this.attrs = attributes; - // Handle a flat object of attributes. - } else { - forEach( attributes, ( value, key ) => { - this.set( key, value ); - } ); + // Parse a string of attributes. + if ( isString( attributes ) ) { + this.attrs = attrs( attributes ); + // Identify a correctly formatted `attrs` object. + } else if ( + isEqual( Object.keys( attributes ), [ 'named', 'numeric' ] ) + ) { + this.attrs = attributes; + // Handle a flat object of attributes. + } else { + forEach( attributes, ( value, key ) => { + this.set( key, value ); + } ); + } + }, + { + next, + replace, + string, + regexp, + attrs, + fromMatch, } -}, { - next, - replace, - string, - regexp, - attrs, - fromMatch, -} ); +); extend( shortcode.prototype, { - /** * Get a shortcode attribute. * @@ -357,7 +378,6 @@ extend( shortcode.prototype, { // Add the closing tag. return text + '[/' + this.tag + ']'; }, - } ); export default shortcode; diff --git a/packages/shortcode/src/test/index.js b/packages/shortcode/src/test/index.js index 9b3ca086abbec1..6d2af91ee70b47 100644 --- a/packages/shortcode/src/test/index.js +++ b/packages/shortcode/src/test/index.js @@ -11,7 +11,10 @@ describe( 'shortcode', () => { } ); it( 'should find the shortcode with attributes', () => { - const result = next( 'foo', 'this has the [foo param="foo"] shortcode' ); + const result = next( + 'foo', + 'this has the [foo param="foo"] shortcode' + ); expect( result.index ).toBe( 13 ); } ); @@ -21,7 +24,10 @@ describe( 'shortcode', () => { } ); it( 'should not find shortcodes with attributes that are not there', () => { - const result = next( 'bar', 'this has the [foo param="bar"] shortcode' ); + const result = next( + 'bar', + 'this has the [foo param="bar"] shortcode' + ); expect( result ).toBe( undefined ); } ); @@ -37,7 +43,11 @@ describe( 'shortcode', () => { } ); it( 'should find the second instances of the shortcode when the starting indice is after the start of the first one', () => { - const result = next( 'foo', 'this has the [foo] shortcode [foo] twice', 14 ); + const result = next( + 'foo', + 'this has the [foo] shortcode [foo] twice', + 14 + ); expect( result.index ).toBe( 29 ); } ); @@ -47,7 +57,10 @@ describe( 'shortcode', () => { } ); it( 'should not find escaped shortcodes with attributes', () => { - const result = next( 'foo', 'this has the [[foo param="foo"]] shortcode' ); + const result = next( + 'foo', + 'this has the [[foo param="foo"]] shortcode' + ); expect( result ).toBe( undefined ); } ); @@ -68,7 +81,10 @@ describe( 'shortcode', () => { } ); it( 'should find the second instances of the shortcode when the first one is escaped', () => { - const result = next( 'foo', 'this has the [[foo]] shortcode [foo] twice' ); + const result = next( + 'foo', + 'this has the [[foo]] shortcode [foo] twice' + ); expect( result.index ).toBe( 31 ); } ); @@ -83,60 +99,124 @@ describe( 'shortcode', () => { describe( 'replace', () => { it( 'should replace the shortcode', () => { - const result1 = replace( 'foo', 'this has the [foo] shortcode', () => 'bar' ); + const result1 = replace( + 'foo', + 'this has the [foo] shortcode', + () => 'bar' + ); expect( result1 ).toBe( 'this has the bar shortcode' ); - const result2 = replace( 'foo', 'this has the [foo param="foo"] shortcode', () => 'bar' ); + const result2 = replace( + 'foo', + 'this has the [foo param="foo"] shortcode', + () => 'bar' + ); expect( result2 ).toBe( 'this has the bar shortcode' ); } ); it( 'should replace the shortcode with data from an attribute', () => { - const result1 = replace( 'foo', 'this [foo param="replacement text"] came from a shortcode attribute', ( match ) => { - return match.attrs.named.param || ''; - } ); - expect( result1 ).toBe( 'this replacement text came from a shortcode attribute' ); + const result1 = replace( + 'foo', + 'this [foo param="replacement text"] came from a shortcode attribute', + ( match ) => { + return match.attrs.named.param || ''; + } + ); + expect( result1 ).toBe( + 'this replacement text came from a shortcode attribute' + ); } ); it( 'should not replace the shortcode when it does not match', () => { - const result1 = replace( 'bar', 'this has the [foo] shortcode', () => 'bar' ); + const result1 = replace( + 'bar', + 'this has the [foo] shortcode', + () => 'bar' + ); expect( result1 ).toBe( 'this has the [foo] shortcode' ); - const result2 = replace( 'bar', 'this has the [foo param="bar"] shortcode', () => 'bar' ); - expect( result2 ).toBe( 'this has the [foo param="bar"] shortcode' ); + const result2 = replace( + 'bar', + 'this has the [foo param="bar"] shortcode', + () => 'bar' + ); + expect( result2 ).toBe( + 'this has the [foo param="bar"] shortcode' + ); } ); it( 'should replace the shortcode in all instances of its use', () => { - const result1 = replace( 'foo', 'this has the [foo] shortcode [foo] twice', () => 'bar' ); + const result1 = replace( + 'foo', + 'this has the [foo] shortcode [foo] twice', + () => 'bar' + ); expect( result1 ).toBe( 'this has the bar shortcode bar twice' ); - const result2 = replace( 'foo', 'this has the [foo param="foo"] shortcode [foo] twice', () => 'bar' ); + const result2 = replace( + 'foo', + 'this has the [foo param="foo"] shortcode [foo] twice', + () => 'bar' + ); expect( result2 ).toBe( 'this has the bar shortcode bar twice' ); } ); it( 'should not replace the escaped shortcodes', () => { - const result1 = replace( 'foo', 'this has the [[foo]] shortcode', () => 'bar' ); + const result1 = replace( + 'foo', + 'this has the [[foo]] shortcode', + () => 'bar' + ); expect( result1 ).toBe( 'this has the [[foo]] shortcode' ); - const result2 = replace( 'foo', 'this has the [[foo param="bar"]] shortcode', () => 'bar' ); - expect( result2 ).toBe( 'this has the [[foo param="bar"]] shortcode' ); - - const result3 = replace( 'foo', 'this [foo] has the [[foo param="bar"]] shortcode escaped', () => 'bar' ); - expect( result3 ).toBe( 'this bar has the [[foo param="bar"]] shortcode escaped' ); + const result2 = replace( + 'foo', + 'this has the [[foo param="bar"]] shortcode', + () => 'bar' + ); + expect( result2 ).toBe( + 'this has the [[foo param="bar"]] shortcode' + ); + + const result3 = replace( + 'foo', + 'this [foo] has the [[foo param="bar"]] shortcode escaped', + () => 'bar' + ); + expect( result3 ).toBe( + 'this bar has the [[foo param="bar"]] shortcode escaped' + ); } ); it( 'should replace improperly escaped shortcodes that include newlines', () => { - const result1 = replace( 'foo', 'this [foo] has the [[foo param="bar"]\n] shortcode ', () => 'bar' ); + const result1 = replace( + 'foo', + 'this [foo] has the [[foo param="bar"]\n] shortcode ', + () => 'bar' + ); expect( result1 ).toBe( 'this bar has the [bar\n] shortcode ' ); - const result2 = replace( 'foo', 'this [foo] has the [\n[foo param="bar"]] shortcode ', () => 'bar' ); + const result2 = replace( + 'foo', + 'this [foo] has the [\n[foo param="bar"]] shortcode ', + () => 'bar' + ); expect( result2 ).toBe( 'this bar has the [\nbar] shortcode ' ); } ); it( 'should not replace the shortcode when it is an incomplete match', () => { - const result1 = replace( 'foo', 'this has the [foobar] shortcode', () => 'bar' ); + const result1 = replace( + 'foo', + 'this has the [foobar] shortcode', + () => 'bar' + ); expect( result1 ).toBe( 'this has the [foobar] shortcode' ); - const result2 = replace( 'foobar', 'this has the [foo] shortcode', () => 'bar' ); + const result2 = replace( + 'foobar', + 'this has the [foo] shortcode', + () => 'bar' + ); expect( result2 ).toBe( 'this has the [foo] shortcode' ); } ); } ); diff --git a/packages/token-list/src/index.js b/packages/token-list/src/index.js index 91bfe60c16a5ec..08f63abfa26128 100644 --- a/packages/token-list/src/index.js +++ b/packages/token-list/src/index.js @@ -76,7 +76,7 @@ export default class TokenList { * * @return {IterableIterator<string>} TokenList iterator. */ - * [ Symbol.iterator ]() { + *[ Symbol.iterator ]() { return yield* this._valueAsArray; } diff --git a/packages/token-list/src/test/index.js b/packages/token-list/src/test/index.js index d221abd620370e..e208742d0fc6cc 100644 --- a/packages/token-list/src/test/index.js +++ b/packages/token-list/src/test/index.js @@ -80,7 +80,9 @@ describe( 'token-list', () => { it( 'returns a generator', () => { const list = new TokenList(); - expect( list[ Symbol.iterator ]().next ).toEqual( expect.any( Function ) ); + expect( list[ Symbol.iterator ]().next ).toEqual( + expect.any( Function ) + ); } ); it( 'yields entries', () => { diff --git a/packages/url/src/clean-for-slug.js b/packages/url/src/clean-for-slug.js index 58c814eced030a..4a1b3317d43dd9 100644 --- a/packages/url/src/clean-for-slug.js +++ b/packages/url/src/clean-for-slug.js @@ -23,5 +23,7 @@ export function cleanForSlug( string ) { if ( ! string ) { return ''; } - return toLower( deburr( trim( string.replace( /[\s\./_]+/g, '-' ), '-' ) ) ); + return toLower( + deburr( trim( string.replace( /[\s\./_]+/g, '-' ), '-' ) ) + ); } diff --git a/packages/url/src/get-authority.js b/packages/url/src/get-authority.js index 68315432f07cde..f29afd5411c11d 100644 --- a/packages/url/src/get-authority.js +++ b/packages/url/src/get-authority.js @@ -12,7 +12,9 @@ * @return {string|void} The authority part of the URL. */ export function getAuthority( url ) { - const matches = /^[^\/\s:]+:(?:\/\/)?\/?([^\/\s#?]+)[\/#?]{0,1}\S*$/.exec( url ); + const matches = /^[^\/\s:]+:(?:\/\/)?\/?([^\/\s#?]+)[\/#?]{0,1}\S*$/.exec( + url + ); if ( matches ) { return matches[ 1 ]; } diff --git a/packages/url/src/get-path.js b/packages/url/src/get-path.js index ddc79b4963c212..2240a668419547 100644 --- a/packages/url/src/get-path.js +++ b/packages/url/src/get-path.js @@ -12,7 +12,9 @@ * @return {string|void} The path part of the URL. */ export function getPath( url ) { - const matches = /^[^\/\s:]+:(?:\/\/)?[^\/\s#?]+[\/]([^\s#?]+)[#?]{0,1}\S*$/.exec( url ); + const matches = /^[^\/\s:]+:(?:\/\/)?[^\/\s#?]+[\/]([^\s#?]+)[#?]{0,1}\S*$/.exec( + url + ); if ( matches ) { return matches[ 1 ]; } diff --git a/packages/url/src/get-query-arg.js b/packages/url/src/get-query-arg.js index 2dffc6fd4135a7..38330aed3ac458 100644 --- a/packages/url/src/get-query-arg.js +++ b/packages/url/src/get-query-arg.js @@ -26,7 +26,10 @@ import { parse } from 'qs'; */ export function getQueryArg( url, arg ) { const queryStringIndex = url.indexOf( '?' ); - const query = queryStringIndex !== -1 ? parse( url.substr( queryStringIndex + 1 ) ) : {}; + const query = + queryStringIndex !== -1 + ? parse( url.substr( queryStringIndex + 1 ) ) + : {}; return query[ arg ]; } diff --git a/packages/url/src/remove-query-args.js b/packages/url/src/remove-query-args.js index 58dc1edd8ebab2..796e46e0ab980c 100644 --- a/packages/url/src/remove-query-args.js +++ b/packages/url/src/remove-query-args.js @@ -18,8 +18,12 @@ import { parse, stringify } from 'qs'; */ export function removeQueryArgs( url, ...args ) { const queryStringIndex = url.indexOf( '?' ); - const query = queryStringIndex !== -1 ? parse( url.substr( queryStringIndex + 1 ) ) : {}; - const baseUrl = queryStringIndex !== -1 ? url.substr( 0, queryStringIndex ) : url; + const query = + queryStringIndex !== -1 + ? parse( url.substr( queryStringIndex + 1 ) ) + : {}; + const baseUrl = + queryStringIndex !== -1 ? url.substr( 0, queryStringIndex ) : url; args.forEach( ( arg ) => delete query[ arg ] ); diff --git a/packages/url/src/test/index.test.js b/packages/url/src/test/index.test.js index aa1ef282f5d066..7aa1247396eeaf 100644 --- a/packages/url/src/test/index.test.js +++ b/packages/url/src/test/index.test.js @@ -42,7 +42,7 @@ describe( 'isURL', () => { expect( every( urls, isURL ) ).toBe( true ); } ); - it( 'returns false when given things that don\'t look like a URL', () => { + it( "returns false when given things that don't look like a URL", () => { const urls = [ 'HTTP: HyperText Transfer Protocol', 'URLs begin with a http:// prefix', @@ -72,14 +72,14 @@ describe( 'isEmail', () => { expect( every( emails, isEmail ) ).toBe( true ); } ); - it( 'returns false when given things that don\'t look like an email', () => { + it( "returns false when given things that don't look like an email", () => { const emails = [ 'Abc.wordpress.org', 'A@b@c@wordpress.org', - 'a"b(c)d,e:f;g<h>i[j\k]l@wordpress.org', + 'a"b(c)d,e:f;g<h>i[jk]l@wordpress.org', 'just"not"right@wordpress.org', - 'this is"not\allowed@wordpress.org', - 'this\ still\"not\\allowed@wordpress.org', + 'this is"notallowed@wordpress.org', + 'this still"not\\allowed@wordpress.org', '1234567890123456789012345678901234567890123456789012345678901234+x@wordpress.org', ]; @@ -89,8 +89,16 @@ describe( 'isEmail', () => { describe( 'getProtocol', () => { it( 'returns the protocol part of a URL', () => { - expect( getProtocol( 'http://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more' ) ).toBe( 'http:' ); - expect( getProtocol( 'https://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more' ) ).toBe( 'https:' ); + expect( + getProtocol( + 'http://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more' + ) + ).toBe( 'http:' ); + expect( + getProtocol( + 'https://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more' + ) + ).toBe( 'https:' ); expect( getProtocol( 'https://wordpress.org#test' ) ).toBe( 'https:' ); expect( getProtocol( 'https://wordpress.org/' ) ).toBe( 'https:' ); expect( getProtocol( 'https://wordpress.org?test' ) ).toBe( 'https:' ); @@ -134,12 +142,28 @@ describe( 'isValidProtocol', () => { describe( 'getAuthority', () => { it( 'returns the authority part of a URL', () => { - expect( getAuthority( 'https://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more' ) ).toBe( 'user:password@www.test-this.com:1020' ); - expect( getAuthority( 'http://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more' ) ).toBe( 'user:password@www.test-this.com:1020' ); - expect( getAuthority( 'https://wordpress.org#test' ) ).toBe( 'wordpress.org' ); - expect( getAuthority( 'https://wordpress.org/' ) ).toBe( 'wordpress.org' ); - expect( getAuthority( 'https://wordpress.org?test' ) ).toBe( 'wordpress.org' ); - expect( getAuthority( 'https://localhost:8080' ) ).toBe( 'localhost:8080' ); + expect( + getAuthority( + 'https://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more' + ) + ).toBe( 'user:password@www.test-this.com:1020' ); + expect( + getAuthority( + 'http://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more' + ) + ).toBe( 'user:password@www.test-this.com:1020' ); + expect( getAuthority( 'https://wordpress.org#test' ) ).toBe( + 'wordpress.org' + ); + expect( getAuthority( 'https://wordpress.org/' ) ).toBe( + 'wordpress.org' + ); + expect( getAuthority( 'https://wordpress.org?test' ) ).toBe( + 'wordpress.org' + ); + expect( getAuthority( 'https://localhost:8080' ) ).toBe( + 'localhost:8080' + ); } ); it( 'returns undefined when the provided value does not contain a URL authority', () => { @@ -155,7 +179,9 @@ describe( 'getAuthority', () => { describe( 'isValidAuthority', () => { it( 'returns true if the authority is valid', () => { - expect( isValidAuthority( 'user:password@www.test-this.com:1020' ) ).toBe( true ); + expect( + isValidAuthority( 'user:password@www.test-this.com:1020' ) + ).toBe( true ); expect( isValidAuthority( 'wordpress.org' ) ).toBe( true ); expect( isValidAuthority( 'localhost' ) ).toBe( true ); expect( isValidAuthority( 'localhost:8080' ) ).toBe( true ); @@ -174,13 +200,33 @@ describe( 'isValidAuthority', () => { describe( 'getPath', () => { it( 'returns the path part of a URL', () => { - expect( getPath( 'https://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more' ) ).toBe( 'test-path/file.extension' ); - expect( getPath( 'http://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more' ) ).toBe( 'test-path/file.extension' ); - expect( getPath( 'https://wordpress.org/test-path#anchor' ) ).toBe( 'test-path' ); - expect( getPath( 'https://wordpress.org/test-path?query' ) ).toBe( 'test-path' ); - expect( getPath( 'https://www.google.com/search?source=hp&ei=tP7kW8-_FoK89QORoa2QBQ&q=test+url&oq=test+url&gs_l=psy-ab.3..0l10' ) ).toBe( 'search' ); - expect( getPath( 'https://wordpress.org/this%20is%20a%20test' ) ).toBe( 'this%20is%20a%20test' ); - expect( getPath( 'https://wordpress.org/this%20is%20a%20test?query' ) ).toBe( 'this%20is%20a%20test' ); + expect( + getPath( + 'https://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more' + ) + ).toBe( 'test-path/file.extension' ); + expect( + getPath( + 'http://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more' + ) + ).toBe( 'test-path/file.extension' ); + expect( getPath( 'https://wordpress.org/test-path#anchor' ) ).toBe( + 'test-path' + ); + expect( getPath( 'https://wordpress.org/test-path?query' ) ).toBe( + 'test-path' + ); + expect( + getPath( + 'https://www.google.com/search?source=hp&ei=tP7kW8-_FoK89QORoa2QBQ&q=test+url&oq=test+url&gs_l=psy-ab.3..0l10' + ) + ).toBe( 'search' ); + expect( getPath( 'https://wordpress.org/this%20is%20a%20test' ) ).toBe( + 'this%20is%20a%20test' + ); + expect( + getPath( 'https://wordpress.org/this%20is%20a%20test?query' ) + ).toBe( 'this%20is%20a%20test' ); } ); it( 'returns undefined when the provided value does not contain a URL path', () => { @@ -221,23 +267,58 @@ describe( 'isValidPath', () => { describe( 'getQueryString', () => { it( 'returns the query string of a URL', () => { - expect( getQueryString( 'https://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more' ) ).toBe( 'query=params&more' ); - expect( getQueryString( 'http://user:password@www.test-this.com:1020/test-path/file.extension?query=params&more#anchor' ) ).toBe( 'query=params&more' ); - expect( getQueryString( 'https://wordpress.org/test-path?query' ) ).toBe( 'query' ); - expect( getQueryString( 'https://www.google.com/search?source=hp&ei=tP7kW8-_FoK89QORoa2QBQ&q=test+url&oq=test+url&gs_l=psy-ab.3..0l10' ) ) - .toBe( 'source=hp&ei=tP7kW8-_FoK89QORoa2QBQ&q=test+url&oq=test+url&gs_l=psy-ab.3..0l10' ); - expect( getQueryString( 'https://wordpress.org/this%20is%20a%20test?query' ) ).toBe( 'query' ); - expect( getQueryString( 'https://wordpress.org/test?query=something%20with%20spaces' ) ).toBe( 'query=something%20with%20spaces' ); - expect( getQueryString( 'https://andalouses.example/beach?foo[]=bar&foo[]=baz' ) ).toBe( 'foo[]=bar&foo[]=baz' ); - expect( getQueryString( 'test.com?foo[]=bar&foo[]=baz' ) ).toBe( 'foo[]=bar&foo[]=baz' ); - expect( getQueryString( 'test.com?foo=bar&foo=baz?test' ) ).toBe( 'foo=bar&foo=baz?test' ); + expect( + getQueryString( + 'https://user:password@www.test-this.com:1020/test-path/file.extension#anchor?query=params&more' + ) + ).toBe( 'query=params&more' ); + expect( + getQueryString( + 'http://user:password@www.test-this.com:1020/test-path/file.extension?query=params&more#anchor' + ) + ).toBe( 'query=params&more' ); + expect( + getQueryString( 'https://wordpress.org/test-path?query' ) + ).toBe( 'query' ); + expect( + getQueryString( + 'https://www.google.com/search?source=hp&ei=tP7kW8-_FoK89QORoa2QBQ&q=test+url&oq=test+url&gs_l=psy-ab.3..0l10' + ) + ).toBe( + 'source=hp&ei=tP7kW8-_FoK89QORoa2QBQ&q=test+url&oq=test+url&gs_l=psy-ab.3..0l10' + ); + expect( + getQueryString( 'https://wordpress.org/this%20is%20a%20test?query' ) + ).toBe( 'query' ); + expect( + getQueryString( + 'https://wordpress.org/test?query=something%20with%20spaces' + ) + ).toBe( 'query=something%20with%20spaces' ); + expect( + getQueryString( + 'https://andalouses.example/beach?foo[]=bar&foo[]=baz' + ) + ).toBe( 'foo[]=bar&foo[]=baz' ); + expect( getQueryString( 'test.com?foo[]=bar&foo[]=baz' ) ).toBe( + 'foo[]=bar&foo[]=baz' + ); + expect( getQueryString( 'test.com?foo=bar&foo=baz?test' ) ).toBe( + 'foo=bar&foo=baz?test' + ); } ); it( 'returns undefined when the provided does not contain a url query string', () => { expect( getQueryString( '' ) ).toBeUndefined(); - expect( getQueryString( 'https://wordpress.org/test-path#anchor' ) ).toBeUndefined(); - expect( getQueryString( 'https://wordpress.org/this%20is%20a%20test' ) ).toBeUndefined(); - expect( getQueryString( 'https://wordpress.org#test' ) ).toBeUndefined(); + expect( + getQueryString( 'https://wordpress.org/test-path#anchor' ) + ).toBeUndefined(); + expect( + getQueryString( 'https://wordpress.org/this%20is%20a%20test' ) + ).toBeUndefined(); + expect( + getQueryString( 'https://wordpress.org#test' ) + ).toBeUndefined(); expect( getQueryString( 'https://wordpress.org/' ) ).toBeUndefined(); expect( getQueryString( 'https://localhost:8080' ) ).toBeUndefined(); expect( getQueryString( 'https://' ) ).toBeUndefined(); @@ -254,9 +335,17 @@ describe( 'isValidQueryString', () => { expect( isValidQueryString( 'test=true' ) ).toBe( true ); expect( isValidQueryString( 'test=true&another' ) ).toBe( true ); expect( isValidQueryString( 'test=true&another=false' ) ).toBe( true ); - expect( isValidQueryString( 'test[]=true&another[]=false' ) ).toBe( true ); - expect( isValidQueryString( 'query=something%20with%20spaces' ) ).toBe( true ); - expect( isValidQueryString( 'source=hp&ei=tP7kW8-_FoK89QORoa2QBQ&q=test+url&oq=test+url&gs_l=psy-ab.3..0l10' ) ).toBe( true ); + expect( isValidQueryString( 'test[]=true&another[]=false' ) ).toBe( + true + ); + expect( isValidQueryString( 'query=something%20with%20spaces' ) ).toBe( + true + ); + expect( + isValidQueryString( + 'source=hp&ei=tP7kW8-_FoK89QORoa2QBQ&q=test+url&oq=test+url&gs_l=psy-ab.3..0l10' + ) + ).toBe( true ); } ); it( 'returns false if the query string is invalid', () => { @@ -276,18 +365,36 @@ describe( 'isValidQueryString', () => { describe( 'getFragment', () => { it( 'returns the fragment of a URL', () => { - expect( getFragment( 'https://user:password@www.test-this.com:1020/test-path/file.extension#fragment?query=params&more' ) ).toBe( '#fragment' ); - expect( getFragment( 'http://user:password@www.test-this.com:1020/test-path/file.extension?query=params&more#fragment' ) ).toBe( '#fragment' ); + expect( + getFragment( + 'https://user:password@www.test-this.com:1020/test-path/file.extension#fragment?query=params&more' + ) + ).toBe( '#fragment' ); + expect( + getFragment( + 'http://user:password@www.test-this.com:1020/test-path/file.extension?query=params&more#fragment' + ) + ).toBe( '#fragment' ); expect( getFragment( 'relative/url/#fragment' ) ).toBe( '#fragment' ); expect( getFragment( '/absolute/url/#fragment' ) ).toBe( '#fragment' ); } ); it( 'returns undefined when the provided does not contain a url fragment', () => { expect( getFragment( '' ) ).toBeUndefined(); - expect( getFragment( 'https://wordpress.org/test-path?query' ) ).toBeUndefined(); - expect( getFragment( 'https://wordpress.org/test-path' ) ).toBeUndefined(); - expect( getFragment( 'https://wordpress.org/this%20is%20a%20test' ) ).toBeUndefined(); - expect( getFragment( 'https://www.google.com/search?source=hp&ei=tP7kW8-_FoK89QORoa2QBQ&q=test+url&oq=test+url&gs_l=psy-ab.3..0l10' ) ).toBeUndefined(); + expect( + getFragment( 'https://wordpress.org/test-path?query' ) + ).toBeUndefined(); + expect( + getFragment( 'https://wordpress.org/test-path' ) + ).toBeUndefined(); + expect( + getFragment( 'https://wordpress.org/this%20is%20a%20test' ) + ).toBeUndefined(); + expect( + getFragment( + 'https://www.google.com/search?source=hp&ei=tP7kW8-_FoK89QORoa2QBQ&q=test+url&oq=test+url&gs_l=psy-ab.3..0l10' + ) + ).toBeUndefined(); expect( getFragment( 'https://wordpress.org' ) ).toBeUndefined(); expect( getFragment( 'https://localhost:8080' ) ).toBeUndefined(); expect( getFragment( 'https://' ) ).toBeUndefined(); @@ -323,42 +430,55 @@ describe( 'addQueryArgs', () => { const url = 'https://andalouses.example/beach'; const args = { sun: 'true', sand: 'false' }; - expect( addQueryArgs( url, args ) ).toBe( 'https://andalouses.example/beach?sun=true&sand=false' ); + expect( addQueryArgs( url, args ) ).toBe( + 'https://andalouses.example/beach?sun=true&sand=false' + ); } ); it( 'should append args to a URL with query string', () => { const url = 'https://andalouses.example/beach?night=false'; const args = { sun: 'true', sand: 'false' }; - expect( addQueryArgs( url, args ) ).toBe( 'https://andalouses.example/beach?night=false&sun=true&sand=false' ); + expect( addQueryArgs( url, args ) ).toBe( + 'https://andalouses.example/beach?night=false&sun=true&sand=false' + ); } ); it( 'should update args to an URL with conflicting query string', () => { - const url = 'https://andalouses.example/beach?night=false&sun=false&sand=true'; + const url = + 'https://andalouses.example/beach?night=false&sun=false&sand=true'; const args = { sun: 'true', sand: 'false' }; - expect( addQueryArgs( url, args ) ).toBe( 'https://andalouses.example/beach?night=false&sun=true&sand=false' ); + expect( addQueryArgs( url, args ) ).toBe( + 'https://andalouses.example/beach?night=false&sun=true&sand=false' + ); } ); it( 'should update args to an URL with array parameters', () => { const url = 'https://andalouses.example/beach?time[]=10&time[]=11'; const args = { beach: [ 'sand', 'rock' ] }; - expect( safeDecodeURI( addQueryArgs( url, args ) ) ).toBe( 'https://andalouses.example/beach?time[0]=10&time[1]=11&beach[0]=sand&beach[1]=rock' ); + expect( safeDecodeURI( addQueryArgs( url, args ) ) ).toBe( + 'https://andalouses.example/beach?time[0]=10&time[1]=11&beach[0]=sand&beach[1]=rock' + ); } ); it( 'should disregard keys with undefined values', () => { const url = 'https://andalouses.example/beach'; const args = { sun: 'true', sand: undefined }; - expect( addQueryArgs( url, args ) ).toBe( 'https://andalouses.example/beach?sun=true' ); + expect( addQueryArgs( url, args ) ).toBe( + 'https://andalouses.example/beach?sun=true' + ); } ); it( 'should encodes spaces by RFC 3986', () => { const url = 'https://andalouses.example/beach'; const args = { activity: 'fun in the sun' }; - expect( addQueryArgs( url, args ) ).toBe( 'https://andalouses.example/beach?activity=fun%20in%20the%20sun' ); + expect( addQueryArgs( url, args ) ).toBe( + 'https://andalouses.example/beach?activity=fun%20in%20the%20sun' + ); } ); it( 'should return only querystring when passed undefined url', () => { @@ -369,11 +489,13 @@ describe( 'addQueryArgs', () => { } ); it( 'should return URL argument unaffected if no query arguments to append', () => { - [ '', 'https://example.com', 'https://example.com?' ].forEach( ( url ) => { - [ undefined, {} ].forEach( ( args ) => { - expect( addQueryArgs( url, args ) ).toBe( url ); - } ); - } ); + [ '', 'https://example.com', 'https://example.com?' ].forEach( + ( url ) => { + [ undefined, {} ].forEach( ( args ) => { + expect( addQueryArgs( url, args ) ).toBe( url ); + } ); + } + ); } ); } ); @@ -433,13 +555,18 @@ describe( 'removeQueryArgs', () => { it( 'should remove existing query args', () => { const url = 'https://andalouses.example/beach?foo=bar&baz=foo&bar=baz'; - expect( removeQueryArgs( url, 'foo', 'bar' ) ).toEqual( 'https://andalouses.example/beach?baz=foo' ); + expect( removeQueryArgs( url, 'foo', 'bar' ) ).toEqual( + 'https://andalouses.example/beach?baz=foo' + ); } ); it( 'should remove array query arg', () => { - const url = 'https://andalouses.example/beach?foo[]=bar&foo[]=baz&bar=foobar'; + const url = + 'https://andalouses.example/beach?foo[]=bar&foo[]=baz&bar=foobar'; - expect( removeQueryArgs( url, 'foo' ) ).toEqual( 'https://andalouses.example/beach?bar=foobar' ); + expect( removeQueryArgs( url, 'foo' ) ).toEqual( + 'https://andalouses.example/beach?bar=foobar' + ); } ); } ); @@ -542,7 +669,9 @@ describe( 'filterURLForDisplay', () => { expect( url ).toBe( 'wordpress.org' ); } ); it( 'should preserve slashes where the url has multiple in the path', () => { - const url = filterURLForDisplay( 'http://www.wordpress.org/something/' ); + const url = filterURLForDisplay( + 'http://www.wordpress.org/something/' + ); expect( url ).toBe( 'wordpress.org/something/' ); } ); it( 'should preserve slash where the url has path after the initial slash', () => { diff --git a/packages/viewport/src/if-viewport-matches.js b/packages/viewport/src/if-viewport-matches.js index 76ea3c5650b562..dd6e299a7821d9 100644 --- a/packages/viewport/src/if-viewport-matches.js +++ b/packages/viewport/src/if-viewport-matches.js @@ -1,7 +1,11 @@ /** * WordPress dependencies */ -import { ifCondition, compose, createHigherOrderComponent } from '@wordpress/compose'; +import { + ifCondition, + compose, + createHigherOrderComponent, +} from '@wordpress/compose'; /** * Internal dependencies @@ -28,14 +32,15 @@ import withViewportMatch from './with-viewport-match'; * * @return {Function} Higher-order component. */ -const ifViewportMatches = ( query ) => createHigherOrderComponent( - compose( [ - withViewportMatch( { - isViewportMatch: query, - } ), - ifCondition( ( props ) => props.isViewportMatch ), - ] ), - 'ifViewportMatches' -); +const ifViewportMatches = ( query ) => + createHigherOrderComponent( + compose( [ + withViewportMatch( { + isViewportMatch: query, + } ), + ifCondition( ( props ) => props.isViewportMatch ), + ] ), + 'ifViewportMatches' + ); export default ifViewportMatches; diff --git a/packages/viewport/src/listener.js b/packages/viewport/src/listener.js index c8cbcaafdc5a0d..4d81e15cc3f31d 100644 --- a/packages/viewport/src/listener.js +++ b/packages/viewport/src/listener.js @@ -13,10 +13,13 @@ const addDimensionsEventListener = ( breakpoints, operators ) => { * Callback invoked when media query state should be updated. Is invoked a * maximum of one time per call stack. */ - const setIsMatching = debounce( () => { - const values = mapValues( queries, ( query ) => query.matches ); - dispatch( 'core/viewport' ).setIsMatching( values ); - }, { leading: true } ); + const setIsMatching = debounce( + () => { + const values = mapValues( queries, ( query ) => query.matches ); + dispatch( 'core/viewport' ).setIsMatching( values ); + }, + { leading: true } + ); /** * Hash of breakpoint names with generated MediaQueryList for corresponding @@ -27,17 +30,23 @@ const addDimensionsEventListener = ( breakpoints, operators ) => { * * @type {Object<string,MediaQueryList>} */ - const queries = reduce( breakpoints, ( result, width, name ) => { - forEach( operators, ( condition, operator ) => { - const list = window.matchMedia( `(${ condition }: ${ width }px)` ); - list.addListener( setIsMatching ); - - const key = [ operator, name ].join( ' ' ); - result[ key ] = list; - } ); - - return result; - }, {} ); + const queries = reduce( + breakpoints, + ( result, width, name ) => { + forEach( operators, ( condition, operator ) => { + const list = window.matchMedia( + `(${ condition }: ${ width }px)` + ); + list.addListener( setIsMatching ); + + const key = [ operator, name ].join( ' ' ); + result[ key ] = list; + } ); + + return result; + }, + {} + ); window.addEventListener( 'orientationchange', setIsMatching ); @@ -47,4 +56,3 @@ const addDimensionsEventListener = ( breakpoints, operators ) => { }; export default addDimensionsEventListener; - diff --git a/packages/viewport/src/listener.native.js b/packages/viewport/src/listener.native.js index 16f49bb84b16f0..064ffbb81505dd 100644 --- a/packages/viewport/src/listener.native.js +++ b/packages/viewport/src/listener.native.js @@ -21,14 +21,18 @@ const matchWidth = ( operator, breakpoint ) => { const addDimensionsEventListener = ( breakpoints, operators ) => { const setIsMatching = () => { - const matches = reduce( breakpoints, ( result, width, name ) => { - forEach( operators, ( condition, operator ) => { - const key = [ operator, name ].join( ' ' ); - result[ key ] = matchWidth( condition, width ); - } ); - - return result; - }, {} ); + const matches = reduce( + breakpoints, + ( result, width, name ) => { + forEach( operators, ( condition, operator ) => { + const key = [ operator, name ].join( ' ' ); + result[ key ] = matchWidth( condition, width ); + } ); + + return result; + }, + {} + ); dispatch( 'core/viewport' ).setIsMatching( matches ); }; diff --git a/packages/viewport/src/store/test/selectors.js b/packages/viewport/src/store/test/selectors.js index d05406ddfe6799..d78a43ec740c86 100644 --- a/packages/viewport/src/store/test/selectors.js +++ b/packages/viewport/src/store/test/selectors.js @@ -6,19 +6,25 @@ import { isViewportMatch } from '../selectors'; describe( 'selectors', () => { describe( 'isViewportMatch()', () => { it( 'should return with omitted operator defaulting to >=', () => { - const result = isViewportMatch( { - '>= wide': true, - '< wide': false, - }, 'wide' ); + const result = isViewportMatch( + { + '>= wide': true, + '< wide': false, + }, + 'wide' + ); expect( result ).toBe( true ); } ); it( 'should return with known query value', () => { - const result = isViewportMatch( { - '>= wide': false, - '< wide': true, - }, '< wide' ); + const result = isViewportMatch( + { + '>= wide': false, + '< wide': true, + }, + '< wide' + ); expect( result ).toBe( true ); } ); diff --git a/packages/viewport/src/test/if-viewport-matches.js b/packages/viewport/src/test/if-viewport-matches.js index 25a8f6b5b95dcb..8f0d4eb3cb27c7 100644 --- a/packages/viewport/src/test/if-viewport-matches.js +++ b/packages/viewport/src/test/if-viewport-matches.js @@ -30,9 +30,13 @@ describe( 'ifViewportMatches()', () => { testRenderer = TestRenderer.create( <EnhancedComponent /> ); } ); - expect( useViewportMatchMock.mock.calls ).toEqual( [ [ 'wide', '<' ] ] ); + expect( useViewportMatchMock.mock.calls ).toEqual( [ + [ 'wide', '<' ], + ] ); - expect( testRenderer.root.findAllByType( Component ) ).toHaveLength( 0 ); + expect( testRenderer.root.findAllByType( Component ) ).toHaveLength( + 0 + ); } ); it( 'should render if query does match', () => { @@ -43,8 +47,12 @@ describe( 'ifViewportMatches()', () => { testRenderer = TestRenderer.create( <EnhancedComponent /> ); } ); - expect( useViewportMatchMock.mock.calls ).toEqual( [ [ 'wide', '>=' ] ] ); + expect( useViewportMatchMock.mock.calls ).toEqual( [ + [ 'wide', '>=' ], + ] ); - expect( testRenderer.root.findAllByType( Component ) ).toHaveLength( 1 ); + expect( testRenderer.root.findAllByType( Component ) ).toHaveLength( + 1 + ); } ); } ); diff --git a/packages/viewport/src/test/with-viewport-match.js b/packages/viewport/src/test/with-viewport-match.js index 1cf40db7b0e344..cfc406bd124f17 100644 --- a/packages/viewport/src/test/with-viewport-match.js +++ b/packages/viewport/src/test/with-viewport-match.js @@ -39,15 +39,16 @@ describe( 'withViewportMatch()', () => { isSmall: '>= small', isLarge: 'large', isLessThanSmall: '< small', - } - )( ChildComponent ); + } )( ChildComponent ); useViewportMatchMock.mockReturnValueOnce( false ); useViewportMatchMock.mockReturnValueOnce( true ); useViewportMatchMock.mockReturnValueOnce( true ); useViewportMatchMock.mockReturnValueOnce( false ); - const wrapper = renderer.create( getTestComponent( EnhancedComponent ) ); + const wrapper = renderer.create( + getTestComponent( EnhancedComponent ) + ); expect( useViewportMatchMock.mock.calls ).toEqual( [ [ 'wide', '>=' ], diff --git a/packages/viewport/src/with-viewport-match.js b/packages/viewport/src/with-viewport-match.js index 0d67062b438a0a..935fdc675b4eec 100644 --- a/packages/viewport/src/with-viewport-match.js +++ b/packages/viewport/src/with-viewport-match.js @@ -6,7 +6,11 @@ import { mapValues } from 'lodash'; /** * WordPress dependencies */ -import { createHigherOrderComponent, pure, useViewportMatch } from '@wordpress/compose'; +import { + createHigherOrderComponent, + pure, + useViewportMatch, +} from '@wordpress/compose'; /** * Higher-order component creator, creating a new component which renders with @@ -32,32 +36,25 @@ import { createHigherOrderComponent, pure, useViewportMatch } from '@wordpress/c * @return {Function} Higher-order component. */ const withViewportMatch = ( queries ) => { - const useViewPortQueriesResult = () => mapValues( queries, ( query ) => { - let [ operator, breakpointName ] = query.split( ' ' ); - if ( breakpointName === undefined ) { - breakpointName = operator; - operator = '>='; - } - // Hooks should unconditionally execute in the same order, - // we are respecting that as from the static query of the HOC we generate - // a hook that calls other hooks always in the same order (because the query never changes). - // eslint-disable-next-line react-hooks/rules-of-hooks - return useViewportMatch( breakpointName, operator ); - } ); - return createHigherOrderComponent( - ( WrappedComponent ) => { - return pure( ( props ) => { - const queriesResult = useViewPortQueriesResult(); - return ( - <WrappedComponent - { ...props } - { ...queriesResult } - /> - ); - } ); - }, - 'withViewportMatch' - ); + const useViewPortQueriesResult = () => + mapValues( queries, ( query ) => { + let [ operator, breakpointName ] = query.split( ' ' ); + if ( breakpointName === undefined ) { + breakpointName = operator; + operator = '>='; + } + // Hooks should unconditionally execute in the same order, + // we are respecting that as from the static query of the HOC we generate + // a hook that calls other hooks always in the same order (because the query never changes). + // eslint-disable-next-line react-hooks/rules-of-hooks + return useViewportMatch( breakpointName, operator ); + } ); + return createHigherOrderComponent( ( WrappedComponent ) => { + return pure( ( props ) => { + const queriesResult = useViewPortQueriesResult(); + return <WrappedComponent { ...props } { ...queriesResult } />; + } ); + }, 'withViewportMatch' ); }; export default withViewportMatch; diff --git a/packages/viewport/src/with-viewport-match.native.js b/packages/viewport/src/with-viewport-match.native.js index 3398fda7e4f3f5..983aa22e365f83 100644 --- a/packages/viewport/src/with-viewport-match.native.js +++ b/packages/viewport/src/with-viewport-match.native.js @@ -32,13 +32,14 @@ import { withSelect } from '@wordpress/data'; * * @return {Function} Higher-order component. */ -const withViewportMatch = ( queries ) => createHigherOrderComponent( - withSelect( ( select ) => { - return mapValues( queries, ( query ) => { - return select( 'core/viewport' ).isViewportMatch( query ); - } ); - } ), - 'withViewportMatch' -); +const withViewportMatch = ( queries ) => + createHigherOrderComponent( + withSelect( ( select ) => { + return mapValues( queries, ( query ) => { + return select( 'core/viewport' ).isViewportMatch( query ); + } ); + } ), + 'withViewportMatch' + ); export default withViewportMatch; diff --git a/packages/warning/babel-plugin.js b/packages/warning/babel-plugin.js index 4b6d7f08cb63ac..7bbb2365a916ac 100644 --- a/packages/warning/babel-plugin.js +++ b/packages/warning/babel-plugin.js @@ -28,13 +28,21 @@ function babelPlugin( { types: t } ) { const nodeEnvCheckExpression = t.binaryExpression( '!==', - t.memberExpression( processEnvExpression, t.identifier( 'NODE_ENV' ), false ), + t.memberExpression( + processEnvExpression, + t.identifier( 'NODE_ENV' ), + false + ), t.stringLiteral( 'production' ) ); const logicalExpression = t.logicalExpression( '&&', - t.logicalExpression( '&&', typeofProcessExpression, processEnvExpression ), + t.logicalExpression( + '&&', + typeofProcessExpression, + processEnvExpression + ), nodeEnvCheckExpression ); @@ -42,7 +50,8 @@ function babelPlugin( { types: t } ) { visitor: { ImportDeclaration( path, state ) { const { node } = path; - const isThisPackageImport = node.source.value.indexOf( pkg.name ) !== -1; + const isThisPackageImport = + node.source.value.indexOf( pkg.name ) !== -1; if ( ! isThisPackageImport ) { return; @@ -76,7 +85,9 @@ function babelPlugin( { types: t } ) { path.replaceWith( t.ifStatement( logicalExpression, - t.blockStatement( [ t.expressionStatement( node ) ] ) + t.blockStatement( [ + t.expressionStatement( node ), + ] ) ) ); } diff --git a/packages/warning/test/babel-plugin.js b/packages/warning/test/babel-plugin.js index f4f2a9c759890c..a6ca6b69a8636b 100644 --- a/packages/warning/test/babel-plugin.js +++ b/packages/warning/test/babel-plugin.js @@ -35,10 +35,7 @@ describe( 'babel-plugin', function() { } ); it( 'should not replace warning calls without import declaration', () => { - compare( - 'warning("a");', - 'warning("a");' - ); + compare( 'warning("a");', 'warning("a");' ); } ); it( 'should replace warning calls without import declaration with plugin options', () => { @@ -55,7 +52,7 @@ describe( 'babel-plugin', function() { 'import warning from "@wordpress/warning";', 'warning("a");', 'warning("b");', - 'warning("c");', + 'warning("c");' ), join( 'import warning from "@wordpress/warning";', @@ -72,7 +69,7 @@ describe( 'babel-plugin', function() { 'import warn from "@wordpress/warning";', 'warn("a");', 'warn("b");', - 'warn("c");', + 'warn("c");' ), join( 'import warn from "@wordpress/warning";', @@ -89,7 +86,7 @@ describe( 'babel-plugin', function() { 'import warn from "@wordpress/warning";', 'warn("a");', 'warn("b");', - 'warn("c");', + 'warn("c");' ), join( 'import warn from "@wordpress/warning";', diff --git a/packages/wordcount/src/defaultSettings.js b/packages/wordcount/src/defaultSettings.js index fd19ea3debc1e2..ad50bd125efbb2 100644 --- a/packages/wordcount/src/defaultSettings.js +++ b/packages/wordcount/src/defaultSettings.js @@ -8,48 +8,51 @@ export const defaultSettings = { connectorRegExp: /--|\u2014/g, // Characters to be removed from input text. - removeRegExp: new RegExp( [ - '[', + removeRegExp: new RegExp( + [ + '[', - // Basic Latin (extract) - '\u0021-\u0040\u005B-\u0060\u007B-\u007E', + // Basic Latin (extract) + '\u0021-\u0040\u005B-\u0060\u007B-\u007E', - // Latin-1 Supplement (extract) - '\u0080-\u00BF\u00D7\u00F7', + // Latin-1 Supplement (extract) + '\u0080-\u00BF\u00D7\u00F7', - /* - * The following range consists of: - * General Punctuation - * Superscripts and Subscripts - * Currency Symbols - * Combining Diacritical Marks for Symbols - * Letterlike Symbols - * Number Forms - * Arrows - * Mathematical Operators - * Miscellaneous Technical - * Control Pictures - * Optical Character Recognition - * Enclosed Alphanumerics - * Box Drawing - * Block Elements - * Geometric Shapes - * Miscellaneous Symbols - * Dingbats - * Miscellaneous Mathematical Symbols-A - * Supplemental Arrows-A - * Braille Patterns - * Supplemental Arrows-B - * Miscellaneous Mathematical Symbols-B - * Supplemental Mathematical Operators - * Miscellaneous Symbols and Arrows - */ - '\u2000-\u2BFF', + /* + * The following range consists of: + * General Punctuation + * Superscripts and Subscripts + * Currency Symbols + * Combining Diacritical Marks for Symbols + * Letterlike Symbols + * Number Forms + * Arrows + * Mathematical Operators + * Miscellaneous Technical + * Control Pictures + * Optical Character Recognition + * Enclosed Alphanumerics + * Box Drawing + * Block Elements + * Geometric Shapes + * Miscellaneous Symbols + * Dingbats + * Miscellaneous Mathematical Symbols-A + * Supplemental Arrows-A + * Braille Patterns + * Supplemental Arrows-B + * Miscellaneous Mathematical Symbols-B + * Supplemental Mathematical Operators + * Miscellaneous Symbols and Arrows + */ + '\u2000-\u2BFF', - // Supplemental Punctuation - '\u2E00-\u2E7F', - ']', - ].join( '' ), 'g' ), + // Supplemental Punctuation + '\u2E00-\u2E7F', + ']', + ].join( '' ), + 'g' + ), // Remove UTF-16 surrogate points, see https://en.wikipedia.org/wiki/UTF-16#U.2BD800_to_U.2BDFFF astralRegExp: /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, diff --git a/packages/wordcount/src/index.js b/packages/wordcount/src/index.js index 676f7ac2564b53..d7f74cbd6b2b05 100644 --- a/packages/wordcount/src/index.js +++ b/packages/wordcount/src/index.js @@ -31,12 +31,18 @@ function loadSettings( type, userSettings ) { settings.shortcodes = settings.l10n.shortcodes || {}; if ( settings.shortcodes && settings.shortcodes.length ) { - settings.shortcodesRegExp = new RegExp( '\\[\\/?(?:' + settings.shortcodes.join( '|' ) + ')[^\\]]*?\\]', 'g' ); + settings.shortcodesRegExp = new RegExp( + '\\[\\/?(?:' + settings.shortcodes.join( '|' ) + ')[^\\]]*?\\]', + 'g' + ); } settings.type = type || settings.l10n.type; - if ( settings.type !== 'characters_excluding_spaces' && settings.type !== 'characters_including_spaces' ) { + if ( + settings.type !== 'characters_excluding_spaces' && + settings.type !== 'characters_including_spaces' + ) { settings.type = 'words'; } @@ -60,7 +66,7 @@ function matchWords( text, regex, settings ) { stripSpaces.bind( this, settings ), stripHTMLEntities.bind( this, settings ), stripConnectors.bind( this, settings ), - stripRemovables.bind( this, settings ), + stripRemovables.bind( this, settings ) )( text ); text = text + '\n'; return text.match( regex ); @@ -82,7 +88,7 @@ function matchCharacters( text, regex, settings ) { stripShortcodes.bind( this, settings ), stripSpaces.bind( this, settings ), transposeAstralsToCountableChar.bind( this, settings ), - transposeHTMLEntitiesToCountableChars.bind( this, settings ), + transposeHTMLEntitiesToCountableChars.bind( this, settings ) )( text ); text = text + '\n'; return text.match( regex ); @@ -112,9 +118,10 @@ export function count( text, type, userSettings ) { if ( text ) { const settings = loadSettings( type, userSettings ); const matchRegExp = settings[ type + 'RegExp' ]; - const results = ( 'words' === settings.type ) ? - matchWords( text, matchRegExp, settings ) : - matchCharacters( text, matchRegExp, settings ); + const results = + 'words' === settings.type + ? matchWords( text, matchRegExp, settings ) + : matchCharacters( text, matchRegExp, settings ); return results ? results.length : 0; } diff --git a/packages/wordcount/src/test/index.test.js b/packages/wordcount/src/test/index.test.js index 2537ca49e213a2..410fbd87e96eb7 100644 --- a/packages/wordcount/src/test/index.test.js +++ b/packages/wordcount/src/test/index.test.js @@ -6,9 +6,7 @@ import { count } from '../'; const mockData = { l10n: { - shortcodes: [ - 'shortcode', - ], + shortcodes: [ 'shortcode' ], }, }; @@ -100,7 +98,11 @@ describe( 'WordCounter', () => { }, ]; - const types = [ 'words', 'characters_excluding_spaces', 'characters_including_spaces' ]; + const types = [ + 'words', + 'characters_excluding_spaces', + 'characters_including_spaces', + ]; dataProvider.forEach( ( item ) => { types.forEach( ( type ) => { @@ -111,4 +113,3 @@ describe( 'WordCounter', () => { } ); } ); } ); - diff --git a/storybook/test/__snapshots__/index.js.snap b/storybook/test/__snapshots__/index.js.snap index beb8d37afddbc8..ed7268b34a7fc2 100644 --- a/storybook/test/__snapshots__/index.js.snap +++ b/storybook/test/__snapshots__/index.js.snap @@ -834,7 +834,7 @@ exports[`Storyshots Components/Card Default 1`] = ` } .emotion-6.is-elevated { - box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 2px 1px -1px rgba(0,0,0,0.12); + box-shadow: 0px 1px 3px 0px rgba( 0,0,0,0.2 ),0px 1px 1px 0px rgba( 0,0,0,0.14 ),0px 2px 1px -1px rgba( 0,0,0,0.12 ); } .emotion-0 { @@ -970,7 +970,7 @@ exports[`Storyshots Components/Card/Body Default 1`] = ` } .emotion-2.is-elevated { - box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 2px 1px -1px rgba(0,0,0,0.12); + box-shadow: 0px 1px 3px 0px rgba( 0,0,0,0.2 ),0px 1px 1px 0px rgba( 0,0,0,0.14 ),0px 2px 1px -1px rgba( 0,0,0,0.12 ); } .emotion-0 { @@ -1026,7 +1026,7 @@ exports[`Storyshots Components/Card/Divider Default 1`] = ` } .emotion-6.is-elevated { - box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 2px 1px -1px rgba(0,0,0,0.12); + box-shadow: 0px 1px 3px 0px rgba( 0,0,0,0.2 ),0px 1px 1px 0px rgba( 0,0,0,0.14 ),0px 2px 1px -1px rgba( 0,0,0,0.12 ); } .emotion-0 { @@ -1100,7 +1100,7 @@ exports[`Storyshots Components/Card/Footer Default 1`] = ` } .emotion-2.is-elevated { - box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 2px 1px -1px rgba(0,0,0,0.12); + box-shadow: 0px 1px 3px 0px rgba( 0,0,0,0.2 ),0px 1px 1px 0px rgba( 0,0,0,0.14 ),0px 2px 1px -1px rgba( 0,0,0,0.12 ); } .emotion-0 { @@ -1167,7 +1167,7 @@ exports[`Storyshots Components/Card/Header Default 1`] = ` } .emotion-2.is-elevated { - box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 2px 1px -1px rgba(0,0,0,0.12); + box-shadow: 0px 1px 3px 0px rgba( 0,0,0,0.2 ),0px 1px 1px 0px rgba( 0,0,0,0.14 ),0px 2px 1px -1px rgba( 0,0,0,0.12 ); } .emotion-0 { @@ -1234,7 +1234,7 @@ exports[`Storyshots Components/Card/Media Default 1`] = ` } .emotion-4.is-elevated { - box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 2px 1px -1px rgba(0,0,0,0.12); + box-shadow: 0px 1px 3px 0px rgba( 0,0,0,0.2 ),0px 1px 1px 0px rgba( 0,0,0,0.14 ),0px 2px 1px -1px rgba( 0,0,0,0.12 ); } .emotion-2 { @@ -1321,7 +1321,7 @@ exports[`Storyshots Components/Card/Media Header And Footer 1`] = ` } .emotion-6.is-elevated { - box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 2px 1px -1px rgba(0,0,0,0.12); + box-shadow: 0px 1px 3px 0px rgba( 0,0,0,0.2 ),0px 1px 1px 0px rgba( 0,0,0,0.14 ),0px 2px 1px -1px rgba( 0,0,0,0.12 ); } .emotion-0 { @@ -1467,7 +1467,7 @@ Array [ } .emotion-7.is-elevated { - box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 2px 1px -1px rgba(0,0,0,0.12); + box-shadow: 0px 1px 3px 0px rgba( 0,0,0,0.2 ),0px 1px 1px 0px rgba( 0,0,0,0.14 ),0px 2px 1px -1px rgba( 0,0,0,0.12 ); } .emotion-7.is-right { @@ -1575,7 +1575,7 @@ exports[`Storyshots Components/Card/Media Iframe Embed 1`] = ` } .emotion-4.is-elevated { - box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 2px 1px -1px rgba(0,0,0,0.12); + box-shadow: 0px 1px 3px 0px rgba( 0,0,0,0.2 ),0px 1px 1px 0px rgba( 0,0,0,0.14 ),0px 2px 1px -1px rgba( 0,0,0,0.12 ); } .emotion-0 { @@ -1678,7 +1678,7 @@ exports[`Storyshots Components/Card/Media On Bottom 1`] = ` } .emotion-4.is-elevated { - box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 2px 1px -1px rgba(0,0,0,0.12); + box-shadow: 0px 1px 3px 0px rgba( 0,0,0,0.2 ),0px 1px 1px 0px rgba( 0,0,0,0.14 ),0px 2px 1px -1px rgba( 0,0,0,0.12 ); } .emotion-0 { @@ -1765,7 +1765,7 @@ exports[`Storyshots Components/Card/Media On Top 1`] = ` } .emotion-4.is-elevated { - box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 2px 1px -1px rgba(0,0,0,0.12); + box-shadow: 0px 1px 3px 0px rgba( 0,0,0,0.2 ),0px 1px 1px 0px rgba( 0,0,0,0.14 ),0px 2px 1px -1px rgba( 0,0,0,0.12 ); } .emotion-2 { @@ -4062,7 +4062,7 @@ exports[`Storyshots Components/Panel With Icon 1`] = ` exports[`Storyshots Components/Placeholder Default 1`] = ` <div - className="components-placeholder is-small" + className="components-placeholder" > <iframe aria-hidden={true} @@ -4865,7 +4865,8 @@ exports[`Storyshots Components/ScrollLock Default 1`] = ` Toggle Scroll Lock </button> <p> - Scroll locked: + Scroll locked: + <strong> No </strong> diff --git a/storybook/test/index.js b/storybook/test/index.js index 3ea7f14605e0f3..8752847743e0b8 100644 --- a/storybook/test/index.js +++ b/storybook/test/index.js @@ -1,7 +1,9 @@ /** * External dependencies */ -import initStoryshots, { snapshotWithOptions } from '@storybook/addon-storyshots'; +import initStoryshots, { + snapshotWithOptions, +} from '@storybook/addon-storyshots'; import path from 'path'; initStoryshots( { @@ -11,7 +13,8 @@ initStoryshots( { // @see https://reactjs.org/blog/2016/11/16/react-v15.4.0.html#mocking-refs-for-snapshot-testing // @see https://github.com/storybookjs/storybook/tree/master/addons/storyshots/storyshots-core#using-createnodemock-to-mock-refs createNodeMock: ( element ) => { - const currentElement = element.type && document.createElement( element.type ); + const currentElement = + element.type && document.createElement( element.type ); if ( story.kind === 'Components/ClipboardButton' ) { currentElement.appendChild( diff --git a/test/integration/blocks-raw-handling.test.js b/test/integration/blocks-raw-handling.test.js index 7099b6b59aac2c..7abe9fb4bb3d60 100644 --- a/test/integration/blocks-raw-handling.test.js +++ b/test/integration/blocks-raw-handling.test.js @@ -18,7 +18,9 @@ import { import { registerCoreBlocks } from '@wordpress/block-library'; function readFile( filePath ) { - return fs.existsSync( filePath ) ? fs.readFileSync( filePath, 'utf8' ).trim() : ''; + return fs.existsSync( filePath ) + ? fs.readFileSync( filePath, 'utf8' ).trim() + : ''; } describe( 'Blocks raw handling', () => { @@ -47,9 +49,9 @@ describe( 'Blocks raw handling', () => { ids: { type: 'array', shortcode: ( { named: { ids } } ) => - ids.split( ',' ).map( ( id ) => ( - parseInt( id, 10 ) - ) ), + ids + .split( ',' ) + .map( ( id ) => parseInt( id, 10 ) ), }, }, priority: 9, @@ -70,11 +72,14 @@ describe( 'Blocks raw handling', () => { { type: 'raw', isMatch: ( node ) => { - return 'words to live by' === node.textContent.trim(); + return ( + 'words to live by' === node.textContent.trim() + ); }, transform: () => { return createBlock( 'core-embed/youtube', { - url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', + url: + 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', } ); }, }, @@ -119,9 +124,13 @@ describe( 'Blocks raw handling', () => { HTML: '* one<br>* two<br>* three', plainText: '* one\n* two\n* three', mode: 'AUTO', - } ).map( getBlockContent ).join( '' ); + } ) + .map( getBlockContent ) + .join( '' ); - expect( filtered ).toBe( '<ul><li>one</li><li>two</li><li>three</li></ul>' ); + expect( filtered ).toBe( + '<ul><li>one</li><li>two</li><li>three</li></ul>' + ); expect( console ).toHaveLogged(); } ); @@ -138,7 +147,8 @@ describe( 'Blocks raw handling', () => { it( 'should parse HTML in plainText', () => { const filtered = pasteHandler( { - HTML: '&lt;p&gt;Some &lt;strong&gt;bold&lt;/strong&gt; text.&lt;/p&gt;', + HTML: + '&lt;p&gt;Some &lt;strong&gt;bold&lt;/strong&gt; text.&lt;/p&gt;', plainText: '<p>Some <strong>bold</strong> text.</p>', mode: 'AUTO', } ); @@ -152,9 +162,13 @@ describe( 'Blocks raw handling', () => { HTML: '', plainText: '# Some <em>heading</em>\n\nA paragraph.', mode: 'AUTO', - } ).map( getBlockContent ).join( '' ); + } ) + .map( getBlockContent ) + .join( '' ); - expect( filtered ).toBe( '<h1>Some <em>heading</em></h1><p>A paragraph.</p>' ); + expect( filtered ).toBe( + '<h1>Some <em>heading</em></h1><p>A paragraph.</p>' + ); expect( console ).toHaveLogged(); } ); @@ -217,9 +231,13 @@ describe( 'Blocks raw handling', () => { HTML: '<ul><li>One</li><li>Two</li><li>Three</li></ul>', plainText: 'One\nTwo\nThree\n', mode: 'AUTO', - } ).map( getBlockContent ).join( '' ); + } ) + .map( getBlockContent ) + .join( '' ); - expect( filtered ).toBe( '<ul><li>One</li><li>Two</li><li>Three</li></ul>' ); + expect( filtered ).toBe( + '<ul><li>One</li><li>Two</li><li>Three</li></ul>' + ); expect( console ).toHaveLogged(); } ); @@ -227,18 +245,26 @@ describe( 'Blocks raw handling', () => { const filtered = pasteHandler( { HTML: '<blockquote><p>chicken</p></blockquote>', mode: 'AUTO', - } ).map( getBlockContent ).join( '' ); + } ) + .map( getBlockContent ) + .join( '' ); - expect( filtered ).toBe( '<blockquote class="wp-block-quote"><p>chicken</p></blockquote>' ); + expect( filtered ).toBe( + '<blockquote class="wp-block-quote"><p>chicken</p></blockquote>' + ); expect( console ).toHaveLogged(); } ); it( 'should correctly handle quotes with multiple paragraphs and no citation', () => { const filtered = pasteHandler( { HTML: '<blockquote><p>chicken</p><p>ribs</p></blockquote>', mode: 'AUTO', - } ).map( getBlockContent ).join( '' ); + } ) + .map( getBlockContent ) + .join( '' ); - expect( filtered ).toBe( '<blockquote class="wp-block-quote"><p>chicken</p><p>ribs</p></blockquote>' ); + expect( filtered ).toBe( + '<blockquote class="wp-block-quote"><p>chicken</p><p>ribs</p></blockquote>' + ); expect( console ).toHaveLogged(); } ); @@ -246,9 +272,13 @@ describe( 'Blocks raw handling', () => { const filtered = pasteHandler( { HTML: '<blockquote><p>chicken</p><cite>ribs</cite></blockquote>', mode: 'AUTO', - } ).map( getBlockContent ).join( '' ); + } ) + .map( getBlockContent ) + .join( '' ); - expect( filtered ).toBe( '<blockquote class="wp-block-quote"><p>chicken</p><cite>ribs</cite></blockquote>' ); + expect( filtered ).toBe( + '<blockquote class="wp-block-quote"><p>chicken</p><cite>ribs</cite></blockquote>' + ); expect( console ).toHaveLogged(); } ); @@ -256,19 +286,28 @@ describe( 'Blocks raw handling', () => { const filtered = pasteHandler( { HTML: '<blockquote><cite>ribs</cite><p>ribs</p></blockquote>', mode: 'AUTO', - } ).map( getBlockContent ).join( '' ); + } ) + .map( getBlockContent ) + .join( '' ); - expect( filtered ).toBe( '<blockquote class="wp-block-quote"><p>ribs</p><cite>ribs</cite></blockquote>' ); + expect( filtered ).toBe( + '<blockquote class="wp-block-quote"><p>ribs</p><cite>ribs</cite></blockquote>' + ); expect( console ).toHaveLogged(); } ); it( 'should handle a citation in the middle of the content', () => { const filtered = pasteHandler( { - HTML: '<blockquote><p>chicken</p><cite>ribs</cite><p>ribs</p></blockquote>', + HTML: + '<blockquote><p>chicken</p><cite>ribs</cite><p>ribs</p></blockquote>', mode: 'AUTO', - } ).map( getBlockContent ).join( '' ); + } ) + .map( getBlockContent ) + .join( '' ); - expect( filtered ).toBe( '<blockquote class="wp-block-quote"><p>chicken</p><p>ribs</p><cite>ribs</cite></blockquote>' ); + expect( filtered ).toBe( + '<blockquote class="wp-block-quote"><p>chicken</p><p>ribs</p><cite>ribs</cite></blockquote>' + ); expect( console ).toHaveLogged(); } ); @@ -276,9 +315,13 @@ describe( 'Blocks raw handling', () => { const filtered = pasteHandler( { HTML: '<blockquote><cite>ribs</cite></blockquote>', mode: 'AUTO', - } ).map( getBlockContent ).join( '' ); + } ) + .map( getBlockContent ) + .join( '' ); - expect( filtered ).toBe( '<blockquote class="wp-block-quote"><p></p><cite>ribs</cite></blockquote>' ); + expect( filtered ).toBe( + '<blockquote class="wp-block-quote"><p></p><cite>ribs</cite></blockquote>' + ); expect( console ).toHaveLogged(); } ); @@ -286,7 +329,9 @@ describe( 'Blocks raw handling', () => { const filtered = pasteHandler( { HTML: '<blockquote><cite>ribs</cite><cite>ribs</cite></blockquote>', mode: 'AUTO', - } ).map( getBlockContent ).join( '' ); + } ) + .map( getBlockContent ) + .join( '' ); expect( filtered ).toBe( '<p>ribsribs</p>' ); expect( console ).toHaveLogged(); @@ -294,9 +339,12 @@ describe( 'Blocks raw handling', () => { it( 'should convert to paragraph quotes with more than one cite and at least one paragraph', () => { const filtered = pasteHandler( { - HTML: '<blockquote><p>chicken</p><cite>ribs</cite><cite>ribs</cite></blockquote>', + HTML: + '<blockquote><p>chicken</p><cite>ribs</cite><cite>ribs</cite></blockquote>', mode: 'AUTO', - } ).map( getBlockContent ).join( '' ); + } ) + .map( getBlockContent ) + .join( '' ); expect( filtered ).toBe( '<p>chickenribsribs</p>' ); expect( console ).toHaveLogged(); @@ -304,10 +352,14 @@ describe( 'Blocks raw handling', () => { it( 'should paste gutenberg content from plain text', () => { const block = '<!-- wp:latest-posts /-->'; - expect( serialize( pasteHandler( { - plainText: block, - mode: 'AUTO', - } ) ) ).toBe( block ); + expect( + serialize( + pasteHandler( { + plainText: block, + mode: 'AUTO', + } ) + ) + ).toBe( block ); } ); describe( 'pasteHandler', () => { @@ -332,16 +384,29 @@ describe( 'Blocks raw handling', () => { 'shortcode-matching', ].forEach( ( type ) => { it( type, () => { - const HTML = readFile( path.join( __dirname, `fixtures/${ type }-in.html` ) ); - const plainText = readFile( path.join( __dirname, `fixtures/${ type }-in.txt` ) ); - const output = readFile( path.join( __dirname, `fixtures/${ type }-out.html` ) ); + const HTML = readFile( + path.join( __dirname, `fixtures/${ type }-in.html` ) + ); + const plainText = readFile( + path.join( __dirname, `fixtures/${ type }-in.txt` ) + ); + const output = readFile( + path.join( __dirname, `fixtures/${ type }-out.html` ) + ); if ( ! ( HTML || plainText ) || ! output ) { throw new Error( `Expected fixtures for type ${ type }` ); } - const converted = pasteHandler( { HTML, plainText, canUserUseUnfilteredHTML: true } ); - const serialized = typeof converted === 'string' ? converted : serialize( converted ); + const converted = pasteHandler( { + HTML, + plainText, + canUserUseUnfilteredHTML: true, + } ); + const serialized = + typeof converted === 'string' + ? converted + : serialize( converted ); expect( serialized ).toBe( output ); @@ -358,18 +423,27 @@ describe( 'Blocks raw handling', () => { } ); it( 'should remove extra blank lines', () => { - const HTML = readFile( path.join( __dirname, 'fixtures/google-docs-blank-lines.html' ) ); + const HTML = readFile( + path.join( __dirname, 'fixtures/google-docs-blank-lines.html' ) + ); expect( serialize( pasteHandler( { HTML } ) ) ).toMatchSnapshot(); expect( console ).toHaveLogged(); } ); it( 'should strip windows data', () => { - const HTML = readFile( path.join( __dirname, 'fixtures/windows.html' ) ); + const HTML = readFile( + path.join( __dirname, 'fixtures/windows.html' ) + ); expect( serialize( pasteHandler( { HTML } ) ) ).toMatchSnapshot(); } ); it( 'should strip HTML formatting space from inline text', () => { - const HTML = readFile( path.join( __dirname, 'fixtures/inline-with-html-formatting-space.html' ) ); + const HTML = readFile( + path.join( + __dirname, + 'fixtures/inline-with-html-formatting-space.html' + ) + ); expect( pasteHandler( { HTML } ) ).toMatchSnapshot(); expect( console ).toHaveLogged(); } ); @@ -378,27 +452,40 @@ describe( 'Blocks raw handling', () => { describe( 'rawHandler', () => { it( 'should convert HTML post to blocks with minimal content changes', () => { - const HTML = readFile( path.join( __dirname, 'fixtures/wordpress-convert.html' ) ); + const HTML = readFile( + path.join( __dirname, 'fixtures/wordpress-convert.html' ) + ); expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); } ); it( 'should convert a caption shortcode', () => { - const HTML = readFile( path.join( __dirname, 'fixtures/shortcode-caption.html' ) ); + const HTML = readFile( + path.join( __dirname, 'fixtures/shortcode-caption.html' ) + ); expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); } ); it( 'should convert a caption shortcode with link', () => { - const HTML = readFile( path.join( __dirname, 'fixtures/shortcode-caption-with-link.html' ) ); + const HTML = readFile( + path.join( __dirname, 'fixtures/shortcode-caption-with-link.html' ) + ); expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); } ); it( 'should convert a caption shortcode with caption', () => { - const HTML = readFile( path.join( __dirname, 'fixtures/shortcode-caption-with-caption-link.html' ) ); + const HTML = readFile( + path.join( + __dirname, + 'fixtures/shortcode-caption-with-caption-link.html' + ) + ); expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); } ); it( 'should convert a list with attributes', () => { - const HTML = readFile( path.join( __dirname, 'fixtures/list-with-attributes.html' ) ); + const HTML = readFile( + path.join( __dirname, 'fixtures/list-with-attributes.html' ) + ); expect( serialize( rawHandler( { HTML } ) ) ).toMatchSnapshot(); } ); diff --git a/test/integration/full-content/full-content.test.js b/test/integration/full-content/full-content.test.js index 953eba86306206..77c237540def9a 100644 --- a/test/integration/full-content/full-content.test.js +++ b/test/integration/full-content/full-content.test.js @@ -14,8 +14,12 @@ import { unstable__bootstrapServerSideBlockDefinitions, // eslint-disable-line camelcase } from '@wordpress/blocks'; import { parse as grammarParse } from '@wordpress/block-serialization-default-parser'; -import { registerCoreBlocks, __experimentalRegisterExperimentalCoreBlocks } from '@wordpress/block-library'; -import { //eslint-disable-line no-restricted-syntax +import { + registerCoreBlocks, + __experimentalRegisterExperimentalCoreBlocks, +} from '@wordpress/block-library'; +//eslint-disable-next-line no-restricted-syntax +import { blockNameToFixtureBasename, getAvailableBlockFixturesBasenames, getBlockFixtureHTML, @@ -34,7 +38,9 @@ function normalizeParsedBlocks( blocks ) { // Clone and remove React-instance-specific stuff; also, attribute // values that equal `undefined` will be removed. Validation issues // add too much noise so they get removed as well. - block = JSON.parse( JSON.stringify( omit( block, 'validationIssues' ) ) ); + block = JSON.parse( + JSON.stringify( omit( block, 'validationIssues' ) ) + ); // Change client IDs to a predictable value block.clientId = '_clientId_' + index; @@ -48,7 +54,9 @@ function normalizeParsedBlocks( blocks ) { describe( 'full post content fixture', () => { beforeAll( () => { - unstable__bootstrapServerSideBlockDefinitions( require( './server-registered.json' ) ); + unstable__bootstrapServerSideBlockDefinitions( + require( './server-registered.json' ) + ); const settings = { __experimentalEnableLegacyWidgetBlock: true, __experimentalEnableFullSiteEditing: true, @@ -82,29 +90,31 @@ describe( 'full post content fixture', () => { if ( parsedJSONFixtureContent ) { parserOutputExpectedString = parsedJSONFixtureContent; } else if ( process.env.GENERATE_MISSING_FIXTURES ) { - parserOutputExpectedString = JSON.stringify( - parserOutputActual, - null, - 4 - ) + '\n'; - writeBlockFixtureParsedJSON( basename, parserOutputExpectedString ); + parserOutputExpectedString = + JSON.stringify( parserOutputActual, null, 4 ) + '\n'; + writeBlockFixtureParsedJSON( + basename, + parserOutputExpectedString + ); } else { throw new Error( `Missing fixture file: ${ parsedJSONFixtureFileName }` ); } - const parserOutputExpected = JSON.parse( parserOutputExpectedString ); + const parserOutputExpected = JSON.parse( + parserOutputExpectedString + ); try { - expect( - parserOutputActual - ).toEqual( parserOutputExpected ); + expect( parserOutputActual ).toEqual( parserOutputExpected ); } catch ( err ) { - throw new Error( format( - "File '%s' does not match expected value:\n\n%s", - parsedJSONFixtureFileName, - err.message - ) ); + throw new Error( + format( + "File '%s' does not match expected value:\n\n%s", + parsedJSONFixtureFileName, + err.message + ) + ); } const blocksActual = parse( htmlFixtureContent ); @@ -121,7 +131,9 @@ describe( 'full post content fixture', () => { /* eslint-enable no-console */ } - const blocksActualNormalized = normalizeParsedBlocks( blocksActual ); + const blocksActualNormalized = normalizeParsedBlocks( + blocksActual + ); const { filename: jsonFixtureFileName, file: jsonFixtureContent, @@ -132,11 +144,8 @@ describe( 'full post content fixture', () => { if ( jsonFixtureContent ) { blocksExpectedString = jsonFixtureContent; } else if ( process.env.GENERATE_MISSING_FIXTURES ) { - blocksExpectedString = JSON.stringify( - blocksActualNormalized, - null, - 4 - ) + '\n'; + blocksExpectedString = + JSON.stringify( blocksActualNormalized, null, 4 ) + '\n'; writeBlockFixtureJSON( basename, blocksExpectedString ); } else { throw new Error( @@ -146,15 +155,15 @@ describe( 'full post content fixture', () => { const blocksExpected = JSON.parse( blocksExpectedString ); try { - expect( - blocksActualNormalized - ).toEqual( blocksExpected ); + expect( blocksActualNormalized ).toEqual( blocksExpected ); } catch ( err ) { - throw new Error( format( - "File '%s' does not match expected value:\n\n%s", - jsonFixtureFileName, - err.message - ) ); + throw new Error( + format( + "File '%s' does not match expected value:\n\n%s", + jsonFixtureFileName, + err.message + ) + ); } // `serialize` doesn't have a trailing newline, but the fixture @@ -163,7 +172,7 @@ describe( 'full post content fixture', () => { const { filename: serializedHTMLFileName, file: serializedHTMLFixtureContent, - } = getBlockFixtureSerializedHTML( basename ); + } = getBlockFixtureSerializedHTML( basename ); let serializedExpected; if ( serializedHTMLFixtureContent ) { @@ -180,11 +189,13 @@ describe( 'full post content fixture', () => { try { expect( serializedActual ).toEqual( serializedExpected ); } catch ( err ) { - throw new Error( format( - "File '%s' does not match expected value:\n\n%s", - serializedHTMLFileName, - err.message - ) ); + throw new Error( + format( + "File '%s' does not match expected value:\n\n%s", + serializedHTMLFileName, + err.message + ) + ); } } ); } ); @@ -197,14 +208,19 @@ describe( 'full post content fixture', () => { // We don't want tests for each oembed provider, which all have the same // `save` functions and attributes. // The `core/template` is not worth testing here because it's never saved, it's covered better in e2e tests. - .filter( ( name ) => name.indexOf( 'core-embed' ) !== 0 && name !== 'core/template' ) + .filter( + ( name ) => + name.indexOf( 'core-embed' ) !== 0 && + name !== 'core/template' + ) .forEach( ( name ) => { const nameToFilename = blockNameToFixtureBasename( name ); const foundFixtures = blockBasenames - .filter( ( basename ) => ( - basename === nameToFilename || - startsWith( basename, nameToFilename + '__' ) - ) ) + .filter( + ( basename ) => + basename === nameToFilename || + startsWith( basename, nameToFilename + '__' ) + ) .map( ( basename ) => { const { filename: htmlFixtureFileName, @@ -214,12 +230,14 @@ describe( 'full post content fixture', () => { } = getBlockFixtureJSON( basename ); // The parser output for this test. For missing files, // JSON.parse( null ) === null. - const parserOutput = JSON.parse( - jsonFixtureContent, - ); + const parserOutput = JSON.parse( jsonFixtureContent ); // The name of the first block that this fixture file // contains (if any). - const firstBlock = get( parserOutput, [ '0', 'name' ], null ); + const firstBlock = get( + parserOutput, + [ '0', 'name' ], + null + ); return { filename: htmlFixtureFileName, parserOutput, @@ -229,20 +247,24 @@ describe( 'full post content fixture', () => { .filter( ( fixture ) => fixture.parserOutput !== null ); if ( ! foundFixtures.length ) { - errors.push( format( - "Expected a fixture file called '%s.html' or '%s__*.html'.", - nameToFilename, - nameToFilename - ) ); + errors.push( + format( + "Expected a fixture file called '%s.html' or '%s__*.html'.", + nameToFilename, + nameToFilename + ) + ); } foundFixtures.forEach( ( fixture ) => { if ( name !== fixture.firstBlock ) { - errors.push( format( - "Expected fixture file '%s' to test the '%s' block.", - fixture.filename, - name - ) ); + errors.push( + format( + "Expected fixture file '%s' to test the '%s' block.", + fixture.filename, + name + ) + ); } } ); } ); diff --git a/test/integration/is-valid-block.test.js b/test/integration/is-valid-block.test.js index f10ef5f34d09f1..4c5be6703710af 100644 --- a/test/integration/is-valid-block.test.js +++ b/test/integration/is-valid-block.test.js @@ -13,7 +13,8 @@ describe( 'isValidBlockContent', () => { it( 'should use the namespace in the classname for non-core blocks', () => { const valid = isValidBlockContent( { - save: ( { attributes } ) => createElement( 'div', null, attributes.fruit ), + save: ( { attributes } ) => + createElement( 'div', null, attributes.fruit ), name: 'myplugin/fruit', }, { fruit: 'Bananas' }, @@ -26,9 +27,14 @@ describe( 'isValidBlockContent', () => { it( 'should include additional classes in block attributes', () => { const valid = isValidBlockContent( { - save: ( { attributes } ) => createElement( 'div', { - className: 'fruit', - }, attributes.fruit ), + save: ( { attributes } ) => + createElement( + 'div', + { + className: 'fruit', + }, + attributes.fruit + ), name: 'myplugin/fruit', }, { @@ -44,7 +50,8 @@ describe( 'isValidBlockContent', () => { it( 'should not add a className if falsy', () => { const valid = isValidBlockContent( { - save: ( { attributes } ) => createElement( 'div', null, attributes.fruit ), + save: ( { attributes } ) => + createElement( 'div', null, attributes.fruit ), name: 'myplugin/fruit', supports: { className: false, diff --git a/test/integration/non-matched-tags-handling.test.js b/test/integration/non-matched-tags-handling.test.js index 71ce3a13b6137c..2b2725e8f9ad54 100644 --- a/test/integration/non-matched-tags-handling.test.js +++ b/test/integration/non-matched-tags-handling.test.js @@ -1,10 +1,7 @@ /** * WordPress dependencies */ -import { - pasteHandler, - unregisterBlockType, -} from '@wordpress/blocks'; +import { pasteHandler, unregisterBlockType } from '@wordpress/blocks'; import { registerCoreBlocks } from '@wordpress/block-library'; describe( 'Handling of non matched tags in block transforms', () => { @@ -22,7 +19,9 @@ describe( 'Handling of non matched tags in block transforms', () => { expect( simplePreformattedResult ).toHaveLength( 1 ); expect( simplePreformattedResult[ 0 ].name ).toBe( 'core/paragraph' ); - expect( simplePreformattedResult[ 0 ].attributes.content ).toBe( 'Pre' ); + expect( simplePreformattedResult[ 0 ].attributes.content ).toBe( + 'Pre' + ); const codeResult = pasteHandler( { HTML: '<pre><code>code</code></pre>', diff --git a/test/integration/shortcode-converter.test.js b/test/integration/shortcode-converter.test.js index 1690bdebc5a654..5f5fa0dfc44442 100644 --- a/test/integration/shortcode-converter.test.js +++ b/test/integration/shortcode-converter.test.js @@ -30,9 +30,9 @@ describe( 'segmentHTMLToShortcodeBlock', () => { ids: { type: 'array', shortcode: ( { named: { ids } } ) => - ids.split( ',' ).map( ( id ) => ( - parseInt( id, 10 ) - ) ), + ids + .split( ',' ) + .map( ( id ) => parseInt( id, 10 ) ), }, }, }, @@ -56,7 +56,8 @@ describe( 'segmentHTMLToShortcodeBlock', () => { attributes: { id: { type: 'number', - shortcode: ( { named: { id } } ) => parseInt( id, 10 ), + shortcode: ( { named: { id } } ) => + parseInt( id, 10 ), }, }, isMatch( { named: { id } } ) { @@ -83,7 +84,8 @@ describe( 'segmentHTMLToShortcodeBlock', () => { attributes: { id: { type: 'number', - shortcode: ( { named: { id } } ) => parseInt( id, 10 ), + shortcode: ( { named: { id } } ) => + parseInt( id, 10 ), }, }, isMatch( { named: { id } } ) { @@ -130,7 +132,9 @@ describe( 'segmentHTMLToShortcodeBlock', () => { it( 'should not convert a shortcode to a block type with a failing `isMatch`', () => { const original = `<p>[my-broccoli id="1000"]</p>`; const transformed = segmentHTMLToShortcodeBlock( original, 0 ); - const expectedBlock = createBlock( 'core/shortcode', { text: '[my-broccoli id="1000"]' } ); + const expectedBlock = createBlock( 'core/shortcode', { + text: '[my-broccoli id="1000"]', + } ); expectedBlock.clientId = transformed[ 1 ].clientId; expect( transformed[ 1 ] ).toEqual( expectedBlock ); } ); @@ -141,11 +145,15 @@ describe( 'segmentHTMLToShortcodeBlock', () => { <p>[my-broccoli id="1000"]</p>`; const transformed = segmentHTMLToShortcodeBlock( original ); - const firstExpectedBlock = createBlock( 'test/fallback-broccoli', { id: 1001 } ); + const firstExpectedBlock = createBlock( 'test/fallback-broccoli', { + id: 1001, + } ); firstExpectedBlock.clientId = transformed[ 1 ].clientId; const secondExpectedBlock = createBlock( 'test/broccoli', { id: 42 } ); secondExpectedBlock.clientId = transformed[ 3 ].clientId; - const thirdExpectedBlock = createBlock( 'core/shortcode', { text: '[my-broccoli id="1000"]' } ); + const thirdExpectedBlock = createBlock( 'core/shortcode', { + text: '[my-broccoli id="1000"]', + } ); thirdExpectedBlock.clientId = transformed[ 5 ].clientId; expect( transformed[ 1 ] ).toEqual( firstExpectedBlock ); expect( transformed[ 3 ] ).toEqual( secondExpectedBlock ); @@ -220,12 +228,14 @@ describe( 'segmentHTMLToShortcodeBlock', () => { it( 'should not convert inline shortcodes', () => { const originalInASentence = `<p>Here is a nice [foo shortcode].</p>`; - expect( segmentHTMLToShortcodeBlock( originalInASentence, 0 ) ) - .toEqual( [ originalInASentence ] ); + expect( + segmentHTMLToShortcodeBlock( originalInASentence, 0 ) + ).toEqual( [ originalInASentence ] ); const originalMultipleShortcodes = `<p>[foo bar] [baz quux]</p>`; - expect( segmentHTMLToShortcodeBlock( originalMultipleShortcodes, 0 ) ) - .toEqual( [ originalMultipleShortcodes ] ); + expect( + segmentHTMLToShortcodeBlock( originalMultipleShortcodes, 0 ) + ).toEqual( [ originalMultipleShortcodes ] ); } ); it( 'should convert regardless of shortcode alias', () => { diff --git a/test/native/__mocks__/react-native-aztec/index.js b/test/native/__mocks__/react-native-aztec/index.js index 6c4c0f0d1353b2..4d963f6ff80647 100644 --- a/test/native/__mocks__/react-native-aztec/index.js +++ b/test/native/__mocks__/react-native-aztec/index.js @@ -4,18 +4,16 @@ import React from 'react'; class AztecView extends React.Component { - blur = () => { - } + blur = () => {}; - focus = () => { - } + focus = () => {}; isFocused = () => { return false; - } + }; render() { - return ( <></> ); + return <></>; } } diff --git a/test/native/babel.config.js b/test/native/babel.config.js index 7fa61e90babc8f..57085b9b062973 100644 --- a/test/native/babel.config.js +++ b/test/native/babel.config.js @@ -1,25 +1,20 @@ module.exports = function( api ) { api.cache( true ); return { - presets: [ - 'module:metro-react-native-babel-preset', - ], + presets: [ 'module:metro-react-native-babel-preset' ], plugins: [ '@babel/plugin-proposal-async-generator-functions', '@babel/plugin-transform-runtime', [ 'react-native-platform-specific-extensions', { - extensions: [ - 'css', - 'scss', - 'sass', - ], + extensions: [ 'css', 'scss', 'sass' ], }, ], ], overrides: [ - { // Transforms JSX into JS function calls and use `createElement` instead of the default `React.createElement` + { + // Transforms JSX into JS function calls and use `createElement` instead of the default `React.createElement` plugins: [ [ '@babel/plugin-transform-react-jsx', @@ -31,7 +26,8 @@ module.exports = function( api ) { ], exclude: /node_modules\/react-native/, }, - { // Auto-add `import { createElement } from '@wordpress/element';` when JSX is found + { + // Auto-add `import { createElement } from '@wordpress/element';` when JSX is found plugins: [ [ '../../packages/babel-plugin-import-jsx-pragma', @@ -48,9 +44,7 @@ module.exports = function( api ) { ], env: { development: { - plugins: [ - '@babel/transform-react-jsx-source', - ], + plugins: [ '@babel/transform-react-jsx-source' ], }, }, }; diff --git a/test/native/jest.config.js b/test/native/jest.config.js index a48e3ea52659f7..01fc8351391e31 100644 --- a/test/native/jest.config.js +++ b/test/native/jest.config.js @@ -15,8 +15,9 @@ if ( process.env.TEST_RN_PLATFORM ) { const configPath = 'test/native'; -const transpiledPackageNames = glob( '../../packages/*/src/index.js' ) - .map( ( fileName ) => fileName.split( '/' )[ 3 ] ); +const transpiledPackageNames = glob( '../../packages/*/src/index.js' ).map( + ( fileName ) => fileName.split( '/' )[ 3 ] +); module.exports = { verbose: true, @@ -29,9 +30,7 @@ module.exports = { '<rootDir>/' + configPath + '/enzyme.config.js', ], testEnvironment: 'jsdom', - testMatch: [ - '**/test/*.native.[jt]s?(x)', - ], + testMatch: [ '**/test/*.native.[jt]s?(x)' ], testPathIgnorePatterns: [ '/node_modules/', '/wordpress/', @@ -40,23 +39,21 @@ module.exports = { testURL: 'http://localhost/', // Add the `Libraries/Utilities` subfolder to the module directories, otherwise haste/jest doesn't find Platform.js on Travis, // and add it first so https://github.com/facebook/react-native/blob/v0.60.0/Libraries/react-native/react-native-implementation.js#L324-L326 doesn't pick up the Platform npm module. - moduleDirectories: [ 'node_modules/react-native/Libraries/Utilities', 'node_modules' ], + moduleDirectories: [ + 'node_modules/react-native/Libraries/Utilities', + 'node_modules', + ], moduleNameMapper: { // Mock the CSS modules. See https://facebook.github.io/jest/docs/en/webpack.html#handling-static-assets '\\.(scss)$': '<rootDir>/' + configPath + '/__mocks__/styleMock.js', - [ `@wordpress\\/(${ transpiledPackageNames.join( '|' ) })$` ]: '<rootDir>/packages/$1/src', + [ `@wordpress\\/(${ transpiledPackageNames.join( + '|' + ) })$` ]: '<rootDir>/packages/$1/src', }, haste: { defaultPlatform: rnPlatform, - platforms: [ - 'android', - 'ios', - 'native', - ], - providesModuleNodeModules: [ - 'react-native', - 'react-native-svg', - ], + platforms: [ 'android', 'ios', 'native' ], + providesModuleNodeModules: [ 'react-native', 'react-native-svg' ], }, transformIgnorePatterns: [ // This is required for now to have jest transform some of our modules @@ -65,9 +62,6 @@ module.exports = { // https://github.com/facebook/react-native/blob/master/jest-preset.json#L20 'node_modules/(?!(simple-html-tokenizer|(jest-)?react-native|react-clone-referenced-element))', ], - snapshotSerializers: [ - 'enzyme-to-json/serializer', - 'jest-emotion', - ], + snapshotSerializers: [ 'enzyme-to-json/serializer', 'jest-emotion' ], reporters: [ 'default', 'jest-junit' ], }; diff --git a/test/native/setup.js b/test/native/setup.js index 33ee2d2b746d4a..a901ea63cbef13 100644 --- a/test/native/setup.js +++ b/test/native/setup.js @@ -67,7 +67,9 @@ jest.mock( 'react-native-safe-area', () => { jest.mock( 'react-native-recyclerview-list' ); -jest.mock( '@react-native-community/slider', () => () => 'Slider', { virtual: true } ); +jest.mock( '@react-native-community/slider', () => () => 'Slider', { + virtual: true, +} ); if ( ! global.window.matchMedia ) { global.window.matchMedia = () => ( { @@ -92,6 +94,8 @@ Object.keys( mockNativeModules ).forEach( ( module ) => { try { jest.doMock( module, () => mockNativeModules[ module ] ); // needed by FacebookSDK-test } catch ( error ) { - jest.doMock( module, () => mockNativeModules[ module ], { virtual: true } ); + jest.doMock( module, () => mockNativeModules[ module ], { + virtual: true, + } ); } } ); diff --git a/test/unit/config/gutenberg-phase.js b/test/unit/config/gutenberg-phase.js index 1b6117b3236a6a..2e119cd3e89f07 100644 --- a/test/unit/config/gutenberg-phase.js +++ b/test/unit/config/gutenberg-phase.js @@ -2,5 +2,8 @@ global.process.env = { ...global.process.env, // Inject the `GUTENBERG_PHASE` global, used for feature flagging. // eslint-disable-next-line @wordpress/gutenberg-phase - GUTENBERG_PHASE: parseInt( process.env.npm_package_config_GUTENBERG_PHASE, 10 ), + GUTENBERG_PHASE: parseInt( + process.env.npm_package_config_GUTENBERG_PHASE, + 10 + ), }; diff --git a/test/unit/jest.config.js b/test/unit/jest.config.js index 757d1d4e8796d0..d9836adf7eb016 100644 --- a/test/unit/jest.config.js +++ b/test/unit/jest.config.js @@ -4,13 +4,16 @@ const glob = require( 'glob' ).sync; // Finds all packages which are transpiled with Babel to force Jest to use their source code. -const transpiledPackageNames = glob( 'packages/*/src/index.js' ) - .map( ( fileName ) => fileName.split( '/' )[ 1 ] ); +const transpiledPackageNames = glob( 'packages/*/src/index.js' ).map( + ( fileName ) => fileName.split( '/' )[ 1 ] +); module.exports = { rootDir: '../../', moduleNameMapper: { - [ `@wordpress\\/(${ transpiledPackageNames.join( '|' ) })$` ]: 'packages/$1/src', + [ `@wordpress\\/(${ transpiledPackageNames.join( + '|' + ) })$` ]: 'packages/$1/src', }, preset: '@wordpress/jest-preset-default', setupFiles: [ @@ -20,13 +23,13 @@ module.exports = { ], testURL: 'http://localhost', testPathIgnorePatterns: [ - '/\.git/', + '/.git/', '/node_modules/', '/packages/e2e-tests', '/wordpress/', '<rootDir>/.*/build/', '<rootDir>/.*/build-module/', - '<rootDir>/.+\.native\.js$', + '<rootDir>/.+.native.js$', ], transform: { '^.+\\.[jt]sx?$': '<rootDir>/test/unit/scripts/babel-transformer.js', diff --git a/test/unit/scripts/babel-transformer.js b/test/unit/scripts/babel-transformer.js index 8c433576cedf64..34c8dcea3017b2 100644 --- a/test/unit/scripts/babel-transformer.js +++ b/test/unit/scripts/babel-transformer.js @@ -18,7 +18,9 @@ module.exports = { * @return {string} The cache key for the file. */ getCacheKey( src, filename, ...args ) { - const isBlockIndex = /block-library[\/\\]src[\/\\].+[\/\\]index\.js/.test( filename ); + const isBlockIndex = /block-library[\/\\]src[\/\\].+[\/\\]index\.js/.test( + filename + ); if ( ! isBlockIndex ) { return babelJestTransformer.getCacheKey( src, filename, ...args ); @@ -35,6 +37,10 @@ module.exports = { // src together. This will result in the cache key changing and the cache being // invalidated for the index when any changes to the json are made. const blockJSONSrc = fs.readFileSync( blockJSONFilename ); - return babelJestTransformer.getCacheKey( `${ src }\n${ blockJSONSrc }`, filename, ...args ); + return babelJestTransformer.getCacheKey( + `${ src }\n${ blockJSONSrc }`, + filename, + ...args + ); }, }; diff --git a/webpack.config.js b/webpack.config.js index 66a13dd8cd619d..2251ffe338e383 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -22,16 +22,17 @@ const { dependencies } = require( './package' ); const { NODE_ENV: mode = 'development', - WP_DEVTOOL: devtool = ( mode === 'production' ? false : 'source-map' ), + WP_DEVTOOL: devtool = mode === 'production' ? false : 'source-map', } = process.env; const WORDPRESS_NAMESPACE = '@wordpress/'; const BUNDLED_PACKAGES = [ '@wordpress/icons' ]; const gutenbergPackages = Object.keys( dependencies ) - .filter( ( packageName ) => - ! BUNDLED_PACKAGES.includes( packageName ) && - packageName.startsWith( WORDPRESS_NAMESPACE ) + .filter( + ( packageName ) => + ! BUNDLED_PACKAGES.includes( packageName ) && + packageName.startsWith( WORDPRESS_NAMESPACE ) ) .map( ( packageName ) => packageName.replace( WORDPRESS_NAMESPACE, '' ) ); @@ -61,8 +62,15 @@ module.exports = { plugins: [ new DefinePlugin( { // Inject the `GUTENBERG_PHASE` global, used for feature flagging. - 'process.env.GUTENBERG_PHASE': JSON.stringify( parseInt( process.env.npm_package_config_GUTENBERG_PHASE, 10 ) || 1 ), - 'process.env.FORCE_REDUCED_MOTION': JSON.stringify( process.env.FORCE_REDUCED_MOTION ), + 'process.env.GUTENBERG_PHASE': JSON.stringify( + parseInt( + process.env.npm_package_config_GUTENBERG_PHASE, + 10 + ) || 1 + ), + 'process.env.FORCE_REDUCED_MOTION': JSON.stringify( + process.env.FORCE_REDUCED_MOTION + ), } ), new CustomTemplatedPathPlugin( { basename( path, data ) { @@ -86,16 +94,18 @@ module.exports = { return path; }, } ), - new LibraryExportDefaultPlugin( [ - 'api-fetch', - 'deprecated', - 'dom-ready', - 'redux-routine', - 'token-list', - 'server-side-render', - 'shortcode', - 'warning', - ].map( camelCaseDash ) ), + new LibraryExportDefaultPlugin( + [ + 'api-fetch', + 'deprecated', + 'dom-ready', + 'redux-routine', + 'token-list', + 'server-side-render', + 'shortcode', + 'warning', + ].map( camelCaseDash ) + ), new CopyWebpackPlugin( gutenbergPackages.map( ( packageName ) => ( { from: `./packages/${ packageName }/build-style/*.css`, @@ -105,14 +115,20 @@ module.exports = { if ( mode === 'production' ) { return postcss( [ require( 'cssnano' )( { - preset: [ 'default', { - discardComments: { - removeAll: true, + preset: [ + 'default', + { + discardComments: { + removeAll: true, + }, }, - } ], + ], } ), ] ) - .process( content, { from: 'src/app.css', to: 'dest/app.css' } ) + .process( content, { + from: 'src/app.css', + to: 'dest/app.css', + } ) .then( ( result ) => result.css ); } return content; @@ -122,40 +138,51 @@ module.exports = { new CopyWebpackPlugin( [ { from: './packages/block-library/src/**/index.php', - test: new RegExp( `([\\w-]+)${ escapeRegExp( sep ) }index\\.php$` ), + test: new RegExp( + `([\\w-]+)${ escapeRegExp( sep ) }index\\.php$` + ), to: 'build/block-library/blocks/[1].php', transform( content ) { content = content.toString(); // Within content, search for any function definitions. For // each, replace every other reference to it in the file. - return content - .match( /^function [^\(]+/gm ) - .reduce( ( result, functionName ) => { - // Trim leading "function " prefix from match. - functionName = functionName.slice( 9 ); + return ( + content + .match( /^function [^\(]+/gm ) + .reduce( ( result, functionName ) => { + // Trim leading "function " prefix from match. + functionName = functionName.slice( 9 ); - // Prepend the Gutenberg prefix, substituting any - // other core prefix (e.g. "wp_"). - return result.replace( - new RegExp( functionName, 'g' ), - ( match ) => 'gutenberg_' + match.replace( /^wp_/, '' ) - ); - }, content ) - // The core blocks override procedure takes place in - // the init action default priority to ensure that core - // blocks would have been registered already. Since the - // blocks implementations occur at the default priority - // and due to WordPress hooks behavior not considering - // mutations to the same priority during another's - // callback, the Gutenberg build blocks are modified - // to occur at a later priority. - .replace( /(add_action\(\s*'init',\s*'gutenberg_register_block_[^']+'(?!,))/, '$1, 20' ); + // Prepend the Gutenberg prefix, substituting any + // other core prefix (e.g. "wp_"). + return result.replace( + new RegExp( functionName, 'g' ), + ( match ) => + 'gutenberg_' + + match.replace( /^wp_/, '' ) + ); + }, content ) + // The core blocks override procedure takes place in + // the init action default priority to ensure that core + // blocks would have been registered already. Since the + // blocks implementations occur at the default priority + // and due to WordPress hooks behavior not considering + // mutations to the same priority during another's + // callback, the Gutenberg build blocks are modified + // to occur at a later priority. + .replace( + /(add_action\(\s*'init',\s*'gutenberg_register_block_[^']+'(?!,))/, + '$1, 20' + ) + ); }, }, { from: './packages/block-library/src/*/block.json', - test: new RegExp( `([\\w-]+)${ escapeRegExp( sep ) }block\\.json$` ), + test: new RegExp( + `([\\w-]+)${ escapeRegExp( sep ) }block\\.json$` + ), to: 'build/block-library/blocks/[1]/block.json', }, ] ), From c4faefdb49449db2889ffd23170c9824c3efb413 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Tue, 4 Feb 2020 09:18:03 +0100 Subject: [PATCH 105/183] merge master --- changelog.txt | 135 ++++++ gutenberg.php | 2 +- lib/compat.php | 50 --- package-lock.json | 3 +- package.json | 2 +- packages/base-styles/_colors.scss | 11 + .../src/components/block-mover/index.js | 33 +- .../src/components/block-toolbar/index.js | 4 +- .../src/components/link-control/index.js | 4 +- .../src/components/link-control/test/index.js | 52 ++- packages/block-editor/src/store/defaults.js | 8 +- packages/block-library/package.json | 1 + packages/block-library/src/button/edit.js | 14 +- packages/block-library/src/button/save.js | 4 + .../src/gallery/gallery-image.js | 35 +- .../block-library/src/navigation-link/edit.js | 37 +- .../src/navigation-link/editor.scss | 33 +- .../src/navigation-link/icons.js | 16 +- packages/block-library/src/navigation/edit.js | 17 +- .../block-library/src/navigation/editor.scss | 31 -- .../block-library/src/navigation/index.php | 75 ++-- .../block-library/src/navigation/style.scss | 402 ++++++++++-------- .../src/social-link/icons/amazon.js | 3 +- .../src/social-link/icons/bandcamp.js | 2 +- .../src/social-link/icons/behance.js | 3 +- .../src/social-link/icons/chain.js | 3 +- .../src/social-link/icons/codepen.js | 3 +- .../src/social-link/icons/deviantart.js | 3 +- .../src/social-link/icons/dribbble.js | 3 +- .../src/social-link/icons/dropbox.js | 3 +- .../src/social-link/icons/etsy.js | 3 +- .../src/social-link/icons/facebook.js | 3 +- .../src/social-link/icons/feed.js | 3 +- .../src/social-link/icons/fivehundredpx.js | 3 +- .../src/social-link/icons/flickr.js | 3 +- .../src/social-link/icons/foursquare.js | 3 +- .../src/social-link/icons/github.js | 3 +- .../src/social-link/icons/goodreads.js | 3 +- .../src/social-link/icons/google.js | 3 +- .../src/social-link/icons/instagram.js | 3 +- .../src/social-link/icons/lastfm.js | 3 +- .../src/social-link/icons/linkedin.js | 3 +- .../src/social-link/icons/mail.js | 3 +- .../src/social-link/icons/mastodon.js | 3 +- .../src/social-link/icons/medium.js | 3 +- .../src/social-link/icons/meetup.js | 3 +- .../src/social-link/icons/pinterest.js | 3 +- .../src/social-link/icons/pocket.js | 3 +- .../src/social-link/icons/reddit.js | 3 +- .../src/social-link/icons/skype.js | 3 +- .../src/social-link/icons/snapchat.js | 3 +- .../src/social-link/icons/soundcloud.js | 3 +- .../src/social-link/icons/spotify.js | 3 +- .../src/social-link/icons/tumblr.js | 3 +- .../src/social-link/icons/twitch.js | 3 +- .../src/social-link/icons/twitter.js | 3 +- .../src/social-link/icons/vimeo.js | 3 +- .../block-library/src/social-link/icons/vk.js | 3 +- .../src/social-link/icons/wordpress.js | 3 +- .../src/social-link/icons/yelp.js | 3 +- .../src/social-link/icons/youtube.js | 3 +- .../readable-content-view/index.native.js | 10 +- .../blocks/__snapshots__/buttons.test.js.snap | 2 +- .../specs/editor/blocks/classic.test.js | 30 +- .../specs/editor/various/links.test.js | 190 +++++---- .../components/header/header-toolbar/index.js | 2 +- packages/format-library/src/link/index.js | 21 +- packages/format-library/src/link/inline.js | 253 +++-------- .../format-library/src/link/test/inline.js | 24 -- packages/keyboard-shortcuts/CHANGELOG.md | 2 +- .../lib/add-milestone.js | 41 +- .../lib/if-not-fork.js | 21 + .../lib/index.js | 7 +- readme.txt | 2 +- 74 files changed, 884 insertions(+), 806 deletions(-) create mode 100644 packages/project-management-automation/lib/if-not-fork.js diff --git a/changelog.txt b/changelog.txt index 6f7485ebae45d5..f6f5f2ff1b935d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,140 @@ == Changelog == += 7.4.0-rc.1 = + +## Enhancements + +- Add background color support to Columns block [#17813](https://github.com/WordPress/gutenberg/pull/17813) +- Added shippedProposals [#19065](https://github.com/WordPress/gutenberg/pull/19065) 👏 @devdivdev +- Navigation Block: Add submenu chevron w/ setting [#19601](https://github.com/WordPress/gutenberg/pull/19601) +- Navigation: set item ID [#18641](https://github.com/WordPress/gutenberg/pull/18641) +- Select Parent Navigation Block after clicking "Create from all top-level pages" [#19817](https://github.com/WordPress/gutenberg/pull/19817) +- Try/group block custom text color [#19181](https://github.com/WordPress/gutenberg/pull/19181) +- Update: Readme.txt Link to changelog instead of adding it inline [#19761](https://github.com/WordPress/gutenberg/pull/19761) + +## Bug Fixes + +- Block Editor: Handle LinkControl submission via form handler [#19651](https://github.com/WordPress/gutenberg/pull/19651) +- Block Editor: Link Control: Use URL as link when title empty [#19739](https://github.com/WordPress/gutenberg/pull/19739) +- Block Editor: LinkControl: Prevent focus loss in edit mode toggle [#19931](https://github.com/WordPress/gutenberg/pull/19931) +- Block Editor: LinkControl: Resolve error when undefined value, "view" state [#19856](https://github.com/WordPress/gutenberg/pull/19856) +- Block Library: Handle Popover onClose for LinkControl [#19885](https://github.com/WordPress/gutenberg/pull/19885) +- Block Library: Stop gallery images from creating undo levels as they load. [#19937](https://github.com/WordPress/gutenberg/pull/19937) +- Components: FontSizePicker: Adjust Select Button sizing [#19479](https://github.com/WordPress/gutenberg/pull/19479) +- Create block: Code quality improvements for the block scaffolding [#19867](https://github.com/WordPress/gutenberg/pull/19867) +- Editor: Remove post title escaping [#19955](https://github.com/WordPress/gutenberg/pull/19955) +- Embed: Fix failure placeholder alignment/sizing [#19673](https://github.com/WordPress/gutenberg/pull/19673) +- Fix RTL styling for Media Text block [#18764](https://github.com/WordPress/gutenberg/pull/18764) +- Fix panel header styles [#19842](https://github.com/WordPress/gutenberg/pull/19842) 👏 @adekbadek +- Fix: Admin menu collapses for 960px width but editor doesn't [#19970](https://github.com/WordPress/gutenberg/pull/19970) +- Fix: Color Gradients component was not able to handle only gradient picking [#19925](https://github.com/WordPress/gutenberg/pull/19925) +- Fix: Crash when creating a hierarchical post without title [#19936](https://github.com/WordPress/gutenberg/pull/19936) +- Fix: Media & Text: "Crop image to fill entire column" resets on image change [#19765](https://github.com/WordPress/gutenberg/pull/19765) +- Multi selection: fix intermittent e2e failure [#19865](https://github.com/WordPress/gutenberg/pull/19865) +- Popover: clean up requestAnimationFrame [#19771](https://github.com/WordPress/gutenberg/pull/19771) +- Popover: fix typo in removing event listener [#19978](https://github.com/WordPress/gutenberg/pull/19978) +- Project Management: Fix pull request merge automation errors [#19768](https://github.com/WordPress/gutenberg/pull/19768) +- Project Management: Run pull request automation on closed [#19742](https://github.com/WordPress/gutenberg/pull/19742) +- Rich text: enable if multi selection is aborted [#19839](https://github.com/WordPress/gutenberg/pull/19839) +- Use Select: Fix render queue. [#19286](https://github.com/WordPress/gutenberg/pull/19286) +- Use require.resolve() instead of <rootDir> in @wordpress/jest-preset-default [#19957](https://github.com/WordPress/gutenberg/pull/19957) +- [RNMobile] Adjust vertical margins in InnerBlock [#19960](https://github.com/WordPress/gutenberg/pull/19960) +- [RNMobile] change order of composing style in SVG primitive [#19927](https://github.com/WordPress/gutenberg/pull/19927) +- [RNMobile] fix show appender and separator in Group block [#19908](https://github.com/WordPress/gutenberg/pull/19908) + +## Documentation + +- Add docs for LocalAutosaveMonitor and \_\_experimentalUpdateLocalAutosaveInterval [#19915](https://github.com/WordPress/gutenberg/pull/19915) +- Add markdownlint script to lint docs markup [#19855](https://github.com/WordPress/gutenberg/pull/19855) +- Added changelog entry for @wordpress/jest-preset-default [#19958](https://github.com/WordPress/gutenberg/pull/19958) +- Docs: Add details for format-js to @wordpress/scripts package [#19946](https://github.com/WordPress/gutenberg/pull/19946) +- Organize Contributors Guide [#19853](https://github.com/WordPress/gutenberg/pull/19853) +- fix typo "Th" to "The" [#19833](https://github.com/WordPress/gutenberg/pull/19833) 👏 @nekomajin +- Storybook: Add Placeholder component story [#19734](https://github.com/WordPress/gutenberg/pull/19734) +- Update component to components in CONTRIBUTING.md [#19914](https://github.com/WordPress/gutenberg/pull/19914) + +## Various + +- Bump @babel/preset-env to 7.8.3 (Optional Chaining!) [#19831](https://github.com/WordPress/gutenberg/pull/19831) 👏 @sainthkh +- Build Tooling: Build: Include block.json files in the build output [#19786](https://github.com/WordPress/gutenberg/pull/19786) +- Build Tooling: Bundle the icons package instead of using it as an external [#19809](https://github.com/WordPress/gutenberg/pull/19809) +- Code Quality: Add Prettier formatting script [#18048](https://github.com/WordPress/gutenberg/pull/18048) +- Code Quality: Add WordPress primitives package [#19781](https://github.com/WordPress/gutenberg/pull/19781) +- Code Quality: Block Editor: LinkControl: Align documentation with current behavior [#19736](https://github.com/WordPress/gutenberg/pull/19736) +- Code Quality: Block Editor: Refactor ObserveTyping as function component [#19881](https://github.com/WordPress/gutenberg/pull/19881) +- Code Quality: Block editor: move is-navigate-mode class to WritingFlow [#19868](https://github.com/WordPress/gutenberg/pull/19868) +- Code Quality: Block: use context to provide selected element [#19782](https://github.com/WordPress/gutenberg/pull/19782) +- Code Quality: Blocks: Rename patterns to variations in the Block API [#19966](https://github.com/WordPress/gutenberg/pull/19966) +- Code Quality: Eslint Plugin: Lint code formatting. [#19963](https://github.com/WordPress/gutenberg/pull/19963) +- Code Quality: Eslint: set line width to 80 [#19992](https://github.com/WordPress/gutenberg/pull/19992) +- Code Quality: Move Alignment, movers and trash icons to the icons package [#19944](https://github.com/WordPress/gutenberg/pull/19944) +- Code Quality: Move a dozen of block icons to the icons package [#19808](https://github.com/WordPress/gutenberg/pull/19808) +- Code Quality: Move more block SVGs to the icons package [#19834](https://github.com/WordPress/gutenberg/pull/19834) +- Code Quality: Move more block icons to the icons library [#19862](https://github.com/WordPress/gutenberg/pull/19862) +- Code Quality: Move more block icons to the icons package [#19838](https://github.com/WordPress/gutenberg/pull/19838) +- Code Quality: Move the insert dashicon to the icons package [#19943](https://github.com/WordPress/gutenberg/pull/19943) +- Code Quality: Multi-select: don't focus first selected block [#19762](https://github.com/WordPress/gutenberg/pull/19762) +- Code Quality: Paragraph block: remove min-height [#19835](https://github.com/WordPress/gutenberg/pull/19835) +- Code Quality: Paragraph block: remove unnecessary CSS after shortcuts removal [#19821](https://github.com/WordPress/gutenberg/pull/19821) +- Code Quality: Remove dead is-hovered selectors [#19870](https://github.com/WordPress/gutenberg/pull/19870) +- Code Quality: Remove editor dependency from the block library [#16160](https://github.com/WordPress/gutenberg/pull/16160) +- Code Quality: Remove unnecessary import from playground [#19893](https://github.com/WordPress/gutenberg/pull/19893) 👏 @costasovo +- Code Quality: Replace all occurences of the yes dashicon with the check icon from the icons package [#19926](https://github.com/WordPress/gutenberg/pull/19926) +- Code Quality: RichText: try using hooks for wrapper component [#19095](https://github.com/WordPress/gutenberg/pull/19095) +- Code Quality: Try the link control in the link format [#19462](https://github.com/WordPress/gutenberg/pull/19462) +- Code Quality: Update: Removed editor store usage from native mobile block editor inserter [#18794](https://github.com/WordPress/gutenberg/pull/18794) +- Copy: Apply sentence case formatting to PanelBody titles [#19901](https://github.com/WordPress/gutenberg/pull/19901) +- Experimental: RichText API: Limit "prefix" transformations to Paragraph blocks [#19727](https://github.com/WordPress/gutenberg/pull/19727) +- Feature: Packages: New create-block package for block scaffolding [#19773](https://github.com/WordPress/gutenberg/pull/19773) +- Project Management: Project management: Add step that updates CHANGELOG files before npm releases [#19764](https://github.com/WordPress/gutenberg/pull/19764) +- Regression: Block editor: Alt+F10 shouldn't scroll to top [#19896](https://github.com/WordPress/gutenberg/pull/19896) +- Regression: Multi-selection: fix clearing with side click [#19787](https://github.com/WordPress/gutenberg/pull/19787) +- Regression: Update hover and focus selectors for Move to Trash to ensure the link is always red [#19974](https://github.com/WordPress/gutenberg/pull/19974) 👏 @andrewserong +- Task: Add AnglePicker Component; Add useDragging hook [#19637](https://github.com/WordPress/gutenberg/pull/19637) +- Task: Add: Global styles css variables generation mechanism [#19883](https://github.com/WordPress/gutenberg/pull/19883) +- Task: Introduce Icons package [#17055](https://github.com/WordPress/gutenberg/pull/17055) +- Technical Prototype: Blocks: Match blocks in the inserter using keywords from patterns [#19243](https://github.com/WordPress/gutenberg/pull/19243) +- Various: Add a prop to RichText native to allow configuration of selection/caret color [#19635](https://github.com/WordPress/gutenberg/pull/19635) +- Various: Add mobile code owners for gallery block [#19829](https://github.com/WordPress/gutenberg/pull/19829) +- Various: Add/post documentation for apiFetch [#19759](https://github.com/WordPress/gutenberg/pull/19759) +- Various: Added conditions and new translation strings for BlockMover [#19757](https://github.com/WordPress/gutenberg/pull/19757) +- Various: Block Directory: Refactor the reducer by breaking out the block management actions into their own reducer. [#19330](https://github.com/WordPress/gutenberg/pull/19330) 👏 @StevenDufresne +- Various: Block Editor: Link Control: Initialize inputValue state from value [#19737](https://github.com/WordPress/gutenberg/pull/19737) +- Various: Clarify when isEligible function is called [#19899](https://github.com/WordPress/gutenberg/pull/19899) 👏 @bfintal +- Various: Components: Apply width-based modifier classes to Placeholder only when width is known [#19825](https://github.com/WordPress/gutenberg/pull/19825) +- Various: Core-data: do not publish outdated state to subscribers during updates [#19752](https://github.com/WordPress/gutenberg/pull/19752) 👏 @alshakero +- Various: Disable Autocomplete in shortcode block [#19848](https://github.com/WordPress/gutenberg/pull/19848) 👏 @chipsnyder +- Various: Do advanced rendering by hooking into render_block for navigation block [#19991](https://github.com/WordPress/gutenberg/pull/19991) +- Various: Do not use the deprecated package editor for InnerBlocks component [#19869](https://github.com/WordPress/gutenberg/pull/19869) 👏 @Mahjouba91 +- Various: Expose @wordpress/icons to react-native [#19810](https://github.com/WordPress/gutenberg/pull/19810) +- Various: Fix card component sub-component example code [#19802](https://github.com/WordPress/gutenberg/pull/19802) 👏 @ediamin +- Various: Fix, update, and sort _rc_ `hasProjectFile` filenames [#19994](https://github.com/WordPress/gutenberg/pull/19994) +- Various: Framework: Fix server-registered fixtures script [#19884](https://github.com/WordPress/gutenberg/pull/19884) +- Various: Innerblock Templates Docs Link Typo Issue Fixed [#19813](https://github.com/WordPress/gutenberg/pull/19813) 👏 @delowardev +- Various: Lib: Limit `pre_render_block` extension. [#19989](https://github.com/WordPress/gutenberg/pull/19989) +- Various: Makes appenders visible only for the current selection [#19598](https://github.com/WordPress/gutenberg/pull/19598) +- Various: Navigation Block: Move the Link Settings panel [#19917](https://github.com/WordPress/gutenberg/pull/19917) +- Various: Navigation: Improve UX on adding links [#19686](https://github.com/WordPress/gutenberg/pull/19686) +- Various: Navigation: Manage navigation link appender visibility [#19846](https://github.com/WordPress/gutenberg/pull/19846) +- Various: Rich text: remove is-selected class [#19822](https://github.com/WordPress/gutenberg/pull/19822) +- Various: Shortcode Design Review [#19852](https://github.com/WordPress/gutenberg/pull/19852) 👏 @chipsnyder +- Various: Style improvements for template previews [#19763](https://github.com/WordPress/gutenberg/pull/19763) +- Various: Testing: Use deterministic selectors for incremented IDs [#19844](https://github.com/WordPress/gutenberg/pull/19844) +- Various: Update Primitives README.md [#19876](https://github.com/WordPress/gutenberg/pull/19876) +- Various: [Mobile] Disable gallery size options on mobile in v1.21.0 [#19828](https://github.com/WordPress/gutenberg/pull/19828) +- Various: [Mobile] Fix gallery upload sync [#19941](https://github.com/WordPress/gutenberg/pull/19941) +- Various: [RNMobile] Add media edit icon to image block [#19723](https://github.com/WordPress/gutenberg/pull/19723) 👏 @leandroalonso +- Various: [RNMobile] Correct isMobile condition in nested Media&Text [#19778](https://github.com/WordPress/gutenberg/pull/19778) +- Various: [RNMobile] Fix crash on list block controls [#19818](https://github.com/WordPress/gutenberg/pull/19818) +- Various: [RNMobile] Fix image preview with small image size selected [#19247](https://github.com/WordPress/gutenberg/pull/19247) +- Various: [RNMobile] Long-press on inserter to show options for "add above" and "add below" [#18791](https://github.com/WordPress/gutenberg/pull/18791) 👏 @ceyhun +- Various: [RNMobile] Release v1.21.0 to master [#19854](https://github.com/WordPress/gutenberg/pull/19854) +- Various: [RNMobile] Revert change to fix Action Sheet [#19934](https://github.com/WordPress/gutenberg/pull/19934) 👏 @leandroalonso +- Various: [RNMobile] Show the media edit icon only if the block is selected [#19961](https://github.com/WordPress/gutenberg/pull/19961) 👏 @leandroalonso +- Various: [RNmobile] Upgrade to RN 0.61.5 [#19369](https://github.com/WordPress/gutenberg/pull/19369) + + + = 7.3.0 = diff --git a/gutenberg.php b/gutenberg.php index 24b06d606747f5..597e9672299cad 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -3,7 +3,7 @@ * Plugin Name: Gutenberg * Plugin URI: https://github.com/WordPress/gutenberg * Description: Printing since 1440. This is the development plugin for the new block editor in core. - * Version: 7.3.0 + * Version: 7.4.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/lib/compat.php b/lib/compat.php index cb85b3e3c72d70..4df03c8bf99b9b 100644 --- a/lib/compat.php +++ b/lib/compat.php @@ -30,56 +30,6 @@ function gutenberg_safe_style_css_column_flex_basis( $attr ) { } add_filter( 'safe_style_css', 'gutenberg_safe_style_css_column_flex_basis' ); -/** - * Shim that hooks into `pre_render_block` so as to override `render_block` - * with a function that passes `render_callback` the block object as the - * argument. - * - * @see https://core.trac.wordpress.org/ticket/48104 - * - * @param string $pre_render The pre-rendered content. Default null. - * @param array $block The block being rendered. - * - * @return string String of rendered HTML. - */ -function gutenberg_provide_render_callback_with_block_object( $pre_render, $block ) { - global $post; - if ( 'core/navigation' !== $block['blockName'] ) { - return $pre_render; - } - - $source_block = $block; - - /** This filter is documented in src/wp-includes/blocks.php */ - $block = apply_filters( 'render_block_data', $block, $source_block ); - - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); - $is_dynamic = $block['blockName'] && null !== $block_type && $block_type->is_dynamic(); - $block_content = ''; - $index = 0; - - foreach ( $block['innerContent'] as $chunk ) { - $block_content .= is_string( $chunk ) ? $chunk : render_block( $block['innerBlocks'][ $index++ ] ); - } - - if ( ! is_array( $block['attrs'] ) ) { - $block['attrs'] = array(); - } - - if ( $is_dynamic ) { - $global_post = $post; - - $prepared_attributes = $block_type->prepare_attributes_for_render( $block['attrs'] ); - $block_content = (string) call_user_func( $block_type->render_callback, $prepared_attributes, $block_content, $block ); - - $post = $global_post; - } - - /** This filter is documented in src/wp-includes/blocks.php */ - return apply_filters( 'render_block', $block_content, $block ); -} -add_filter( 'pre_render_block', 'gutenberg_provide_render_callback_with_block_object', 10, 2 ); - /** * Sets the current post for usage in template blocks. * diff --git a/package-lock.json b/package-lock.json index 1d5a493bf8e4e8..9fd0ce179aa06a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "7.3.0", + "version": "7.4.0-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -10123,6 +10123,7 @@ "@wordpress/icons": "file:packages/icons", "@wordpress/is-shallow-equal": "file:packages/is-shallow-equal", "@wordpress/keycodes": "file:packages/keycodes", + "@wordpress/primitives": "file:packages/primitives", "@wordpress/rich-text": "file:packages/rich-text", "@wordpress/server-side-render": "file:packages/server-side-render", "@wordpress/url": "file:packages/url", diff --git a/package.json b/package.json index 38f2036891596d..081362bb0a2b74 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "7.3.0", + "version": "7.4.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", diff --git a/packages/base-styles/_colors.scss b/packages/base-styles/_colors.scss index 2a8b07f79364cf..d6fd0e2b968ce6 100644 --- a/packages/base-styles/_colors.scss +++ b/packages/base-styles/_colors.scss @@ -89,3 +89,14 @@ $blue-medium-focus-dark: #fff; $alert-yellow: #f0b849; $alert-red: #d94f4f; $alert-green: #4ab866; + +// Navigation. +$light-style-sub-menu-background-color: #fff; +$light-style-sub-menu-border-color: #ddd; +$light-style-sub-menu-text-color: #111; +$light-style-sub-menu-text-color-hover: #333; + +$dark-style-sub-menu-background-color: #333; +$dark-style-sub-menu-border-color: #111; +$dark-style-sub-menu-text-color: #fff; +$dark-style-sub-menu-text-color-hover: #eee; diff --git a/packages/block-editor/src/components/block-mover/index.js b/packages/block-editor/src/components/block-mover/index.js index 7e29326458fd13..1a7290b10dcb00 100644 --- a/packages/block-editor/src/components/block-mover/index.js +++ b/packages/block-editor/src/components/block-mover/index.js @@ -59,6 +59,7 @@ export class BlockMover extends Component { instanceId, isHidden, rootClientId, + hideDragHandle, } = this.props; const { isFocused } = this.state; const blocksCount = castArray( clientIds ).length; @@ -122,21 +123,23 @@ export class BlockMover extends Component { onBlur={ this.onBlur } /> - <BlockDraggable clientIds={ clientIds }> - { ( { onDraggableStart, onDraggableEnd } ) => ( - <Button - icon={ dragHandle } - className="block-editor-block-mover__control-drag-handle block-editor-block-mover__control" - aria-hidden="true" - // Should not be able to tab to drag handle as this - // button can only be used with a pointer device. - tabIndex="-1" - onDragStart={ onDraggableStart } - onDragEnd={ onDraggableEnd } - draggable - /> - ) } - </BlockDraggable> + { ! hideDragHandle && ( + <BlockDraggable clientIds={ clientIds }> + { ( { onDraggableStart, onDraggableEnd } ) => ( + <Button + icon={ dragHandle } + className="block-editor-block-mover__control-drag-handle block-editor-block-mover__control" + aria-hidden="true" + // Should not be able to tab to drag handle as this + // button can only be used with a pointer device. + tabIndex="-1" + onDragStart={ onDraggableStart } + onDragEnd={ onDraggableEnd } + draggable + /> + ) } + </BlockDraggable> + ) } <Button className="block-editor-block-mover__control" diff --git a/packages/block-editor/src/components/block-toolbar/index.js b/packages/block-editor/src/components/block-toolbar/index.js index 656527954ed840..a3eed1acd3b2d4 100644 --- a/packages/block-editor/src/components/block-toolbar/index.js +++ b/packages/block-editor/src/components/block-toolbar/index.js @@ -14,7 +14,7 @@ import BlockSwitcher from '../block-switcher'; import MultiBlocksSwitcher from '../block-switcher/multi-blocks-switcher'; import BlockMover from '../block-mover'; -export default function BlockToolbar() { +export default function BlockToolbar( { hideDragHandle } ) { const { blockClientIds, isValid, @@ -64,6 +64,7 @@ export default function BlockToolbar() { <BlockMover clientIds={ blockClientIds } __experimentalOrientation={ moverDirection } + hideDragHandle={ hideDragHandle } /> ) } <MultiBlocksSwitcher /> @@ -78,6 +79,7 @@ export default function BlockToolbar() { <BlockMover clientIds={ blockClientIds } __experimentalOrientation={ moverDirection } + hideDragHandle={ hideDragHandle } /> ) } { mode === 'visual' && isValid && ( diff --git a/packages/block-editor/src/components/link-control/index.js b/packages/block-editor/src/components/link-control/index.js index cda7986a9f8c44..5f95d64adde6d9 100644 --- a/packages/block-editor/src/components/link-control/index.js +++ b/packages/block-editor/src/components/link-control/index.js @@ -292,8 +292,8 @@ function LinkControl( { ) } suggestion={ suggestion } onClick={ () => { - stopEditing(); onChange( { ...value, ...suggestion } ); + stopEditing(); } } isSelected={ index === selectedSuggestion } isURL={ manualLinkEntryTypes.includes( @@ -318,8 +318,8 @@ function LinkControl( { value={ inputValue } onChange={ onInputChange } onSelect={ ( suggestion ) => { - stopEditing(); onChange( { ...value, ...suggestion } ); + stopEditing(); } } renderSuggestions={ renderSearchResults } fetchSuggestions={ getSearchHandler } diff --git a/packages/block-editor/src/components/link-control/test/index.js b/packages/block-editor/src/components/link-control/test/index.js index 44424efe334295..65d7729c973dc4 100644 --- a/packages/block-editor/src/components/link-control/test/index.js +++ b/packages/block-editor/src/components/link-control/test/index.js @@ -7,7 +7,7 @@ import { first, last, nth } from 'lodash'; /** * WordPress dependencies */ -import { useState } from '@wordpress/element'; +import { useState, useRef } from '@wordpress/element'; import { UP, DOWN, ENTER } from '@wordpress/keycodes'; /** * Internal dependencies @@ -766,6 +766,56 @@ describe( 'Selecting links', () => { } ); } ); + + it( 'does not forcefully regain focus if onChange handler had shifted it', () => { + // Regression: Previously, there had been issues where if `onChange` + // would programmatically shift focus, LinkControl would try to force it + // back, based on its internal logic to determine whether it had focus + // when finishing an edit occuring _before_ `onChange` having been run. + // + // See: https://github.com/WordPress/gutenberg/pull/19462 + + const LinkControlConsumer = () => { + const focusTarget = useRef(); + + return ( + <> + <div tabIndex={ -1 } data-expected ref={ focusTarget } /> + <LinkControl + onChange={ () => focusTarget.current.focus() } + /> + </> + ); + }; + + act( () => { + render( <LinkControlConsumer />, container ); + } ); + + // Change value. + const form = container.querySelector( 'form' ); + const searchInput = container.querySelector( + 'input[aria-label="URL"]' + ); + + // Simulate searching for a term + act( () => { + Simulate.change( searchInput, { + target: { value: 'https://example.com' }, + } ); + } ); + act( () => { + Simulate.keyDown( searchInput, { keyCode: ENTER } ); + } ); + act( () => { + Simulate.submit( form ); + } ); + + const isExpectedFocusTarget = document.activeElement.hasAttribute( + 'data-expected' + ); + expect( isExpectedFocusTarget ).toBe( true ); + } ); } ); describe( 'Addition Settings UI', () => { diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js index a0ab78569c5083..790d65a5ae6dc6 100644 --- a/packages/block-editor/src/store/defaults.js +++ b/packages/block-editor/src/store/defaults.js @@ -126,10 +126,10 @@ export const SETTINGS_DEFAULTS = { ], imageSizes: [ - { slug: 'thumbnail', label: __( 'Thumbnail' ) }, - { slug: 'medium', label: __( 'Medium' ) }, - { slug: 'large', label: __( 'Large' ) }, - { slug: 'full', label: __( 'Full Size' ) }, + { slug: 'thumbnail', name: __( 'Thumbnail' ) }, + { slug: 'medium', name: __( 'Medium' ) }, + { slug: 'large', name: __( 'Large' ) }, + { slug: 'full', name: __( 'Full Size' ) }, ], // This is current max width of the block inner area diff --git a/packages/block-library/package.json b/packages/block-library/package.json index 1a99a38df771fb..9f040c95070518 100644 --- a/packages/block-library/package.json +++ b/packages/block-library/package.json @@ -45,6 +45,7 @@ "@wordpress/icons": "file:../icons", "@wordpress/is-shallow-equal": "file:../is-shallow-equal", "@wordpress/keycodes": "file:../keycodes", + "@wordpress/primitives": "file:../primitives", "@wordpress/rich-text": "file:../rich-text", "@wordpress/server-side-render": "file:../server-side-render", "@wordpress/url": "file:../url", diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 69277fd52c9766..e2805329391fe7 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -2,7 +2,6 @@ * External dependencies */ import classnames from 'classnames'; -import { escape } from 'lodash'; /** * WordPress dependencies @@ -86,7 +85,6 @@ function BorderPanel( { borderRadius = '', setAttributes } ) { function URLPicker( { isSelected, url, - title, setAttributes, opensInNewTab, onToggleOpenInNewTab, @@ -102,16 +100,12 @@ function URLPicker( { > <LinkControl className="wp-block-navigation-link__inline-link-input" - value={ { url, title, opensInNewTab } } + value={ { url, opensInNewTab } } onChange={ ( { - title: newTitle = '', url: newURL = '', opensInNewTab: newOpensInNewTab, } ) => { - setAttributes( { - title: escape( newTitle ), - url: newURL, - } ); + setAttributes( { url: newURL } ); if ( opensInNewTab !== newOpensInNewTab ) { onToggleOpenInNewTab( newOpensInNewTab ); @@ -164,7 +158,6 @@ function ButtonEdit( { placeholder, rel, text, - title, url, } = attributes; const onSetLinkRel = useCallback( @@ -199,7 +192,7 @@ function ButtonEdit( { } = __experimentalUseGradient(); return ( - <div className={ className } title={ title }> + <div className={ className }> <RichText placeholder={ placeholder || __( 'Add text…' ) } value={ text } @@ -225,7 +218,6 @@ function ButtonEdit( { } } /> <URLPicker - title={ title } url={ url } setAttributes={ setAttributes } isSelected={ isSelected } diff --git a/packages/block-library/src/button/save.js b/packages/block-library/src/button/save.js index c171dfa39ef34b..542222f49e98c2 100644 --- a/packages/block-library/src/button/save.js +++ b/packages/block-library/src/button/save.js @@ -57,6 +57,10 @@ export default function save( { attributes } ) { borderRadius: borderRadius ? borderRadius + 'px' : undefined, }; + // The use of a `title` attribute here is soft-deprecated, but still applied + // if it had already been assigned, for the sake of backward-compatibility. + // A title will no longer be assigned for new or updated button block links. + return ( <div> <RichText.Content diff --git a/packages/block-library/src/gallery/gallery-image.js b/packages/block-library/src/gallery/gallery-image.js index 089141fb5e2c66..f618e90b776d59 100644 --- a/packages/block-library/src/gallery/gallery-image.js +++ b/packages/block-library/src/gallery/gallery-image.js @@ -10,9 +10,10 @@ import { Component } from '@wordpress/element'; import { Button, Spinner } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { BACKSPACE, DELETE } from '@wordpress/keycodes'; -import { withSelect } from '@wordpress/data'; +import { withSelect, withDispatch } from '@wordpress/data'; import { RichText } from '@wordpress/block-editor'; import { isBlobURL } from '@wordpress/blob'; +import { compose } from '@wordpress/compose'; /** * Internal dependencies @@ -74,8 +75,14 @@ class GalleryImage extends Component { } componentDidUpdate( prevProps ) { - const { isSelected, image, url } = this.props; + const { + isSelected, + image, + url, + __unstableMarkNextChangeAsNotPersistent, + } = this.props; if ( image && ! url ) { + __unstableMarkNextChangeAsNotPersistent(); this.props.setAttributes( { url: image.source_url, alt: image.alt_text, @@ -200,11 +207,21 @@ class GalleryImage extends Component { } } -export default withSelect( ( select, ownProps ) => { - const { getMedia } = select( 'core' ); - const { id } = ownProps; +export default compose( [ + withSelect( ( select, ownProps ) => { + const { getMedia } = select( 'core' ); + const { id } = ownProps; - return { - image: id ? getMedia( id ) : null, - }; -} )( GalleryImage ); + return { + image: id ? getMedia( id ) : null, + }; + } ), + withDispatch( ( dispatch ) => { + const { __unstableMarkNextChangeAsNotPersistent } = dispatch( + 'core/block-editor' + ); + return { + __unstableMarkNextChangeAsNotPersistent, + }; + } ), +] )( GalleryImage ); diff --git a/packages/block-library/src/navigation-link/edit.js b/packages/block-library/src/navigation-link/edit.js index ff55c5dabfecd6..ee0a93e6f43f27 100644 --- a/packages/block-library/src/navigation-link/edit.js +++ b/packages/block-library/src/navigation-link/edit.js @@ -36,7 +36,7 @@ import { placeCaretAtHorizontalEdge } from '@wordpress/dom'; /** * Internal dependencies */ -import { toolbarSubmenuIcon, itemSubmenuIcon } from './icons'; +import { ToolbarSubmenuIcon, ItemSubmenuIcon } from './icons'; function NavigationLinkEdit( { attributes, @@ -46,6 +46,7 @@ function NavigationLinkEdit( { setAttributes, showSubmenuIcon, insertLinkBlock, + navigationBlockAttributes, } ) { const { label, @@ -55,6 +56,17 @@ function NavigationLinkEdit( { nofollow, description, } = attributes; + + /* + * Navigation Block attributes. + */ + const { + textColor, + rgbTextColor, + backgroundColor, + rgbBackgroundColor, + } = navigationBlockAttributes; + const link = { title: title ? unescape( title ) : '', url, @@ -137,7 +149,7 @@ function NavigationLinkEdit( { /> <ToolbarButton name="submenu" - icon={ toolbarSubmenuIcon } + icon={ <ToolbarSubmenuIcon /> } title={ __( 'Add submenu' ) } onClick={ insertLinkBlock } /> @@ -196,7 +208,16 @@ function NavigationLinkEdit( { 'is-editing': isSelected || isParentOfSelectedBlock, 'is-selected': isSelected, 'has-link': !! url, + 'has-child': hasDescendants, + 'has-text-color': rgbTextColor, + [ `has-${ textColor }-color` ]: !! textColor, + 'has-background': rgbBackgroundColor, + [ `has-${ backgroundColor }-background-color` ]: !! backgroundColor, } ) } + style={ { + color: rgbTextColor, + backgroundColor: rgbBackgroundColor, + } } > <div className="wp-block-navigation-link__content"> <RichText @@ -218,7 +239,7 @@ function NavigationLinkEdit( { /> { showSubmenuIcon && ( <span className="wp-block-navigation-link__submenu-icon"> - { itemSubmenuIcon } + <ItemSubmenuIcon /> </span> ) } { isLinkOpen && ( @@ -286,7 +307,6 @@ function NavigationLinkEdit( { export default compose( [ withSelect( ( select, ownProps ) => { const { - getBlockName, getBlockAttributes, getBlockParents, getClientIdsOfDescendants, @@ -294,21 +314,18 @@ export default compose( [ } = select( 'core/block-editor' ); const { clientId } = ownProps; const rootBlock = getBlockParents( clientId )[ 0 ]; - const parentBlock = getBlockParents( clientId, true )[ 0 ]; - const rootBlockAttributes = getBlockAttributes( rootBlock ); + const navigationBlockAttributes = getBlockAttributes( rootBlock ); const hasDescendants = !! getClientIdsOfDescendants( [ clientId ] ) .length; - const isLevelZero = getBlockName( parentBlock ) === 'core/navigation'; const showSubmenuIcon = - rootBlockAttributes.showSubmenuIcon && - isLevelZero && - hasDescendants; + !! navigationBlockAttributes.showSubmenuIcon && hasDescendants; const isParentOfSelectedBlock = hasSelectedInnerBlock( clientId, true ); return { isParentOfSelectedBlock, hasDescendants, showSubmenuIcon, + navigationBlockAttributes, }; } ), withDispatch( ( dispatch, ownProps, registry ) => { diff --git a/packages/block-library/src/navigation-link/editor.scss b/packages/block-library/src/navigation-link/editor.scss index 13d7ce777e36fe..74856642cc23af 100644 --- a/packages/block-library/src/navigation-link/editor.scss +++ b/packages/block-library/src/navigation-link/editor.scss @@ -11,22 +11,8 @@ * Adjust Navigation Item. */ .wp-block-navigation-link { - margin-left: $grid-size; - margin-right: $grid-size; - // Provide a base menu item margin. - // This should be the same inside the field, - // and the edit container should compensate. - // This is to make sure the edit and view are the same. - padding: 0 $grid-size; - .block-editor-block-list__layout { display: block; - margin: $grid-size; - } - - // Only display inner blocks when the block is being edited. - .block-editor-inner-blocks { - display: none; } &.is-editing, @@ -34,21 +20,18 @@ min-width: 20px; } - &.is-editing .block-editor-inner-blocks { - display: block; - } - - .wp-block-navigation-link__content { - display: flex; - align-items: center; - } - &.has-link .wp-block-navigation-link__label { text-decoration: underline; } - .wp-block-navigation-link__submenu-icon { - margin-left: 4px; + .block-editor-rich-text__editable.is-selected:not(.keep-placeholder-on-focus):not(:focus) [data-rich-text-placeholder]::after { + display: inline-block; + } + + .block-list-appender { + margin: $grid-size * 2; + margin-left: $grid-size * 1.25; + margin-top: $grid-size * 1.25; } } diff --git a/packages/block-library/src/navigation-link/icons.js b/packages/block-library/src/navigation-link/icons.js index c01b5979dc36a5..65332cd2555ba5 100644 --- a/packages/block-library/src/navigation-link/icons.js +++ b/packages/block-library/src/navigation-link/icons.js @@ -1,22 +1,24 @@ /** * WordPress dependencies */ -import { Polygon, Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/components'; -export const toolbarSubmenuIcon = ( +export const ToolbarSubmenuIcon = () => ( <SVG xmlns="http://www.w3.org/2000/svg" width="24" height="24"> <Path d="M14 5h8v2h-8zm0 5.5h8v2h-8zm0 5.5h8v2h-8zM2 11.5C2 15.08 4.92 18 8.5 18H9v2l3-3-3-3v2h-.5C6.02 16 4 13.98 4 11.5S6.02 7 8.5 7H12V5H8.5C4.92 5 2 7.92 2 11.5z" /> <Path fill="none" d="M0 0h24v24H0z" /> </SVG> ); -export const itemSubmenuIcon = ( +export const ItemSubmenuIcon = () => ( <SVG - width="18" - height="18" xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 18 18" + width="12" + height="12" + viewBox="0 0 24 24" + transform="rotate(90)" > - <Polygon points="9,13.5 14.7,7.9 13.2,6.5 9,10.7 4.8,6.5 3.3,7.9 " /> + <Path d="M8 5v14l11-7z" /> + <Path d="M0 0h24v24H0z" fill="none" /> </SVG> ); diff --git a/packages/block-library/src/navigation/edit.js b/packages/block-library/src/navigation/edit.js index c50f07f8863af8..d3b38c43fa83a5 100644 --- a/packages/block-library/src/navigation/edit.js +++ b/packages/block-library/src/navigation/edit.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { useMemo, Fragment, useRef } from '@wordpress/element'; +import { useMemo, Fragment, useRef, useEffect } from '@wordpress/element'; import { InnerBlocks, InspectorControls, @@ -51,6 +51,7 @@ function Navigation( { setAttributes, setFontSize, updateNavItemBlocks, + className, } ) { // // HOOKS @@ -67,7 +68,7 @@ function Navigation( { } = __experimentalUseColors( [ { name: 'textColor', property: 'color' }, - { name: 'backgroundColor', className: 'background-color' }, + { name: 'backgroundColor', className: 'has-background' }, ], { contrastCheckers: [ @@ -85,6 +86,14 @@ function Navigation( { [ fontSize.size ] ); + // Pickup and store text and background colors in grb format into attrs object. + useEffect( () => { + setAttributes( { + rgbTextColor: TextColor.color, + rgbBackgroundColor: BackgroundColor.color, + } ); + }, [ TextColor.color, BackgroundColor.color ] ); + /* eslint-enable @wordpress/no-unused-vars-before-return */ const { navigatorToolbarButton, navigatorModal } = useBlockNavigator( clientId @@ -135,7 +144,7 @@ function Navigation( { const hasPages = hasResolvedPages && pages && pages.length; - const blockClassNames = classnames( 'wp-block-navigation', { + const blockClassNames = classnames( className, { [ `items-justification-${ attributes.itemsJustification }` ]: attributes.itemsJustification, [ fontSize.class ]: fontSize.class, } ); @@ -250,7 +259,7 @@ function Navigation( { onChange={ ( value ) => { setAttributes( { showSubmenuIcon: value } ); } } - label={ __( 'Show submenu icon for top-level items' ) } + label={ __( 'Show submenu indicator icons' ) } /> </PanelBody> </InspectorControls> diff --git a/packages/block-library/src/navigation/editor.scss b/packages/block-library/src/navigation/editor.scss index 726b86053c49eb..845f7dc147f42f 100644 --- a/packages/block-library/src/navigation/editor.scss +++ b/packages/block-library/src/navigation/editor.scss @@ -28,20 +28,6 @@ $navigation-item-height: 46px; justify-content: flex-end; } - // 2. Remove paddings on subsequent immediate children. - .wp-block-navigation .block-editor-inner-blocks > .block-editor-block-list__layout > .wp-block { - width: auto; - padding-left: 0; - padding-right: 0; - margin: 0; - } - - // 3. Set a min height for nav items. - // It's needed in order to hide the sub-items when the navigation is not selected. - .wp-block-navigation .block-editor-inner-blocks > .block-editor-block-list__layout .wp-block > [data-block] { - min-height: $navigation-item-height; - } - .wp-block-navigation .block-editor-block-list__block::before { left: 0; right: 0; @@ -60,11 +46,8 @@ $navigation-item-height: 46px; // Polish the Appender. .wp-block-navigation .block-list-appender { - margin: 0; width: 28px; height: 28px; - margin-top: $grid-size-small; - margin-left: $grid-size + $block-side-ui-clearance; .block-editor-button-block-appender { padding: $grid-size; @@ -76,20 +59,6 @@ $navigation-item-height: 46px; } } -.wp-block-navigation .block-editor-block-list__layout, -.wp-block-navigation { - display: flex; - flex-wrap: wrap; -} - -// Vertical aligment. -.wp-block-navigation { - min-height: $navigation-height; - padding-top: 7px; - padding-bottom: 7px; - align-items: center; -} - .wp-block-navigation__inserter-content { padding: $grid-size-large; } diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index ddf0e2a23662db..e54f90a9e62b0c 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -20,7 +20,7 @@ function build_css_colors( $attributes ) { // Text color. $has_named_text_color = array_key_exists( 'textColor', $attributes ); - $has_custom_text_color = array_key_exists( 'customTextColor', $attributes ); + $has_custom_text_color = array_key_exists( 'rgbTextColor', $attributes ); // If has text color. if ( $has_custom_text_color || $has_named_text_color ) { @@ -33,17 +33,17 @@ function build_css_colors( $attributes ) { $colors['css_classes'][] = sprintf( 'has-%s-color', $attributes['textColor'] ); } elseif ( $has_custom_text_color ) { // Add the custom color inline style. - $colors['inline_styles'] .= sprintf( 'color: %s;', $attributes['customTextColor'] ); + $colors['inline_styles'] .= sprintf( 'color: %s;', $attributes['rgbTextColor'] ); } // Background color. $has_named_background_color = array_key_exists( 'backgroundColor', $attributes ); - $has_custom_background_color = array_key_exists( 'customBackgroundColor', $attributes ); + $has_custom_background_color = array_key_exists( 'rgbBackgroundColor', $attributes ); // If has background color. if ( $has_custom_background_color || $has_named_background_color ) { - // Add has-background-color class. - $colors['css_classes'][] = 'has-background-color'; + // Add has-background class. + $colors['css_classes'][] = 'has-background'; } if ( $has_named_background_color ) { @@ -51,7 +51,7 @@ function build_css_colors( $attributes ) { $colors['css_classes'][] = sprintf( 'has-%s-background-color', $attributes['backgroundColor'] ); } elseif ( $has_custom_background_color ) { // Add the custom background-color inline style. - $colors['inline_styles'] .= sprintf( 'background-color: %s;', $attributes['customBackgroundColor'] ); + $colors['inline_styles'] .= sprintf( 'background-color: %s;', $attributes['rgbBackgroundColor'] ); } return $colors; @@ -116,19 +116,24 @@ function( $block ) { * @return string */ function render_submenu_icon() { - return '<svg width="18" height="18" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" role="img" aria-hidden="true" focusable="false"><polygon points="9,13.5 14.7,7.9 13.2,6.5 9,10.7 4.8,6.5 3.3,7.9 "></polygon></svg>'; + return '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" transform="rotate(90)"><path d="M8 5v14l11-7z"/><path d="M0 0h24v24H0z" fill="none"/></svg>'; } /** * Renders the `core/navigation` block on server. * - * @param array $attributes The block attributes. * @param array $content The saved content. * @param array $block The parsed block. * * @return string Returns the post content with the legacy widget added. */ -function render_block_navigation( $attributes, $content, $block ) { +function render_block_navigation( $content, $block ) { + + if ( 'core/navigation' !== $block['blockName'] ) { + return $content; + } + + $attributes = $block['attrs']; $block['innerBlocks'] = gutenberg_remove_empty_navigation_links_recursive( $block['innerBlocks'] ); if ( empty( $block['innerBlocks'] ) ) { @@ -165,18 +170,17 @@ function render_block_navigation( $attributes, $content, $block ) { * @param array $block The NavigationItem block. * @param array $colors Contains inline styles and CSS classes to apply to navigation item. * @param array $font_sizes Contains inline styles and CSS classes to apply to navigation item. - * @param bool $is_level_zero True whether is main menu (level zero). Otherwise, False. * * @return string Returns an HTML list from innerBlocks. */ -function build_navigation_html( $attributes, $block, $colors, $font_sizes, $is_level_zero = true ) { +function build_navigation_html( $attributes, $block, $colors, $font_sizes ) { $html = ''; $classes = array_merge( $colors['css_classes'], $font_sizes['css_classes'] ); - $css_classes = implode( ' ', $classes ); - $class_attribute = sprintf( ' class="wp-block-navigation-link__content %s"', esc_attr( trim( $css_classes ) ) ); + $classes[] = 'wp-block-navigation-link'; + $css_classes = trim( implode( ' ', $classes ) ); $style_attribute = ( $colors['inline_styles'] || $font_sizes['inline_styles'] ) ? sprintf( ' style="%s"', esc_attr( $colors['inline_styles'] ) . esc_attr( $font_sizes['inline_styles'] ) ) : ''; @@ -184,12 +188,8 @@ function build_navigation_html( $attributes, $block, $colors, $font_sizes, $is_l foreach ( (array) $block['innerBlocks'] as $key => $block ) { $has_submenu = count( (array) $block['innerBlocks'] ) > 0; - $html .= '<li class="wp-block-navigation-link' . ( $has_submenu ? ' has-submenu' : '' ) . '">' . - '<a'; - - if ( $is_level_zero ) { - $html .= $class_attribute . $style_attribute; - } + $html .= '<li class="' . esc_attr( $css_classes . ( $has_submenu ? ' has-child' : '' ) ) . '"' . $style_attribute . '>' . + '<a class="wp-block-navigation-link__content"'; // Start appending HTML attributes to anchor tag. if ( isset( $block['attrs']['url'] ) ) { @@ -234,7 +234,15 @@ function build_navigation_html( $attributes, $block, $colors, $font_sizes, $is_l $html .= '</span>'; // Append submenu icon to top-level item. - if ( $attributes['showSubmenuIcon'] && $is_level_zero && $has_submenu ) { + // it shows the icon as default, when 'showSubmenuIcon' is not set, + // or when it's set and also not False. + if ( + ( + isset( $attributes['showSubmenuIcon'] ) && false !== $attributes['showSubmenuIcon'] || + ! isset( $attributes['showSubmenuIcon'] ) + ) && + $has_submenu + ) { $html .= '<span class="wp-block-navigation-link__submenu-icon">' . render_submenu_icon() . '</span>'; } @@ -247,7 +255,7 @@ function build_navigation_html( $attributes, $block, $colors, $font_sizes, $is_l $html .= '</li>'; } - return '<ul>' . $html . '</ul>'; + return '<ul class="wp-block-navigation__container">' . $html . '</ul>'; } /** @@ -261,39 +269,38 @@ function register_block_core_navigation() { register_block_type( 'core/navigation', array( - 'attributes' => array( - 'className' => array( + 'attributes' => array( + 'className' => array( 'type' => 'string', ), - 'textColor' => array( + 'textColor' => array( 'type' => 'string', ), - 'customTextColor' => array( + 'rgbTextColor' => array( 'type' => 'string', ), - 'backgroundColor' => array( + 'backgroundColor' => array( 'type' => 'string', ), - 'customBackgroundColor' => array( + 'rgbBackgroundColor' => array( 'type' => 'string', ), - 'fontSize' => array( + 'fontSize' => array( 'type' => 'string', ), - 'customFontSize' => array( + 'customFontSize' => array( 'type' => 'number', ), - 'itemsJustification' => array( + 'itemsJustification' => array( 'type' => 'string', ), - 'showSubmenuIcon' => array( + 'showSubmenuIcon' => array( 'type' => 'boolean', - 'default' => false, + 'default' => true, ), ), - - 'render_callback' => 'render_block_navigation', ) ); } add_action( 'init', 'register_block_core_navigation' ); +add_filter( 'render_block', 'render_block_navigation', 10, 2 ); diff --git a/packages/block-library/src/navigation/style.scss b/packages/block-library/src/navigation/style.scss index 23df9aa3ec9ee0..f3c0415a6244c9 100644 --- a/packages/block-library/src/navigation/style.scss +++ b/packages/block-library/src/navigation/style.scss @@ -1,236 +1,280 @@ -.wp-block-navigation { - & > ul { - display: block; +$navigation-menu-height: $grid-size * 7; +$navigation-sub-menu-height: $grid-size * 5; +$navigation-sub-menu-width: $grid-size * 25; + +/* +* Frontend: reset the default list styles +*/ + +.wp-block-navigation > ul { + display: block; + list-style: none; + margin: 0; + padding-left: 0; + + @include break-small { + display: flex; + flex-wrap: wrap; + } + + // Submenu + ul { list-style: none; - margin: 0; - max-width: none; padding-left: 0; - position: relative; + margin-top: 0; + margin-left: 0; - @include break-small { - display: flex; - flex-wrap: wrap; + li { + margin: 0; } + } +} - ul { - padding-left: 0; - } +/* +* Frontend: styles for submenu flyout +*/ - li { - position: relative; - z-index: 1; +.wp-block-navigation > ul { + li { + z-index: 1; - &:hover, - &:focus-within { - cursor: pointer; - z-index: 99999; - } + &:hover, + &:focus-within { + cursor: pointer; + z-index: 99999; + } - // Submenu Display - &:hover > ul, - &:focus-within > ul, - & ul:hover, - & ul:focus { - visibility: visible; - opacity: 1; - display: block; - } + // Submenu Display + &:hover > ul, + &:focus-within > ul, + & ul:hover, + & ul:focus { + visibility: visible; + opacity: 1; + display: flex; + flex-direction: column; } + } - // ToDo: move navigation-link styles to navigation-link/style.scss - & > li { + & > li ul { + position: absolute; + left: 0; + top: 100%; + min-width: $navigation-sub-menu-width; + max-width: $navigation-sub-menu-width; + opacity: 0; + transition: opacity 0.1s linear; + visibility: hidden; + } +} - & > a { - display: flex; - align-items: center; - padding-left: 0; +/* +* Styles shared between editor and frontend +*/ +.wp-block-navigation, +.wp-block-navigation .block-editor-block-list__layout { + display: flex; + flex-wrap: wrap; +} - @include break-small { - padding-left: 16px; - } - } +.wp-block-navigation { - &:first-of-type > a { - padding-left: 0; - } + // set a width on the editor submenus + .block-editor-block-list__layout .block-editor-block-list__layout { + width: $navigation-sub-menu-width; + } - &:last-of-type > a { - padding-right: 0; - } + // Remove paddings on subsequent immediate children. + .block-editor-inner-blocks > .block-editor-block-list__layout > .wp-block { + margin: 0; + width: auto; + } - .wp-block-navigation-link__submenu-icon { - margin-left: 4px; + &, + > .wp-block-navigation__container { + align-items: center; + width: 100%; - svg { - fill: currentColor; - } - } + > .wp-block-navigation-link { + display: flex; + margin-top: 0; + margin-bottom: 0; + } + } + + // Main menu + .wp-block-navigation-link { + position: relative; + margin: 0; + min-height: $navigation-menu-height; + display: flex; + line-height: 1.4; + + // Sub menus + .wp-block, + .wp-block-navigation-link { + min-height: auto; // reset the min-height and rely on padding + padding: 0; } - // Sub-menus Flyout - & > li > ul { - -webkit-box-shadow: 0 3px 30px rgba(25, 30, 35, 0.1); - -moz-box-shadow: 0 3px 30px rgba(25, 30, 35, 0.1); - border-radius: 3px; - box-shadow: 0 3px 30px rgba(25, 30, 35, 0.1); - border-width: 1px; - border-style: solid; + // Sub menus (editor canvas) + .wp-block .wp-block-navigation-link { margin: 0; - padding: 0 30px 0 10px; + } + + > .block-editor-inner-blocks { + display: none; + } + + &.has-child > .wp-block-navigation__container, + &.is-editing.has-child > .block-editor-inner-blocks { + // Box model + display: flex; + border: $border-width solid rgba(0, 0, 0, 0.15); + + // Position (first level) position: absolute; - left: 0; - top: 80%; - min-width: max-content; - opacity: 0; - transition: opacity 0.15s linear, transform 0.15s linear, right 0s 0.15s; - transform: translateY(0.6rem); - visibility: hidden; - - li { - margin: 0; - - & > ul { - padding-left: 10px; - } + z-index: 1; + top: 100%; + left: 0; // just under the main menu item. + + // Position (nested levels) + .block-editor-inner-blocks, + .wp-block-navigation__container { + left: 100%; + top: - $border-width; } + } - a { - text-decoration: none; + // Inherit colors from menu + .block-editor-inner-blocks, + .wp-block-navigation__container { + background-color: inherit; + color: inherit; + } - &:hover { - text-decoration: underline; - } - } + // All links + .wp-block-navigation-link__content { + display: flex; + align-items: center; + width: max-content; + padding: $grid-size * 0.75 $grid-size * 2; + } - // Sub menu arrow. - $light-style-sub-menu-arrow-width: 20px; - &::after, - &::before { - bottom: 100%; - left: 22px; - border: solid transparent; - content: " "; - height: 0; - width: 0; - position: absolute; - pointer-events: none; - } + // Submenu links only + .wp-block-navigation-link { - // Sub menu arrow. Background. - &::after { - border-width: $light-style-sub-menu-arrow-width / 2; - margin-left: -($light-style-sub-menu-arrow-width / 2); - bottom: calc(100% - 1px); + &:first-child:not(:only-child) .wp-block-navigation-link__content { + padding-top: $grid-size; } - // Sub menu arrow. Border. - &::before { - border-width: $light-style-sub-menu-arrow-width / 2 + 1px; - margin-left: -($light-style-sub-menu-arrow-width / 2 + 1px); + &:last-child .wp-block-navigation-link__content { + padding-bottom: $grid-size; } + } - ul { - width: 100%; + &.has-child .wp-block-navigation-link__content { + min-width: 100%; + padding-right: $grid-size * 4; + position: relative; + } + + .wp-block-navigation-link__submenu-icon { + position: absolute; + right: $grid-size * 2; + + svg { + fill: currentColor; } } - } - // Navigation Link - a { - display: block; - padding: 16px; + // reset rotation of submenu indicator icons on nested levels + .wp-block-navigation-link svg { + transform: rotate(0); + } } +} - // Sub-menu depth indicators - ul ul { - list-style: none; - margin-left: 0; - - li a { - padding-top: 8px; - padding-bottom: 8px; +// Styles +.wp-block-navigation { - &:first-child { - padding-top: 13px; - } + // Default / Light styles + .wp-block-navigation-link, + &.is-style-light .wp-block-navigation-link { + // No text color + &:not(.has-text-color) > .block-editor-inner-blocks, + &:not(.has-text-color) > .wp-block-navigation__container { + color: $light-style-sub-menu-text-color; + } - &:last-child { - padding-bottom: 13px; - } + // No background color + &:not(.has-background) > .block-editor-inner-blocks, + &:not(.has-background) > .wp-block-navigation__container { + background-color: $light-style-sub-menu-background-color; } } - &.items-justified-left > ul { - justify-content: flex-start; - } + // Dark styles. + &.is-style-dark .wp-block-navigation-link { + // No text color + &:not(.has-text-color) > .block-editor-inner-blocks, + &:not(.has-text-color) > .wp-block-navigation__container { + color: $dark-style-sub-menu-text-color; + } - &.items-justified-center > ul { - justify-content: center; + // No background color + &:not(.has-background) > .block-editor-inner-blocks, + &:not(.has-background) > .wp-block-navigation__container { + background-color: $dark-style-sub-menu-background-color; + } } +} - &.items-justified-right > ul { - justify-content: flex-end; - } +/* +* Frontend: non-shared styles & overrides +*/ - // Colors. - $light-style-sub-menu-background-color: #fff; - $light-style-sub-menu-border-color: #e2e4e7; - $light-style-sub-menu-text-color: #111; - $light-style-sub-menu-text-color-hover: #333; +.wp-block-navigation { - $dark-style-sub-menu-background-color: #333; - $dark-style-sub-menu-border-color: #111; - $dark-style-sub-menu-text-color: #fff; - $dark-style-sub-menu-text-color-hover: #eee; + .wp-block-navigation-link.has-child > .wp-block-navigation__container { + display: flex; + flex-direction: column; + padding: 0; + } +} - // Default Style / Ligth style. - & > ul > li > ul, - &.is-light-style > ul > li > ul { - background-color: $light-style-sub-menu-background-color; - border-color: $light-style-sub-menu-border-color; +/* +* TODO: organize/untangle styles below this line +*/ - a { - color: $light-style-sub-menu-text-color; +.wp-block-navigation { - &:hover { - color: $light-style-sub-menu-text-color-hover; + & > ul { + & > li { + & > a { + display: flex; + align-items: center; } - } - // Sub menu arrow - background color. - &::after { - border-color: transparent; - border-bottom-color: $light-style-sub-menu-background-color; - } + &:first-of-type > a { + padding-left: 0; + } - // Sub menu arrow - border color. - &::before { - border-color: transparent; - border-bottom-color: $light-style-sub-menu-border-color; + &:last-of-type > a { + padding-right: 0; + } } } - // Dark Style. - &.is-style-dark > ul > li > ul { - background-color: $dark-style-sub-menu-background-color; - border-color: $dark-style-sub-menu-border-color; - - a { - color: $dark-style-sub-menu-text-color; - - &:hover { - color: $dark-style-sub-menu-text-color-hover; - } - } + &.items-justified-left > ul { + justify-content: flex-start; + } - // Sub menu arrow - background color. - &::after { - border-bottom-color: $dark-style-sub-menu-background-color; - } + &.items-justified-center > ul { + justify-content: center; + } - // Sub menu arrow - border color. - &::before { - border-bottom-color: $dark-style-sub-menu-border-color; - } + &.items-justified-right > ul { + justify-content: flex-end; } } diff --git a/packages/block-library/src/social-link/icons/amazon.js b/packages/block-library/src/social-link/icons/amazon.js index bac1ca6a7ddd6b..928c9722ed1d86 100644 --- a/packages/block-library/src/social-link/icons/amazon.js +++ b/packages/block-library/src/social-link/icons/amazon.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const AmazonIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/bandcamp.js b/packages/block-library/src/social-link/icons/bandcamp.js index e9c80c2a13656e..793a314d36f4b3 100644 --- a/packages/block-library/src/social-link/icons/bandcamp.js +++ b/packages/block-library/src/social-link/icons/bandcamp.js @@ -2,7 +2,7 @@ * WordPress dependencies */ -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const BandcampIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/behance.js b/packages/block-library/src/social-link/icons/behance.js index 65097726167d66..e45cdda50336ee 100644 --- a/packages/block-library/src/social-link/icons/behance.js +++ b/packages/block-library/src/social-link/icons/behance.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const BehanceIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/chain.js b/packages/block-library/src/social-link/icons/chain.js index 676269b0a9ff4b..a1285ba053c710 100644 --- a/packages/block-library/src/social-link/icons/chain.js +++ b/packages/block-library/src/social-link/icons/chain.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const ChainIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/codepen.js b/packages/block-library/src/social-link/icons/codepen.js index 8ea31018ab2d76..59bae6fc33e881 100644 --- a/packages/block-library/src/social-link/icons/codepen.js +++ b/packages/block-library/src/social-link/icons/codepen.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const CodepenIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/deviantart.js b/packages/block-library/src/social-link/icons/deviantart.js index 4cf01fdeed0281..901d989433b02c 100644 --- a/packages/block-library/src/social-link/icons/deviantart.js +++ b/packages/block-library/src/social-link/icons/deviantart.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const DeviantArtIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/dribbble.js b/packages/block-library/src/social-link/icons/dribbble.js index ec250898473c6a..56b8d34ac63921 100644 --- a/packages/block-library/src/social-link/icons/dribbble.js +++ b/packages/block-library/src/social-link/icons/dribbble.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const DribbbleIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/dropbox.js b/packages/block-library/src/social-link/icons/dropbox.js index e0de4bda6d2318..e1970ae73172a9 100644 --- a/packages/block-library/src/social-link/icons/dropbox.js +++ b/packages/block-library/src/social-link/icons/dropbox.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const DropboxIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/etsy.js b/packages/block-library/src/social-link/icons/etsy.js index d691d9dca09eb1..2e0712334286a5 100644 --- a/packages/block-library/src/social-link/icons/etsy.js +++ b/packages/block-library/src/social-link/icons/etsy.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const EtsyIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/facebook.js b/packages/block-library/src/social-link/icons/facebook.js index 77343d7e9d1ad3..1219552a6df713 100644 --- a/packages/block-library/src/social-link/icons/facebook.js +++ b/packages/block-library/src/social-link/icons/facebook.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const FacebookIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/feed.js b/packages/block-library/src/social-link/icons/feed.js index e3521f3d13b197..92d4a1001c853c 100644 --- a/packages/block-library/src/social-link/icons/feed.js +++ b/packages/block-library/src/social-link/icons/feed.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const FeedIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/fivehundredpx.js b/packages/block-library/src/social-link/icons/fivehundredpx.js index 6b0111eaeb7d92..a3f30412e39a69 100644 --- a/packages/block-library/src/social-link/icons/fivehundredpx.js +++ b/packages/block-library/src/social-link/icons/fivehundredpx.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const FivehundredpxIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/flickr.js b/packages/block-library/src/social-link/icons/flickr.js index a9d21c2c300f9e..7716d7d868168f 100644 --- a/packages/block-library/src/social-link/icons/flickr.js +++ b/packages/block-library/src/social-link/icons/flickr.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const FlickrIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/foursquare.js b/packages/block-library/src/social-link/icons/foursquare.js index dbd91521a4e740..ba1cacdf29f731 100644 --- a/packages/block-library/src/social-link/icons/foursquare.js +++ b/packages/block-library/src/social-link/icons/foursquare.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const FoursquareIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/github.js b/packages/block-library/src/social-link/icons/github.js index 9f9e1d1e98f25d..6b22e5a05f6cf7 100644 --- a/packages/block-library/src/social-link/icons/github.js +++ b/packages/block-library/src/social-link/icons/github.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const GitHubIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/goodreads.js b/packages/block-library/src/social-link/icons/goodreads.js index 2a9374bc2f781b..8324a9f4c54ae6 100644 --- a/packages/block-library/src/social-link/icons/goodreads.js +++ b/packages/block-library/src/social-link/icons/goodreads.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const GoodreadsIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/google.js b/packages/block-library/src/social-link/icons/google.js index 336e8924ae7852..10ede42a16ed7a 100644 --- a/packages/block-library/src/social-link/icons/google.js +++ b/packages/block-library/src/social-link/icons/google.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const GoogleIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/instagram.js b/packages/block-library/src/social-link/icons/instagram.js index 39c2fc7ba3b09b..9ff7b18d2d5170 100644 --- a/packages/block-library/src/social-link/icons/instagram.js +++ b/packages/block-library/src/social-link/icons/instagram.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const InstagramIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/lastfm.js b/packages/block-library/src/social-link/icons/lastfm.js index aa3e5d9f279ee7..c27562ec85f6fd 100644 --- a/packages/block-library/src/social-link/icons/lastfm.js +++ b/packages/block-library/src/social-link/icons/lastfm.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const LastfmIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/linkedin.js b/packages/block-library/src/social-link/icons/linkedin.js index b4afc099625375..21cdd0ac93101f 100644 --- a/packages/block-library/src/social-link/icons/linkedin.js +++ b/packages/block-library/src/social-link/icons/linkedin.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const LinkedInIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/mail.js b/packages/block-library/src/social-link/icons/mail.js index ab43f106adca94..7752dc1f60079e 100644 --- a/packages/block-library/src/social-link/icons/mail.js +++ b/packages/block-library/src/social-link/icons/mail.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const MailIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/mastodon.js b/packages/block-library/src/social-link/icons/mastodon.js index 5ee0ed256a2641..add394cc085230 100644 --- a/packages/block-library/src/social-link/icons/mastodon.js +++ b/packages/block-library/src/social-link/icons/mastodon.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const MastodonIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/medium.js b/packages/block-library/src/social-link/icons/medium.js index 4cf5b265398d89..88de49e65be046 100644 --- a/packages/block-library/src/social-link/icons/medium.js +++ b/packages/block-library/src/social-link/icons/medium.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const MediumIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/meetup.js b/packages/block-library/src/social-link/icons/meetup.js index 8f12e1566ce970..c7c9ddf44e7193 100644 --- a/packages/block-library/src/social-link/icons/meetup.js +++ b/packages/block-library/src/social-link/icons/meetup.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const MeetupIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/pinterest.js b/packages/block-library/src/social-link/icons/pinterest.js index 70782c6189f81c..97bcba825db3ce 100644 --- a/packages/block-library/src/social-link/icons/pinterest.js +++ b/packages/block-library/src/social-link/icons/pinterest.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const PinterestIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/pocket.js b/packages/block-library/src/social-link/icons/pocket.js index 43f3661cd201d1..564335384c4d70 100644 --- a/packages/block-library/src/social-link/icons/pocket.js +++ b/packages/block-library/src/social-link/icons/pocket.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const PocketIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/reddit.js b/packages/block-library/src/social-link/icons/reddit.js index 9a17f0a19a1bfa..d0b8437df58d58 100644 --- a/packages/block-library/src/social-link/icons/reddit.js +++ b/packages/block-library/src/social-link/icons/reddit.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const RedditIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/skype.js b/packages/block-library/src/social-link/icons/skype.js index 4dc946541a83f6..2544f2559faaf1 100644 --- a/packages/block-library/src/social-link/icons/skype.js +++ b/packages/block-library/src/social-link/icons/skype.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const SkypeIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/snapchat.js b/packages/block-library/src/social-link/icons/snapchat.js index 73f93ee6279289..825ea99842ba9e 100644 --- a/packages/block-library/src/social-link/icons/snapchat.js +++ b/packages/block-library/src/social-link/icons/snapchat.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const SnapchatIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/soundcloud.js b/packages/block-library/src/social-link/icons/soundcloud.js index 538d1f867d4191..ffc740aae1a077 100644 --- a/packages/block-library/src/social-link/icons/soundcloud.js +++ b/packages/block-library/src/social-link/icons/soundcloud.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const SoundCloudIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/spotify.js b/packages/block-library/src/social-link/icons/spotify.js index 03256e1b31e048..63d2ddadc77e9a 100644 --- a/packages/block-library/src/social-link/icons/spotify.js +++ b/packages/block-library/src/social-link/icons/spotify.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const SpotifyIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/tumblr.js b/packages/block-library/src/social-link/icons/tumblr.js index 946f7e1b040de0..5999fe4ead681d 100644 --- a/packages/block-library/src/social-link/icons/tumblr.js +++ b/packages/block-library/src/social-link/icons/tumblr.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const TumblrIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/twitch.js b/packages/block-library/src/social-link/icons/twitch.js index 6bbbc13b2a4358..1d6691e28995e1 100644 --- a/packages/block-library/src/social-link/icons/twitch.js +++ b/packages/block-library/src/social-link/icons/twitch.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const TwitchIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/twitter.js b/packages/block-library/src/social-link/icons/twitter.js index 95e7435c6176e8..eda1a53f7f2be0 100644 --- a/packages/block-library/src/social-link/icons/twitter.js +++ b/packages/block-library/src/social-link/icons/twitter.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const TwitterIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/vimeo.js b/packages/block-library/src/social-link/icons/vimeo.js index 1e11bda150ed44..fd8b29f45db997 100644 --- a/packages/block-library/src/social-link/icons/vimeo.js +++ b/packages/block-library/src/social-link/icons/vimeo.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const VimeoIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/vk.js b/packages/block-library/src/social-link/icons/vk.js index 97ac09e842fe1b..d877d28b577059 100644 --- a/packages/block-library/src/social-link/icons/vk.js +++ b/packages/block-library/src/social-link/icons/vk.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const VkIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/wordpress.js b/packages/block-library/src/social-link/icons/wordpress.js index 0fc780d08954ef..2a51d200bc871b 100644 --- a/packages/block-library/src/social-link/icons/wordpress.js +++ b/packages/block-library/src/social-link/icons/wordpress.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const WordPressIcon = () => ( <SVG diff --git a/packages/block-library/src/social-link/icons/yelp.js b/packages/block-library/src/social-link/icons/yelp.js index 83b1d76200258c..8df9da231504c1 100644 --- a/packages/block-library/src/social-link/icons/yelp.js +++ b/packages/block-library/src/social-link/icons/yelp.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const YelpIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/block-library/src/social-link/icons/youtube.js b/packages/block-library/src/social-link/icons/youtube.js index 7256a177262f7e..e788d6e8d6ceeb 100644 --- a/packages/block-library/src/social-link/icons/youtube.js +++ b/packages/block-library/src/social-link/icons/youtube.js @@ -1,8 +1,7 @@ /** * WordPress dependencies */ - -import { Path, SVG } from '@wordpress/components'; +import { Path, SVG } from '@wordpress/primitives'; export const YouTubeIcon = () => ( <SVG width="24" height="24" viewBox="0 0 24 24" version="1.1"> diff --git a/packages/components/src/mobile/readable-content-view/index.native.js b/packages/components/src/mobile/readable-content-view/index.native.js index de71700bc18374..65adf00cff6226 100644 --- a/packages/components/src/mobile/readable-content-view/index.native.js +++ b/packages/components/src/mobile/readable-content-view/index.native.js @@ -9,8 +9,14 @@ import { View, Dimensions } from 'react-native'; import styles from './style.scss'; const ReadableContentView = ( { reversed, children, style } ) => ( - <View style={ [ styles.container, style ] } > - <View style={ reversed ? styles.reversedCenteredContent : styles.centeredContent } > + <View style={ [ styles.container, style ] }> + <View + style={ + reversed + ? styles.reversedCenteredContent + : styles.centeredContent + } + > { children } </View> </View> diff --git a/packages/e2e-tests/specs/editor/blocks/__snapshots__/buttons.test.js.snap b/packages/e2e-tests/specs/editor/blocks/__snapshots__/buttons.test.js.snap index 067425ce07d6c1..66f803ddcf139f 100644 --- a/packages/e2e-tests/specs/editor/blocks/__snapshots__/buttons.test.js.snap +++ b/packages/e2e-tests/specs/editor/blocks/__snapshots__/buttons.test.js.snap @@ -3,7 +3,7 @@ exports[`Buttons can jump to the link editor using the keyboard shortcut 1`] = ` "<!-- wp:buttons --> <div class=\\"wp-block-buttons\\"><!-- wp:button --> -<div class=\\"wp-block-button\\"><a class=\\"wp-block-button__link\\" href=\\"https://www.wordpress.org/\\" title=\\"\\">WordPress</a></div> +<div class=\\"wp-block-button\\"><a class=\\"wp-block-button__link\\" href=\\"https://www.wordpress.org/\\">WordPress</a></div> <!-- /wp:button --></div> <!-- /wp:buttons -->" `; diff --git a/packages/e2e-tests/specs/editor/blocks/classic.test.js b/packages/e2e-tests/specs/editor/blocks/classic.test.js index 456ce33f557930..b8295c64d53df1 100644 --- a/packages/e2e-tests/specs/editor/blocks/classic.test.js +++ b/packages/e2e-tests/specs/editor/blocks/classic.test.js @@ -14,6 +14,8 @@ import { createNewPost, insertBlock, pressKeyWithModifier, + clickBlockToolbarButton, + clickButton, } from '@wordpress/e2e-test-utils'; describe( 'Classic', () => { @@ -34,7 +36,7 @@ describe( 'Classic', () => { expect( await getEditedPostContent() ).toMatchSnapshot(); } ); - it( 'should insert media', async () => { + it( 'should insert media, convert to blocks, and undo in one step', async () => { await insertBlock( 'Classic' ); // Wait for TinyMCE to initialise. await page.waitForSelector( '.mce-content-body' ); @@ -45,6 +47,7 @@ describe( 'Classic', () => { // Click the image button. await page.waitForSelector( 'div[aria-label^="Add Media"]' ); await page.click( 'div[aria-label^="Add Media"]' ); + await page.click( '.media-menu-item#menu-item-gallery' ); // Wait for media modal to appear and upload image. await page.waitForSelector( '.media-modal input[type=file]' ); @@ -68,17 +71,34 @@ describe( 'Classic', () => { ); // Insert the uploaded image. + await page.click( '.media-modal button.media-button-gallery' ); await page.click( '.media-modal button.media-button-insert' ); // Wait for image to be inserted. await page.waitForSelector( '.mce-content-body img' ); - // Move focus away. + // Move focus away and verify gallery was inserted. await pressKeyWithModifier( 'shift', 'Tab' ); + expect( await getEditedPostContent() ).toMatch( + /\[gallery ids=\"\d+\"\]/ + ); - const regExp = new RegExp( - `test<img class="alignnone size-full wp-image-\\d+" src="[^"]+\\/${ filename }\\.png" alt="" width="10" height="10" \\/>` + // Convert to blocks and verify it worked correctly. + await clickBlockToolbarButton( 'More options' ); + await clickButton( 'Convert to Blocks' ); + await page.waitForSelector( '.wp-block[data-type="core/gallery"]' ); + expect( await getEditedPostContent() ).toMatch( /<!-- wp:gallery/ ); + + // Check that you can undo back to a Classic block gallery in one step. + await pressKeyWithModifier( 'primary', 'z' ); + expect( await getEditedPostContent() ).toMatch( + /\[gallery ids=\"\d+\"\]/ ); - expect( await getEditedPostContent() ).toMatch( regExp ); + + // Convert to blocks again and verify it worked correctly. + await clickBlockToolbarButton( 'More options' ); + await clickButton( 'Convert to Blocks' ); + await page.waitForSelector( '.wp-block[data-type="core/gallery"]' ); + expect( await getEditedPostContent() ).toMatch( /<!-- wp:gallery/ ); } ); } ); diff --git a/packages/e2e-tests/specs/editor/various/links.test.js b/packages/e2e-tests/specs/editor/various/links.test.js index c6d8d12263e75d..0f21d248a86500 100644 --- a/packages/e2e-tests/specs/editor/various/links.test.js +++ b/packages/e2e-tests/specs/editor/various/links.test.js @@ -7,7 +7,6 @@ import { getEditedPostContent, createNewPost, pressKeyWithModifier, - insertBlock, } from '@wordpress/e2e-test-utils'; /** @@ -44,8 +43,8 @@ describe( 'Links', () => { // Type a URL await page.keyboard.type( 'https://wordpress.org/gutenberg' ); - // Click on the Apply button - await page.click( 'button[aria-label="Apply"]' ); + // Submit the link + await page.keyboard.press( 'Enter' ); // The link should have been inserted expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -80,9 +79,6 @@ describe( 'Links', () => { await clickBlockAppender(); await page.keyboard.type( 'This is Gutenberg: ' ); - // Press escape to show the block toolbar - await page.keyboard.press( 'Escape' ); - // Press Cmd+K to insert a link await pressKeyWithModifier( 'primary', 'K' ); @@ -157,15 +153,16 @@ describe( 'Links', () => { // Type a URL await page.keyboard.type( 'https://wordpress.org/gutenberg' ); - // Click on the Apply button - await page.click( 'button[aria-label="Apply"]' ); + // Click on the Submit button + await page.keyboard.press( 'Enter' ); }; it( 'can be edited', async () => { await createAndReselectLink(); // Click on the Edit button - await page.click( 'button[aria-label="Edit"]' ); + const [ editButton ] = await page.$x( '//button[text()="Edit"]' ); + await editButton.click(); // Wait for the URL field to auto-focus await waitForAutoFocus(); @@ -173,8 +170,8 @@ describe( 'Links', () => { // Change the URL await page.keyboard.type( '/handbook' ); - // Click on the Apply button - await page.click( 'button[aria-label="Apply"]' ); + // Submit the link + await page.keyboard.press( 'Enter' ); // The link should have been updated expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -211,12 +208,16 @@ describe( 'Links', () => { // Typing "left" should not close the dialog await page.keyboard.press( 'ArrowLeft' ); - let popover = await page.$( '.block-editor-url-popover' ); + let popover = await page.$( + '.components-popover__content .block-editor-link-control' + ); expect( popover ).not.toBeNull(); // Escape should close the dialog still. await page.keyboard.press( 'Escape' ); - popover = await page.$( '.block-editor-url-popover' ); + popover = await page.$( + '.components-popover__content .block-editor-link-control' + ); expect( popover ).toBeNull(); } ); @@ -230,12 +231,16 @@ describe( 'Links', () => { // Typing "left" should not close the dialog await page.keyboard.press( 'ArrowLeft' ); - let popover = await page.$( '.block-editor-url-popover' ); + let popover = await page.$( + '.components-popover__content .block-editor-link-control' + ); expect( popover ).not.toBeNull(); // Escape should close the dialog still. await page.keyboard.press( 'Escape' ); - popover = await page.$( '.block-editor-url-popover' ); + popover = await page.$( + '.components-popover__content .block-editor-link-control' + ); expect( popover ).toBeNull(); } ); @@ -247,10 +252,11 @@ describe( 'Links', () => { // Move the mouse to show the block toolbar await page.mouse.move( 0, 0 ); await page.mouse.move( 10, 10 ); - await page.click( 'button[aria-label="Edit"]' ); + const [ editButton ] = await page.$x( '//button[text()="Edit"]' ); + await editButton.click(); await waitForAutoFocus(); await page.keyboard.type( '/handbook' ); - await page.click( 'button[aria-label="Apply"]' ); + await page.keyboard.press( 'Enter' ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); @@ -295,34 +301,54 @@ describe( 'Links', () => { // Wait for the URL field to auto-focus await waitForAutoFocus(); - expect( await page.$( '.block-editor-url-popover' ) ).not.toBeNull(); + expect( + await page.$( + '.components-popover__content .block-editor-link-control' + ) + ).not.toBeNull(); // Trigger the autocomplete suggestion list and select the first suggestion. await page.keyboard.type( titleText ); - await page.waitForSelector( '.block-editor-url-input__suggestion' ); + await page.waitForSelector( '.block-editor-link-control__search-item' ); await page.keyboard.press( 'ArrowDown' ); // Expect the the escape key to dismiss the popover when the autocomplete suggestion list is open. await page.keyboard.press( 'Escape' ); - expect( await page.$( '.block-editor-url-popover' ) ).toBeNull(); + expect( + await page.$( + '.components-popover__content .block-editor-link-control' + ) + ).toBeNull(); // Press Cmd+K to insert a link await pressKeyWithModifier( 'primary', 'K' ); // Wait for the URL field to auto-focus await waitForAutoFocus(); - expect( await page.$( '.block-editor-url-popover' ) ).not.toBeNull(); + expect( + await page.$( + '.components-popover__content .block-editor-link-control' + ) + ).not.toBeNull(); // Expect the the escape key to dismiss the popover normally. await page.keyboard.press( 'Escape' ); - expect( await page.$( '.block-editor-url-popover' ) ).toBeNull(); + expect( + await page.$( + '.components-popover__content .block-editor-link-control' + ) + ).toBeNull(); // Press Cmd+K to insert a link await pressKeyWithModifier( 'primary', 'K' ); // Wait for the URL field to auto-focus await waitForAutoFocus(); - expect( await page.$( '.block-editor-url-popover' ) ).not.toBeNull(); + expect( + await page.$( + '.components-popover__content .block-editor-link-control' + ) + ).not.toBeNull(); // Tab to the settings icon button. await page.keyboard.press( 'Tab' ); @@ -330,7 +356,11 @@ describe( 'Links', () => { // Expect the the escape key to dismiss the popover normally. await page.keyboard.press( 'Escape' ); - expect( await page.$( '.block-editor-url-popover' ) ).toBeNull(); + expect( + await page.$( + '.components-popover__content .block-editor-link-control' + ) + ).toBeNull(); } ); it( 'can be modified using the keyboard once a link has been set', async () => { @@ -348,16 +378,30 @@ describe( 'Links', () => { // Deselect the link text by moving the caret to the end of the line // and the link popover should not be displayed. await page.keyboard.press( 'End' ); - expect( await page.$( '.block-editor-url-popover' ) ).toBeNull(); + expect( + await page.$( + '.components-popover__content .block-editor-link-control' + ) + ).toBeNull(); // Move the caret back into the link text and the link popover // should be displayed. await page.keyboard.press( 'ArrowLeft' ); - expect( await page.$( '.block-editor-url-popover' ) ).not.toBeNull(); + expect( + await page.$( + '.components-popover__content .block-editor-link-control' + ) + ).not.toBeNull(); // Press Cmd+K to edit the link and the url-input should become // focused with the value previously inserted. await pressKeyWithModifier( 'primary', 'K' ); + await page.waitForSelector( + ':focus.block-editor-link-control__search-item-title' + ); + await page.keyboard.press( 'Tab' ); // Shift focus to "Edit" button + await page.keyboard.press( 'Enter' ); // Click "Edit" button + await waitForAutoFocus(); const activeElementParentClasses = await page.evaluate( () => Object.values( @@ -389,44 +433,6 @@ describe( 'Links', () => { ); } ); - it( 'link popover remains visible after a mouse drag event', async () => { - // Create some blocks so we have components with event handlers on the page - for ( let loop = 0; loop < 5; loop++ ) { - await insertBlock( 'Paragraph' ); - await page.keyboard.type( 'This is Gutenberg' ); - } - - // Focus on first paragraph, so the link popover will appear over the subsequent ones - await page.click( '[aria-label="Block navigation"]' ); - await page.click( '.block-editor-block-navigation__item button' ); - - // Select some text - await pressKeyWithModifier( 'shiftAlt', 'ArrowLeft' ); - - // Click on the Link button - await page.click( 'button[aria-label="Link"]' ); - // Wait for the URL field to auto-focus - await waitForAutoFocus(); - - // Click on the Link Settings button - await page.click( 'button[aria-label="Link settings"]' ); - - // Move mouse over the 'open in new tab' section, then click and drag - const settings = await page.$( '.block-editor-url-popover__settings' ); - const bounds = await settings.boundingBox(); - - await page.mouse.move( bounds.x, bounds.y ); - await page.mouse.down(); - await page.mouse.move( bounds.x + bounds.width / 2, bounds.y, { - steps: 10, - } ); - await page.mouse.up(); - - // The link popover should still be visible - const popover = await page.$$( '.block-editor-url-popover' ); - expect( popover ).toHaveLength( 1 ); - } ); - it( 'should contain a label when it should open in a new tab', async () => { await clickBlockAppender(); await page.keyboard.type( 'This is WordPress' ); @@ -435,19 +441,22 @@ describe( 'Links', () => { await pressKeyWithModifier( 'primary', 'k' ); await waitForAutoFocus(); await page.keyboard.type( 'w.org' ); - // Navigate to the settings toggle. - await page.keyboard.press( 'Tab' ); - await page.keyboard.press( 'Tab' ); - // Open settings. - await page.keyboard.press( 'Space' ); + + // Insert the link + await page.keyboard.press( 'Enter' ); + + // Navigate back to the popover + await pressKeyWithModifier( 'primary', 'k' ); + await page.waitForSelector( + '.components-popover__content .block-editor-link-control' + ); + // Navigate to the "Open in New Tab" checkbox. await page.keyboard.press( 'Tab' ); + await page.keyboard.press( 'Tab' ); + // Check the checkbox. await page.keyboard.press( 'Space' ); - // Navigate back to the input field. - await page.keyboard.press( 'Tab' ); - // Submit the form. - await page.keyboard.press( 'Enter' ); expect( await getEditedPostContent() ).toMatchSnapshot(); @@ -463,36 +472,29 @@ describe( 'Links', () => { await page.keyboard.press( 'ArrowRight' ); // Edit link. await pressKeyWithModifier( 'primary', 'k' ); + await page.waitForSelector( + ':focus.block-editor-link-control__search-item-title' + ); + await page.keyboard.press( 'Tab' ); // Shift focus to "Edit" button + await page.keyboard.press( 'Enter' ); // Click "Edit" button await waitForAutoFocus(); await pressKeyWithModifier( 'primary', 'a' ); await page.keyboard.type( 'wordpress.org' ); - // Navigate to the settings toggle. - await page.keyboard.press( 'Tab' ); - await page.keyboard.press( 'Tab' ); - // Open settings. - await page.keyboard.press( 'Space' ); - // Navigate to the "Open in New Tab" checkbox. - await page.keyboard.press( 'Tab' ); - // Uncheck the checkbox. - await page.keyboard.press( 'Space' ); - // Navigate back to the input field. - await page.keyboard.press( 'Tab' ); - // Submit the form. + + // Update the link await page.keyboard.press( 'Enter' ); - // Navigate back to inputs to verify appears as changed. + // Navigate back to the popover await pressKeyWithModifier( 'primary', 'k' ); - await waitForAutoFocus(); - const link = await page.evaluate( () => document.activeElement.value ); - expect( link ).toBe( 'http://wordpress.org' ); + await page.waitForSelector( + '.components-popover__content .block-editor-link-control' + ); + + // Navigate to the "Open in New Tab" checkbox. await page.keyboard.press( 'Tab' ); await page.keyboard.press( 'Tab' ); + // Uncheck the checkbox. await page.keyboard.press( 'Space' ); - await page.keyboard.press( 'Tab' ); - const isChecked = await page.evaluate( - () => document.activeElement.checked - ); - expect( isChecked ).toBe( false ); expect( await getEditedPostContent() ).toMatchSnapshot(); } ); diff --git a/packages/edit-post/src/components/header/header-toolbar/index.js b/packages/edit-post/src/components/header/header-toolbar/index.js index 46873f2ee9aa0f..5f8bc98dc3a904 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.js @@ -57,7 +57,7 @@ function HeaderToolbar() { <ToolSelector /> { ( hasFixedToolbar || ! isLargeViewport ) && ( <div className="edit-post-header-toolbar__block-toolbar"> - <BlockToolbar /> + <BlockToolbar hideDragHandle /> </div> ) } </NavigableToolbar> diff --git a/packages/format-library/src/link/index.js b/packages/format-library/src/link/index.js index 1a63d9621e8059..8680d006e41e7e 100644 --- a/packages/format-library/src/link/index.js +++ b/packages/format-library/src/link/index.js @@ -149,16 +149,17 @@ export const link = { shortcutCharacter="k" /> ) } - <InlineLinkUI - key={ isActive } // Make sure link UI state resets when switching between links. - addingLink={ this.state.addingLink } - stopAddingLink={ this.stopAddingLink } - isActive={ isActive } - activeAttributes={ activeAttributes } - value={ value } - onChange={ onChange } - onFocus={ onFocus } - /> + { ( this.state.addingLink || isActive ) && ( + <InlineLinkUI + addingLink={ this.state.addingLink } + stopAddingLink={ this.stopAddingLink } + isActive={ isActive } + activeAttributes={ activeAttributes } + value={ value } + onChange={ onChange } + onFocus={ onFocus } + /> + ) } </> ); } diff --git a/packages/format-library/src/link/inline.js b/packages/format-library/src/link/inline.js index aaeeb5006caa8e..58e767b2262620 100644 --- a/packages/format-library/src/link/inline.js +++ b/packages/format-library/src/link/inline.js @@ -1,10 +1,14 @@ +/** + * External dependencies + */ +import { uniqueId } from 'lodash'; + /** * WordPress dependencies */ +import { useMemo } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { Component, createRef, useMemo } from '@wordpress/element'; -import { ToggleControl, withSpokenMessages } from '@wordpress/components'; -import { LEFT, RIGHT, UP, DOWN, BACKSPACE, ENTER } from '@wordpress/keycodes'; +import { withSpokenMessages, Popover } from '@wordpress/components'; import { prependHTTP } from '@wordpress/url'; import { create, @@ -14,20 +18,39 @@ import { getTextContent, slice, } from '@wordpress/rich-text'; -import { URLPopover } from '@wordpress/block-editor'; +import { __experimentalLinkControl as LinkControl } from '@wordpress/block-editor'; /** * Internal dependencies */ import { createLinkFormat, isValidHref } from './utils'; -const stopKeyPropagation = ( event ) => event.stopPropagation(); +function InlineLinkUI( { + isActive, + activeAttributes, + addingLink, + value, + onChange, + onFocus, + speak, + stopAddingLink, +} ) { + /** + * A unique key is generated when switching between editing and not editing + * a link, based on: + * + * - This component may be rendered _either_ when a link is active _or_ + * when adding or editing a link. + * - It's only desirable to shift focus into the Popover when explicitly + * adding or editing a link, not when in the inline boundary of a link. + * - Focus behavior can only be controlled on a Popover at the time it + * mounts, so a new instance of the component must be mounted to + * programmatically enact the focusOnMount behavior. + * + * @type {string} + */ + const mountingKey = useMemo( uniqueId, [ addingLink ] ); -function isShowingInput( props, state ) { - return props.addingLink || state.editLink; -} - -const URLPopoverAtLink = ( { isActive, addingLink, value, ...props } ) => { const anchorRef = useMemo( () => { const selection = window.getSelection(); @@ -37,7 +60,7 @@ const URLPopoverAtLink = ( { isActive, addingLink, value, ...props } ) => { const range = selection.getRangeAt( 0 ); - if ( addingLink ) { + if ( addingLink && ! isActive ) { return range; } @@ -51,120 +74,28 @@ const URLPopoverAtLink = ( { isActive, addingLink, value, ...props } ) => { } return element.closest( 'a' ); - }, [ isActive, addingLink, value.start, value.end ] ); - - if ( ! anchorRef ) { - return null; - } - - return <URLPopover anchorRef={ anchorRef } { ...props } />; -}; - -class InlineLinkUI extends Component { - constructor() { - super( ...arguments ); - - this.editLink = this.editLink.bind( this ); - this.submitLink = this.submitLink.bind( this ); - this.onKeyDown = this.onKeyDown.bind( this ); - this.onChangeInputValue = this.onChangeInputValue.bind( this ); - this.setLinkTarget = this.setLinkTarget.bind( this ); - this.onFocusOutside = this.onFocusOutside.bind( this ); - this.resetState = this.resetState.bind( this ); - this.autocompleteRef = createRef(); - - this.state = { - opensInNewWindow: false, - inputValue: '', - }; - } - - static getDerivedStateFromProps( props, state ) { - const { - activeAttributes: { url, target }, - } = props; - const opensInNewWindow = target === '_blank'; - - if ( ! isShowingInput( props, state ) ) { - const update = {}; - if ( url !== state.inputValue ) { - update.inputValue = url; - } - - if ( opensInNewWindow !== state.opensInNewWindow ) { - update.opensInNewWindow = opensInNewWindow; - } - return Object.keys( update ).length ? update : null; - } - - return null; - } - - onKeyDown( event ) { - if ( - [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( - event.keyCode - ) > -1 - ) { - // Stop the key event from propagating up to ObserveTyping.startTypingInTextField. - event.stopPropagation(); - } - } - - onChangeInputValue( inputValue ) { - this.setState( { inputValue } ); - } + }, [ addingLink, value.start, value.end ] ); - setLinkTarget( opensInNewWindow ) { - const { - activeAttributes: { url = '' }, - value, - onChange, - } = this.props; + const linkValue = { + url: activeAttributes.url, + opensInNewTab: activeAttributes.target === '_blank', + }; - this.setState( { opensInNewWindow } ); - - // Apply now if URL is not being edited. - if ( ! isShowingInput( this.props, this.state ) ) { - const selectedText = getTextContent( slice( value ) ); - - onChange( - applyFormat( - value, - createLinkFormat( { - url, - opensInNewWindow, - text: selectedText, - } ) - ) - ); - } - } - - editLink( event ) { - this.setState( { editLink: true } ); - event.preventDefault(); - } - - submitLink( event ) { - const { isActive, value, onChange, onFocus, speak } = this.props; - const { inputValue, opensInNewWindow } = this.state; - const url = prependHTTP( inputValue ); + function onChangeLink( nextValue ) { + const newUrl = prependHTTP( nextValue.url ); const selectedText = getTextContent( slice( value ) ); const format = createLinkFormat( { - url, - opensInNewWindow, + url: newUrl, + opensInNewWindow: nextValue.opensInNewTab, text: selectedText, } ); - event.preventDefault(); - if ( isCollapsed( value ) && ! isActive ) { const toInsert = applyFormat( - create( { text: url } ), + create( { text: newUrl } ), format, 0, - url.length + newUrl.length ); onChange( insert( value, toInsert ) ); } else { @@ -172,10 +103,9 @@ class InlineLinkUI extends Component { } onFocus(); + stopAddingLink(); - this.resetState(); - - if ( ! isValidHref( url ) ) { + if ( ! isValidHref( newUrl ) ) { speak( __( 'Warning: the link has been inserted but may have errors. Please test it.' @@ -189,84 +119,17 @@ class InlineLinkUI extends Component { } } - onFocusOutside() { - // The autocomplete suggestions list renders in a separate popover (in a portal), - // so onFocusOutside fails to detect that a click on a suggestion occurred in the - // LinkContainer. Detect clicks on autocomplete suggestions using a ref here, and - // return to avoid the popover being closed. - const autocompleteElement = this.autocompleteRef.current; - if ( - autocompleteElement && - autocompleteElement.contains( document.activeElement ) - ) { - return; - } - - this.resetState(); - } - - resetState() { - this.props.stopAddingLink(); - this.setState( { editLink: false } ); - } - - render() { - const { - isActive, - activeAttributes: { url }, - addingLink, - value, - } = this.props; - - if ( ! isActive && ! addingLink ) { - return null; - } - - const { inputValue, opensInNewWindow } = this.state; - const showInput = isShowingInput( this.props, this.state ); - - return ( - <URLPopoverAtLink - value={ value } - isActive={ isActive } - addingLink={ addingLink } - onFocusOutside={ this.onFocusOutside } - onClose={ this.resetState } - focusOnMount={ showInput ? 'firstElement' : false } - renderSettings={ () => ( - <ToggleControl - label={ __( 'Open in New Tab' ) } - checked={ opensInNewWindow } - onChange={ this.setLinkTarget } - /> - ) } - > - { showInput ? ( - <URLPopover.LinkEditor - className="block-editor-format-toolbar__link-container-content" - value={ inputValue } - onChangeInputValue={ this.onChangeInputValue } - onKeyDown={ this.onKeyDown } - onKeyPress={ stopKeyPropagation } - onSubmit={ this.submitLink } - autocompleteRef={ this.autocompleteRef } - /> - ) : ( - <URLPopover.LinkViewer - className="block-editor-format-toolbar__link-container-content" - onKeyPress={ stopKeyPropagation } - url={ url } - onEditLinkClick={ this.editLink } - linkClassName={ - isValidHref( prependHTTP( url ) ) - ? undefined - : 'has-invalid-link' - } - /> - ) } - </URLPopoverAtLink> - ); - } + return ( + <Popover + key={ mountingKey } + anchorRef={ anchorRef } + focusOnMount={ addingLink ? 'firstElement' : false } + onClose={ stopAddingLink } + position="bottom center" + > + <LinkControl value={ linkValue } onChange={ onChangeLink } /> + </Popover> + ); } export default withSpokenMessages( InlineLinkUI ); diff --git a/packages/format-library/src/link/test/inline.js b/packages/format-library/src/link/test/inline.js index cc59087e8c429b..b3be2c1de0720b 100644 --- a/packages/format-library/src/link/test/inline.js +++ b/packages/format-library/src/link/test/inline.js @@ -12,28 +12,4 @@ describe( 'InlineLinkUI', () => { const wrapper = shallow( <InlineLinkUI /> ); expect( wrapper ).toBeTruthy(); } ); - - it( 'should set state.opensInNewWindow to false by default', () => { - const wrapper = shallow( - <InlineLinkUI activeAttributes={ {} } /> - ).dive(); - - expect( wrapper.state( 'opensInNewWindow' ) ).toEqual( false ); - } ); - - it( 'should set state.opensInNewWindow to true if props.activeAttributes.target is _blank', () => { - const givenProps = { - addingLink: false, - activeAttributes: { - url: 'http://www.google.com', - target: '_blank', - }, - }; - - const wrapper = shallow( - <InlineLinkUI activeAttributes={ {} } /> - ).dive(); - wrapper.setProps( givenProps ); - expect( wrapper.state( 'opensInNewWindow' ) ).toEqual( true ); - } ); } ); diff --git a/packages/keyboard-shortcuts/CHANGELOG.md b/packages/keyboard-shortcuts/CHANGELOG.md index 0a7aea6e822a5c..07595288b70282 100644 --- a/packages/keyboard-shortcuts/CHANGELOG.md +++ b/packages/keyboard-shortcuts/CHANGELOG.md @@ -1,3 +1,3 @@ -# Master +## 0.2.0 (2020-01-13) Initial release. diff --git a/packages/project-management-automation/lib/add-milestone.js b/packages/project-management-automation/lib/add-milestone.js index 456503a978953e..cd4e2a5f8aa2f0 100644 --- a/packages/project-management-automation/lib/add-milestone.js +++ b/packages/project-management-automation/lib/add-milestone.js @@ -12,6 +12,23 @@ const REFERENCE_DATE = '2019-08-12'; // Releases are every 14 days. const DAYS_PER_RELEASE = 14; +/** + * Returns true if the given error object represents a duplicate entry error, or + * false otherwise. The error is expected to be (though not verified explicitly + * as) an instance of Octokit's RequestError (`@octokit/request-error`). + * + * @see https://github.com/octokit/rest.js/issues/684 + * @see https://developer.github.com/v3/#client-errors + * @see https://github.com/octokit/request.js/blob/2a1d768/src/fetch-wrapper.ts#L79-L80 + * + * @param {Error} error Error to test. + * + * @return {boolean} Whether error is a duplicate validation request error. + */ +const isDuplicateValidationError = ( error ) => + Array.isArray( error.errors ) && + error.errors.some( ( { code } ) => code === 'already_exists' ); + /** * Assigns the correct milestone to PRs once merged. * @@ -85,12 +102,24 @@ async function addMilestone( payload, octokit ) { `add-milestone: Creating 'Gutenberg ${ major }.${ minor }' milestone, due on ${ dueDate.toISOString() }` ); - await octokit.issues.createMilestone( { - owner: payload.repository.owner.login, - repo: payload.repository.name, - title: `Gutenberg ${ major }.${ minor }`, - due_on: dueDate.toISOString(), - } ); + try { + await octokit.issues.createMilestone( { + owner: payload.repository.owner.login, + repo: payload.repository.name, + title: `Gutenberg ${ major }.${ minor }`, + due_on: dueDate.toISOString(), + } ); + + debug( 'add-milestone: Milestone created' ); + } catch ( error ) { + if ( ! isDuplicateValidationError( error ) ) { + throw error; + } + + debug( + 'add-milestone: Milestone already exists, proceeding with assignment' + ); + } debug( 'add-milestone: Fetching all milestones' ); diff --git a/packages/project-management-automation/lib/if-not-fork.js b/packages/project-management-automation/lib/if-not-fork.js new file mode 100644 index 00000000000000..a4fc4ba611b4b7 --- /dev/null +++ b/packages/project-management-automation/lib/if-not-fork.js @@ -0,0 +1,21 @@ +/** + * Higher-order function which executes and returns the result of the given + * handler only if the enhanced function is called with a payload indicating a + * pull request event which did not originate from a forked repository. + * + * @param {Function} handler Original task. + * + * @return {Function} Enhanced task. + */ +function ifNotFork( handler ) { + return ( payload, ...args ) => { + if ( + payload.pull_request.head.repo.full_name === + payload.pull_request.base.repo.full_name + ) { + return handler( payload, ...args ); + } + }; +} + +module.exports = ifNotFork; diff --git a/packages/project-management-automation/lib/index.js b/packages/project-management-automation/lib/index.js index 00b0cf3c8d2a0c..15d2844c38e48a 100644 --- a/packages/project-management-automation/lib/index.js +++ b/packages/project-management-automation/lib/index.js @@ -11,17 +11,18 @@ const assignFixedIssues = require( './assign-fixed-issues' ); const addFirstTimeContributorLabel = require( './add-first-time-contributor-label' ); const addMilestone = require( './add-milestone' ); const debug = require( './debug' ); +const ifNotFork = require( './if-not-fork' ); const automations = [ { event: 'pull_request', action: 'opened', - task: assignFixedIssues, + task: ifNotFork( assignFixedIssues ), }, { event: 'pull_request', action: 'opened', - task: addFirstTimeContributorLabel, + task: ifNotFork( addFirstTimeContributorLabel ), }, { event: 'pull_request', @@ -52,7 +53,7 @@ const automations = [ debug( `main: Starting task ${ task.name }` ); await task( context.payload, octokit ); } catch ( error ) { - debug( + setFailed( `main: Task ${ task.name } failed with error: ${ error }` ); } diff --git a/readme.txt b/readme.txt index 1e0517e92872d6..86bda8af83511f 100644 --- a/readme.txt +++ b/readme.txt @@ -79,4 +79,4 @@ See also <a href="https://github.com/WordPress/gutenberg/blob/master/CONTRIBUTIN == Changelog == -To read the changelog for Gutenberg 7.3.0, please navigate to the <a href="https://github.com/WordPress/gutenberg/releases/tag/v7.3.0">release page</a>. +To read the changelog for Gutenberg 7.4.0-rc.1, please navigate to the <a href="https://github.com/WordPress/gutenberg/releases/tag/v7.4.0-rc.1">release page</a>. From c66d9e517da5134012110f5fd424b0b3093b4a37 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Tue, 4 Feb 2020 10:11:54 +0100 Subject: [PATCH 106/183] fix lint issue --- .../block-library/src/column/edit.native.js | 67 ++++-- .../block-library/src/columns/edit.native.js | 205 ++++++++++-------- packages/block-library/src/columns/icon.js | 9 +- .../keyboard-aware-flat-list/index.android.js | 4 +- .../readable-content-view/index.native.js | 2 +- 5 files changed, 173 insertions(+), 114 deletions(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 606d6187dbac36..8726eb258d2c73 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -1,4 +1,3 @@ - /** * External dependencies */ @@ -15,10 +14,7 @@ import { BlockControls, BlockVerticalAlignmentToolbar, } from '@wordpress/block-editor'; -import { - Toolbar, - ToolbarButton, -} from '@wordpress/components'; +import { Toolbar, ToolbarButton } from '@wordpress/components'; import { withViewportMatch } from '@wordpress/viewport'; /** * Internal dependencies @@ -63,19 +59,29 @@ function ColumnEdit( { if ( isMobile ) { return; } - - const pullWidth = ( name ) => ( styles[`column-${name}-margin`] || {} ).width + + const pullWidth = ( name ) => + ( styles[ `column-${ name }-margin` ] || {} ).width; let width = columnBaseWidth; if ( isParentSelected ) { - width -= pullWidth( placeholder ? 'placeholder-selected' : 'parent-selected' ); + width -= pullWidth( + placeholder ? 'placeholder-selected' : 'parent-selected' + ); } else if ( isSelected && ! placeholder ) { - width -= ( ! hasChildren ? pullWidth('selected') : pullWidth('descendant-selected') ); + width -= ! hasChildren + ? pullWidth( 'selected' ) + : pullWidth( 'descendant-selected' ); } else if ( isDescendantOfParentSelected ) { - width += pullWidth( placeholder ? 'selected' : 'descendant-selected'); + width += pullWidth( + placeholder ? 'selected' : 'descendant-selected' + ); } else if ( placeholder ) { - width -= ( columnsInRow === 1 ? pullWidth('parent-selected') : pullWidth('placeholder-multicol') ); + width -= + columnsInRow === 1 + ? pullWidth( 'parent-selected' ) + : pullWidth( 'placeholder-multicol' ); } return { width }; @@ -87,11 +93,20 @@ function ColumnEdit( { if ( ! isSelected && ! hasChildren ) { return ( - <View style={ [ - ! isParentSelected && getStylesFromColorScheme( styles.columnPlaceholder, styles.columnPlaceholderDark ), - applyBlockStyle( true ), - { ...styles.marginVerticalDense, ...styles.marginHorizontalNone }, - ] } > + <View + style={ [ + ! isParentSelected && + getStylesFromColorScheme( + styles.columnPlaceholder, + styles.columnPlaceholderDark + ), + applyBlockStyle( true ), + { + ...styles.marginVerticalDense, + ...styles.marginHorizontalNone, + }, + ] } + > { isParentSelected && <InnerBlocks.ButtonBlockAppender /> } </View> ); @@ -113,9 +128,11 @@ function ColumnEdit( { isCollapsed={ false } /> </BlockControls> - <View style={ applyBlockStyle() } > + <View style={ applyBlockStyle() }> <InnerBlocks - renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } + renderAppender={ + isSelected && InnerBlocks.ButtonBlockAppender + } /> </View> </> @@ -141,12 +158,18 @@ export default compose( [ const columnsCount = getBlockCount( parentId ); const hasChildren = getBlockCount( clientId ); - const columnsContainerWidth = columnsContainerSettings && columnsContainerSettings.width; + const columnsContainerWidth = + columnsContainerSettings && columnsContainerSettings.width; - const isParentSelected = selectedBlockClientId && selectedBlockClientId === parentId; + const isParentSelected = + selectedBlockClientId && selectedBlockClientId === parentId; - const selectedParents = selectedBlockClientId ? getBlockParents( selectedBlockClientId ) : []; - const isDescendantOfParentSelected = selectedParents.includes( parentId ); + const selectedParents = selectedBlockClientId + ? getBlockParents( selectedBlockClientId ) + : []; + const isDescendantOfParentSelected = selectedParents.includes( + parentId + ); return { hasChildren, diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 505cf7da970a55..ab948d42dd16ed 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -20,10 +20,7 @@ import { BlockControls, BlockVerticalAlignmentToolbar, } from '@wordpress/block-editor'; -import { - withDispatch, - useSelect, -} from '@wordpress/data'; +import { withDispatch, useSelect } from '@wordpress/data'; import { useEffect } from '@wordpress/element'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { createBlock } from '@wordpress/blocks'; @@ -75,7 +72,10 @@ function ColumnsEditContainer( { } ); useEffect( () => { - updateColumns( count, Math.min( MAX_COLUMNS_NUMBER, count || DEFAULT_COLUMNS ) ); + updateColumns( + count, + Math.min( MAX_COLUMNS_NUMBER, count || DEFAULT_COLUMNS ) + ); }, [] ); return ( @@ -87,7 +87,9 @@ function ColumnsEditContainer( { icon="columns" value={ count } defaultValue={ DEFAULT_COLUMNS } - onChangeValue={ ( value ) => updateColumns( count, value ) } + onChangeValue={ ( value ) => + updateColumns( count, value ) + } minValue={ MIN_COLUMNS_NUMBER } maxValue={ MAX_COLUMNS_NUMBER } /> @@ -107,14 +109,21 @@ function ColumnsEditContainer( { isCollapsed={ false } /> </BlockControls> - <View onLayout={ ( event ) => { - const { width: newWidth } = event.nativeEvent.layout; - if ( newWidth !== width ) { - updateBlockSettings( { ...blockListSettings, width: newWidth } ); - } - } }> + <View + onLayout={ ( event ) => { + const { width: newWidth } = event.nativeEvent.layout; + if ( newWidth !== width ) { + updateBlockSettings( { + ...blockListSettings, + width: newWidth, + } ); + } + } } + > <InnerBlocks - containerStyle={ ! isMobile ? styles.columnsContainer : undefined } + containerStyle={ + ! isMobile ? styles.columnsContainer : undefined + } allowedBlocks={ ALLOWED_BLOCKS } /> </View> @@ -122,93 +131,111 @@ function ColumnsEditContainer( { ); } -const ColumnsEditContainerWrapper = withDispatch( ( dispatch, ownProps, registry ) => ( { - /** - * Update all child Column blocks with a new vertical alignment setting - * based on whatever alignment is passed in. This allows change to parent - * to overide anything set on a individual column basis. - * - * @param {string} verticalAlignment the vertical alignment setting - */ - updateAlignment( verticalAlignment ) { - const { clientId, setAttributes } = ownProps; - const { updateBlockAttributes } = dispatch( 'core/block-editor' ); - const { getBlockOrder } = registry.select( 'core/block-editor' ); - - // Update own alignment. - setAttributes( { verticalAlignment } ); - - // Update all child Column Blocks to match - const innerBlockClientIds = getBlockOrder( clientId ); - innerBlockClientIds.forEach( ( innerBlockClientId ) => { - updateBlockAttributes( innerBlockClientId, { - verticalAlignment, +const ColumnsEditContainerWrapper = withDispatch( + ( dispatch, ownProps, registry ) => ( { + /** + * Update all child Column blocks with a new vertical alignment setting + * based on whatever alignment is passed in. This allows change to parent + * to overide anything set on a individual column basis. + * + * @param {string} verticalAlignment the vertical alignment setting + */ + updateAlignment( verticalAlignment ) { + const { clientId, setAttributes } = ownProps; + const { updateBlockAttributes } = dispatch( 'core/block-editor' ); + const { getBlockOrder } = registry.select( 'core/block-editor' ); + + // Update own alignment. + setAttributes( { verticalAlignment } ); + + // Update all child Column Blocks to match + const innerBlockClientIds = getBlockOrder( clientId ); + innerBlockClientIds.forEach( ( innerBlockClientId ) => { + updateBlockAttributes( innerBlockClientId, { + verticalAlignment, + } ); } ); - } ); - }, - updateBlockSettings( settings ) { - const { clientId } = ownProps; - const { updateBlockListSettings } = dispatch( 'core/block-editor' ); - updateBlockListSettings( clientId, settings ); - }, - /** - * Updates the column count, including necessary revisions to child Column - * blocks to grant required or redistribute available space. - * - * @param {number} previousColumns Previous column count. - * @param {number} newColumns New column count. - */ - updateColumns( previousColumns, newColumns ) { - const { clientId } = ownProps; - const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); - const { getBlocks } = registry.select( 'core/block-editor' ); - - let innerBlocks = getBlocks( clientId ); - - // Redistribute available width for existing inner blocks. - const isAddingColumn = newColumns > previousColumns; - - if ( isAddingColumn ) { - innerBlocks = [ - ...innerBlocks, - ...times( newColumns - previousColumns, () => { - return createBlock( 'core/column' ); - } ), - ]; - } else { - // The removed column will be the last of the inner blocks. - innerBlocks = dropRight( innerBlocks, previousColumns - newColumns ); - } - - replaceInnerBlocks( clientId, innerBlocks, false ); - }, -} ) )( ColumnsEditContainer ); + }, + updateBlockSettings( settings ) { + const { clientId } = ownProps; + const { updateBlockListSettings } = dispatch( 'core/block-editor' ); + updateBlockListSettings( clientId, settings ); + }, + /** + * Updates the column count, including necessary revisions to child Column + * blocks to grant required or redistribute available space. + * + * @param {number} previousColumns Previous column count. + * @param {number} newColumns New column count. + */ + updateColumns( previousColumns, newColumns ) { + const { clientId } = ownProps; + const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); + const { getBlocks } = registry.select( 'core/block-editor' ); + + let innerBlocks = getBlocks( clientId ); + + // Redistribute available width for existing inner blocks. + const isAddingColumn = newColumns > previousColumns; + + if ( isAddingColumn ) { + innerBlocks = [ + ...innerBlocks, + ...times( newColumns - previousColumns, () => { + return createBlock( 'core/column' ); + } ), + ]; + } else { + // The removed column will be the last of the inner blocks. + innerBlocks = dropRight( + innerBlocks, + previousColumns - newColumns + ); + } + + replaceInnerBlocks( clientId, innerBlocks, false ); + }, + } ) +)( ColumnsEditContainer ); const ColumnsEdit = ( props ) => { const { clientId, isSelected, getStylesFromColorScheme } = props; - const { hasChildren, blockListSettings } = useSelect( ( select ) => { - const { - getBlocks, - getBlockListSettings, - } = select( 'core/block-editor' ); - - return { - hasChildren: getBlocks( clientId ).length > 0, - blockListSettings: getBlockListSettings( clientId ) || {}, - }; - }, [ clientId ] ); + const { hasChildren, blockListSettings } = useSelect( + ( select ) => { + const { getBlocks, getBlockListSettings } = select( + 'core/block-editor' + ); + + return { + hasChildren: getBlocks( clientId ).length > 0, + blockListSettings: getBlockListSettings( clientId ) || {}, + }; + }, + [ clientId ] + ); if ( ! isSelected && ! hasChildren ) { return ( - <View style={ [ - getStylesFromColorScheme( styles.columnsPlaceholder, styles.columnsPlaceholderDark ), - ! hasChildren && { ...styles.marginVerticalDense, ...styles.marginHorizontalNone }, - ] } /> + <View + style={ [ + getStylesFromColorScheme( + styles.columnsPlaceholder, + styles.columnsPlaceholderDark + ), + ! hasChildren && { + ...styles.marginVerticalDense, + ...styles.marginHorizontalNone, + }, + ] } + /> ); } return ( - <ColumnsEditContainerWrapper blockListSettings={ blockListSettings } { ...props } /> + <ColumnsEditContainerWrapper + blockListSettings={ blockListSettings } + { ...props } + /> ); }; diff --git a/packages/block-library/src/columns/icon.js b/packages/block-library/src/columns/icon.js index 594a80b5d78e86..42ab77645c27aa 100644 --- a/packages/block-library/src/columns/icon.js +++ b/packages/block-library/src/columns/icon.js @@ -3,6 +3,13 @@ */ import { G, Path, SVG } from '@wordpress/components'; -const Icon = ( props ) => <SVG { ...props } viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M4,4H20a2,2,0,0,1,2,2V18a2,2,0,0,1-2,2H4a2,2,0,0,1-2-2V6A2,2,0,0,1,4,4ZM4 6V18H8V6Zm6 0V18h4V6Zm6 0V18h4V6Z" /></G></SVG>; +const Icon = ( props ) => ( + <SVG { ...props } viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <Path fill="none" d="M0 0h24v24H0V0z" /> + <G> + <Path d="M4,4H20a2,2,0,0,1,2,2V18a2,2,0,0,1-2,2H4a2,2,0,0,1-2-2V6A2,2,0,0,1,4,4ZM4 6V18H8V6Zm6 0V18h4V6Zm6 0V18h4V6Z" /> + </G> + </SVG> +); export default Icon; diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js index 687b3013bc501d..8fa33bfa319e94 100644 --- a/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js +++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js @@ -13,7 +13,9 @@ export const KeyboardAwareFlatList = ( { containerStyle, ...props } ) => ( <FlatList scrollEnabled={ containerStyle ? false : true } horizontal={ containerStyle ? true : false } - contentContainerStyle={ containerStyle ? containerStyle : undefined } + contentContainerStyle={ + containerStyle ? containerStyle : undefined + } { ...props } /> </KeyboardAvoidingView> diff --git a/packages/components/src/mobile/readable-content-view/index.native.js b/packages/components/src/mobile/readable-content-view/index.native.js index 6fda081a897fd7..65adf00cff6226 100644 --- a/packages/components/src/mobile/readable-content-view/index.native.js +++ b/packages/components/src/mobile/readable-content-view/index.native.js @@ -9,7 +9,7 @@ import { View, Dimensions } from 'react-native'; import styles from './style.scss'; const ReadableContentView = ( { reversed, children, style } ) => ( - <View style={ [ styles.container, style ] } > + <View style={ [ styles.container, style ] }> <View style={ reversed From 9fc95cb38f505f5647836b44477a7c50775034b9 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Wed, 5 Feb 2020 09:41:59 +0100 Subject: [PATCH 107/183] refactor --- .../src/components/block-list/index.native.js | 25 +++++++------------ packages/block-library/src/columns/block.json | 3 --- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 91c0a3a6921766..83dc31460a0bd8 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -157,24 +157,17 @@ export class BlockList extends Component { getBlockAttributes, } = this.props; - const getVerticalAlignmentRemap = ( newAlignment ) => { - let alingment; - switch ( newAlignment ) { - case 'center': - alingment = 'center'; - break; + const getVerticalAlignmentRemap = ( alingment ) => { + if ( ! alingment ) return; - case 'bottom': - alingment = 'flex-end'; - break; + const mapping = { + top: 'flex-start', + center: 'center', + bottom: 'flex-end', + }; - case 'top': - alingment = 'flex-start'; - break; - - default: - alingment = newAlignment; - } + const newAlingment = mapping[ alingment ]; + if ( ! newAlingment ) return; return { justifyContent: alingment }; }; diff --git a/packages/block-library/src/columns/block.json b/packages/block-library/src/columns/block.json index 968fc73da67930..022d64285361e9 100644 --- a/packages/block-library/src/columns/block.json +++ b/packages/block-library/src/columns/block.json @@ -5,9 +5,6 @@ "verticalAlignment": { "type": "string" }, - "width": { - "type": "number" - }, "backgroundColor": { "type": "string" }, From 2976b5fd2e344478b65904790b85982752f31287 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Mon, 3 Feb 2020 10:13:17 +0100 Subject: [PATCH 108/183] change placeholder dashed bordering in Column --- .../src/components/block-list/block.native.js | 3 +++ .../src/components/block-list/block.native.scss | 10 ++++++++++ packages/block-library/src/column/editor.native.scss | 4 ++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 08ec82de98cb74..9a149c9357fd57 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -143,6 +143,9 @@ class BlockListBlock extends Component { styles.dashedBorderColor, styles.dashedBorderColorDark ), + ...( ! hasChildren && + this.props.name === 'core/column' && + styles.alternatePlaceholderStyle ), }; // return apply childOfSelected or childOfSelectedLeaf diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index f22e0712553535..23d7768718f1e5 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -67,6 +67,16 @@ margin-bottom: 0; } +.alternatePlaceholderStyle { + border-color: transparent; + margin-top: 0; + margin-right: 0; + margin-left: 0; + padding-left: 2; + padding-right: 2; + padding-top: 0; +} + .blockTitle { background-color: $gray; padding-left: 8px; diff --git a/packages/block-library/src/column/editor.native.scss b/packages/block-library/src/column/editor.native.scss index 790b8bf479b27d..4baed0c5577330 100644 --- a/packages/block-library/src/column/editor.native.scss +++ b/packages/block-library/src/column/editor.native.scss @@ -32,11 +32,11 @@ } .column-placeholder-selected-margin { - width: 2 * $block-selected-to-content; + width: $block-selected-to-content; } .column-placeholder-multicol-margin { - width: 2 * $block-edge-to-content; + width: 2 * $block-selected-to-content; } .column-parent-selected-margin { From 81828ae1c64ca3e0c2ae93bc5a7b0b3fe8b0a3a4 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Wed, 5 Feb 2020 12:17:37 +0100 Subject: [PATCH 109/183] move container calculation to Columns, prevent to override settings --- .../components/inner-blocks/index.native.js | 1 + .../block-library/src/column/edit.native.js | 30 +++++----------- .../block-library/src/columns/edit.native.js | 34 ++++++++++++++----- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index efdaa4cd8e9e24..c32297c4b4233d 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -99,6 +99,7 @@ class InnerBlocks extends Component { } = this.props; const newSettings = { + ...blockListSettings, allowedBlocks, templateLock: this.getTemplateLock(), }; diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 8726eb258d2c73..2375c463fb396c 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -29,11 +29,14 @@ function ColumnEdit( { getStylesFromColorScheme, isParentSelected, isDescendantOfParentSelected, - columnsCount, - columnsContainerWidth, + columnsContainerSettings, isMobile, } ) { const { verticalAlignment } = attributes; + const { + columnsInRow = 1, + columnsContainerWidth, + } = columnsContainerSettings; const columnContainerBaseWidth = styles[ 'column-container-base' ].maxWidth; const containerMaxWidth = styles[ 'columns-container' ].maxWidth; @@ -41,18 +44,6 @@ function ColumnEdit( { const containerWidth = columnsContainerWidth || containerMaxWidth; const minWidth = Math.min( containerWidth, columnContainerBaseWidth ); - - const getColumnsInRow = ( columnsNumber ) => { - if ( minWidth < 480 ) { - return 1; - } - if ( minWidth >= 480 && minWidth < 768 ) { - return 2; - } - return columnsNumber; - }; - - const columnsInRow = getColumnsInRow( columnsCount ); const columnBaseWidth = minWidth / columnsInRow; const applyBlockStyle = ( placeholder = false ) => { @@ -61,7 +52,7 @@ function ColumnEdit( { } const pullWidth = ( name ) => - ( styles[ `column-${ name }-margin` ] || {} ).width; + ( styles[ `column-${ name }-margin` ] || {} ).width || 0; let width = columnBaseWidth; @@ -153,14 +144,10 @@ export default compose( [ const isSelected = selectedBlockClientId === clientId; const parentId = getBlockRootClientId( clientId ); + const hasChildren = getBlockCount( clientId ); const columnsContainerSettings = getBlockListSettings( parentId ); - const columnsCount = getBlockCount( parentId ); - const hasChildren = getBlockCount( clientId ); - const columnsContainerWidth = - columnsContainerSettings && columnsContainerSettings.width; - const isParentSelected = selectedBlockClientId && selectedBlockClientId === parentId; @@ -175,8 +162,7 @@ export default compose( [ hasChildren, isParentSelected, isSelected, - columnsCount, - columnsContainerWidth, + columnsContainerSettings, isDescendantOfParentSelected, }; } ), diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index ab948d42dd16ed..9825883a9fe485 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -65,19 +65,31 @@ function ColumnsEditContainer( { const { verticalAlignment } = attributes; const { width } = blockListSettings; - const { count } = useSelect( ( select ) => { + const { columnCount } = useSelect( ( select ) => { return { - count: select( 'core/block-editor' ).getBlockCount( clientId ), + columnCount: select( 'core/block-editor' ).getBlockCount( + clientId + ), }; } ); useEffect( () => { updateColumns( - count, - Math.min( MAX_COLUMNS_NUMBER, count || DEFAULT_COLUMNS ) + columnCount, + Math.min( MAX_COLUMNS_NUMBER, columnCount || DEFAULT_COLUMNS ) ); }, [] ); + const getColumnsInRow = ( containerWidth, columnsNumber ) => { + if ( containerWidth < 480 ) { + return 1; + } + if ( containerWidth >= 480 && containerWidth < 768 ) { + return 2; + } + return columnsNumber; + }; + return ( <> <InspectorControls> @@ -85,10 +97,10 @@ function ColumnsEditContainer( { <StepperControl label={ __( 'Number of columns' ) } icon="columns" - value={ count } + value={ columnCount } defaultValue={ DEFAULT_COLUMNS } onChangeValue={ ( value ) => - updateColumns( count, value ) + updateColumns( columnCount, value ) } minValue={ MIN_COLUMNS_NUMBER } maxValue={ MAX_COLUMNS_NUMBER } @@ -115,6 +127,10 @@ function ColumnsEditContainer( { if ( newWidth !== width ) { updateBlockSettings( { ...blockListSettings, + columnsInRow: getColumnsInRow( + newWidth, + columnCount + ), width: newWidth, } ); } @@ -162,11 +178,11 @@ const ColumnsEditContainerWrapper = withDispatch( updateBlockListSettings( clientId, settings ); }, /** - * Updates the column count, including necessary revisions to child Column + * Updates the column columnCount, including necessary revisions to child Column * blocks to grant required or redistribute available space. * - * @param {number} previousColumns Previous column count. - * @param {number} newColumns New column count. + * @param {number} previousColumns Previous column columnCount. + * @param {number} newColumns New column columnCount. */ updateColumns( previousColumns, newColumns ) { const { clientId } = ownProps; From daed5940e07159007df1262387587d7b75dd00b2 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Wed, 5 Feb 2020 12:37:45 +0100 Subject: [PATCH 110/183] adjust logic --- packages/block-library/src/column/edit.native.js | 8 +++++--- .../src/mobile/keyboard-aware-flat-list/index.ios.js | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 2375c463fb396c..759816e7afc6df 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -65,9 +65,11 @@ function ColumnEdit( { ? pullWidth( 'selected' ) : pullWidth( 'descendant-selected' ); } else if ( isDescendantOfParentSelected ) { - width += pullWidth( - placeholder ? 'selected' : 'descendant-selected' - ); + if ( placeholder ) { + width -= pullWidth( 'selected' ); + } else { + width += pullWidth( 'descendant-selected' ); + } } else if ( placeholder ) { width -= columnsInRow === 1 diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js index e8f5e5c6474519..6b7fe12e8b1640 100644 --- a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js +++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js @@ -57,6 +57,7 @@ export const KeyboardAwareFlatList = ( { <FlatList { ...listProps } horizontal={ containerStyle ? true : false } + scrollEnabled={ containerStyle ? false : true } contentContainerStyle={ containerStyle ? containerStyle : undefined } From 3569a0d0353069a880f76760407aae1a27a7554a Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 6 Feb 2020 09:45:04 +0100 Subject: [PATCH 111/183] changes after Piotr code review --- .../block-list/breadcrumb.native.js | 94 +++++++++---------- .../src/components/block-list/index.native.js | 35 +++---- 2 files changed, 65 insertions(+), 64 deletions(-) diff --git a/packages/block-editor/src/components/block-list/breadcrumb.native.js b/packages/block-editor/src/components/block-list/breadcrumb.native.js index 5ea87291a65315..458cd8fa95cfcd 100644 --- a/packages/block-editor/src/components/block-list/breadcrumb.native.js +++ b/packages/block-editor/src/components/block-list/breadcrumb.native.js @@ -19,63 +19,61 @@ import SubdirectorSVG from './subdirectory-icon'; import styles from './breadcrumb.scss'; -const BlockBreadcrumb = ( { - clientId, - blockIcon, - rootClientId, - rootBlockIcon, -} ) => { - const renderIcon = ( icon, key ) => { - if ( typeof icon.src === 'function' ) { - return ( - <Icon - key={ key } - icon={ icon.src( { size: 24, fill: styles.icon.color } ) } - /> - ); - } +const renderIcon = ( icon, key ) => { + if ( typeof icon.src === 'function' ) { return ( <Icon key={ key } - size={ 24 } - icon={ icon.src } - fill={ styles.icon.color } + icon={ icon.src( { size: 24, fill: styles.icon.color } ) } /> ); - }; - + } return ( - <View style={ styles.breadcrumbContainer }> - <TouchableOpacity - style={ styles.button } - onPress={ () => { - /* Open BottomSheet with markup */ - } } - disabled={ - true - } /* Disable temporarily since onPress function is empty */ - > - { rootClientId && - rootBlockIcon && [ - renderIcon( rootBlockIcon, 'parent-icon' ), - <View key="subdirectory-icon" style={ styles.arrow }> - <SubdirectorSVG fill={ styles.arrow.color } /> - </View>, - ] } - { renderIcon( blockIcon ) } - <Text - maxFontSizeMultiplier={ 1.25 } - ellipsizeMode="tail" - numberOfLines={ 1 } - style={ styles.breadcrumbTitle } - > - <BlockTitle clientId={ clientId } /> - </Text> - </TouchableOpacity> - </View> + <Icon + key={ key } + size={ 24 } + icon={ icon.src } + fill={ styles.icon.color } + /> ); }; +const BlockBreadcrumb = ( { + clientId, + blockIcon, + rootClientId, + rootBlockIcon, +} ) => ( + <View style={ styles.breadcrumbContainer }> + <TouchableOpacity + style={ styles.button } + onPress={ () => { + /* Open BottomSheet with markup */ + } } + disabled={ + true + } /* Disable temporarily since onPress function is empty */ + > + { rootClientId && + rootBlockIcon && [ + renderIcon( rootBlockIcon, 'parent-icon' ), + <View key="subdirectory-icon" style={ styles.arrow }> + <SubdirectorSVG fill={ styles.arrow.color } /> + </View>, + ] } + { renderIcon( blockIcon ) } + <Text + maxFontSizeMultiplier={ 1.25 } + ellipsizeMode="tail" + numberOfLines={ 1 } + style={ styles.breadcrumbTitle } + > + <BlockTitle clientId={ clientId } /> + </Text> + </TouchableOpacity> + </View> +); + export default compose( [ withSelect( ( select, { clientId } ) => { const { getBlockRootClientId, getBlockName } = select( diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 83dc31460a0bd8..358198290879ab 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -47,6 +47,9 @@ export class BlockList extends Component { this.shouldShowInnerBlockAppender = this.shouldShowInnerBlockAppender.bind( this ); + this.getVerticalAlignmentRemap = this.getVerticalAlignmentRemap.bind( + this + ); } addBlockToEndOfPost( newBlock ) { @@ -89,6 +92,20 @@ export class BlockList extends Component { return renderAppender && blockClientIds.length > 0; } + getVerticalAlignmentRemap( alingment ) { + if ( ! alingment ) return; + + const mapping = { + top: 'flex-start', + center: 'center', + bottom: 'flex-end', + }; + + const newAlingment = mapping[ alingment ]; + if ( ! newAlingment ) return; + return { justifyContent: alingment }; + } + render() { const { clearSelectedBlock, @@ -157,31 +174,17 @@ export class BlockList extends Component { getBlockAttributes, } = this.props; - const getVerticalAlignmentRemap = ( alingment ) => { - if ( ! alingment ) return; - - const mapping = { - top: 'flex-start', - center: 'center', - bottom: 'flex-end', - }; - - const newAlingment = mapping[ alingment ]; - if ( ! newAlingment ) return; - return { justifyContent: alingment }; - }; - const attributes = getBlockAttributes( clientId ); let columnContainerStyle = {}; if ( attributes ) { - columnContainerStyle = getVerticalAlignmentRemap( + columnContainerStyle = this.getVerticalAlignmentRemap( attributes.verticalAlignment ); } return ( <ReadableContentView - style={ containerStyle ? columnContainerStyle : undefined } + style={ containerStyle && columnContainerStyle } > <View pointerEvents={ isReadOnly ? 'box-only' : 'auto' }> { shouldShowInsertionPointBefore( clientId ) && ( From 562d6740ea8c01da4ea628fdf306cc7da4ba3f81 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 7 Feb 2020 08:28:33 +0100 Subject: [PATCH 112/183] fix crash after refactor --- .../src/components/block-list/index.native.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 358198290879ab..c7b76703387054 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { identity } from 'lodash'; +import { identity, get } from 'lodash'; import { View, Platform, TouchableWithoutFeedback } from 'react-native'; /** @@ -92,18 +92,14 @@ export class BlockList extends Component { return renderAppender && blockClientIds.length > 0; } - getVerticalAlignmentRemap( alingment ) { - if ( ! alingment ) return; - + getVerticalAlignmentRemap( alignment ) { const mapping = { top: 'flex-start', center: 'center', bottom: 'flex-end', }; - - const newAlingment = mapping[ alingment ]; - if ( ! newAlingment ) return; - return { justifyContent: alingment }; + const newAlignment = get( mapping, alignment ); + return newAlignment ? { justifyContent: newAlignment } : undefined; } render() { From 7828fa34fec61b06f79486ec4173296cb6ed8e54 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 7 Feb 2020 09:47:04 +0100 Subject: [PATCH 113/183] revert breadcrumb icon changes --- .../block-list/breadcrumb.native.js | 88 +++++++++---------- 1 file changed, 40 insertions(+), 48 deletions(-) diff --git a/packages/block-editor/src/components/block-list/breadcrumb.native.js b/packages/block-editor/src/components/block-list/breadcrumb.native.js index 458cd8fa95cfcd..37d26abe1cff55 100644 --- a/packages/block-editor/src/components/block-list/breadcrumb.native.js +++ b/packages/block-editor/src/components/block-list/breadcrumb.native.js @@ -19,60 +19,52 @@ import SubdirectorSVG from './subdirectory-icon'; import styles from './breadcrumb.scss'; -const renderIcon = ( icon, key ) => { - if ( typeof icon.src === 'function' ) { - return ( - <Icon - key={ key } - icon={ icon.src( { size: 24, fill: styles.icon.color } ) } - /> - ); - } - return ( - <Icon - key={ key } - size={ 24 } - icon={ icon.src } - fill={ styles.icon.color } - /> - ); -}; - const BlockBreadcrumb = ( { clientId, blockIcon, rootClientId, rootBlockIcon, -} ) => ( - <View style={ styles.breadcrumbContainer }> - <TouchableOpacity - style={ styles.button } - onPress={ () => { - /* Open BottomSheet with markup */ - } } - disabled={ - true - } /* Disable temporarily since onPress function is empty */ - > - { rootClientId && - rootBlockIcon && [ - renderIcon( rootBlockIcon, 'parent-icon' ), - <View key="subdirectory-icon" style={ styles.arrow }> - <SubdirectorSVG fill={ styles.arrow.color } /> - </View>, - ] } - { renderIcon( blockIcon ) } - <Text - maxFontSizeMultiplier={ 1.25 } - ellipsizeMode="tail" - numberOfLines={ 1 } - style={ styles.breadcrumbTitle } +} ) => { + return ( + <View style={ styles.breadcrumbContainer }> + <TouchableOpacity + style={ styles.button } + onPress={ () => { + /* Open BottomSheet with markup */ + } } + disabled={ + true + } /* Disable temporarily since onPress function is empty */ > - <BlockTitle clientId={ clientId } /> - </Text> - </TouchableOpacity> - </View> -); + { rootClientId && + rootBlockIcon && [ + <Icon + key="parent-icon" + size={ 20 } + icon={ rootBlockIcon.src } + fill={ styles.icon.color } + />, + <View key="subdirectory-icon" style={ styles.arrow }> + <SubdirectorSVG fill={ styles.arrow.color } /> + </View>, + ] } + <Icon + size={ 24 } + icon={ blockIcon.src } + fill={ styles.icon.color } + /> + <Text + maxFontSizeMultiplier={ 1.25 } + ellipsizeMode="tail" + numberOfLines={ 1 } + style={ styles.breadcrumbTitle } + > + <BlockTitle clientId={ clientId } /> + </Text> + </TouchableOpacity> + </View> + ); +}; export default compose( [ withSelect( ( select, { clientId } ) => { From 46dd5201d799ebc39c7e520d676114e3368cb066 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 7 Feb 2020 10:01:51 +0100 Subject: [PATCH 114/183] refactor to use existing API for icons --- packages/block-library/src/column/edit.native.js | 3 ++- packages/block-library/src/columns/edit.native.js | 5 ++--- packages/block-library/src/columns/icon.js | 15 --------------- 3 files changed, 4 insertions(+), 19 deletions(-) delete mode 100644 packages/block-library/src/columns/icon.js diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 759816e7afc6df..87218320ee3611 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -16,6 +16,7 @@ import { } from '@wordpress/block-editor'; import { Toolbar, ToolbarButton } from '@wordpress/components'; import { withViewportMatch } from '@wordpress/viewport'; +import { column as icon } from '@wordpress/icons'; /** * Internal dependencies */ @@ -111,7 +112,7 @@ function ColumnEdit( { <Toolbar> <ToolbarButton title={ __( 'ColumnButton' ) } - icon="columns" + icon={ icon } onClick={ () => {} } /> </Toolbar> diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 9825883a9fe485..56876552d78e73 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -25,12 +25,11 @@ import { useEffect } from '@wordpress/element'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { createBlock } from '@wordpress/blocks'; import { withViewportMatch } from '@wordpress/viewport'; - +import { columns as icon } from '@wordpress/icons' /** * Internal dependencies */ import styles from './editor.scss'; -import Icon from './icon'; /** * Allowed blocks constant is passed to InnerBlocks precisely as specified here. @@ -111,7 +110,7 @@ function ColumnsEditContainer( { <Toolbar> <ToolbarButton title={ __( 'ColumnsButton' ) } - icon={ <Icon width={ 20 } height={ 20 } /> } + icon={ icon } onClick={ () => {} } /> </Toolbar> diff --git a/packages/block-library/src/columns/icon.js b/packages/block-library/src/columns/icon.js deleted file mode 100644 index 42ab77645c27aa..00000000000000 --- a/packages/block-library/src/columns/icon.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * WordPress dependencies - */ -import { G, Path, SVG } from '@wordpress/components'; - -const Icon = ( props ) => ( - <SVG { ...props } viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> - <Path fill="none" d="M0 0h24v24H0V0z" /> - <G> - <Path d="M4,4H20a2,2,0,0,1,2,2V18a2,2,0,0,1-2,2H4a2,2,0,0,1-2-2V6A2,2,0,0,1,4,4ZM4 6V18H8V6Zm6 0V18h4V6Zm6 0V18h4V6Z" /> - </G> - </SVG> -); - -export default Icon; From cd97df2ea8705ce28a15ab69efca278a3ce5de58 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 7 Feb 2020 10:02:30 +0100 Subject: [PATCH 115/183] refactor to use existing API for icons --- packages/block-library/src/columns/edit.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 56876552d78e73..187ba9739244f9 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -25,7 +25,7 @@ import { useEffect } from '@wordpress/element'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { createBlock } from '@wordpress/blocks'; import { withViewportMatch } from '@wordpress/viewport'; -import { columns as icon } from '@wordpress/icons' +import { columns as icon } from '@wordpress/icons'; /** * Internal dependencies */ From 631621de55fbf147234d756f499e2009d4450438 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 7 Feb 2020 11:18:26 +0100 Subject: [PATCH 116/183] refactor getVerticalAlingnment function --- .../src/components/block-list/index.native.js | 14 +++----------- .../src/components/block-list/style.native.scss | 12 ++++++++++++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index c7b76703387054..14eb0b1d78c795 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { identity, get } from 'lodash'; +import { identity } from 'lodash'; import { View, Platform, TouchableWithoutFeedback } from 'react-native'; /** @@ -47,9 +47,6 @@ export class BlockList extends Component { this.shouldShowInnerBlockAppender = this.shouldShowInnerBlockAppender.bind( this ); - this.getVerticalAlignmentRemap = this.getVerticalAlignmentRemap.bind( - this - ); } addBlockToEndOfPost( newBlock ) { @@ -93,13 +90,8 @@ export class BlockList extends Component { } getVerticalAlignmentRemap( alignment ) { - const mapping = { - top: 'flex-start', - center: 'center', - bottom: 'flex-end', - }; - const newAlignment = get( mapping, alignment ); - return newAlignment ? { justifyContent: newAlignment } : undefined; + if ( ! alignment ) return; + return styles[ `is-vertically-aligned-${ alignment }` ]; } render() { diff --git a/packages/block-editor/src/components/block-list/style.native.scss b/packages/block-editor/src/components/block-list/style.native.scss index c102d1644d838b..74d876b70097d7 100644 --- a/packages/block-editor/src/components/block-list/style.native.scss +++ b/packages/block-editor/src/components/block-list/style.native.scss @@ -50,3 +50,15 @@ padding-left: $block-custom-appender-to-content; padding-right: $block-custom-appender-to-content; } + +.is-vertically-aligned-top { + justify-content: flex-start; +} + +.is-vertically-aligned-center { + justify-content: center; +} + +.is-vertically-aligned-bottom { + justify-content: flex-end; +} From 3ee4e95c83cb1512201ff2624ac1d67e6bb820eb Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 7 Feb 2020 11:42:50 +0100 Subject: [PATCH 117/183] refactor verticalAlignment logic --- .../src/components/block-list/index.native.js | 26 ++----------------- .../components/block-list/style.native.scss | 12 --------- .../components/inner-blocks/index.native.js | 8 +++++- .../block-library/src/column/edit.native.js | 8 ++++++ .../src/column/editor.native.scss | 12 +++++++++ .../block-library/src/columns/edit.native.js | 8 ++++++ .../src/columns/editor.native.scss | 12 +++++++++ 7 files changed, 49 insertions(+), 37 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 14eb0b1d78c795..f8321868bbf579 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -89,11 +89,6 @@ export class BlockList extends Component { return renderAppender && blockClientIds.length > 0; } - getVerticalAlignmentRemap( alignment ) { - if ( ! alignment ) return; - return styles[ `is-vertically-aligned-${ alignment }` ]; - } - render() { const { clearSelectedBlock, @@ -158,22 +153,11 @@ export class BlockList extends Component { isReadOnly, shouldShowInsertionPointBefore, shouldShowInsertionPointAfter, - containerStyle, - getBlockAttributes, + readableContentViewStyle, } = this.props; - const attributes = getBlockAttributes( clientId ); - let columnContainerStyle = {}; - if ( attributes ) { - columnContainerStyle = this.getVerticalAlignmentRemap( - attributes.verticalAlignment - ); - } - return ( - <ReadableContentView - style={ containerStyle && columnContainerStyle } - > + <ReadableContentView style={ readableContentViewStyle }> <View pointerEvents={ isReadOnly ? 'box-only' : 'auto' }> { shouldShowInsertionPointBefore( clientId ) && ( <BlockInsertionPoint /> @@ -223,7 +207,6 @@ export default compose( [ getBlockInsertionPoint, isBlockInsertionPointVisible, getSettings, - __unstableGetBlockWithoutInnerBlocks, } = select( 'core/block-editor' ); const selectedBlockClientId = getSelectedBlockClientId(); @@ -253,10 +236,6 @@ export default compose( [ const isReadOnly = getSettings().readOnly; - const getBlockAttributes = ( clientId ) => - ( __unstableGetBlockWithoutInnerBlocks( clientId ) || {} ) - .attributes; - return { blockClientIds, blockCount: getBlockCount( rootClientId ), @@ -266,7 +245,6 @@ export default compose( [ selectedBlockClientId, isReadOnly, isRootList: rootClientId === undefined, - getBlockAttributes, }; } ), withDispatch( ( dispatch ) => { diff --git a/packages/block-editor/src/components/block-list/style.native.scss b/packages/block-editor/src/components/block-list/style.native.scss index 74d876b70097d7..c102d1644d838b 100644 --- a/packages/block-editor/src/components/block-list/style.native.scss +++ b/packages/block-editor/src/components/block-list/style.native.scss @@ -50,15 +50,3 @@ padding-left: $block-custom-appender-to-content; padding-right: $block-custom-appender-to-content; } - -.is-vertically-aligned-top { - justify-content: flex-start; -} - -.is-vertically-aligned-center { - justify-content: center; -} - -.is-vertically-aligned-bottom { - justify-content: flex-end; -} diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index c32297c4b4233d..3f68072d2b49dd 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -110,7 +110,12 @@ class InnerBlocks extends Component { } render() { - const { clientId, renderAppender, containerStyle } = this.props; + const { + clientId, + renderAppender, + containerStyle, + readableContentViewStyle, + } = this.props; const { templateInProcess } = this.state; return ( @@ -122,6 +127,7 @@ class InnerBlocks extends Component { withFooter={ false } isFullyBordered={ true } containerStyle={ containerStyle } + readableContentViewStyle={ readableContentViewStyle } /> ) } </> diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 87218320ee3611..0e6f2f21238868 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -106,6 +106,11 @@ function ColumnEdit( { ); } + const getVerticalAlignmentRemap = ( alignment ) => { + if ( ! alignment ) return; + return styles[ `is-vertically-aligned-${ alignment }` ]; + }; + return ( <> <BlockControls> @@ -127,6 +132,9 @@ function ColumnEdit( { renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } + readableContentViewStyle={ getVerticalAlignmentRemap( + verticalAlignment + ) } /> </View> </> diff --git a/packages/block-library/src/column/editor.native.scss b/packages/block-library/src/column/editor.native.scss index 4baed0c5577330..4907797c4b622c 100644 --- a/packages/block-library/src/column/editor.native.scss +++ b/packages/block-library/src/column/editor.native.scss @@ -50,3 +50,15 @@ .column-descendant-selected-margin { width: $block-selected-margin + $block-selected-border-width; } + +.is-vertically-aligned-top { + justify-content: flex-start; +} + +.is-vertically-aligned-center { + justify-content: center; +} + +.is-vertically-aligned-bottom { + justify-content: flex-end; +} diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 187ba9739244f9..a0dbbff2588465 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -89,6 +89,11 @@ function ColumnsEditContainer( { return columnsNumber; }; + const getVerticalAlignmentRemap = ( alignment ) => { + if ( ! alignment ) return; + return styles[ `is-vertically-aligned-${ alignment }` ]; + }; + return ( <> <InspectorControls> @@ -140,6 +145,9 @@ function ColumnsEditContainer( { ! isMobile ? styles.columnsContainer : undefined } allowedBlocks={ ALLOWED_BLOCKS } + readableContentViewStyle={ getVerticalAlignmentRemap( + verticalAlignment + ) } /> </View> </> diff --git a/packages/block-library/src/columns/editor.native.scss b/packages/block-library/src/columns/editor.native.scss index 4b59f9833b7d29..e8f73f155af989 100644 --- a/packages/block-library/src/columns/editor.native.scss +++ b/packages/block-library/src/columns/editor.native.scss @@ -29,3 +29,15 @@ max-width: $content-width; overflow: hidden; } + +.is-vertically-aligned-top { + justify-content: flex-start; +} + +.is-vertically-aligned-center { + justify-content: center; +} + +.is-vertically-aligned-bottom { + justify-content: flex-end; +} From d495d20b03385ae6cb8b703a124b46796bb08496 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 7 Feb 2020 12:44:32 +0100 Subject: [PATCH 118/183] pass flatListProp to InnerBlock --- .../src/components/block-list/index.native.js | 8 ++++---- .../src/components/inner-blocks/index.native.js | 4 ++-- packages/block-library/src/column/edit.native.js | 2 +- packages/block-library/src/columns/edit.native.js | 12 ++++++++---- .../mobile/keyboard-aware-flat-list/index.android.js | 11 ++--------- .../src/mobile/keyboard-aware-flat-list/index.ios.js | 12 ++---------- 6 files changed, 19 insertions(+), 30 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index f8321868bbf579..bf3a7f21ae6e6d 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -99,7 +99,7 @@ export class BlockList extends Component { withFooter = true, isReadOnly, isRootList, - containerStyle, + flatListProps, } = this.props; return ( @@ -108,6 +108,7 @@ export class BlockList extends Component { onAccessibilityEscape={ clearSelectedBlock } > <KeyboardAwareFlatList + { ...flatListProps } { ...( Platform.OS === 'android' ? { removeClippedSubviews: false } : {} ) } // Disable clipping on Android to fix focus losing. See https://github.com/wordpress-mobile/gutenberg-mobile/pull/741#issuecomment-472746541 @@ -117,7 +118,6 @@ export class BlockList extends Component { extraScrollHeight={ innerToolbarHeight + 10 } keyboardShouldPersistTaps="always" scrollViewStyle={ { flex: isRootList ? 1 : 0 } } - containerStyle={ containerStyle } data={ blockClientIds } extraData={ [ isFullyBordered ] } keyExtractor={ identity } @@ -153,11 +153,11 @@ export class BlockList extends Component { isReadOnly, shouldShowInsertionPointBefore, shouldShowInsertionPointAfter, - readableContentViewStyle, + containerStyle, } = this.props; return ( - <ReadableContentView style={ readableContentViewStyle }> + <ReadableContentView style={ containerStyle }> <View pointerEvents={ isReadOnly ? 'box-only' : 'auto' }> { shouldShowInsertionPointBefore( clientId ) && ( <BlockInsertionPoint /> diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index 3f68072d2b49dd..c076c1ab17893a 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -114,7 +114,7 @@ class InnerBlocks extends Component { clientId, renderAppender, containerStyle, - readableContentViewStyle, + flatListProps, } = this.props; const { templateInProcess } = this.state; @@ -127,7 +127,7 @@ class InnerBlocks extends Component { withFooter={ false } isFullyBordered={ true } containerStyle={ containerStyle } - readableContentViewStyle={ readableContentViewStyle } + flatListProps={ flatListProps } /> ) } </> diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 0e6f2f21238868..b63e898945f811 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -132,7 +132,7 @@ function ColumnEdit( { renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } - readableContentViewStyle={ getVerticalAlignmentRemap( + containerStyle={ getVerticalAlignmentRemap( verticalAlignment ) } /> diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index a0dbbff2588465..d0f46fe03b1a60 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -141,11 +141,15 @@ function ColumnsEditContainer( { } } > <InnerBlocks - containerStyle={ - ! isMobile ? styles.columnsContainer : undefined - } + flatListProps={ { + ...( ! isMobile && { + contentContainerStyle: styles.columnsContainer, + } ), + horizontal: ! isMobile, + scrollEnabled: false, + } } allowedBlocks={ ALLOWED_BLOCKS } - readableContentViewStyle={ getVerticalAlignmentRemap( + containerStyle={ getVerticalAlignmentRemap( verticalAlignment ) } /> diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js index 8fa33bfa319e94..0774c845ebcf54 100644 --- a/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js +++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js @@ -8,16 +8,9 @@ import { FlatList } from 'react-native'; */ import KeyboardAvoidingView from '../keyboard-avoiding-view'; -export const KeyboardAwareFlatList = ( { containerStyle, ...props } ) => ( +export const KeyboardAwareFlatList = ( props ) => ( <KeyboardAvoidingView style={ { flex: 1 } }> - <FlatList - scrollEnabled={ containerStyle ? false : true } - horizontal={ containerStyle ? true : false } - contentContainerStyle={ - containerStyle ? containerStyle : undefined - } - { ...props } - /> + <FlatList { ...props } /> </KeyboardAvoidingView> ); diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js index 6b7fe12e8b1640..428a1ba23572e6 100644 --- a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js +++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js @@ -10,7 +10,6 @@ export const KeyboardAwareFlatList = ( { innerRef, autoScroll, scrollViewStyle, - containerStyle, ...listProps } ) => ( <KeyboardAwareScrollView @@ -49,19 +48,12 @@ export const KeyboardAwareFlatList = ( { onKeyboardWillShow={ () => { this.keyboardWillShowIndicator = true; } } - scrollEnabled={ containerStyle ? false : true } + scrollEnabled={ listProps.scrollEnabled } onScroll={ ( event ) => { this.latestContentOffsetY = event.nativeEvent.contentOffset.y; } } > - <FlatList - { ...listProps } - horizontal={ containerStyle ? true : false } - scrollEnabled={ containerStyle ? false : true } - contentContainerStyle={ - containerStyle ? containerStyle : undefined - } - /> + <FlatList { ...listProps } /> </KeyboardAwareScrollView> ); From 7261e3c8319a9558dd8991bf53b5945c6c64c89b Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 7 Feb 2020 15:14:16 +0100 Subject: [PATCH 119/183] pass render prop to get verticalAlignment --- .../src/components/block-list/index.native.js | 4 ++- .../block-library/src/column/edit.native.js | 8 ----- .../block-library/src/columns/edit.native.js | 31 ++++++++++++++----- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index bf3a7f21ae6e6d..f80d2831dc4b96 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -157,7 +157,9 @@ export class BlockList extends Component { } = this.props; return ( - <ReadableContentView style={ containerStyle }> + <ReadableContentView + style={ containerStyle && containerStyle( clientId ) } + > <View pointerEvents={ isReadOnly ? 'box-only' : 'auto' }> { shouldShowInsertionPointBefore( clientId ) && ( <BlockInsertionPoint /> diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index b63e898945f811..87218320ee3611 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -106,11 +106,6 @@ function ColumnEdit( { ); } - const getVerticalAlignmentRemap = ( alignment ) => { - if ( ! alignment ) return; - return styles[ `is-vertically-aligned-${ alignment }` ]; - }; - return ( <> <BlockControls> @@ -132,9 +127,6 @@ function ColumnEdit( { renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } - containerStyle={ getVerticalAlignmentRemap( - verticalAlignment - ) } /> </View> </> diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index d0f46fe03b1a60..641072474459f9 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -56,6 +56,7 @@ function ColumnsEditContainer( { attributes, updateBlockSettings, blockListSettings, + getBlockAttributes, updateAlignment, updateColumns, clientId, @@ -149,9 +150,16 @@ function ColumnsEditContainer( { scrollEnabled: false, } } allowedBlocks={ ALLOWED_BLOCKS } - containerStyle={ getVerticalAlignmentRemap( - verticalAlignment - ) } + containerStyle={ ( columnClientId ) => { + const columnAttributes = getBlockAttributes( + columnClientId + ); + if ( columnAttributes ) { + return getVerticalAlignmentRemap( + columnAttributes.verticalAlignment + ); + } + } } /> </View> </> @@ -227,15 +235,23 @@ const ColumnsEditContainerWrapper = withDispatch( const ColumnsEdit = ( props ) => { const { clientId, isSelected, getStylesFromColorScheme } = props; - const { hasChildren, blockListSettings } = useSelect( + const { hasChildren, blockListSettings, getBlockAttributes } = useSelect( ( select ) => { - const { getBlocks, getBlockListSettings } = select( - 'core/block-editor' - ); + const { + getBlocks, + getBlockListSettings, + __unstableGetBlockWithoutInnerBlocks, + } = select( 'core/block-editor' ); return { hasChildren: getBlocks( clientId ).length > 0, blockListSettings: getBlockListSettings( clientId ) || {}, + getBlockAttributes: ( columnClientId ) => + ( + __unstableGetBlockWithoutInnerBlocks( + columnClientId + ) || {} + ).attributes, }; }, [ clientId ] @@ -260,6 +276,7 @@ const ColumnsEdit = ( props ) => { return ( <ColumnsEditContainerWrapper + getBlockAttributes={ getBlockAttributes } blockListSettings={ blockListSettings } { ...props } /> From 3fb166e0a841da91529ede901748905d8f803f69 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 7 Feb 2020 15:35:51 +0100 Subject: [PATCH 120/183] refactor on verticalAlignment logic --- .../src/components/block-list/index.native.js | 6 ++++-- .../src/components/inner-blocks/index.native.js | 4 ++-- packages/block-library/src/column/editor.native.scss | 12 ------------ packages/block-library/src/columns/edit.native.js | 2 +- 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index f80d2831dc4b96..4c83f4e4034fcc 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -153,12 +153,14 @@ export class BlockList extends Component { isReadOnly, shouldShowInsertionPointBefore, shouldShowInsertionPointAfter, - containerStyle, + getBlockContainerStyle, } = this.props; return ( <ReadableContentView - style={ containerStyle && containerStyle( clientId ) } + style={ + getBlockContainerStyle && getBlockContainerStyle( clientId ) + } > <View pointerEvents={ isReadOnly ? 'box-only' : 'auto' }> { shouldShowInsertionPointBefore( clientId ) && ( diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index c076c1ab17893a..a2bb3653a22f7c 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -113,7 +113,7 @@ class InnerBlocks extends Component { const { clientId, renderAppender, - containerStyle, + getBlockContainerStyle, flatListProps, } = this.props; const { templateInProcess } = this.state; @@ -126,7 +126,7 @@ class InnerBlocks extends Component { renderAppender={ renderAppender } withFooter={ false } isFullyBordered={ true } - containerStyle={ containerStyle } + getBlockContainerStyle={ getBlockContainerStyle } flatListProps={ flatListProps } /> ) } diff --git a/packages/block-library/src/column/editor.native.scss b/packages/block-library/src/column/editor.native.scss index 4907797c4b622c..4baed0c5577330 100644 --- a/packages/block-library/src/column/editor.native.scss +++ b/packages/block-library/src/column/editor.native.scss @@ -50,15 +50,3 @@ .column-descendant-selected-margin { width: $block-selected-margin + $block-selected-border-width; } - -.is-vertically-aligned-top { - justify-content: flex-start; -} - -.is-vertically-aligned-center { - justify-content: center; -} - -.is-vertically-aligned-bottom { - justify-content: flex-end; -} diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 641072474459f9..6d1740b07e0d3b 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -150,7 +150,7 @@ function ColumnsEditContainer( { scrollEnabled: false, } } allowedBlocks={ ALLOWED_BLOCKS } - containerStyle={ ( columnClientId ) => { + getBlockContainerStyle={ ( columnClientId ) => { const columnAttributes = getBlockAttributes( columnClientId ); From 055e1aa2ea292ab4e8f48d62e4ed5f52cca49ca9 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Mon, 10 Feb 2020 10:16:47 +0100 Subject: [PATCH 121/183] simplify margin adjustment logic --- .../block-library/src/column/edit.native.js | 48 ++++++++++++------- .../src/column/editor.native.scss | 4 -- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 87218320ee3611..5c65cb235b038e 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -52,30 +52,42 @@ function ColumnEdit( { return; } - const pullWidth = ( name ) => - ( styles[ `column-${ name }-margin` ] || {} ).width || 0; + const pullWidths = ( names ) => + names.map( + ( name ) => + ( styles[ `column-${ name }-margin` ] || {} ).width || 0 + ); let width = columnBaseWidth; + const names = [ + 'selected', + 'parent-selected', + 'descendant-selected', + 'placeholder-selected', + ]; + const [ + selected, + parentSelected, + descendantSelected, + placeholderSelected, + ] = pullWidths( names ); if ( isParentSelected ) { - width -= pullWidth( - placeholder ? 'placeholder-selected' : 'parent-selected' - ); - } else if ( isSelected && ! placeholder ) { - width -= ! hasChildren - ? pullWidth( 'selected' ) - : pullWidth( 'descendant-selected' ); - } else if ( isDescendantOfParentSelected ) { - if ( placeholder ) { - width -= pullWidth( 'selected' ); + width -= parentSelected; + return { width }; + } + + if ( placeholder ) { + if ( isDescendantOfParentSelected ) { + width -= selected; } else { - width += pullWidth( 'descendant-selected' ); + width -= + columnsInRow === 1 ? parentSelected : placeholderSelected; } - } else if ( placeholder ) { - width -= - columnsInRow === 1 - ? pullWidth( 'parent-selected' ) - : pullWidth( 'placeholder-multicol' ); + } else if ( isSelected ) { + width -= ! hasChildren ? selected : descendantSelected; + } else if ( isDescendantOfParentSelected ) { + width += descendantSelected; } return { width }; diff --git a/packages/block-library/src/column/editor.native.scss b/packages/block-library/src/column/editor.native.scss index 4baed0c5577330..87c15d4b7a604b 100644 --- a/packages/block-library/src/column/editor.native.scss +++ b/packages/block-library/src/column/editor.native.scss @@ -32,10 +32,6 @@ } .column-placeholder-selected-margin { - width: $block-selected-to-content; -} - -.column-placeholder-multicol-margin { width: 2 * $block-selected-to-content; } From 546fca9ef7d1ec4e3d125cdf2212897738143432 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 14 Feb 2020 10:47:44 +0100 Subject: [PATCH 122/183] refactor Column Block after code review --- .../src/components/block-list/block.native.js | 9 +++-- .../src/components/block-list/index.native.js | 13 ++++--- .../block-mobile-toolbar/index.native.js | 30 +++++++++------- .../components/inner-blocks/index.native.js | 4 +-- .../block-library/src/column/edit.native.js | 25 +++++++++++-- .../src/column/editor.native.scss | 16 +++++++++ .../block-library/src/columns/edit.native.js | 36 ++++--------------- .../src/columns/editor.native.scss | 12 ------- .../readable-content-view/index.native.js | 11 +++--- 9 files changed, 81 insertions(+), 75 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 9a149c9357fd57..b94e54753e7657 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -143,9 +143,6 @@ class BlockListBlock extends Component { styles.dashedBorderColor, styles.dashedBorderColorDark ), - ...( ! hasChildren && - this.props.name === 'core/column' && - styles.alternatePlaceholderStyle ), }; // return apply childOfSelected or childOfSelectedLeaf @@ -155,7 +152,8 @@ class BlockListBlock extends Component { ? styles.childOfSelected : styles.childOfSelectedLeaf ), ...dashedBorderStyle, - ...( ! isLastBlock && styles.marginVerticalChild ), + ...( ( ! isLastBlock || this.props.name === 'core/column' ) && + styles.marginVerticalChild ), }; } @@ -177,13 +175,14 @@ class BlockListBlock extends Component { } applyBlockStyle() { - const { isSelected, isDimmed } = this.props; + const { isSelected, isDimmed, name } = this.props; return [ isSelected ? this.applySelectedBlockStyle() : this.applyUnSelectedBlockStyle(), isDimmed && styles.dimmed, + name === 'core/column' && { flex: 1 }, ]; } diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 4c83f4e4034fcc..4ad1372409a33f 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -153,16 +153,15 @@ export class BlockList extends Component { isReadOnly, shouldShowInsertionPointBefore, shouldShowInsertionPointAfter, - getBlockContainerStyle, + containerStyle, } = this.props; return ( - <ReadableContentView - style={ - getBlockContainerStyle && getBlockContainerStyle( clientId ) - } - > - <View pointerEvents={ isReadOnly ? 'box-only' : 'auto' }> + <ReadableContentView style={ containerStyle }> + <View + style={ containerStyle } + pointerEvents={ isReadOnly ? 'box-only' : 'auto' } + > { shouldShowInsertionPointBefore( clientId ) && ( <BlockInsertionPoint /> ) } diff --git a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js index 43a34c0466b54d..3bc04d573f1366 100644 --- a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js +++ b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js @@ -19,7 +19,7 @@ import styles from './style.scss'; import BlockMover from '../block-mover'; import { BlockSettingsButton } from '../block-settings'; -const BlockMobileToolbar = ( { clientId, onDelete, order } ) => ( +const BlockMobileToolbar = ( { clientId, onDelete, order, hideDelete } ) => ( <View style={ styles.toolbar }> <BlockMover clientIds={ [ clientId ] } /> @@ -27,25 +27,31 @@ const BlockMobileToolbar = ( { clientId, onDelete, order } ) => ( <BlockSettingsButton.Slot /> - <ToolbarButton - title={ sprintf( - /* translators: accessibility text. %s: current block position (number). */ - __( 'Remove block at row %s' ), - order + 1 - ) } - onClick={ onDelete } - icon={ trash } - extraProps={ { hint: __( 'Double tap to remove the block' ) } } - /> + { ! hideDelete && ( + <ToolbarButton + title={ sprintf( + /* translators: accessibility text. %s: current block position (number). */ + __( 'Remove block at row %s' ), + order + 1 + ) } + onClick={ onDelete } + icon={ trash } + extraProps={ { hint: __( 'Double tap to remove the block' ) } } + /> + ) } </View> ); export default compose( withSelect( ( select, { clientId } ) => { - const { getBlockIndex } = select( 'core/block-editor' ); + const { getBlockIndex, __unstableGetBlockWithoutInnerBlocks } = select( + 'core/block-editor' + ); + const { name } = __unstableGetBlockWithoutInnerBlocks( clientId ) || {}; return { order: getBlockIndex( clientId ), + hideDelete: name === 'core/column', }; } ), withDispatch( ( dispatch, { clientId, rootClientId } ) => { diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index a2bb3653a22f7c..c076c1ab17893a 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -113,7 +113,7 @@ class InnerBlocks extends Component { const { clientId, renderAppender, - getBlockContainerStyle, + containerStyle, flatListProps, } = this.props; const { templateInProcess } = this.state; @@ -126,7 +126,7 @@ class InnerBlocks extends Component { renderAppender={ renderAppender } withFooter={ false } isFullyBordered={ true } - getBlockContainerStyle={ getBlockContainerStyle } + containerStyle={ containerStyle } flatListProps={ flatListProps } /> ) } diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 5c65cb235b038e..65e590eaa1acf6 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -73,7 +73,7 @@ function ColumnEdit( { ] = pullWidths( names ); if ( isParentSelected ) { - width -= parentSelected; + width -= placeholder ? placeholderSelected : parentSelected; return { width }; } @@ -136,6 +136,9 @@ function ColumnEdit( { </BlockControls> <View style={ applyBlockStyle() }> <InnerBlocks + flatListProps={ { + scrollEnabled: false, + } } renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } @@ -145,6 +148,24 @@ function ColumnEdit( { ); } +function ColumnEditWrapper( props ) { + const { verticalAlignment } = props.attributes; + + const getVerticalAlignmentRemap = ( alignment ) => { + if ( ! alignment ) return styles.flexBase; + return { + ...styles.flexBase, + ...styles[ `is-vertically-aligned-${ alignment }` ], + }; + }; + + return ( + <View style={ getVerticalAlignmentRemap( verticalAlignment ) }> + <ColumnEdit { ...props } /> + </View> + ); +} + export default compose( [ withSelect( ( select, { clientId } ) => { const { @@ -183,4 +204,4 @@ export default compose( [ } ), withViewportMatch( { isMobile: '< mobile' } ), withPreferredColorScheme, -] )( ColumnEdit ); +] )( ColumnEditWrapper ); diff --git a/packages/block-library/src/column/editor.native.scss b/packages/block-library/src/column/editor.native.scss index 87c15d4b7a604b..2061ab4b7912dc 100644 --- a/packages/block-library/src/column/editor.native.scss +++ b/packages/block-library/src/column/editor.native.scss @@ -46,3 +46,19 @@ .column-descendant-selected-margin { width: $block-selected-margin + $block-selected-border-width; } + +.is-vertically-aligned-top { + justify-content: flex-start; +} + +.is-vertically-aligned-center { + justify-content: center; +} + +.is-vertically-aligned-bottom { + justify-content: flex-end; +} + +.flexBase { + flex: 1; +} diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 6d1740b07e0d3b..d1ccea06263e59 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -56,7 +56,6 @@ function ColumnsEditContainer( { attributes, updateBlockSettings, blockListSettings, - getBlockAttributes, updateAlignment, updateColumns, clientId, @@ -85,16 +84,11 @@ function ColumnsEditContainer( { return 1; } if ( containerWidth >= 480 && containerWidth < 768 ) { - return 2; + return Math.min( columnCount, 2 ); } return columnsNumber; }; - const getVerticalAlignmentRemap = ( alignment ) => { - if ( ! alignment ) return; - return styles[ `is-vertically-aligned-${ alignment }` ]; - }; - return ( <> <InspectorControls> @@ -149,17 +143,8 @@ function ColumnsEditContainer( { horizontal: ! isMobile, scrollEnabled: false, } } + containerStyle={ { flex: 1 } } allowedBlocks={ ALLOWED_BLOCKS } - getBlockContainerStyle={ ( columnClientId ) => { - const columnAttributes = getBlockAttributes( - columnClientId - ); - if ( columnAttributes ) { - return getVerticalAlignmentRemap( - columnAttributes.verticalAlignment - ); - } - } } /> </View> </> @@ -235,23 +220,15 @@ const ColumnsEditContainerWrapper = withDispatch( const ColumnsEdit = ( props ) => { const { clientId, isSelected, getStylesFromColorScheme } = props; - const { hasChildren, blockListSettings, getBlockAttributes } = useSelect( + const { hasChildren, blockListSettings } = useSelect( ( select ) => { - const { - getBlocks, - getBlockListSettings, - __unstableGetBlockWithoutInnerBlocks, - } = select( 'core/block-editor' ); + const { getBlocks, getBlockListSettings } = select( + 'core/block-editor' + ); return { hasChildren: getBlocks( clientId ).length > 0, blockListSettings: getBlockListSettings( clientId ) || {}, - getBlockAttributes: ( columnClientId ) => - ( - __unstableGetBlockWithoutInnerBlocks( - columnClientId - ) || {} - ).attributes, }; }, [ clientId ] @@ -276,7 +253,6 @@ const ColumnsEdit = ( props ) => { return ( <ColumnsEditContainerWrapper - getBlockAttributes={ getBlockAttributes } blockListSettings={ blockListSettings } { ...props } /> diff --git a/packages/block-library/src/columns/editor.native.scss b/packages/block-library/src/columns/editor.native.scss index e8f73f155af989..4b59f9833b7d29 100644 --- a/packages/block-library/src/columns/editor.native.scss +++ b/packages/block-library/src/columns/editor.native.scss @@ -29,15 +29,3 @@ max-width: $content-width; overflow: hidden; } - -.is-vertically-aligned-top { - justify-content: flex-start; -} - -.is-vertically-aligned-center { - justify-content: center; -} - -.is-vertically-aligned-bottom { - justify-content: flex-end; -} diff --git a/packages/components/src/mobile/readable-content-view/index.native.js b/packages/components/src/mobile/readable-content-view/index.native.js index 65adf00cff6226..55200c4b6541fc 100644 --- a/packages/components/src/mobile/readable-content-view/index.native.js +++ b/packages/components/src/mobile/readable-content-view/index.native.js @@ -9,13 +9,14 @@ import { View, Dimensions } from 'react-native'; import styles from './style.scss'; const ReadableContentView = ( { reversed, children, style } ) => ( - <View style={ [ styles.container, style ] }> + <View style={ styles.container }> <View - style={ - reversed + style={ { + ...( reversed ? styles.reversedCenteredContent - : styles.centeredContent - } + : styles.centeredContent ), + ...style, + } } > { children } </View> From f5926be0e74cea5862cf64d31f972fad8e57fc0a Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 14 Feb 2020 10:53:37 +0100 Subject: [PATCH 123/183] remove alternate placeholder style --- .../src/components/block-list/block.native.scss | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index 23d7768718f1e5..f22e0712553535 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -67,16 +67,6 @@ margin-bottom: 0; } -.alternatePlaceholderStyle { - border-color: transparent; - margin-top: 0; - margin-right: 0; - margin-left: 0; - padding-left: 2; - padding-right: 2; - padding-top: 0; -} - .blockTitle { background-color: $gray; padding-left: 8px; From e782cd8ddd388a33da9b75a1c5a3ebd08ba686b2 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 14 Feb 2020 15:46:07 +0100 Subject: [PATCH 124/183] update mobile-toolbar margins --- packages/base-styles/_variables.scss | 1 + .../src/components/block-mobile-toolbar/style.native.scss | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/base-styles/_variables.scss b/packages/base-styles/_variables.scss index 2f6776824c45b0..73f775e4c671e4 100644 --- a/packages/base-styles/_variables.scss +++ b/packages/base-styles/_variables.scss @@ -78,6 +78,7 @@ $block-custom-appender-to-content: $block-selected-margin - $block-selected-bord $block-media-container-to-content: $block-selected-child-margin + $block-selected-border-width; $block-selected-vertical-margin-descendant: 2 * $block-selected-to-content; $block-selected-vertical-margin-child: $block-edge-to-content; +$block-toolbar-margin: $block-selected-margin + $block-selected-border-width; // Buttons & UI Widgets $radius-round-rectangle: 4px; diff --git a/packages/block-editor/src/components/block-mobile-toolbar/style.native.scss b/packages/block-editor/src/components/block-mobile-toolbar/style.native.scss index 2633fc1f1c2fcc..ce118262183253 100644 --- a/packages/block-editor/src/components/block-mobile-toolbar/style.native.scss +++ b/packages/block-editor/src/components/block-mobile-toolbar/style.native.scss @@ -2,8 +2,8 @@ flex-direction: row; height: 44px; align-items: flex-start; - margin-left: 2px; - margin-right: 2px; + margin-left: $block-toolbar-margin; + margin-right: $block-toolbar-margin; } .spacer { From 6fbd26b4f3f16e0f43aa60737313f6b0b68dbfa8 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Mon, 17 Feb 2020 08:03:32 +0100 Subject: [PATCH 125/183] fix layout with two placeholsers --- packages/block-library/src/column/edit.native.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 65e590eaa1acf6..be1c33b30d0158 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -36,7 +36,7 @@ function ColumnEdit( { const { verticalAlignment } = attributes; const { columnsInRow = 1, - columnsContainerWidth, + width: columnsContainerWidth, } = columnsContainerSettings; const columnContainerBaseWidth = styles[ 'column-container-base' ].maxWidth; @@ -83,6 +83,7 @@ function ColumnEdit( { } else { width -= columnsInRow === 1 ? parentSelected : placeholderSelected; + if ( ! hasChildren ) width -= 4; } } else if ( isSelected ) { width -= ! hasChildren ? selected : descendantSelected; From bbb9ae97bf02e355ef20a42ecc2057d17dcee849 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Mon, 17 Feb 2020 08:56:40 +0100 Subject: [PATCH 126/183] fix layout with two placeholsers --- packages/block-library/src/column/edit.native.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index be1c33b30d0158..96dba9e48badd7 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -65,18 +65,16 @@ function ColumnEdit( { 'descendant-selected', 'placeholder-selected', ]; - const [ - selected, - parentSelected, - descendantSelected, - placeholderSelected, - ] = pullWidths( names ); + const widths = pullWidths( names ); + const [ , parentSelected, , placeholderSelected ] = widths; if ( isParentSelected ) { width -= placeholder ? placeholderSelected : parentSelected; return { width }; } + const [ selected, , descendantSelected ] = widths; + if ( placeholder ) { if ( isDescendantOfParentSelected ) { width -= selected; From 6685a2d573b81d5cfa1f6940ea198a1dd64802c3 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Tue, 18 Feb 2020 08:50:41 +0100 Subject: [PATCH 127/183] remove Column and Columns toolbar icons --- packages/block-library/src/column/edit.native.js | 10 ---------- packages/block-library/src/columns/edit.native.js | 15 +-------------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 96dba9e48badd7..0c74cb0e4e5307 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -6,7 +6,6 @@ import { View } from 'react-native'; /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; import { withSelect } from '@wordpress/data'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { @@ -14,9 +13,7 @@ import { BlockControls, BlockVerticalAlignmentToolbar, } from '@wordpress/block-editor'; -import { Toolbar, ToolbarButton } from '@wordpress/components'; import { withViewportMatch } from '@wordpress/viewport'; -import { column as icon } from '@wordpress/icons'; /** * Internal dependencies */ @@ -120,13 +117,6 @@ function ColumnEdit( { return ( <> <BlockControls> - <Toolbar> - <ToolbarButton - title={ __( 'ColumnButton' ) } - icon={ icon } - onClick={ () => {} } - /> - </Toolbar> <BlockVerticalAlignmentToolbar onChange={ updateAlignment } value={ verticalAlignment } diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index d1ccea06263e59..5821313dd3de9f 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -8,12 +8,7 @@ import { dropRight, times } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { - PanelBody, - StepperControl, - Toolbar, - ToolbarButton, -} from '@wordpress/components'; +import { PanelBody, StepperControl } from '@wordpress/components'; import { InspectorControls, InnerBlocks, @@ -25,7 +20,6 @@ import { useEffect } from '@wordpress/element'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { createBlock } from '@wordpress/blocks'; import { withViewportMatch } from '@wordpress/viewport'; -import { columns as icon } from '@wordpress/icons'; /** * Internal dependencies */ @@ -107,13 +101,6 @@ function ColumnsEditContainer( { </PanelBody> </InspectorControls> <BlockControls> - <Toolbar> - <ToolbarButton - title={ __( 'ColumnsButton' ) } - icon={ icon } - onClick={ () => {} } - /> - </Toolbar> <BlockVerticalAlignmentToolbar onChange={ updateAlignment } value={ verticalAlignment } From 52e995c1cc144368d3f83589f25f87ed9ca8842d Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Tue, 18 Feb 2020 15:43:58 +0100 Subject: [PATCH 128/183] handle remove columns in block --- .../block-mobile-toolbar/index.native.js | 30 ++++++++----------- .../block-library/src/columns/edit.native.js | 27 +++++++++-------- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js index 3bc04d573f1366..43a34c0466b54d 100644 --- a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js +++ b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js @@ -19,7 +19,7 @@ import styles from './style.scss'; import BlockMover from '../block-mover'; import { BlockSettingsButton } from '../block-settings'; -const BlockMobileToolbar = ( { clientId, onDelete, order, hideDelete } ) => ( +const BlockMobileToolbar = ( { clientId, onDelete, order } ) => ( <View style={ styles.toolbar }> <BlockMover clientIds={ [ clientId ] } /> @@ -27,31 +27,25 @@ const BlockMobileToolbar = ( { clientId, onDelete, order, hideDelete } ) => ( <BlockSettingsButton.Slot /> - { ! hideDelete && ( - <ToolbarButton - title={ sprintf( - /* translators: accessibility text. %s: current block position (number). */ - __( 'Remove block at row %s' ), - order + 1 - ) } - onClick={ onDelete } - icon={ trash } - extraProps={ { hint: __( 'Double tap to remove the block' ) } } - /> - ) } + <ToolbarButton + title={ sprintf( + /* translators: accessibility text. %s: current block position (number). */ + __( 'Remove block at row %s' ), + order + 1 + ) } + onClick={ onDelete } + icon={ trash } + extraProps={ { hint: __( 'Double tap to remove the block' ) } } + /> </View> ); export default compose( withSelect( ( select, { clientId } ) => { - const { getBlockIndex, __unstableGetBlockWithoutInnerBlocks } = select( - 'core/block-editor' - ); - const { name } = __unstableGetBlockWithoutInnerBlocks( clientId ) || {}; + const { getBlockIndex } = select( 'core/block-editor' ); return { order: getBlockIndex( clientId ), - hideDelete: name === 'core/column', }; } ), withDispatch( ( dispatch, { clientId, rootClientId } ) => { diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 5821313dd3de9f..d4e11a4e508f42 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -52,26 +52,18 @@ function ColumnsEditContainer( { blockListSettings, updateAlignment, updateColumns, - clientId, isMobile, + columnCount, } ) { const { verticalAlignment } = attributes; const { width } = blockListSettings; - const { columnCount } = useSelect( ( select ) => { - return { - columnCount: select( 'core/block-editor' ).getBlockCount( - clientId - ), - }; - } ); - useEffect( () => { updateColumns( columnCount, Math.min( MAX_COLUMNS_NUMBER, columnCount || DEFAULT_COLUMNS ) ); - }, [] ); + }, [ columnCount ] ); const getColumnsInRow = ( containerWidth, columnsNumber ) => { if ( containerWidth < 480 ) { @@ -200,6 +192,15 @@ const ColumnsEditContainerWrapper = withDispatch( ); } + if ( innerBlocks.length < MIN_COLUMNS_NUMBER ) { + innerBlocks = [ + ...innerBlocks, + ...times( MIN_COLUMNS_NUMBER - innerBlocks.length, () => { + return createBlock( 'core/column' ); + } ), + ]; + } + replaceInnerBlocks( clientId, innerBlocks, false ); }, } ) @@ -207,15 +208,16 @@ const ColumnsEditContainerWrapper = withDispatch( const ColumnsEdit = ( props ) => { const { clientId, isSelected, getStylesFromColorScheme } = props; - const { hasChildren, blockListSettings } = useSelect( + const { hasChildren, blockListSettings, columnCount } = useSelect( ( select ) => { - const { getBlocks, getBlockListSettings } = select( + const { getBlocks, getBlockListSettings, getBlockCount } = select( 'core/block-editor' ); return { hasChildren: getBlocks( clientId ).length > 0, blockListSettings: getBlockListSettings( clientId ) || {}, + columnCount: getBlockCount( clientId ), }; }, [ clientId ] @@ -241,6 +243,7 @@ const ColumnsEdit = ( props ) => { return ( <ColumnsEditContainerWrapper blockListSettings={ blockListSettings } + columnCount={ columnCount } { ...props } /> ); From 874f62bbef9e909fe907859bcd1c1de9d02cc756 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Tue, 18 Feb 2020 15:44:31 +0100 Subject: [PATCH 129/183] minor fix --- packages/block-library/src/column/edit.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 0c74cb0e4e5307..762e0441740188 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -169,7 +169,7 @@ export default compose( [ const isSelected = selectedBlockClientId === clientId; const parentId = getBlockRootClientId( clientId ); - const hasChildren = getBlockCount( clientId ); + const hasChildren = !! getBlockCount( clientId ); const columnsContainerSettings = getBlockListSettings( parentId ); From d1130707c0c913443adf56566f70ddea813dee35 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Wed, 19 Feb 2020 09:43:25 +0100 Subject: [PATCH 130/183] redirect selection after column remove --- packages/block-library/src/columns/edit.native.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index d4e11a4e508f42..a4873d2f5dfe51 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -169,7 +169,9 @@ const ColumnsEditContainerWrapper = withDispatch( */ updateColumns( previousColumns, newColumns ) { const { clientId } = ownProps; - const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); + const { replaceInnerBlocks, selectBlock } = dispatch( + 'core/block-editor' + ); const { getBlocks } = registry.select( 'core/block-editor' ); let innerBlocks = getBlocks( clientId ); @@ -199,6 +201,7 @@ const ColumnsEditContainerWrapper = withDispatch( return createBlock( 'core/column' ); } ), ]; + selectBlock( clientId ); } replaceInnerBlocks( clientId, innerBlocks, false ); From 202e28ac2a988e16988fac75bcf437ce3b95bb1f Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 20 Feb 2020 13:51:52 +0100 Subject: [PATCH 131/183] fix Stepper according to APi change --- packages/block-library/src/columns/edit.native.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index a4873d2f5dfe51..e3dc6f476096b8 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -83,12 +83,11 @@ function ColumnsEditContainer( { label={ __( 'Number of columns' ) } icon="columns" value={ columnCount } - defaultValue={ DEFAULT_COLUMNS } - onChangeValue={ ( value ) => + onChange={ ( value ) => updateColumns( columnCount, value ) } - minValue={ MIN_COLUMNS_NUMBER } - maxValue={ MAX_COLUMNS_NUMBER } + min={ MIN_COLUMNS_NUMBER } + max={ MAX_COLUMNS_NUMBER } /> </PanelBody> </InspectorControls> From a21bc78662dccf585741f6689ba8711f3a7cad15 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 20 Feb 2020 14:43:47 +0100 Subject: [PATCH 132/183] fix verticalAlignment behaviour --- .../block-editor/src/components/block-list/block.native.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 3d843d3b94f916..bdcdf84e23ea3a 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -205,7 +205,10 @@ class BlockListBlock extends Component { accessible={ ! isSelected } accessibilityRole={ 'button' } > - <View accessibilityLabel={ accessibilityLabel }> + <View + style={ { flex: 1 } } + accessibilityLabel={ accessibilityLabel } + > { isSelected && ( <FloatingToolbar> <Toolbar passedStyle={ styles.toolbar }> From e7dd896b6c0fbd11e4b6219350525aa713a4d654 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 20 Feb 2020 16:40:58 +0100 Subject: [PATCH 133/183] disable inserter depending on setting --- .../src/components/inserter/index.native.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/inserter/index.native.js b/packages/block-editor/src/components/inserter/index.native.js index 6cc160b7e8dd4e..e31e83b71bf607 100644 --- a/packages/block-editor/src/components/inserter/index.native.js +++ b/packages/block-editor/src/components/inserter/index.native.js @@ -148,6 +148,7 @@ export class Inserter extends Component { renderToggle = defaultRenderToggle, getStylesFromColorScheme, showSeparator, + blockDoNotSupportInserter, } = this.props; if ( showSeparator && isOpen ) { return <BlockInsertionPoint />; @@ -192,7 +193,7 @@ export class Inserter extends Component { { renderToggle( { onToggle: onPress, isOpen, - disabled, + disabled: disabled || blockDoNotSupportInserter, style, onLongPress, } ) } @@ -256,8 +257,11 @@ export default compose( [ getBlockOrder, getBlockIndex, getBlock, + getBlockName, } = select( 'core/block-editor' ); + const { getBlockSupport } = select( 'core/blocks' ); + const end = getBlockSelectionEnd(); // `end` argument (id) can refer to the component which is removed // due to pressing `undo` button, that's why we need to check @@ -315,6 +319,10 @@ export default compose( [ ? selectedBlockIndex + 1 : endOfRootIndex; + const blockDoNotSupportInserter = + ! isAppender && + getBlockSupport( getBlockName( end ), 'inserter' ) === false; + return { destinationRootClientId, insertionIndexDefault: getDefaultInsertionIndex(), @@ -322,6 +330,7 @@ export default compose( [ insertionIndexAfter, isAnyBlockSelected, isSelectedBlockReplaceable: isSelectedUnmodifiedDefaultBlock, + blockDoNotSupportInserter, }; } ), From 12f6501f6731c67d60b339ac7841ce577e8df795 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 21 Feb 2020 09:02:47 +0100 Subject: [PATCH 134/183] allow to pass custonOnDelete to clear Column content --- .../src/components/block-list/block.native.js | 2 ++ .../src/components/block-list/index.native.js | 2 ++ .../block-mobile-toolbar/index.native.js | 3 +- .../components/inner-blocks/index.native.js | 2 ++ .../block-library/src/columns/edit.native.js | 28 ++++++++----------- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index b2d056c04746ff..3051a99ce78d3b 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -193,6 +193,7 @@ class BlockListBlock extends Component { isTouchable, disallowRemoveInnerBlocks, hasChildren, + customOnDelete, } = this.props; const accessibilityLabel = getAccessibleBlockLabel( @@ -247,6 +248,7 @@ class BlockListBlock extends Component { disallowRemoveInnerBlocks && ! hasChildren } + customOnDelete={ customOnDelete } /> ) } </View> diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 9e722c063a3df6..7272ba4d6c57d9 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -158,6 +158,7 @@ export class BlockList extends Component { shouldShowInsertionPointAfter, containerStyle, disallowRemoveInnerBlocks, + customOnDelete, } = this.props; return ( @@ -178,6 +179,7 @@ export class BlockList extends Component { this.onCaretVerticalPositionChange } disallowRemoveInnerBlocks={ disallowRemoveInnerBlocks } + customOnDelete={ customOnDelete } /> { ! this.shouldShowInnerBlockAppender() && shouldShowInsertionPointAfter( clientId ) && ( diff --git a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js index ec9f99824786f5..3b638274295032 100644 --- a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js +++ b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js @@ -24,6 +24,7 @@ const BlockMobileToolbar = ( { onDelete, order, disallowRemoveBlock, + customOnDelete, } ) => ( <View style={ styles.toolbar }> <BlockMover clientIds={ [ clientId ] } /> @@ -38,7 +39,7 @@ const BlockMobileToolbar = ( { __( 'Remove block at row %s' ), order + 1 ) } - onClick={ onDelete } + onClick={ customOnDelete || onDelete } isDisabled={ disallowRemoveBlock } icon={ trash } extraProps={ { hint: __( 'Double tap to remove the block' ) } } diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index 7a503ae77e3ba8..57e4a06bbfbcda 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -116,6 +116,7 @@ class InnerBlocks extends Component { containerStyle, flatListProps, disallowRemoveInnerBlocks, + customOnDelete, } = this.props; const { templateInProcess } = this.state; @@ -129,6 +130,7 @@ class InnerBlocks extends Component { containerStyle={ containerStyle } flatListProps={ flatListProps } disallowRemoveInnerBlocks={ disallowRemoveInnerBlocks } + customOnDelete={ customOnDelete } /> ) } </> diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 72b00a38036241..3a2a1ef8f477d6 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -54,6 +54,7 @@ function ColumnsEditContainer( { updateColumns, isMobile, columnCount, + onDeleteColumn, } ) { const { verticalAlignment } = attributes; const { width } = blockListSettings; @@ -123,9 +124,8 @@ function ColumnsEditContainer( { } } containerStyle={ { flex: 1 } } allowedBlocks={ ALLOWED_BLOCKS } - disallowRemoveInnerBlocks={ - columnCount <= MIN_COLUMNS_NUMBER - } + disallowRemoveInnerBlocks + customOnDelete={ onDeleteColumn } /> </View> </> @@ -162,6 +162,14 @@ const ColumnsEditContainerWrapper = withDispatch( const { updateBlockListSettings } = dispatch( 'core/block-editor' ); updateBlockListSettings( clientId, settings ); }, + onDeleteColumn: () => { + const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); + const { getSelectedBlockClientId } = registry.select( + 'core/block-editor' + ); + + replaceInnerBlocks( getSelectedBlockClientId(), [], false ); + }, /** * Updates the column columnCount, including necessary revisions to child Column * blocks to grant required or redistribute available space. @@ -171,9 +179,7 @@ const ColumnsEditContainerWrapper = withDispatch( */ updateColumns( previousColumns, newColumns ) { const { clientId } = ownProps; - const { replaceInnerBlocks, selectBlock } = dispatch( - 'core/block-editor' - ); + const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); const { getBlocks } = registry.select( 'core/block-editor' ); let innerBlocks = getBlocks( clientId ); @@ -196,16 +202,6 @@ const ColumnsEditContainerWrapper = withDispatch( ); } - if ( innerBlocks.length < MIN_COLUMNS_NUMBER ) { - innerBlocks = [ - ...innerBlocks, - ...times( MIN_COLUMNS_NUMBER - innerBlocks.length, () => { - return createBlock( 'core/column' ); - } ), - ]; - selectBlock( clientId ); - } - replaceInnerBlocks( clientId, innerBlocks, false ); }, } ) From 1f29bb31ecfeeb431f76cf72fe46e470f57d5982 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 21 Feb 2020 09:12:32 +0100 Subject: [PATCH 135/183] add separator to Column placeholder --- packages/block-library/src/column/edit.native.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 762e0441740188..07743c55aace5f 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -109,7 +109,9 @@ function ColumnEdit( { }, ] } > - { isParentSelected && <InnerBlocks.ButtonBlockAppender /> } + { isParentSelected && ( + <InnerBlocks.ButtonBlockAppender showSeparator /> + ) } </View> ); } From a58f81523c65124018b17639b359d7479d5a854b Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 27 Feb 2020 10:26:45 +0100 Subject: [PATCH 136/183] pass columnsSetting with props --- .../src/components/block-list/block.native.js | 1 + .../src/components/block-list/index.native.js | 2 ++ .../components/inner-blocks/index.native.js | 3 +- .../block-library/src/column/edit.native.js | 20 +++++------ .../src/column/editor.native.scss | 3 -- .../block-library/src/columns/edit.native.js | 36 ++++++++----------- 6 files changed, 28 insertions(+), 37 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 3051a99ce78d3b..55de5fb25ce777 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -67,6 +67,7 @@ class BlockListBlock extends Component { this.props.onCaretVerticalPositionChange } clientId={ this.props.clientId } + columnsSettings={ this.props.columnsSettings } /> ); } diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 7272ba4d6c57d9..c7a9478392da38 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -159,6 +159,7 @@ export class BlockList extends Component { containerStyle, disallowRemoveInnerBlocks, customOnDelete, + columnsSettings, } = this.props; return ( @@ -180,6 +181,7 @@ export class BlockList extends Component { } disallowRemoveInnerBlocks={ disallowRemoveInnerBlocks } customOnDelete={ customOnDelete } + columnsSettings={ columnsSettings } /> { ! this.shouldShowInnerBlockAppender() && shouldShowInsertionPointAfter( clientId ) && ( diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index 57e4a06bbfbcda..ca61eb730af2e6 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -99,7 +99,6 @@ class InnerBlocks extends Component { } = this.props; const newSettings = { - ...blockListSettings, allowedBlocks, templateLock: this.getTemplateLock(), }; @@ -117,6 +116,7 @@ class InnerBlocks extends Component { flatListProps, disallowRemoveInnerBlocks, customOnDelete, + columnsSettings, } = this.props; const { templateInProcess } = this.state; @@ -131,6 +131,7 @@ class InnerBlocks extends Component { flatListProps={ flatListProps } disallowRemoveInnerBlocks={ disallowRemoveInnerBlocks } customOnDelete={ customOnDelete } + columnsSettings={ columnsSettings } /> ) } </> diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 07743c55aace5f..748cff0df28db9 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -27,21 +27,17 @@ function ColumnEdit( { getStylesFromColorScheme, isParentSelected, isDescendantOfParentSelected, - columnsContainerSettings, isMobile, + columnsSettings, } ) { const { verticalAlignment } = attributes; - const { - columnsInRow = 1, - width: columnsContainerWidth, - } = columnsContainerSettings; + const { columnsInRow, width: columnsContainerWidth } = columnsSettings; - const columnContainerBaseWidth = styles[ 'column-container-base' ].maxWidth; const containerMaxWidth = styles[ 'columns-container' ].maxWidth; const containerWidth = columnsContainerWidth || containerMaxWidth; - const minWidth = Math.min( containerWidth, columnContainerBaseWidth ); + const minWidth = Math.min( containerWidth, containerMaxWidth ); const columnBaseWidth = minWidth / columnsInRow; const applyBlockStyle = ( placeholder = false ) => { @@ -164,7 +160,6 @@ export default compose( [ getBlockCount, getBlockRootClientId, getSelectedBlockClientId, - getBlockListSettings, } = select( 'core/block-editor' ); const selectedBlockClientId = getSelectedBlockClientId(); @@ -173,8 +168,6 @@ export default compose( [ const parentId = getBlockRootClientId( clientId ); const hasChildren = !! getBlockCount( clientId ); - const columnsContainerSettings = getBlockListSettings( parentId ); - const isParentSelected = selectedBlockClientId && selectedBlockClientId === parentId; @@ -185,12 +178,17 @@ export default compose( [ parentId ); + const parents = getBlockParents( clientId, true ); + + const isAncestorSelected = + selectedBlockClientId && parents.includes( selectedBlockClientId ); + return { hasChildren, isParentSelected, isSelected, - columnsContainerSettings, isDescendantOfParentSelected, + isAncestorSelected, }; } ), withViewportMatch( { isMobile: '< mobile' } ), diff --git a/packages/block-library/src/column/editor.native.scss b/packages/block-library/src/column/editor.native.scss index 2061ab4b7912dc..4113e59cff573d 100644 --- a/packages/block-library/src/column/editor.native.scss +++ b/packages/block-library/src/column/editor.native.scss @@ -1,8 +1,5 @@ .columnPlaceholder { padding: $block-selected-to-content; - margin: $block-edge-to-content; - margin-left: $block-selected-child-margin; - margin-right: $block-selected-child-margin; background-color: $white; border: $border-width dashed $gray; border-radius: 4px; diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 3a2a1ef8f477d6..30e7b278600b84 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -16,7 +16,7 @@ import { BlockVerticalAlignmentToolbar, } from '@wordpress/block-editor'; import { withDispatch, useSelect } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; +import { useEffect, useState } from '@wordpress/element'; import { compose, withPreferredColorScheme } from '@wordpress/compose'; import { createBlock } from '@wordpress/blocks'; import { withViewportMatch } from '@wordpress/viewport'; @@ -48,8 +48,6 @@ const MAX_COLUMNS_NUMBER = 6; function ColumnsEditContainer( { attributes, - updateBlockSettings, - blockListSettings, updateAlignment, updateColumns, isMobile, @@ -57,7 +55,10 @@ function ColumnsEditContainer( { onDeleteColumn, } ) { const { verticalAlignment } = attributes; - const { width } = blockListSettings; + const [ columnsSettings, setColumnsSettings ] = useState( { + columnsInRow: 2, + } ); + const { width } = columnsSettings; useEffect( () => { updateColumns( @@ -103,8 +104,7 @@ function ColumnsEditContainer( { onLayout={ ( event ) => { const { width: newWidth } = event.nativeEvent.layout; if ( newWidth !== width ) { - updateBlockSettings( { - ...blockListSettings, + setColumnsSettings( { columnsInRow: getColumnsInRow( newWidth, columnCount @@ -117,7 +117,10 @@ function ColumnsEditContainer( { <InnerBlocks flatListProps={ { ...( ! isMobile && { - contentContainerStyle: styles.columnsContainer, + contentContainerStyle: { + ...styles.columnsContainer, + maxWidth: width, + }, } ), horizontal: ! isMobile, scrollEnabled: false, @@ -126,6 +129,7 @@ function ColumnsEditContainer( { allowedBlocks={ ALLOWED_BLOCKS } disallowRemoveInnerBlocks customOnDelete={ onDeleteColumn } + columnsSettings={ columnsSettings } /> </View> </> @@ -157,11 +161,6 @@ const ColumnsEditContainerWrapper = withDispatch( } ); } ); }, - updateBlockSettings( settings ) { - const { clientId } = ownProps; - const { updateBlockListSettings } = dispatch( 'core/block-editor' ); - updateBlockListSettings( clientId, settings ); - }, onDeleteColumn: () => { const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); const { getSelectedBlockClientId } = registry.select( @@ -209,15 +208,12 @@ const ColumnsEditContainerWrapper = withDispatch( const ColumnsEdit = ( props ) => { const { clientId, isSelected, getStylesFromColorScheme } = props; - const { hasChildren, blockListSettings, columnCount } = useSelect( + const { hasChildren, columnCount } = useSelect( ( select ) => { - const { getBlocks, getBlockListSettings, getBlockCount } = select( - 'core/block-editor' - ); + const { getBlocks, getBlockCount } = select( 'core/block-editor' ); return { hasChildren: getBlocks( clientId ).length > 0, - blockListSettings: getBlockListSettings( clientId ) || {}, columnCount: getBlockCount( clientId ), }; }, @@ -242,11 +238,7 @@ const ColumnsEdit = ( props ) => { } return ( - <ColumnsEditContainerWrapper - blockListSettings={ blockListSettings } - columnCount={ columnCount } - { ...props } - /> + <ColumnsEditContainerWrapper columnCount={ columnCount } { ...props } /> ); }; From 079ea703b44c13d7271e5985ba3bd0e0ba9604df Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 27 Feb 2020 11:56:12 +0100 Subject: [PATCH 137/183] set bordering logic to keep columns layaout splitted --- .../block-library/src/column/edit.native.js | 78 ++++++++++++++----- .../src/column/editor.native.scss | 8 +- 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 748cff0df28db9..2d0757d4162c1b 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -27,8 +27,12 @@ function ColumnEdit( { getStylesFromColorScheme, isParentSelected, isDescendantOfParentSelected, + isDescendantSelected, + isAncestorSelected, isMobile, columnsSettings, + isColumnsInRootList, + isDeepNested, } ) { const { verticalAlignment } = attributes; const { columnsInRow, width: columnsContainerWidth } = columnsSettings; @@ -57,29 +61,56 @@ function ColumnEdit( { 'parent-selected', 'descendant-selected', 'placeholder-selected', + 'dashed-border', ]; const widths = pullWidths( names ); - const [ , parentSelected, , placeholderSelected ] = widths; - - if ( isParentSelected ) { - width -= placeholder ? placeholderSelected : parentSelected; - return { width }; - } - - const [ selected, , descendantSelected ] = widths; - - if ( placeholder ) { - if ( isDescendantOfParentSelected ) { - width -= selected; - } else { + const [ + emptyColumnSelected, + parentSelected, + columnSelected, + placeholderParentSelected, + dashedBorderWidth, + ] = widths; + + switch ( true ) { + case isSelected: + width = columnBaseWidth; + width -= ! hasChildren + ? emptyColumnSelected + : columnSelected + dashedBorderWidth; + break; + + case isParentSelected: + width -= placeholder + ? placeholderParentSelected + : parentSelected; + break; + + case isDescendantSelected: + width = columnBaseWidth; + break; + + case isDescendantOfParentSelected: + width = columnBaseWidth; + if ( ! hasChildren ) width -= emptyColumnSelected; + break; + + case isAncestorSelected: + width = columnBaseWidth; + if ( ! hasChildren ) width -= parentSelected; + break; + + case placeholder: width -= - columnsInRow === 1 ? parentSelected : placeholderSelected; - if ( ! hasChildren ) width -= 4; - } - } else if ( isSelected ) { - width -= ! hasChildren ? selected : descendantSelected; - } else if ( isDescendantOfParentSelected ) { - width += descendantSelected; + columnsInRow === 1 + ? parentSelected + : placeholderParentSelected; + width -= columnSelected; + if ( ! isDeepNested ) width -= dashedBorderWidth; + if ( isColumnsInRootList ) width -= parentSelected; + break; + + default: } return { width }; @@ -179,9 +210,13 @@ export default compose( [ ); const parents = getBlockParents( clientId, true ); + const isColumnsInRootList = !! parents[ 2 ]; const isAncestorSelected = selectedBlockClientId && parents.includes( selectedBlockClientId ); + const isDescendantSelected = selectedParents.includes( clientId ); + + const isDeepNested = parents.length > 2; return { hasChildren, @@ -189,6 +224,9 @@ export default compose( [ isSelected, isDescendantOfParentSelected, isAncestorSelected, + isDescendantSelected, + isColumnsInRootList, + isDeepNested, }; } ), withViewportMatch( { isMobile: '< mobile' } ), diff --git a/packages/block-library/src/column/editor.native.scss b/packages/block-library/src/column/editor.native.scss index 4113e59cff573d..6cb9e1746c00d9 100644 --- a/packages/block-library/src/column/editor.native.scss +++ b/packages/block-library/src/column/editor.native.scss @@ -37,11 +37,15 @@ } .column-selected-margin { - width: 2 * $block-padding; + width: 2 * $block-edge-to-content; } .column-descendant-selected-margin { - width: $block-selected-margin + $block-selected-border-width; + width: 2 * $block-selected-margin; +} + +.column-dashed-border-margin { + width: 2 * $block-selected-border-width; } .is-vertically-aligned-top { From b7fdc3c1e765bea77fbb49886d8399bd01872ea1 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 27 Feb 2020 12:23:51 +0100 Subject: [PATCH 138/183] Revert "allow to pass custonOnDelete to clear Column content" This reverts commit 12f6501f6731c67d60b339ac7841ce577e8df795. --- .../src/components/block-list/block.native.js | 2 -- .../src/components/block-list/index.native.js | 2 -- .../block-mobile-toolbar/index.native.js | 3 +-- .../components/inner-blocks/index.native.js | 2 -- .../block-library/src/columns/edit.native.js | 27 ++++++++++++------- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 55de5fb25ce777..bb1ce29c101ee0 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -194,7 +194,6 @@ class BlockListBlock extends Component { isTouchable, disallowRemoveInnerBlocks, hasChildren, - customOnDelete, } = this.props; const accessibilityLabel = getAccessibleBlockLabel( @@ -249,7 +248,6 @@ class BlockListBlock extends Component { disallowRemoveInnerBlocks && ! hasChildren } - customOnDelete={ customOnDelete } /> ) } </View> diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index c7a9478392da38..9c8e4e0dcfb2dc 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -158,7 +158,6 @@ export class BlockList extends Component { shouldShowInsertionPointAfter, containerStyle, disallowRemoveInnerBlocks, - customOnDelete, columnsSettings, } = this.props; @@ -180,7 +179,6 @@ export class BlockList extends Component { this.onCaretVerticalPositionChange } disallowRemoveInnerBlocks={ disallowRemoveInnerBlocks } - customOnDelete={ customOnDelete } columnsSettings={ columnsSettings } /> { ! this.shouldShowInnerBlockAppender() && diff --git a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js index 3b638274295032..ec9f99824786f5 100644 --- a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js +++ b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js @@ -24,7 +24,6 @@ const BlockMobileToolbar = ( { onDelete, order, disallowRemoveBlock, - customOnDelete, } ) => ( <View style={ styles.toolbar }> <BlockMover clientIds={ [ clientId ] } /> @@ -39,7 +38,7 @@ const BlockMobileToolbar = ( { __( 'Remove block at row %s' ), order + 1 ) } - onClick={ customOnDelete || onDelete } + onClick={ onDelete } isDisabled={ disallowRemoveBlock } icon={ trash } extraProps={ { hint: __( 'Double tap to remove the block' ) } } diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index ca61eb730af2e6..24961f7d3b4ab8 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -115,7 +115,6 @@ class InnerBlocks extends Component { containerStyle, flatListProps, disallowRemoveInnerBlocks, - customOnDelete, columnsSettings, } = this.props; const { templateInProcess } = this.state; @@ -130,7 +129,6 @@ class InnerBlocks extends Component { containerStyle={ containerStyle } flatListProps={ flatListProps } disallowRemoveInnerBlocks={ disallowRemoveInnerBlocks } - customOnDelete={ customOnDelete } columnsSettings={ columnsSettings } /> ) } diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 30e7b278600b84..b0fc0d04e811e4 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -52,7 +52,6 @@ function ColumnsEditContainer( { updateColumns, isMobile, columnCount, - onDeleteColumn, } ) { const { verticalAlignment } = attributes; const [ columnsSettings, setColumnsSettings ] = useState( { @@ -128,7 +127,6 @@ function ColumnsEditContainer( { containerStyle={ { flex: 1 } } allowedBlocks={ ALLOWED_BLOCKS } disallowRemoveInnerBlocks - customOnDelete={ onDeleteColumn } columnsSettings={ columnsSettings } /> </View> @@ -161,13 +159,10 @@ const ColumnsEditContainerWrapper = withDispatch( } ); } ); }, - onDeleteColumn: () => { - const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); - const { getSelectedBlockClientId } = registry.select( - 'core/block-editor' - ); - - replaceInnerBlocks( getSelectedBlockClientId(), [], false ); + updateBlockSettings( settings ) { + const { clientId } = ownProps; + const { updateBlockListSettings } = dispatch( 'core/block-editor' ); + updateBlockListSettings( clientId, settings ); }, /** * Updates the column columnCount, including necessary revisions to child Column @@ -178,7 +173,9 @@ const ColumnsEditContainerWrapper = withDispatch( */ updateColumns( previousColumns, newColumns ) { const { clientId } = ownProps; - const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); + const { replaceInnerBlocks, selectBlock } = dispatch( + 'core/block-editor' + ); const { getBlocks } = registry.select( 'core/block-editor' ); let innerBlocks = getBlocks( clientId ); @@ -201,6 +198,16 @@ const ColumnsEditContainerWrapper = withDispatch( ); } + if ( innerBlocks.length < MIN_COLUMNS_NUMBER ) { + innerBlocks = [ + ...innerBlocks, + ...times( MIN_COLUMNS_NUMBER - innerBlocks.length, () => { + return createBlock( 'core/column' ); + } ), + ]; + selectBlock( clientId ); + } + replaceInnerBlocks( clientId, innerBlocks, false ); }, } ) From 52ee2117d629c28061363e33119ecf54321b0d6e Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 27 Feb 2020 12:41:48 +0100 Subject: [PATCH 139/183] redirect disallowRemoveInnerBlock to hide trash button --- .../src/components/block-list/block.native.js | 4 +--- .../block-mobile-toolbar/index.native.js | 23 ++++++++++--------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index bb1ce29c101ee0..0d49ae495ffa26 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -193,7 +193,6 @@ class BlockListBlock extends Component { parentId, isTouchable, disallowRemoveInnerBlocks, - hasChildren, } = this.props; const accessibilityLabel = getAccessibleBlockLabel( @@ -245,8 +244,7 @@ class BlockListBlock extends Component { <BlockMobileToolbar clientId={ clientId } disallowRemoveBlock={ - disallowRemoveInnerBlocks && - ! hasChildren + disallowRemoveInnerBlocks } /> ) } diff --git a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js index ec9f99824786f5..79c53f60f26efe 100644 --- a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js +++ b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js @@ -32,17 +32,18 @@ const BlockMobileToolbar = ( { <BlockSettingsButton.Slot /> - <ToolbarButton - title={ sprintf( - /* translators: accessibility text. %s: current block position (number). */ - __( 'Remove block at row %s' ), - order + 1 - ) } - onClick={ onDelete } - isDisabled={ disallowRemoveBlock } - icon={ trash } - extraProps={ { hint: __( 'Double tap to remove the block' ) } } - /> + { ! disallowRemoveBlock && ( + <ToolbarButton + title={ sprintf( + /* translators: accessibility text. %s: current block position (number). */ + __( 'Remove block at row %s' ), + order + 1 + ) } + onClick={ onDelete } + icon={ trash } + extraProps={ { hint: __( 'Double tap to remove the block' ) } } + /> + ) } </View> ); From 0507f5da023821f9eef48358460414d64476ceec Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Tue, 10 Mar 2020 16:55:56 +0100 Subject: [PATCH 140/183] prevent Group block bounce inside Columns --- packages/block-library/src/group/edit.native.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/block-library/src/group/edit.native.js b/packages/block-library/src/group/edit.native.js index a1b261f3340c6c..0c1b4bd27a13ee 100644 --- a/packages/block-library/src/group/edit.native.js +++ b/packages/block-library/src/group/edit.native.js @@ -35,6 +35,9 @@ function GroupEdit( { hasInnerBlocks, isSelected, getStylesFromColorScheme } ) { return ( <InnerBlocks renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } + flatListProps={ { + scrollEnabled: false, + } } /> ); } From 2d252711fa1e2c8db707be3a9a0b96d8ec413efe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Est=C3=AAv=C3=A3o?= <sergioestevao@gmail.com> Date: Wed, 11 Mar 2020 09:08:31 +0000 Subject: [PATCH 141/183] Implement mock block-wrapper for mobile version. (#20772) --- .../block-list/block-wrapper.native.js | 22 +++++++++++++++++++ .../src/components/index.native.js | 1 + 2 files changed, 23 insertions(+) create mode 100644 packages/block-editor/src/components/block-list/block-wrapper.native.js diff --git a/packages/block-editor/src/components/block-list/block-wrapper.native.js b/packages/block-editor/src/components/block-list/block-wrapper.native.js new file mode 100644 index 00000000000000..69cbf52723125a --- /dev/null +++ b/packages/block-editor/src/components/block-list/block-wrapper.native.js @@ -0,0 +1,22 @@ +const elements = [ + 'p', + 'div', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'ol', + 'ul', + 'li', + 'figure', + 'nav', +]; + +const ExtendedBlockComponent = elements.reduce( ( acc, element ) => { + acc[ element ] = element; + return acc; +}, String ); + +export const Block = ExtendedBlockComponent; diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js index 9fe95c7d93cd25..cddc6dd39e77db 100644 --- a/packages/block-editor/src/components/index.native.js +++ b/packages/block-editor/src/components/index.native.js @@ -45,6 +45,7 @@ export { default as BlockMover } from './block-mover'; export { default as BlockToolbar } from './block-toolbar'; export { default as DefaultBlockAppender } from './default-block-appender'; export { default as Inserter } from './inserter'; +export { Block as __experimentalBlock } from './block-list/block-wrapper'; // State Related Components export { default as BlockEditorProvider } from './provider'; From cc6108033a0f85c92b2ae4e3cfdec270f718a85b Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 12 Mar 2020 07:49:44 +0100 Subject: [PATCH 142/183] Revert "Set flex-shrink to 0 on the cell container (#20768)" This reverts commit dd0e49c2485169fbc29755177671a3e7162e4251. --- packages/components/src/mobile/bottom-sheet/styles.native.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/mobile/bottom-sheet/styles.native.scss b/packages/components/src/mobile/bottom-sheet/styles.native.scss index 254817bfb44490..04f1651bfc795a 100644 --- a/packages/components/src/mobile/bottom-sheet/styles.native.scss +++ b/packages/components/src/mobile/bottom-sheet/styles.native.scss @@ -112,7 +112,7 @@ .cellRowContainer { flex-direction: row; align-items: center; - flex-shrink: 0; + flex-shrink: 1; } .cellIcon { From 0be334df94e3a731ac42b719c9109d03cc0f2ed7 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 12 Mar 2020 14:07:06 +0100 Subject: [PATCH 143/183] fix measureLayout warn/crash --- .../block-library/src/column/edit.native.js | 5 ----- .../block-library/src/columns/edit.native.js | 21 +++++++------------ 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 2d0757d4162c1b..12e8712149a556 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -29,7 +29,6 @@ function ColumnEdit( { isDescendantOfParentSelected, isDescendantSelected, isAncestorSelected, - isMobile, columnsSettings, isColumnsInRootList, isDeepNested, @@ -45,10 +44,6 @@ function ColumnEdit( { const columnBaseWidth = minWidth / columnsInRow; const applyBlockStyle = ( placeholder = false ) => { - if ( isMobile ) { - return; - } - const pullWidths = ( names ) => names.map( ( name ) => diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index b0fc0d04e811e4..e4c7ea4ec2e96a 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -17,9 +17,8 @@ import { } from '@wordpress/block-editor'; import { withDispatch, useSelect } from '@wordpress/data'; import { useEffect, useState } from '@wordpress/element'; -import { compose, withPreferredColorScheme } from '@wordpress/compose'; +import { withPreferredColorScheme } from '@wordpress/compose'; import { createBlock } from '@wordpress/blocks'; -import { withViewportMatch } from '@wordpress/viewport'; /** * Internal dependencies */ @@ -50,7 +49,6 @@ function ColumnsEditContainer( { attributes, updateAlignment, updateColumns, - isMobile, columnCount, } ) { const { verticalAlignment } = attributes; @@ -115,13 +113,11 @@ function ColumnsEditContainer( { > <InnerBlocks flatListProps={ { - ...( ! isMobile && { - contentContainerStyle: { - ...styles.columnsContainer, - maxWidth: width, - }, - } ), - horizontal: ! isMobile, + contentContainerStyle: { + ...styles.columnsContainer, + // maxWidth: width, + }, + horizontal: true, scrollEnabled: false, } } containerStyle={ { flex: 1 } } @@ -249,7 +245,4 @@ const ColumnsEdit = ( props ) => { ); }; -export default compose( [ - withViewportMatch( { isMobile: '< mobile' } ), - withPreferredColorScheme, -] )( ColumnsEdit ); +export default withPreferredColorScheme( ColumnsEdit ); From 1ae3647a6c7e6c1062fffdaecf7690f2af21c988 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 12 Mar 2020 14:26:56 +0100 Subject: [PATCH 144/183] fix accidentionally commented line --- packages/block-library/src/columns/edit.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index e4c7ea4ec2e96a..17f653e46b39a7 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -115,7 +115,7 @@ function ColumnsEditContainer( { flatListProps={ { contentContainerStyle: { ...styles.columnsContainer, - // maxWidth: width, + maxWidth: width, }, horizontal: true, scrollEnabled: false, From 8118b958904a1c701b5a5dc270f036c56d23b4d5 Mon Sep 17 00:00:00 2001 From: jbinda <jakub.binda@gmail.com> Date: Mon, 16 Mar 2020 11:01:21 +0100 Subject: [PATCH 145/183] [RNMobile] Add `useResizeObserver()` to Column Block (#20838) * use resizeObserver in Columns --- .../block-library/src/column/edit.native.js | 2 +- .../block-library/src/columns/edit.native.js | 34 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 12e8712149a556..5a0ef78f8e1185 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -41,7 +41,7 @@ function ColumnEdit( { const containerWidth = columnsContainerWidth || containerMaxWidth; const minWidth = Math.min( containerWidth, containerMaxWidth ); - const columnBaseWidth = minWidth / columnsInRow; + const columnBaseWidth = minWidth / Math.max( 1, columnsInRow ); const applyBlockStyle = ( placeholder = false ) => { const pullWidths = ( names ) => diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 17f653e46b39a7..3008d9a1a082f1 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -17,7 +17,10 @@ import { } from '@wordpress/block-editor'; import { withDispatch, useSelect } from '@wordpress/data'; import { useEffect, useState } from '@wordpress/element'; -import { withPreferredColorScheme } from '@wordpress/compose'; +import { + withPreferredColorScheme, + useResizeObserver, +} from '@wordpress/compose'; import { createBlock } from '@wordpress/blocks'; /** * Internal dependencies @@ -51,11 +54,13 @@ function ColumnsEditContainer( { updateColumns, columnCount, } ) { - const { verticalAlignment } = attributes; + const [ resizeListener, sizes ] = useResizeObserver(); const [ columnsSettings, setColumnsSettings ] = useState( { columnsInRow: 2, } ); - const { width } = columnsSettings; + + const { verticalAlignment } = attributes; + const { width } = sizes || {}; useEffect( () => { updateColumns( @@ -64,6 +69,13 @@ function ColumnsEditContainer( { ); }, [ columnCount ] ); + useEffect( () => { + setColumnsSettings( { + columnsInRow: getColumnsInRow( width, columnCount ), + width, + } ); + }, [ width ] ); + const getColumnsInRow = ( containerWidth, columnsNumber ) => { if ( containerWidth < 480 ) { return 1; @@ -97,20 +109,8 @@ function ColumnsEditContainer( { isCollapsed={ false } /> </BlockControls> - <View - onLayout={ ( event ) => { - const { width: newWidth } = event.nativeEvent.layout; - if ( newWidth !== width ) { - setColumnsSettings( { - columnsInRow: getColumnsInRow( - newWidth, - columnCount - ), - width: newWidth, - } ); - } - } } - > + <View> + { resizeListener } <InnerBlocks flatListProps={ { contentContainerStyle: { From f57e6ace80b0bb38c46a69e53abfae2a6115fac4 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 12 Mar 2020 09:55:33 +0100 Subject: [PATCH 146/183] apply disscussed changes with behaviour --- .../src/components/block-list/block.native.js | 4 ++ .../src/components/block-list/index.native.js | 21 ++++++- .../block-mobile-toolbar/index.native.js | 9 ++- .../components/block-mover/index.native.js | 51 ++++++++++++---- .../button-block-appender/index.native.js | 3 +- .../inner-blocks/button-block-appender.js | 7 ++- .../components/inner-blocks/index.native.js | 6 ++ .../block-library/src/column/edit.native.js | 19 ++---- .../src/column/editor.native.scss | 4 +- .../block-library/src/columns/edit.native.js | 60 ++++++++++++++++--- 10 files changed, 141 insertions(+), 43 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index f42c85bc438c86..6a25c4cc729e63 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -193,6 +193,8 @@ class BlockListBlock extends Component { parentId, isTouchable, disallowRemoveInnerBlocks, + customOnDelete, + horizontalDirection, hasParent, onSelect, showFloatingToolbar, @@ -249,6 +251,8 @@ class BlockListBlock extends Component { disallowRemoveBlock={ disallowRemoveInnerBlocks } + customOnDelete={ customOnDelete } + horizontalDirection={ horizontalDirection } /> ) } </View> diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 0b173b26328c37..140ca76b33084c 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -69,10 +69,13 @@ export class BlockList extends Component { } renderDefaultBlockAppender() { - const { shouldShowInsertionPointBefore } = this.props; + const { shouldShowInsertionPointBefore, columnsSettings } = this.props; const willShowInsertionPoint = shouldShowInsertionPointBefore(); // call without the client_id argument since this is the appender + const parentWidth = columnsSettings && columnsSettings.width; return ( - <ReadableContentView> + <ReadableContentView + style={ parentWidth && { maxWidth: parentWidth } } + > <BlockListAppender // show the default appender, anormal, when not inserting a block rootClientId={ this.props.rootClientId } renderAppender={ this.props.renderAppender } @@ -160,11 +163,16 @@ export class BlockList extends Component { isReadOnly, shouldShowInsertionPointBefore, shouldShowInsertionPointAfter, + __experimentalMoverDirection, + customOnDelete, containerStyle, disallowRemoveInnerBlocks, columnsSettings, } = this.props; + const horizontalDirection = + __experimentalMoverDirection === 'horizontal'; + return ( <ReadableContentView style={ containerStyle }> <View @@ -183,6 +191,8 @@ export class BlockList extends Component { this.onCaretVerticalPositionChange } disallowRemoveInnerBlocks={ disallowRemoveInnerBlocks } + customOnDelete={ customOnDelete } + horizontalDirection={ horizontalDirection } columnsSettings={ columnsSettings } /> { ! this.shouldShowInnerBlockAppender() && @@ -212,7 +222,7 @@ export class BlockList extends Component { } export default compose( [ - withSelect( ( select, { rootClientId } ) => { + withSelect( ( select, { rootClientId, __experimentalMoverDirection } ) => { const { getBlockCount, getBlockOrder, @@ -222,12 +232,16 @@ export default compose( [ getSettings, } = select( 'core/block-editor' ); + const horizontalDirection = + __experimentalMoverDirection === 'horizontal'; + const selectedBlockClientId = getSelectedBlockClientId(); const blockClientIds = getBlockOrder( rootClientId ); const insertionPoint = getBlockInsertionPoint(); const blockInsertionPointIsVisible = isBlockInsertionPointVisible(); const shouldShowInsertionPointBefore = ( clientId ) => { return ( + ! horizontalDirection && blockInsertionPointIsVisible && insertionPoint.rootClientId === rootClientId && // if list is empty, show the insertion point (via the default appender) @@ -238,6 +252,7 @@ export default compose( [ }; const shouldShowInsertionPointAfter = ( clientId ) => { return ( + ! horizontalDirection && blockInsertionPointIsVisible && insertionPoint.rootClientId === rootClientId && // if the insertion point is at the end of the list diff --git a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js index 79c53f60f26efe..5eb453b9189969 100644 --- a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js +++ b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js @@ -24,9 +24,14 @@ const BlockMobileToolbar = ( { onDelete, order, disallowRemoveBlock, + customOnDelete, + horizontalDirection, } ) => ( <View style={ styles.toolbar }> - <BlockMover clientIds={ [ clientId ] } /> + <BlockMover + clientIds={ [ clientId ] } + horizontalDirection={ horizontalDirection } + /> <View style={ styles.spacer } /> @@ -39,7 +44,7 @@ const BlockMobileToolbar = ( { __( 'Remove block at row %s' ), order + 1 ) } - onClick={ onDelete } + onClick={ customOnDelete || onDelete } icon={ trash } extraProps={ { hint: __( 'Double tap to remove the block' ) } } /> diff --git a/packages/block-editor/src/components/block-mover/index.native.js b/packages/block-editor/src/components/block-mover/index.native.js index b572e21ee79dd8..8eda82942ad04b 100644 --- a/packages/block-editor/src/components/block-mover/index.native.js +++ b/packages/block-editor/src/components/block-mover/index.native.js @@ -10,7 +10,7 @@ import { ToolbarButton } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { withSelect, withDispatch } from '@wordpress/data'; import { withInstanceId, compose } from '@wordpress/compose'; -import { arrowUp, arrowDown } from '@wordpress/icons'; +import { arrowUp, arrowDown, arrowLeft, arrowRight } from '@wordpress/icons'; const BlockMover = ( { isFirst, @@ -20,7 +20,30 @@ const BlockMover = ( { onMoveUp, firstIndex, rootClientId, + horizontalDirection, } ) => { + const firstButtonIcon = horizontalDirection ? arrowLeft : arrowUp; + const secondButtonIcon = horizontalDirection ? arrowRight : arrowDown; + + const firstButtonHint = horizontalDirection + ? __( 'Double tap to move the block to the left' ) + : __( 'Double tap to move the block up' ); + const secondButtonHint = horizontalDirection + ? __( 'Double tap to move the block to the right' ) + : __( 'Double tap to move the block down' ); + + const firstBlockTitle = horizontalDirection + ? __( 'Move block left' ) + : __( 'Move block up' ); + const lastBlockTitle = horizontalDirection + ? __( 'Move block right' ) + : __( 'Move block down' ); + + const firstButtonDirection = horizontalDirection ? 'left' : 'up'; + const secondButtonDirection = horizontalDirection ? 'right' : 'down'; + + const location = horizontalDirection ? 'position' : 'row'; + if ( isLocked || ( isFirst && isLast && ! rootClientId ) ) { return null; } @@ -31,37 +54,43 @@ const BlockMover = ( { title={ ! isFirst ? sprintf( - /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ - __( 'Move block up from row %1$s to row %2$s' ), + /* translators: accessibility text. %1: block moving direction (string). %2: location of block - row or order number (string). %3: current block position (number). %4: next block position (number) */ + __( + 'Move block %2$s from %1$s %3$s to %1$s %4$s' + ), + location, + firstButtonDirection, firstIndex + 1, firstIndex ) - : __( 'Move block up' ) + : firstBlockTitle } isDisabled={ isFirst } onClick={ onMoveUp } - icon={ arrowUp } - extraProps={ { hint: __( 'Double tap to move the block up' ) } } + icon={ firstButtonIcon } + extraProps={ { hint: firstButtonHint } } /> <ToolbarButton title={ ! isLast ? sprintf( - /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ + /* translators: accessibility text. %1: block moving direction (string). %2: location of block - row or order number (string). %3: current block position (number). %4: next block position (number) */ __( - 'Move block down from row %1$s to row %2$s' + 'Move block %2$s from %1$s %3$s to %1$s %4$s' ), + location, + secondButtonDirection, firstIndex + 1, firstIndex + 2 ) - : __( 'Move block down' ) + : lastBlockTitle } isDisabled={ isLast } onClick={ onMoveDown } - icon={ arrowDown } + icon={ secondButtonIcon } extraProps={ { - hint: __( 'Double tap to move the block down' ), + hint: secondButtonHint, } } /> </> diff --git a/packages/block-editor/src/components/button-block-appender/index.native.js b/packages/block-editor/src/components/button-block-appender/index.native.js index 692c013a69f729..34453d5254b9cd 100644 --- a/packages/block-editor/src/components/button-block-appender/index.native.js +++ b/packages/block-editor/src/components/button-block-appender/index.native.js @@ -20,6 +20,7 @@ function ButtonBlockAppender( { rootClientId, getStylesFromColorScheme, showSeparator, + customOnAdd, } ) { const appenderStyle = { ...styles.appender, @@ -39,7 +40,7 @@ function ButtonBlockAppender( { rootClientId={ rootClientId } renderToggle={ ( { onToggle, disabled, isOpen } ) => ( <Button - onClick={ onToggle } + onClick={ customOnAdd || onToggle } aria-expanded={ isOpen } disabled={ disabled } fixedRatio={ false } diff --git a/packages/block-editor/src/components/inner-blocks/button-block-appender.js b/packages/block-editor/src/components/inner-blocks/button-block-appender.js index 5823a5e42c7b0c..ac9c5ddea72846 100644 --- a/packages/block-editor/src/components/inner-blocks/button-block-appender.js +++ b/packages/block-editor/src/components/inner-blocks/button-block-appender.js @@ -4,11 +4,16 @@ import BaseButtonBlockAppender from '../button-block-appender'; import withClientId from './with-client-id'; -export const ButtonBlockAppender = ( { clientId, showSeparator } ) => { +export const ButtonBlockAppender = ( { + clientId, + showSeparator, + customOnAdd, +} ) => { return ( <BaseButtonBlockAppender rootClientId={ clientId } showSeparator={ showSeparator } + customOnAdd={ customOnAdd } /> ); }; diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index 24961f7d3b4ab8..c75efb87427ce3 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -112,6 +112,8 @@ class InnerBlocks extends Component { const { clientId, renderAppender, + __experimentalMoverDirection, + customOnDelete, containerStyle, flatListProps, disallowRemoveInnerBlocks, @@ -126,6 +128,10 @@ class InnerBlocks extends Component { rootClientId={ clientId } renderAppender={ renderAppender } withFooter={ false } + __experimentalMoverDirection={ + __experimentalMoverDirection + } + customOnDelete={ customOnDelete } containerStyle={ containerStyle } flatListProps={ flatListProps } disallowRemoveInnerBlocks={ disallowRemoveInnerBlocks } diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 5a0ef78f8e1185..41249a1d3a674d 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -30,8 +30,6 @@ function ColumnEdit( { isDescendantSelected, isAncestorSelected, columnsSettings, - isColumnsInRootList, - isDeepNested, } ) { const { verticalAlignment } = attributes; const { columnsInRow, width: columnsContainerWidth } = columnsSettings; @@ -100,9 +98,9 @@ function ColumnEdit( { columnsInRow === 1 ? parentSelected : placeholderParentSelected; - width -= columnSelected; - if ( ! isDeepNested ) width -= dashedBorderWidth; - if ( isColumnsInRootList ) width -= parentSelected; + width -= + columnSelected + + ( columnsInRow === 1 ? parentSelected : dashedBorderWidth ); break; default: @@ -130,11 +128,7 @@ function ColumnEdit( { ...styles.marginHorizontalNone, }, ] } - > - { isParentSelected && ( - <InnerBlocks.ButtonBlockAppender showSeparator /> - ) } - </View> + ></View> ); } @@ -205,14 +199,11 @@ export default compose( [ ); const parents = getBlockParents( clientId, true ); - const isColumnsInRootList = !! parents[ 2 ]; const isAncestorSelected = selectedBlockClientId && parents.includes( selectedBlockClientId ); const isDescendantSelected = selectedParents.includes( clientId ); - const isDeepNested = parents.length > 2; - return { hasChildren, isParentSelected, @@ -220,8 +211,6 @@ export default compose( [ isDescendantOfParentSelected, isAncestorSelected, isDescendantSelected, - isColumnsInRootList, - isDeepNested, }; } ), withViewportMatch( { isMobile: '< mobile' } ), diff --git a/packages/block-library/src/column/editor.native.scss b/packages/block-library/src/column/editor.native.scss index 6cb9e1746c00d9..25fbd03803878e 100644 --- a/packages/block-library/src/column/editor.native.scss +++ b/packages/block-library/src/column/editor.native.scss @@ -11,8 +11,8 @@ } .marginVerticalDense { - margin-top: $block-selected-child-margin; - margin-bottom: $block-selected-child-margin; + margin-top: $block-selected-child-to-content; + margin-bottom: $block-selected-child-to-content; } .marginHorizontalNone { diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 3008d9a1a082f1..f964359a8df9dd 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -45,28 +45,32 @@ const ALLOWED_BLOCKS = [ 'core/column' ]; * @type {number} */ const DEFAULT_COLUMNS = 2; -const MIN_COLUMNS_NUMBER = 2; -const MAX_COLUMNS_NUMBER = 6; +const MIN_COLUMNS_NUMBER = 1; function ColumnsEditContainer( { attributes, updateAlignment, updateColumns, columnCount, + isSelected, + onAddNextColumn, + onDelete, } ) { const [ resizeListener, sizes ] = useResizeObserver(); const [ columnsSettings, setColumnsSettings ] = useState( { - columnsInRow: 2, + columnsInRow: MIN_COLUMNS_NUMBER, } ); const { verticalAlignment } = attributes; const { width } = sizes || {}; useEffect( () => { - updateColumns( - columnCount, - Math.min( MAX_COLUMNS_NUMBER, columnCount || DEFAULT_COLUMNS ) - ); + const newColumnCount = ! columnCount ? DEFAULT_COLUMNS : columnCount; + updateColumns( columnCount, newColumnCount ); + setColumnsSettings( { + ...columnsSettings, + columnsInRow: getColumnsInRow( width, newColumnCount ), + } ); }, [ columnCount ] ); useEffect( () => { @@ -86,6 +90,17 @@ function ColumnsEditContainer( { return columnsNumber; }; + const renderAppender = () => { + if ( isSelected ) { + return ( + <InnerBlocks.ButtonBlockAppender + customOnAdd={ onAddNextColumn } + /> + ); + } + return null; + }; + return ( <> <InspectorControls> @@ -98,7 +113,7 @@ function ColumnsEditContainer( { updateColumns( columnCount, value ) } min={ MIN_COLUMNS_NUMBER } - max={ MAX_COLUMNS_NUMBER } + max={ columnCount + 1 } /> </PanelBody> </InspectorControls> @@ -112,6 +127,12 @@ function ColumnsEditContainer( { <View> { resizeListener } <InnerBlocks + renderAppender={ renderAppender } + __experimentalMoverDirection={ + getColumnsInRow( width, columnCount ) > 1 + ? 'horizontal' + : '' + } flatListProps={ { contentContainerStyle: { ...styles.columnsContainer, @@ -124,6 +145,8 @@ function ColumnsEditContainer( { allowedBlocks={ ALLOWED_BLOCKS } disallowRemoveInnerBlocks columnsSettings={ columnsSettings } + customOnAdd={ onAddNextColumn } + customOnDelete={ columnCount === 1 && onDelete } /> </View> </> @@ -206,6 +229,27 @@ const ColumnsEditContainerWrapper = withDispatch( replaceInnerBlocks( clientId, innerBlocks, false ); }, + onAddNextColumn: () => { + const { clientId } = ownProps; + const { replaceInnerBlocks, selectBlock } = dispatch( + 'core/block-editor' + ); + const { getBlocks } = registry.select( 'core/block-editor' ); + + const innerBlocks = getBlocks( clientId ); + + const insertedBlock = createBlock( 'core/column' ); + + innerBlocks.push( insertedBlock ); + + replaceInnerBlocks( clientId, innerBlocks, true ); + selectBlock( insertedBlock.clientId ); + }, + onDelete: () => { + const { clientId } = ownProps; + const { removeBlock } = dispatch( 'core/block-editor' ); + removeBlock( clientId ); + }, } ) )( ColumnsEditContainer ); From 50358cd6a560fb73dd63fe4254a3cea9af1c1a16 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 19 Mar 2020 14:00:23 +0100 Subject: [PATCH 147/183] cleanup code --- packages/block-library/src/columns/edit.native.js | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index f964359a8df9dd..c8f8cdf9e85a55 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -192,9 +192,7 @@ const ColumnsEditContainerWrapper = withDispatch( */ updateColumns( previousColumns, newColumns ) { const { clientId } = ownProps; - const { replaceInnerBlocks, selectBlock } = dispatch( - 'core/block-editor' - ); + const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); const { getBlocks } = registry.select( 'core/block-editor' ); let innerBlocks = getBlocks( clientId ); @@ -217,16 +215,6 @@ const ColumnsEditContainerWrapper = withDispatch( ); } - if ( innerBlocks.length < MIN_COLUMNS_NUMBER ) { - innerBlocks = [ - ...innerBlocks, - ...times( MIN_COLUMNS_NUMBER - innerBlocks.length, () => { - return createBlock( 'core/column' ); - } ), - ]; - selectBlock( clientId ); - } - replaceInnerBlocks( clientId, innerBlocks, false ); }, onAddNextColumn: () => { From 49fcb66c7c947d597d16e3562dd11596237f9db5 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 19 Mar 2020 14:11:41 +0100 Subject: [PATCH 148/183] revert disable remove Column button changes --- .../src/components/block-list/block.native.js | 4 ---- .../src/components/block-list/index.native.js | 2 -- .../block-mobile-toolbar/index.native.js | 23 ++++++++----------- .../components/inner-blocks/index.native.js | 2 -- .../block-library/src/columns/edit.native.js | 1 - 5 files changed, 10 insertions(+), 22 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 6a25c4cc729e63..61198b31570456 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -192,7 +192,6 @@ class BlockListBlock extends Component { title, parentId, isTouchable, - disallowRemoveInnerBlocks, customOnDelete, horizontalDirection, hasParent, @@ -248,9 +247,6 @@ class BlockListBlock extends Component { { isSelected && ( <BlockMobileToolbar clientId={ clientId } - disallowRemoveBlock={ - disallowRemoveInnerBlocks - } customOnDelete={ customOnDelete } horizontalDirection={ horizontalDirection } /> diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 140ca76b33084c..dad1a780c67e8b 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -166,7 +166,6 @@ export class BlockList extends Component { __experimentalMoverDirection, customOnDelete, containerStyle, - disallowRemoveInnerBlocks, columnsSettings, } = this.props; @@ -190,7 +189,6 @@ export class BlockList extends Component { onCaretVerticalPositionChange={ this.onCaretVerticalPositionChange } - disallowRemoveInnerBlocks={ disallowRemoveInnerBlocks } customOnDelete={ customOnDelete } horizontalDirection={ horizontalDirection } columnsSettings={ columnsSettings } diff --git a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js index 5eb453b9189969..bb96be1e69fab3 100644 --- a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js +++ b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js @@ -23,7 +23,6 @@ const BlockMobileToolbar = ( { clientId, onDelete, order, - disallowRemoveBlock, customOnDelete, horizontalDirection, } ) => ( @@ -37,18 +36,16 @@ const BlockMobileToolbar = ( { <BlockSettingsButton.Slot /> - { ! disallowRemoveBlock && ( - <ToolbarButton - title={ sprintf( - /* translators: accessibility text. %s: current block position (number). */ - __( 'Remove block at row %s' ), - order + 1 - ) } - onClick={ customOnDelete || onDelete } - icon={ trash } - extraProps={ { hint: __( 'Double tap to remove the block' ) } } - /> - ) } + <ToolbarButton + title={ sprintf( + /* translators: accessibility text. %s: current block position (number). */ + __( 'Remove block at row %s' ), + order + 1 + ) } + onClick={ customOnDelete || onDelete } + icon={ trash } + extraProps={ { hint: __( 'Double tap to remove the block' ) } } + /> </View> ); diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index c75efb87427ce3..43c55500cf72e4 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -116,7 +116,6 @@ class InnerBlocks extends Component { customOnDelete, containerStyle, flatListProps, - disallowRemoveInnerBlocks, columnsSettings, } = this.props; const { templateInProcess } = this.state; @@ -134,7 +133,6 @@ class InnerBlocks extends Component { customOnDelete={ customOnDelete } containerStyle={ containerStyle } flatListProps={ flatListProps } - disallowRemoveInnerBlocks={ disallowRemoveInnerBlocks } columnsSettings={ columnsSettings } /> ) } diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index c8f8cdf9e85a55..b0602950380094 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -143,7 +143,6 @@ function ColumnsEditContainer( { } } containerStyle={ { flex: 1 } } allowedBlocks={ ALLOWED_BLOCKS } - disallowRemoveInnerBlocks columnsSettings={ columnsSettings } customOnAdd={ onAddNextColumn } customOnDelete={ columnCount === 1 && onDelete } From f20e9cb7f4d86ce976f24a28d4ace44d2b50b44c Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 19 Mar 2020 14:47:46 +0100 Subject: [PATCH 149/183] remove separator line from Stepper --- packages/block-library/src/columns/edit.native.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index b0602950380094..cf26af3ad2d66a 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -114,6 +114,7 @@ function ColumnsEditContainer( { } min={ MIN_COLUMNS_NUMBER } max={ columnCount + 1 } + separatorType={ 'none' } /> </PanelBody> </InspectorControls> From bb89794ad9b5a8c30db5c03168a343eb06083761 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 19 Mar 2020 14:56:32 +0100 Subject: [PATCH 150/183] Revert "Revert "Set flex-shrink to 0 on the cell container (#20768)"" This reverts commit cc6108033a0f85c92b2ae4e3cfdec270f718a85b. --- packages/components/src/mobile/bottom-sheet/styles.native.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/mobile/bottom-sheet/styles.native.scss b/packages/components/src/mobile/bottom-sheet/styles.native.scss index 04f1651bfc795a..254817bfb44490 100644 --- a/packages/components/src/mobile/bottom-sheet/styles.native.scss +++ b/packages/components/src/mobile/bottom-sheet/styles.native.scss @@ -112,7 +112,7 @@ .cellRowContainer { flex-direction: row; align-items: center; - flex-shrink: 1; + flex-shrink: 0; } .cellIcon { From 49d71b686d0a1e4ab8cb9d429996741c97342e75 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Wed, 25 Mar 2020 14:36:21 +0100 Subject: [PATCH 151/183] remove unused code --- .../block-library/src/column/edit.native.js | 2 -- .../block-library/src/columns/edit.native.js | 31 +++---------------- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 41249a1d3a674d..fc8e6d88287173 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -13,7 +13,6 @@ import { BlockControls, BlockVerticalAlignmentToolbar, } from '@wordpress/block-editor'; -import { withViewportMatch } from '@wordpress/viewport'; /** * Internal dependencies */ @@ -213,6 +212,5 @@ export default compose( [ isDescendantSelected, }; } ), - withViewportMatch( { isMobile: '< mobile' } ), withPreferredColorScheme, ] )( ColumnEditWrapper ); diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index cf26af3ad2d66a..3063b276d3d455 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -17,10 +17,7 @@ import { } from '@wordpress/block-editor'; import { withDispatch, useSelect } from '@wordpress/data'; import { useEffect, useState } from '@wordpress/element'; -import { - withPreferredColorScheme, - useResizeObserver, -} from '@wordpress/compose'; +import { useResizeObserver } from '@wordpress/compose'; import { createBlock } from '@wordpress/blocks'; /** * Internal dependencies @@ -242,39 +239,21 @@ const ColumnsEditContainerWrapper = withDispatch( )( ColumnsEditContainer ); const ColumnsEdit = ( props ) => { - const { clientId, isSelected, getStylesFromColorScheme } = props; - const { hasChildren, columnCount } = useSelect( + const { clientId } = props; + const { columnCount } = useSelect( ( select ) => { - const { getBlocks, getBlockCount } = select( 'core/block-editor' ); + const { getBlockCount } = select( 'core/block-editor' ); return { - hasChildren: getBlocks( clientId ).length > 0, columnCount: getBlockCount( clientId ), }; }, [ clientId ] ); - if ( ! isSelected && ! hasChildren ) { - return ( - <View - style={ [ - getStylesFromColorScheme( - styles.columnsPlaceholder, - styles.columnsPlaceholderDark - ), - ! hasChildren && { - ...styles.marginVerticalDense, - ...styles.marginHorizontalNone, - }, - ] } - /> - ); - } - return ( <ColumnsEditContainerWrapper columnCount={ columnCount } { ...props } /> ); }; -export default withPreferredColorScheme( ColumnsEdit ); +export default ColumnsEdit; From 69f6eafabb6e7f2dff18365774d37ffbe216e7cc Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Wed, 25 Mar 2020 16:36:13 +0100 Subject: [PATCH 152/183] read verticalAlignment from Columns and pass to new Column --- .../block-library/src/columns/edit.native.js | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 3063b276d3d455..7bcb6d8cd5f38a 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -190,7 +190,9 @@ const ColumnsEditContainerWrapper = withDispatch( updateColumns( previousColumns, newColumns ) { const { clientId } = ownProps; const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); - const { getBlocks } = registry.select( 'core/block-editor' ); + const { getBlocks, getBlockAttributes } = registry.select( + 'core/block-editor' + ); let innerBlocks = getBlocks( clientId ); @@ -198,10 +200,15 @@ const ColumnsEditContainerWrapper = withDispatch( const isAddingColumn = newColumns > previousColumns; if ( isAddingColumn ) { + // Get verticalAlignment from Columns block to set the same to new Column + const { verticalAlignment } = getBlockAttributes( clientId ); + innerBlocks = [ ...innerBlocks, ...times( newColumns - previousColumns, () => { - return createBlock( 'core/column' ); + return createBlock( 'core/column', { + verticalAlignment, + } ); } ), ]; } else { @@ -219,11 +226,18 @@ const ColumnsEditContainerWrapper = withDispatch( const { replaceInnerBlocks, selectBlock } = dispatch( 'core/block-editor' ); - const { getBlocks } = registry.select( 'core/block-editor' ); + const { getBlocks, getBlockAttributes } = registry.select( + 'core/block-editor' + ); + + // Get verticalAlignment from Columns block to set the same to new Column + const { verticalAlignment } = getBlockAttributes( clientId ); const innerBlocks = getBlocks( clientId ); - const insertedBlock = createBlock( 'core/column' ); + const insertedBlock = createBlock( 'core/column', { + verticalAlignment, + } ); innerBlocks.push( insertedBlock ); From 87f24bf1bfd9db69418c0ed5b0238d3efe93effe Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Wed, 25 Mar 2020 17:42:28 +0100 Subject: [PATCH 153/183] initial refactor on passed prop name --- .../src/components/block-list/block.native.js | 2 +- .../src/components/block-list/index.native.js | 8 ++++---- .../src/components/inner-blocks/index.native.js | 4 ++-- packages/block-library/src/column/edit.native.js | 4 ++-- packages/block-library/src/columns/edit.native.js | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 61198b31570456..7ac790ade0e1e9 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -67,7 +67,7 @@ class BlockListBlock extends Component { this.props.onCaretVerticalPositionChange } clientId={ this.props.clientId } - columnsSettings={ this.props.columnsSettings } + customBlockProps={ this.props.customBlockProps } /> ); } diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index bd354b5fd077f9..50d84f39516083 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -69,9 +69,9 @@ export class BlockList extends Component { } renderDefaultBlockAppender() { - const { shouldShowInsertionPointBefore, columnsSettings } = this.props; + const { shouldShowInsertionPointBefore, customBlockProps } = this.props; const willShowInsertionPoint = shouldShowInsertionPointBefore(); // call without the client_id argument since this is the appender - const parentWidth = columnsSettings && columnsSettings.width; + const parentWidth = customBlockProps && customBlockProps.width; return ( <ReadableContentView style={ parentWidth && { maxWidth: parentWidth } } @@ -166,7 +166,7 @@ export class BlockList extends Component { __experimentalMoverDirection, customOnDelete, containerStyle, - columnsSettings, + customBlockProps, } = this.props; const horizontalDirection = @@ -191,7 +191,7 @@ export class BlockList extends Component { } customOnDelete={ customOnDelete } horizontalDirection={ horizontalDirection } - columnsSettings={ columnsSettings } + customBlockProps={ customBlockProps } /> { ! this.shouldShowInnerBlockAppender() && shouldShowInsertionPointAfter( clientId ) && ( diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index 43c55500cf72e4..a7be8a81559686 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -116,7 +116,7 @@ class InnerBlocks extends Component { customOnDelete, containerStyle, flatListProps, - columnsSettings, + customBlockProps, } = this.props; const { templateInProcess } = this.state; @@ -133,7 +133,7 @@ class InnerBlocks extends Component { customOnDelete={ customOnDelete } containerStyle={ containerStyle } flatListProps={ flatListProps } - columnsSettings={ columnsSettings } + customBlockProps={ customBlockProps } /> ) } </> diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index fc8e6d88287173..680f870fecb56a 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -28,10 +28,10 @@ function ColumnEdit( { isDescendantOfParentSelected, isDescendantSelected, isAncestorSelected, - columnsSettings, + customBlockProps, } ) { const { verticalAlignment } = attributes; - const { columnsInRow, width: columnsContainerWidth } = columnsSettings; + const { columnsInRow, width: columnsContainerWidth } = customBlockProps; const containerMaxWidth = styles[ 'columns-container' ].maxWidth; diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 7bcb6d8cd5f38a..81de906dd6de9b 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -141,7 +141,7 @@ function ColumnsEditContainer( { } } containerStyle={ { flex: 1 } } allowedBlocks={ ALLOWED_BLOCKS } - columnsSettings={ columnsSettings } + customBlockProps={ columnsSettings } customOnAdd={ onAddNextColumn } customOnDelete={ columnCount === 1 && onDelete } /> From 037482110e54ddae3e93eb9df7b992da432d9cb9 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Wed, 25 Mar 2020 18:10:35 +0100 Subject: [PATCH 154/183] refactor passed props --- .../src/components/block-list/block.native.js | 4 +++- .../src/components/block-list/index.native.js | 9 ++++----- .../src/components/inner-blocks/index.native.js | 4 ---- packages/block-library/src/columns/edit.native.js | 13 ++++++++----- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 7ac790ade0e1e9..9f36833c5b65ba 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -192,7 +192,7 @@ class BlockListBlock extends Component { title, parentId, isTouchable, - customOnDelete, + customBlockProps, horizontalDirection, hasParent, onSelect, @@ -205,6 +205,8 @@ class BlockListBlock extends Component { order + 1 ); + const { customOnDelete } = customBlockProps || {}; + return ( <TouchableWithoutFeedback onPress={ this.onFocus } diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 50d84f39516083..42f604b5e73407 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -164,18 +164,18 @@ export class BlockList extends Component { shouldShowInsertionPointBefore, shouldShowInsertionPointAfter, __experimentalMoverDirection, - customOnDelete, - containerStyle, customBlockProps, } = this.props; const horizontalDirection = __experimentalMoverDirection === 'horizontal'; + const { readableContentViewStyle } = customBlockProps || {}; + return ( - <ReadableContentView style={ containerStyle }> + <ReadableContentView style={ readableContentViewStyle }> <View - style={ containerStyle } + style={ readableContentViewStyle } pointerEvents={ isReadOnly ? 'box-only' : 'auto' } > { shouldShowInsertionPointBefore( clientId ) && ( @@ -189,7 +189,6 @@ export class BlockList extends Component { onCaretVerticalPositionChange={ this.onCaretVerticalPositionChange } - customOnDelete={ customOnDelete } horizontalDirection={ horizontalDirection } customBlockProps={ customBlockProps } /> diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index a7be8a81559686..ce62d50bb8dbb7 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -113,8 +113,6 @@ class InnerBlocks extends Component { clientId, renderAppender, __experimentalMoverDirection, - customOnDelete, - containerStyle, flatListProps, customBlockProps, } = this.props; @@ -130,8 +128,6 @@ class InnerBlocks extends Component { __experimentalMoverDirection={ __experimentalMoverDirection } - customOnDelete={ customOnDelete } - containerStyle={ containerStyle } flatListProps={ flatListProps } customBlockProps={ customBlockProps } /> diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 81de906dd6de9b..41f6679a98b7f7 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -129,7 +129,7 @@ function ColumnsEditContainer( { __experimentalMoverDirection={ getColumnsInRow( width, columnCount ) > 1 ? 'horizontal' - : '' + : undefined } flatListProps={ { contentContainerStyle: { @@ -139,11 +139,14 @@ function ColumnsEditContainer( { horizontal: true, scrollEnabled: false, } } - containerStyle={ { flex: 1 } } allowedBlocks={ ALLOWED_BLOCKS } - customBlockProps={ columnsSettings } - customOnAdd={ onAddNextColumn } - customOnDelete={ columnCount === 1 && onDelete } + customBlockProps={ { + ...columnsSettings, + readableContentViewStyle: { flex: 1 }, + customOnAdd: onAddNextColumn, + customOnDelete: + columnCount === 1 ? onDelete : undefined, + } } /> </View> </> From 37bad8545c7ec3313cb991a2cd88187eae694f99 Mon Sep 17 00:00:00 2001 From: Drapich Piotr <drapich.piotr@gmail.com> Date: Thu, 26 Mar 2020 16:41:44 +0100 Subject: [PATCH 155/183] [RNMobile] Adjust new borders implementations to columns PR (#21073) * adjustments for Columns block to adapt new bordering logic --- packages/base-styles/_variables.scss | 9 -- .../src/components/block-list/index.native.js | 1 + .../block-mobile-toolbar/style.native.scss | 2 - .../block-library/src/column/edit.native.js | 101 +++--------------- .../src/column/editor.native.scss | 43 +++----- .../block-library/src/columns/edit.native.js | 8 +- .../src/columns/editor.native.scss | 36 ++----- 7 files changed, 41 insertions(+), 159 deletions(-) diff --git a/packages/base-styles/_variables.scss b/packages/base-styles/_variables.scss index 5b3c4e9605a69f..84748883392374 100644 --- a/packages/base-styles/_variables.scss +++ b/packages/base-styles/_variables.scss @@ -91,15 +91,6 @@ $block-selected-padding: 0; $block-selected-child-margin: 5px; $block-selected-to-content: $block-edge-to-content - $block-selected-margin - $block-selected-border-width; -$block-selected-child-border-width: 1px; -$block-selected-child-padding: 0; -$block-selected-child-to-content: $block-selected-to-content - $block-selected-child-margin - $block-selected-child-border-width; -$block-custom-appender-to-content: $block-selected-margin - $block-selected-border-width; -$block-media-container-to-content: $block-selected-child-margin + $block-selected-border-width; -$block-selected-vertical-margin-descendant: 2 * $block-selected-to-content; -$block-selected-vertical-margin-child: $block-edge-to-content; -$block-toolbar-margin: $block-selected-margin + $block-selected-border-width; - /** * Border radii. */ diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index c2827bebd5bd16..07171c18f360e8 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -137,6 +137,7 @@ export class BlockList extends Component { keyboardShouldPersistTaps="always" scrollViewStyle={ { flex: isRootList ? 1 : 0, + ...( ! isRootList && { overflow: 'visible' } ), } } data={ blockClientIds } keyExtractor={ identity } diff --git a/packages/block-editor/src/components/block-mobile-toolbar/style.native.scss b/packages/block-editor/src/components/block-mobile-toolbar/style.native.scss index 093b80ea11b2ea..4737858744fcfe 100644 --- a/packages/block-editor/src/components/block-mobile-toolbar/style.native.scss +++ b/packages/block-editor/src/components/block-mobile-toolbar/style.native.scss @@ -2,8 +2,6 @@ flex-direction: row; height: $mobile-block-toolbar-height; align-items: flex-start; - margin-left: $block-toolbar-margin; - margin-right: $block-toolbar-margin; } .spacer { diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 680f870fecb56a..c3e9d2fb7064f3 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -25,9 +25,6 @@ function ColumnEdit( { isSelected, getStylesFromColorScheme, isParentSelected, - isDescendantOfParentSelected, - isDescendantSelected, - isAncestorSelected, customBlockProps, } ) { const { verticalAlignment } = attributes; @@ -40,69 +37,11 @@ function ColumnEdit( { const minWidth = Math.min( containerWidth, containerMaxWidth ); const columnBaseWidth = minWidth / Math.max( 1, columnsInRow ); - const applyBlockStyle = ( placeholder = false ) => { - const pullWidths = ( names ) => - names.map( - ( name ) => - ( styles[ `column-${ name }-margin` ] || {} ).width || 0 - ); - + const applyBlockStyle = () => { let width = columnBaseWidth; - const names = [ - 'selected', - 'parent-selected', - 'descendant-selected', - 'placeholder-selected', - 'dashed-border', - ]; - const widths = pullWidths( names ); - const [ - emptyColumnSelected, - parentSelected, - columnSelected, - placeholderParentSelected, - dashedBorderWidth, - ] = widths; - - switch ( true ) { - case isSelected: - width = columnBaseWidth; - width -= ! hasChildren - ? emptyColumnSelected - : columnSelected + dashedBorderWidth; - break; - - case isParentSelected: - width -= placeholder - ? placeholderParentSelected - : parentSelected; - break; - - case isDescendantSelected: - width = columnBaseWidth; - break; - - case isDescendantOfParentSelected: - width = columnBaseWidth; - if ( ! hasChildren ) width -= emptyColumnSelected; - break; - - case isAncestorSelected: - width = columnBaseWidth; - if ( ! hasChildren ) width -= parentSelected; - break; - - case placeholder: - width -= - columnsInRow === 1 - ? parentSelected - : placeholderParentSelected; - width -= - columnSelected + - ( columnsInRow === 1 ? parentSelected : dashedBorderWidth ); - break; - - default: + if ( columnsInRow > 1 ) { + const margins = columnsInRow * 2 * styles.columnMargin.marginLeft; + width = ( minWidth - margins ) / Math.max( 1, columnsInRow ); } return { width }; @@ -121,11 +60,8 @@ function ColumnEdit( { styles.columnPlaceholder, styles.columnPlaceholderDark ), - applyBlockStyle( true ), - { - ...styles.marginVerticalDense, - ...styles.marginHorizontalNone, - }, + applyBlockStyle(), + styles.columnPlaceholderNotSelected, ] } ></View> ); @@ -140,10 +76,16 @@ function ColumnEdit( { isCollapsed={ false } /> </BlockControls> - <View style={ applyBlockStyle() }> + <View + style={ [ + applyBlockStyle(), + isSelected && hasChildren && styles.innerBlocksBottomSpace, + ] } + > <InnerBlocks flatListProps={ { scrollEnabled: false, + style: styles.innerBlocks, } } renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender @@ -175,7 +117,6 @@ function ColumnEditWrapper( props ) { export default compose( [ withSelect( ( select, { clientId } ) => { const { - getBlockParents, getBlockCount, getBlockRootClientId, getSelectedBlockClientId, @@ -190,26 +131,10 @@ export default compose( [ const isParentSelected = selectedBlockClientId && selectedBlockClientId === parentId; - const selectedParents = selectedBlockClientId - ? getBlockParents( selectedBlockClientId ) - : []; - const isDescendantOfParentSelected = selectedParents.includes( - parentId - ); - - const parents = getBlockParents( clientId, true ); - - const isAncestorSelected = - selectedBlockClientId && parents.includes( selectedBlockClientId ); - const isDescendantSelected = selectedParents.includes( clientId ); - return { hasChildren, isParentSelected, isSelected, - isDescendantOfParentSelected, - isAncestorSelected, - isDescendantSelected, }; } ), withPreferredColorScheme, diff --git a/packages/block-library/src/column/editor.native.scss b/packages/block-library/src/column/editor.native.scss index 25fbd03803878e..64d2045d519c87 100644 --- a/packages/block-library/src/column/editor.native.scss +++ b/packages/block-library/src/column/editor.native.scss @@ -1,4 +1,9 @@ +.columnPlaceholderNotSelected { + padding-top: $block-selected-to-content; +} + .columnPlaceholder { + flex: 1; padding: $block-selected-to-content; background-color: $white; border: $border-width dashed $gray; @@ -10,44 +15,18 @@ border: $border-width dashed $gray-70; } -.marginVerticalDense { - margin-top: $block-selected-child-to-content; - margin-bottom: $block-selected-child-to-content; +.innerBlocks { + overflow: visible; } -.marginHorizontalNone { - margin-left: 0; - margin-right: 0; +.innerBlocksBottomSpace { + margin-bottom: $block-selected-to-content; } .columns-container { max-width: $content-width; } -.column-container-base { - max-width: $content-width - 2 * ( $block-selected-margin + $block-selected-border-width ); -} - -.column-placeholder-selected-margin { - width: 2 * $block-selected-to-content; -} - -.column-parent-selected-margin { - width: $block-selected-to-content; -} - -.column-selected-margin { - width: 2 * $block-edge-to-content; -} - -.column-descendant-selected-margin { - width: 2 * $block-selected-margin; -} - -.column-dashed-border-margin { - width: 2 * $block-selected-border-width; -} - .is-vertically-aligned-top { justify-content: flex-start; } @@ -63,3 +42,7 @@ .flexBase { flex: 1; } + +.columnMargin { + margin: $block-edge-to-content / 2; +} diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 41f6679a98b7f7..5fc2729d36bdd2 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -122,7 +122,7 @@ function ColumnsEditContainer( { isCollapsed={ false } /> </BlockControls> - <View> + <View style={ isSelected && styles.innerBlocksSelected }> { resizeListener } <InnerBlocks renderAppender={ renderAppender } @@ -132,12 +132,10 @@ function ColumnsEditContainer( { : undefined } flatListProps={ { - contentContainerStyle: { - ...styles.columnsContainer, - maxWidth: width, - }, + contentContainerStyle: styles.columnsContainer, horizontal: true, scrollEnabled: false, + style: styles.innerBlocks, } } allowedBlocks={ ALLOWED_BLOCKS } customBlockProps={ { diff --git a/packages/block-library/src/columns/editor.native.scss b/packages/block-library/src/columns/editor.native.scss index 4b59f9833b7d29..90c01e39748129 100644 --- a/packages/block-library/src/columns/editor.native.scss +++ b/packages/block-library/src/columns/editor.native.scss @@ -1,31 +1,17 @@ -.columnsPlaceholder { - padding: 12px; - margin-bottom: 12px; - background-color: $white; - border: $border-width dashed $gray; - border-radius: 4px; -} - -.columnsPlaceholderDark { - background-color: $black; - border: $border-width dashed $gray-70; -} - -.marginVerticalDense { - margin-top: $block-selected-child-margin; - margin-bottom: $block-selected-child-margin; -} - -.marginHorizontalNone { - margin-left: 0; - margin-right: 0; -} - .columnsContainer { flex-direction: row; flex-wrap: wrap; - justify-content: flex-start; + justify-content: space-between; align-items: stretch; max-width: $content-width; - overflow: hidden; + overflow: visible; + width: 100%; +} + +.innerBlocks { + overflow: visible; +} + +.innerBlocksSelected { + margin-bottom: $block-edge-to-content; } From 4b2f1a52b6c92e0aadfce12678c31fc1c17884cd Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 27 Mar 2020 06:40:31 +0100 Subject: [PATCH 156/183] bring back original toolbar margin --- .../src/components/block-mobile-toolbar/style.native.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/block-editor/src/components/block-mobile-toolbar/style.native.scss b/packages/block-editor/src/components/block-mobile-toolbar/style.native.scss index 4737858744fcfe..0a0bcd016d5b37 100644 --- a/packages/block-editor/src/components/block-mobile-toolbar/style.native.scss +++ b/packages/block-editor/src/components/block-mobile-toolbar/style.native.scss @@ -2,6 +2,8 @@ flex-direction: row; height: $mobile-block-toolbar-height; align-items: flex-start; + margin-left: 2px; + margin-right: 2px; } .spacer { From 8b08fb4309509df403dc152f94f485ef88e6f52d Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 27 Mar 2020 08:49:02 +0100 Subject: [PATCH 157/183] extract BREAKPOINTS to const --- packages/block-library/src/columns/edit.native.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 5fc2729d36bdd2..269ec00fa4bd69 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -44,6 +44,11 @@ const ALLOWED_BLOCKS = [ 'core/column' ]; const DEFAULT_COLUMNS = 2; const MIN_COLUMNS_NUMBER = 1; +const BREAKPOINTS = { + mobile: 480, + large: 768, +}; + function ColumnsEditContainer( { attributes, updateAlignment, @@ -78,12 +83,14 @@ function ColumnsEditContainer( { }, [ width ] ); const getColumnsInRow = ( containerWidth, columnsNumber ) => { - if ( containerWidth < 480 ) { + if ( containerWidth < BREAKPOINTS.mobile ) { + // show only 1 Column in row for mobile breakpoint container width return 1; - } - if ( containerWidth >= 480 && containerWidth < 768 ) { + } else if ( containerWidth < BREAKPOINTS.large ) { + // show 2 Column in row for large breakpoint container width return Math.min( columnCount, 2 ); } + // show all Column in one row return columnsNumber; }; From 05bd9baed27fc396b94faced222cc382397a4602 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Tue, 31 Mar 2020 10:53:19 +0200 Subject: [PATCH 158/183] refactor customOnDelete to onDelete --- .../src/components/block-list/block.native.js | 4 ++-- .../block-mobile-toolbar/index.native.js | 15 ++++++++------- packages/block-library/src/columns/edit.native.js | 3 +-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 9144e5814cdb86..b890f38f9fd56e 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -109,7 +109,7 @@ class BlockListBlock extends Component { order + 1 ); - const { customOnDelete } = customBlockProps || {}; + const { onDelete } = customBlockProps || {}; return ( <TouchableWithoutFeedback @@ -178,7 +178,7 @@ class BlockListBlock extends Component { { isSelected && ( <BlockMobileToolbar clientId={ clientId } - customOnDelete={ customOnDelete } + onDelete={ onDelete } horizontalDirection={ horizontalDirection } /> ) } diff --git a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js index bb96be1e69fab3..b4a56abc4df1b0 100644 --- a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js +++ b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js @@ -23,7 +23,6 @@ const BlockMobileToolbar = ( { clientId, onDelete, order, - customOnDelete, horizontalDirection, } ) => ( <View style={ styles.toolbar }> @@ -42,7 +41,7 @@ const BlockMobileToolbar = ( { __( 'Remove block at row %s' ), order + 1 ) } - onClick={ customOnDelete || onDelete } + onClick={ onDelete } icon={ trash } extraProps={ { hint: __( 'Double tap to remove the block' ) } } /> @@ -57,13 +56,15 @@ export default compose( order: getBlockIndex( clientId ), }; } ), - withDispatch( ( dispatch, { clientId, rootClientId } ) => { + withDispatch( ( dispatch, { clientId, rootClientId, onDelete } ) => { const { removeBlock } = dispatch( 'core/block-editor' ); return { - onDelete: () => { - Keyboard.dismiss(); - removeBlock( clientId, rootClientId ); - }, + onDelete: + onDelete || + ( () => { + Keyboard.dismiss(); + removeBlock( clientId, rootClientId ); + } ), }; } ) )( BlockMobileToolbar ); diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 269ec00fa4bd69..8fced831c704e4 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -149,8 +149,7 @@ function ColumnsEditContainer( { ...columnsSettings, readableContentViewStyle: { flex: 1 }, customOnAdd: onAddNextColumn, - customOnDelete: - columnCount === 1 ? onDelete : undefined, + onDelete: columnCount === 1 ? onDelete : undefined, } } /> </View> From 09b58c15514ec8d4a9493dabe3e9dda1bd1407d1 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Tue, 31 Mar 2020 11:24:37 +0200 Subject: [PATCH 159/183] change aplyBlockStyle name to applyColumnWidth --- packages/block-library/src/column/edit.native.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index c3e9d2fb7064f3..bbafed501ca6fe 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -37,7 +37,7 @@ function ColumnEdit( { const minWidth = Math.min( containerWidth, containerMaxWidth ); const columnBaseWidth = minWidth / Math.max( 1, columnsInRow ); - const applyBlockStyle = () => { + const applyColumnWidth = () => { let width = columnBaseWidth; if ( columnsInRow > 1 ) { const margins = columnsInRow * 2 * styles.columnMargin.marginLeft; @@ -60,7 +60,7 @@ function ColumnEdit( { styles.columnPlaceholder, styles.columnPlaceholderDark ), - applyBlockStyle(), + applyColumnWidth(), styles.columnPlaceholderNotSelected, ] } ></View> @@ -78,7 +78,7 @@ function ColumnEdit( { </BlockControls> <View style={ [ - applyBlockStyle(), + applyColumnWidth(), isSelected && hasChildren && styles.innerBlocksBottomSpace, ] } > From bc8230a81bb39a0e8623da255b1d9c2409862c60 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Tue, 31 Mar 2020 11:36:59 +0200 Subject: [PATCH 160/183] return horizontalDIrection prop from composed withSelect --- .../block-editor/src/components/block-list/index.native.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 07171c18f360e8..f7f13dd9f6fda1 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -180,15 +180,12 @@ export class BlockList extends Component { isReadOnly, shouldShowInsertionPointBefore, shouldShowInsertionPointAfter, - __experimentalMoverDirection, customBlockProps, marginVertical = styles.defaultBlock.marginTop, marginHorizontal = styles.defaultBlock.marginLeft, + horizontalDirection, } = this.props; - const horizontalDirection = - __experimentalMoverDirection === 'horizontal'; - const { readableContentViewStyle } = customBlockProps || {}; return ( @@ -291,6 +288,7 @@ export default compose( [ selectedBlockClientId, isReadOnly, isRootList: rootClientId === undefined, + horizontalDirection, }; } ), withDispatch( ( dispatch ) => { From 5082a2b2574a811c916cec4ffd88b0b3c215ea80 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Tue, 31 Mar 2020 11:38:54 +0200 Subject: [PATCH 161/183] remove isCollapsed from alignmentToolbar --- packages/block-library/src/column/edit.native.js | 1 - packages/block-library/src/columns/edit.native.js | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index bbafed501ca6fe..e941fb24882eb6 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -73,7 +73,6 @@ function ColumnEdit( { <BlockVerticalAlignmentToolbar onChange={ updateAlignment } value={ verticalAlignment } - isCollapsed={ false } /> </BlockControls> <View diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 8fced831c704e4..9e402c74c98f98 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -126,7 +126,6 @@ function ColumnsEditContainer( { <BlockVerticalAlignmentToolbar onChange={ updateAlignment } value={ verticalAlignment } - isCollapsed={ false } /> </BlockControls> <View style={ isSelected && styles.innerBlocksSelected }> From 066e7713008a4f32fb1b4d48ab604ca7ffbbaedc Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Tue, 31 Mar 2020 11:40:38 +0200 Subject: [PATCH 162/183] refactor readable content view --- .../src/mobile/readable-content-view/index.native.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/components/src/mobile/readable-content-view/index.native.js b/packages/components/src/mobile/readable-content-view/index.native.js index 55200c4b6541fc..ec1f67a4d98f89 100644 --- a/packages/components/src/mobile/readable-content-view/index.native.js +++ b/packages/components/src/mobile/readable-content-view/index.native.js @@ -11,12 +11,12 @@ import styles from './style.scss'; const ReadableContentView = ( { reversed, children, style } ) => ( <View style={ styles.container }> <View - style={ { - ...( reversed + style={ [ + reversed ? styles.reversedCenteredContent - : styles.centeredContent ), - ...style, - } } + : styles.centeredContent, + style, + ] } > { children } </View> From 8949595c5711c4fc271aff0ebfd08d883af16c5b Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Tue, 31 Mar 2020 11:51:39 +0200 Subject: [PATCH 163/183] refactor block-mover after code review --- .../components/block-mover/index.native.js | 56 ++++++++++++------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/packages/block-editor/src/components/block-mover/index.native.js b/packages/block-editor/src/components/block-mover/index.native.js index 8eda82942ad04b..9c270ca6d0e28f 100644 --- a/packages/block-editor/src/components/block-mover/index.native.js +++ b/packages/block-editor/src/components/block-mover/index.native.js @@ -12,6 +12,30 @@ import { withSelect, withDispatch } from '@wordpress/data'; import { withInstanceId, compose } from '@wordpress/compose'; import { arrowUp, arrowDown, arrowLeft, arrowRight } from '@wordpress/icons'; +const horizontalMover = { + firstButtonIcon: arrowLeft, + secondButtonIcon: arrowRight, + firstButtonHint: __( 'Double tap to move the block to the left' ), + secondButtonHint: __( 'Double tap to move the block to the right' ), + firstBlockTitle: __( 'Move block left' ), + lastBlockTitle: __( 'Move block right' ), + firstButtonDirection: 'left', + secondButtonDirection: 'right', + location: 'position', +} + +const verticalMover = { + firstButtonIcon: arrowUp, + secondButtonIcon: arrowDown, + firstButtonHint: __( 'Double tap to move the block up' ), + secondButtonHint: __( 'Double tap to move the block down' ), + firstBlockTitle: __( 'Move block up' ), + lastBlockTitle: __( 'Move block down' ), + firstButtonDirection: 'up', + secondButtonDirection: 'down', + location: 'row', +} + const BlockMover = ( { isFirst, isLast, @@ -22,27 +46,17 @@ const BlockMover = ( { rootClientId, horizontalDirection, } ) => { - const firstButtonIcon = horizontalDirection ? arrowLeft : arrowUp; - const secondButtonIcon = horizontalDirection ? arrowRight : arrowDown; - - const firstButtonHint = horizontalDirection - ? __( 'Double tap to move the block to the left' ) - : __( 'Double tap to move the block up' ); - const secondButtonHint = horizontalDirection - ? __( 'Double tap to move the block to the right' ) - : __( 'Double tap to move the block down' ); - - const firstBlockTitle = horizontalDirection - ? __( 'Move block left' ) - : __( 'Move block up' ); - const lastBlockTitle = horizontalDirection - ? __( 'Move block right' ) - : __( 'Move block down' ); - - const firstButtonDirection = horizontalDirection ? 'left' : 'up'; - const secondButtonDirection = horizontalDirection ? 'right' : 'down'; - - const location = horizontalDirection ? 'position' : 'row'; + const { + firstButtonIcon, + secondButtonIcon, + firstButtonHint, + secondButtonHint, + firstBlockTitle, + lastBlockTitle, + firstButtonDirection, + secondButtonDirection, + location, + } = horizontalDirection ? horizontalMover : verticalMover; if ( isLocked || ( isFirst && isLast && ! rootClientId ) ) { return null; From f6f80bf69ecc96a43cf5a19f3130df12c89bb89a Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Tue, 31 Mar 2020 11:51:54 +0200 Subject: [PATCH 164/183] refactor block-mover after code review --- .../block-editor/src/components/block-mover/index.native.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/block-mover/index.native.js b/packages/block-editor/src/components/block-mover/index.native.js index 9c270ca6d0e28f..747a52d5c18d7e 100644 --- a/packages/block-editor/src/components/block-mover/index.native.js +++ b/packages/block-editor/src/components/block-mover/index.native.js @@ -16,13 +16,13 @@ const horizontalMover = { firstButtonIcon: arrowLeft, secondButtonIcon: arrowRight, firstButtonHint: __( 'Double tap to move the block to the left' ), - secondButtonHint: __( 'Double tap to move the block to the right' ), + secondButtonHint: __( 'Double tap to move the block to the right' ), firstBlockTitle: __( 'Move block left' ), lastBlockTitle: __( 'Move block right' ), firstButtonDirection: 'left', secondButtonDirection: 'right', location: 'position', -} +}; const verticalMover = { firstButtonIcon: arrowUp, @@ -34,7 +34,7 @@ const verticalMover = { firstButtonDirection: 'up', secondButtonDirection: 'down', location: 'row', -} +}; const BlockMover = ( { isFirst, From cacd2829ffba39d233283ee97eca54658f838904 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 2 Apr 2020 15:45:38 +0200 Subject: [PATCH 165/183] change applyColumnWidth function name --- packages/block-library/src/column/edit.native.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index e941fb24882eb6..b4348ceaebfab5 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -37,7 +37,7 @@ function ColumnEdit( { const minWidth = Math.min( containerWidth, containerMaxWidth ); const columnBaseWidth = minWidth / Math.max( 1, columnsInRow ); - const applyColumnWidth = () => { + const getColumnWidth = () => { let width = columnBaseWidth; if ( columnsInRow > 1 ) { const margins = columnsInRow * 2 * styles.columnMargin.marginLeft; @@ -60,7 +60,7 @@ function ColumnEdit( { styles.columnPlaceholder, styles.columnPlaceholderDark ), - applyColumnWidth(), + getColumnWidth(), styles.columnPlaceholderNotSelected, ] } ></View> @@ -77,7 +77,7 @@ function ColumnEdit( { </BlockControls> <View style={ [ - applyColumnWidth(), + getColumnWidth(), isSelected && hasChildren && styles.innerBlocksBottomSpace, ] } > From 9c326e23020b7c30400e1e22d830ff8ca8debe44 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 2 Apr 2020 16:15:02 +0200 Subject: [PATCH 166/183] apply translation on up/down left/right words --- .../src/components/block-mover/index.native.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/block-mover/index.native.js b/packages/block-editor/src/components/block-mover/index.native.js index 747a52d5c18d7e..a2feee71157a6a 100644 --- a/packages/block-editor/src/components/block-mover/index.native.js +++ b/packages/block-editor/src/components/block-mover/index.native.js @@ -19,8 +19,8 @@ const horizontalMover = { secondButtonHint: __( 'Double tap to move the block to the right' ), firstBlockTitle: __( 'Move block left' ), lastBlockTitle: __( 'Move block right' ), - firstButtonDirection: 'left', - secondButtonDirection: 'right', + firstButtonDirection: __( 'left' ), + secondButtonDirection: __( 'right' ), location: 'position', }; @@ -31,8 +31,8 @@ const verticalMover = { secondButtonHint: __( 'Double tap to move the block down' ), firstBlockTitle: __( 'Move block up' ), lastBlockTitle: __( 'Move block down' ), - firstButtonDirection: 'up', - secondButtonDirection: 'down', + firstButtonDirection: __( 'up' ), + secondButtonDirection: __( 'down' ), location: 'row', }; From 8c09ae0a01cb85d1834b4b73337133773509adab Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 3 Apr 2020 13:17:26 +0200 Subject: [PATCH 167/183] unify name for onAddBlock and onDeleteBlock action --- .../src/components/block-list/block.native.js | 4 ++-- .../components/button-block-appender/index.native.js | 4 ++-- .../components/inner-blocks/button-block-appender.js | 4 ++-- packages/block-library/src/columns/edit.native.js | 11 ++++++----- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index b890f38f9fd56e..c8e89fa7c52bd3 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -109,7 +109,7 @@ class BlockListBlock extends Component { order + 1 ); - const { onDelete } = customBlockProps || {}; + const { onDeleteBlock } = customBlockProps || {}; return ( <TouchableWithoutFeedback @@ -178,7 +178,7 @@ class BlockListBlock extends Component { { isSelected && ( <BlockMobileToolbar clientId={ clientId } - onDelete={ onDelete } + onDelete={ onDeleteBlock } horizontalDirection={ horizontalDirection } /> ) } diff --git a/packages/block-editor/src/components/button-block-appender/index.native.js b/packages/block-editor/src/components/button-block-appender/index.native.js index 34453d5254b9cd..4a3be1a5b5ac06 100644 --- a/packages/block-editor/src/components/button-block-appender/index.native.js +++ b/packages/block-editor/src/components/button-block-appender/index.native.js @@ -20,7 +20,7 @@ function ButtonBlockAppender( { rootClientId, getStylesFromColorScheme, showSeparator, - customOnAdd, + onAddBlock, } ) { const appenderStyle = { ...styles.appender, @@ -40,7 +40,7 @@ function ButtonBlockAppender( { rootClientId={ rootClientId } renderToggle={ ( { onToggle, disabled, isOpen } ) => ( <Button - onClick={ customOnAdd || onToggle } + onClick={ onAddBlock || onToggle } aria-expanded={ isOpen } disabled={ disabled } fixedRatio={ false } diff --git a/packages/block-editor/src/components/inner-blocks/button-block-appender.js b/packages/block-editor/src/components/inner-blocks/button-block-appender.js index ac9c5ddea72846..874366015abd16 100644 --- a/packages/block-editor/src/components/inner-blocks/button-block-appender.js +++ b/packages/block-editor/src/components/inner-blocks/button-block-appender.js @@ -7,13 +7,13 @@ import withClientId from './with-client-id'; export const ButtonBlockAppender = ( { clientId, showSeparator, - customOnAdd, + onAddBlock, } ) => { return ( <BaseButtonBlockAppender rootClientId={ clientId } showSeparator={ showSeparator } - customOnAdd={ customOnAdd } + onAddBlock={ onAddBlock } /> ); }; diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 9e402c74c98f98..649abfb2a00f70 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -56,7 +56,7 @@ function ColumnsEditContainer( { columnCount, isSelected, onAddNextColumn, - onDelete, + onDeleteBlock, } ) { const [ resizeListener, sizes ] = useResizeObserver(); const [ columnsSettings, setColumnsSettings ] = useState( { @@ -98,7 +98,7 @@ function ColumnsEditContainer( { if ( isSelected ) { return ( <InnerBlocks.ButtonBlockAppender - customOnAdd={ onAddNextColumn } + onAddBlock={ onAddNextColumn } /> ); } @@ -147,8 +147,9 @@ function ColumnsEditContainer( { customBlockProps={ { ...columnsSettings, readableContentViewStyle: { flex: 1 }, - customOnAdd: onAddNextColumn, - onDelete: columnCount === 1 ? onDelete : undefined, + onAddBlock: onAddNextColumn, + onDeleteBlock: + columnCount === 1 ? onDeleteBlock : undefined, } } /> </View> @@ -250,7 +251,7 @@ const ColumnsEditContainerWrapper = withDispatch( replaceInnerBlocks( clientId, innerBlocks, true ); selectBlock( insertedBlock.clientId ); }, - onDelete: () => { + onDeleteBlock: () => { const { clientId } = ownProps; const { removeBlock } = dispatch( 'core/block-editor' ); removeBlock( clientId ); From c314d6c8c0dc498e47287daa80477093e219e843 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 3 Apr 2020 15:02:54 +0200 Subject: [PATCH 168/183] refactor on calculate Column width in Columns --- .../block-library/src/column/edit.native.js | 23 ++--------- .../src/column/editor.native.scss | 8 ---- .../block-library/src/columns/edit.native.js | 39 +++++++++++-------- .../src/columns/editor.native.scss | 4 ++ 4 files changed, 29 insertions(+), 45 deletions(-) diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index b4348ceaebfab5..ecb2c07c42d965 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -28,24 +28,7 @@ function ColumnEdit( { customBlockProps, } ) { const { verticalAlignment } = attributes; - const { columnsInRow, width: columnsContainerWidth } = customBlockProps; - - const containerMaxWidth = styles[ 'columns-container' ].maxWidth; - - const containerWidth = columnsContainerWidth || containerMaxWidth; - - const minWidth = Math.min( containerWidth, containerMaxWidth ); - const columnBaseWidth = minWidth / Math.max( 1, columnsInRow ); - - const getColumnWidth = () => { - let width = columnBaseWidth; - if ( columnsInRow > 1 ) { - const margins = columnsInRow * 2 * styles.columnMargin.marginLeft; - width = ( minWidth - margins ) / Math.max( 1, columnsInRow ); - } - - return { width }; - }; + const { width } = customBlockProps; const updateAlignment = ( alignment ) => { setAttributes( { verticalAlignment: alignment } ); @@ -60,7 +43,7 @@ function ColumnEdit( { styles.columnPlaceholder, styles.columnPlaceholderDark ), - getColumnWidth(), + { width }, styles.columnPlaceholderNotSelected, ] } ></View> @@ -77,7 +60,7 @@ function ColumnEdit( { </BlockControls> <View style={ [ - getColumnWidth(), + { width }, isSelected && hasChildren && styles.innerBlocksBottomSpace, ] } > diff --git a/packages/block-library/src/column/editor.native.scss b/packages/block-library/src/column/editor.native.scss index 64d2045d519c87..4657eab7166228 100644 --- a/packages/block-library/src/column/editor.native.scss +++ b/packages/block-library/src/column/editor.native.scss @@ -23,10 +23,6 @@ margin-bottom: $block-selected-to-content; } -.columns-container { - max-width: $content-width; -} - .is-vertically-aligned-top { justify-content: flex-start; } @@ -42,7 +38,3 @@ .flexBase { flex: 1; } - -.columnMargin { - margin: $block-edge-to-content / 2; -} diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 649abfb2a00f70..f20fbdd4bfbdc7 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -59,9 +59,9 @@ function ColumnsEditContainer( { onDeleteBlock, } ) { const [ resizeListener, sizes ] = useResizeObserver(); - const [ columnsSettings, setColumnsSettings ] = useState( { - columnsInRow: MIN_COLUMNS_NUMBER, - } ); + const [ columnsInRow, setColumnsInRow ] = useState( MIN_COLUMNS_NUMBER ); + + const containerMaxWidth = styles.columnsContainer.maxWidth; const { verticalAlignment } = attributes; const { width } = sizes || {}; @@ -69,29 +69,36 @@ function ColumnsEditContainer( { useEffect( () => { const newColumnCount = ! columnCount ? DEFAULT_COLUMNS : columnCount; updateColumns( columnCount, newColumnCount ); - setColumnsSettings( { - ...columnsSettings, - columnsInRow: getColumnsInRow( width, newColumnCount ), - } ); + setColumnsInRow( getColumnsInRow( width, newColumnCount ) ); }, [ columnCount ] ); useEffect( () => { - setColumnsSettings( { - columnsInRow: getColumnsInRow( width, columnCount ), - width, - } ); + setColumnsInRow( getColumnsInRow( width, columnCount ) ); }, [ width ] ); + const getColumnWidth = ( containerWidth = containerMaxWidth ) => { + const minWidth = Math.min( containerWidth, containerMaxWidth ); + const columnBaseWidth = minWidth / columnsInRow; + + let columnWidth = columnBaseWidth; + if ( columnsInRow > 1 ) { + const margins = columnsInRow * 2 * styles.columnMargin.marginLeft; + columnWidth = ( minWidth - margins ) / columnsInRow; + } + + return columnWidth; + }; + const getColumnsInRow = ( containerWidth, columnsNumber ) => { if ( containerWidth < BREAKPOINTS.mobile ) { // show only 1 Column in row for mobile breakpoint container width return 1; } else if ( containerWidth < BREAKPOINTS.large ) { // show 2 Column in row for large breakpoint container width - return Math.min( columnCount, 2 ); + return Math.min( Math.max( 1, columnCount ), 2 ); } // show all Column in one row - return columnsNumber; + return Math.max( 1, columnsNumber ); }; const renderAppender = () => { @@ -133,9 +140,7 @@ function ColumnsEditContainer( { <InnerBlocks renderAppender={ renderAppender } __experimentalMoverDirection={ - getColumnsInRow( width, columnCount ) > 1 - ? 'horizontal' - : undefined + columnsInRow > 1 ? 'horizontal' : undefined } flatListProps={ { contentContainerStyle: styles.columnsContainer, @@ -145,7 +150,7 @@ function ColumnsEditContainer( { } } allowedBlocks={ ALLOWED_BLOCKS } customBlockProps={ { - ...columnsSettings, + width: getColumnWidth( width ), readableContentViewStyle: { flex: 1 }, onAddBlock: onAddNextColumn, onDeleteBlock: diff --git a/packages/block-library/src/columns/editor.native.scss b/packages/block-library/src/columns/editor.native.scss index 90c01e39748129..9e5e978c9ed958 100644 --- a/packages/block-library/src/columns/editor.native.scss +++ b/packages/block-library/src/columns/editor.native.scss @@ -15,3 +15,7 @@ .innerBlocksSelected { margin-bottom: $block-edge-to-content; } + +.columnMargin { + margin: $block-edge-to-content / 2; +} From 1cfb209b7fdfc8fe765ebf9ebf5406968ba9bde2 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 3 Apr 2020 16:16:00 +0200 Subject: [PATCH 169/183] split customBlockProps --- .../src/components/block-list/block.native.js | 6 ++---- .../src/components/block-list/index.native.js | 13 ++++++++++--- .../src/components/inner-blocks/index.native.js | 10 ++++++++-- packages/block-library/src/column/edit.native.js | 7 +++---- packages/block-library/src/columns/edit.native.js | 13 ++++++------- 5 files changed, 29 insertions(+), 20 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index c8e89fa7c52bd3..6b08593d49f4db 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -66,7 +66,7 @@ class BlockListBlock extends Component { this.props.onCaretVerticalPositionChange } clientId={ this.props.clientId } - customBlockProps={ this.props.customBlockProps } + contentStyle={ this.props.contentStyle } /> ); } @@ -92,7 +92,7 @@ class BlockListBlock extends Component { parentId, isDimmed, isTouchable, - customBlockProps, + onDeleteBlock, horizontalDirection, hasParent, isParentSelected, @@ -109,8 +109,6 @@ class BlockListBlock extends Component { order + 1 ); - const { onDeleteBlock } = customBlockProps || {}; - return ( <TouchableWithoutFeedback onPress={ this.onFocus } diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index f7f13dd9f6fda1..c9c1dbe73bac24 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -180,13 +180,18 @@ export class BlockList extends Component { isReadOnly, shouldShowInsertionPointBefore, shouldShowInsertionPointAfter, - customBlockProps, marginVertical = styles.defaultBlock.marginTop, marginHorizontal = styles.defaultBlock.marginLeft, horizontalDirection, + contentResizeMode, + contentStyle, + onAddBlock, + onDeleteBlock, } = this.props; - const { readableContentViewStyle } = customBlockProps || {}; + const readableContentViewStyle = contentResizeMode === 'stretch' && { + flex: 1, + }; return ( <ReadableContentView style={ readableContentViewStyle }> @@ -208,7 +213,9 @@ export class BlockList extends Component { this.onCaretVerticalPositionChange } horizontalDirection={ horizontalDirection } - customBlockProps={ customBlockProps } + contentStyle={ contentStyle } + onAddBlock={ onAddBlock } + onDeleteBlock={ onDeleteBlock } /> { ! this.shouldShowInnerBlockAppender() && shouldShowInsertionPointAfter( clientId ) && ( diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index 71ea9128cf7e7c..f113022cbbdc41 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -114,7 +114,10 @@ class InnerBlocks extends Component { renderAppender, __experimentalMoverDirection, flatListProps, - customBlockProps, + contentResizeMode, + contentStyle, + onAddBlock, + onDeleteBlock, marginVertical, marginHorizontal, } = this.props; @@ -133,7 +136,10 @@ class InnerBlocks extends Component { __experimentalMoverDirection } flatListProps={ flatListProps } - customBlockProps={ customBlockProps } + contentResizeMode={ contentResizeMode } + contentStyle={ contentStyle } + onAddBlock={ onAddBlock } + onDeleteBlock={ onDeleteBlock } /> ) } </> diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index ecb2c07c42d965..9d1d7d39ec785f 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -25,10 +25,9 @@ function ColumnEdit( { isSelected, getStylesFromColorScheme, isParentSelected, - customBlockProps, + contentStyle, } ) { const { verticalAlignment } = attributes; - const { width } = customBlockProps; const updateAlignment = ( alignment ) => { setAttributes( { verticalAlignment: alignment } ); @@ -43,7 +42,7 @@ function ColumnEdit( { styles.columnPlaceholder, styles.columnPlaceholderDark ), - { width }, + contentStyle, styles.columnPlaceholderNotSelected, ] } ></View> @@ -60,7 +59,7 @@ function ColumnEdit( { </BlockControls> <View style={ [ - { width }, + contentStyle, isSelected && hasChildren && styles.innerBlocksBottomSpace, ] } > diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index f20fbdd4bfbdc7..a7e5117a5e620a 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -149,13 +149,12 @@ function ColumnsEditContainer( { style: styles.innerBlocks, } } allowedBlocks={ ALLOWED_BLOCKS } - customBlockProps={ { - width: getColumnWidth( width ), - readableContentViewStyle: { flex: 1 }, - onAddBlock: onAddNextColumn, - onDeleteBlock: - columnCount === 1 ? onDeleteBlock : undefined, - } } + contentResizeMode="stretch" + onAddBlock={ onAddNextColumn } + onDeleteBlock={ + columnCount === 1 ? onDeleteBlock : undefined + } + contentStyle={ { width: getColumnWidth( width ) } } /> </View> </> From 54f389e0acab1ead531c1fad51ba1da1a853b1b7 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Thu, 19 Mar 2020 15:37:04 +0100 Subject: [PATCH 170/183] Revert "disable inserter depending on setting" This reverts commit e7dd896b6c0fbd11e4b6219350525aa713a4d654. --- .../src/components/inserter/index.native.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/inserter/index.native.js b/packages/block-editor/src/components/inserter/index.native.js index e31e83b71bf607..6cc160b7e8dd4e 100644 --- a/packages/block-editor/src/components/inserter/index.native.js +++ b/packages/block-editor/src/components/inserter/index.native.js @@ -148,7 +148,6 @@ export class Inserter extends Component { renderToggle = defaultRenderToggle, getStylesFromColorScheme, showSeparator, - blockDoNotSupportInserter, } = this.props; if ( showSeparator && isOpen ) { return <BlockInsertionPoint />; @@ -193,7 +192,7 @@ export class Inserter extends Component { { renderToggle( { onToggle: onPress, isOpen, - disabled: disabled || blockDoNotSupportInserter, + disabled, style, onLongPress, } ) } @@ -257,11 +256,8 @@ export default compose( [ getBlockOrder, getBlockIndex, getBlock, - getBlockName, } = select( 'core/block-editor' ); - const { getBlockSupport } = select( 'core/blocks' ); - const end = getBlockSelectionEnd(); // `end` argument (id) can refer to the component which is removed // due to pressing `undo` button, that's why we need to check @@ -319,10 +315,6 @@ export default compose( [ ? selectedBlockIndex + 1 : endOfRootIndex; - const blockDoNotSupportInserter = - ! isAppender && - getBlockSupport( getBlockName( end ), 'inserter' ) === false; - return { destinationRootClientId, insertionIndexDefault: getDefaultInsertionIndex(), @@ -330,7 +322,6 @@ export default compose( [ insertionIndexAfter, isAnyBlockSelected, isSelectedBlockReplaceable: isSelectedUnmodifiedDefaultBlock, - blockDoNotSupportInserter, }; } ), From 9d7027d29ddbb5533de2639c4b91f5c31e02583a Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Fri, 3 Apr 2020 18:03:48 +0200 Subject: [PATCH 171/183] add overwritten support inserterin index.native.js --- .../block-library/src/column/index.native.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 packages/block-library/src/column/index.native.js diff --git a/packages/block-library/src/column/index.native.js b/packages/block-library/src/column/index.native.js new file mode 100644 index 00000000000000..d03c4756174bcc --- /dev/null +++ b/packages/block-library/src/column/index.native.js @@ -0,0 +1,17 @@ +/** + * Internal dependencies + */ +import * as webSettings from './index.js'; +import metadata from './block.json'; + +const { name } = metadata; + +export { metadata, name }; + +export const settings = { + ...webSettings.settings, + supports: { + ...webSettings.settings.supports, + inserter: true, + }, +}; From 22e1a62ba98f37d688e759f915c5458f44fb2cd4 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Mon, 6 Apr 2020 09:53:29 +0200 Subject: [PATCH 172/183] remove flatFistProp group --- .../src/components/block-list/index.native.js | 10 ++++++++-- .../src/components/inner-blocks/index.native.js | 10 ++++++++-- packages/block-library/src/column/edit.native.js | 6 ++---- packages/block-library/src/columns/edit.native.js | 10 ++++------ 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index c9c1dbe73bac24..9f3095307dbfe4 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -98,7 +98,10 @@ export class BlockList extends Component { withFooter = true, isReadOnly, isRootList, - flatListProps, + horizontal, + scrollEnabled, + contentContainerStyle, + style, shouldShowInsertionPointBefore, shouldShowInsertionPointAfter, marginVertical = styles.defaultBlock.marginTop, @@ -123,7 +126,6 @@ export class BlockList extends Component { onAccessibilityEscape={ clearSelectedBlock } > <KeyboardAwareFlatList - { ...flatListProps } { ...( Platform.OS === 'android' ? { removeClippedSubviews: false } : {} ) } // Disable clipping on Android to fix focus losing. See https://github.com/wordpress-mobile/gutenberg-mobile/pull/741#issuecomment-472746541 @@ -139,6 +141,10 @@ export class BlockList extends Component { flex: isRootList ? 1 : 0, ...( ! isRootList && { overflow: 'visible' } ), } } + horizontal={ horizontal } + scrollEnabled={ scrollEnabled } + contentContainerStyle={ contentContainerStyle } + style={ style } data={ blockClientIds } keyExtractor={ identity } extraData={ forceRefresh } diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index f113022cbbdc41..34478158554780 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -113,7 +113,10 @@ class InnerBlocks extends Component { clientId, renderAppender, __experimentalMoverDirection, - flatListProps, + horizontal, + scrollEnabled, + contentContainerStyle, + style, contentResizeMode, contentStyle, onAddBlock, @@ -135,7 +138,10 @@ class InnerBlocks extends Component { __experimentalMoverDirection={ __experimentalMoverDirection } - flatListProps={ flatListProps } + contentContainerStyle={ contentContainerStyle } + style={ style } + horizontal={ horizontal } + scrollEnabled={ scrollEnabled } contentResizeMode={ contentResizeMode } contentStyle={ contentStyle } onAddBlock={ onAddBlock } diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 9d1d7d39ec785f..60770c03918c01 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -64,10 +64,8 @@ function ColumnEdit( { ] } > <InnerBlocks - flatListProps={ { - scrollEnabled: false, - style: styles.innerBlocks, - } } + scrollEnabled={ false } + style={ styles.innerBlocks } renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index a7e5117a5e620a..8c50c6fb65e85b 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -142,12 +142,10 @@ function ColumnsEditContainer( { __experimentalMoverDirection={ columnsInRow > 1 ? 'horizontal' : undefined } - flatListProps={ { - contentContainerStyle: styles.columnsContainer, - horizontal: true, - scrollEnabled: false, - style: styles.innerBlocks, - } } + contentContainerStyle={ styles.columnsContainer } + style={ styles.innerBlocks } + horizontal={ true } + scrollEnabled={ false } allowedBlocks={ ALLOWED_BLOCKS } contentResizeMode="stretch" onAddBlock={ onAddNextColumn } From 076c05e2247a790d485db6386c0933509811043d Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Mon, 6 Apr 2020 10:04:29 +0200 Subject: [PATCH 173/183] avoid passing contentContainerStyle from Columns --- .../src/components/block-list/index.native.js | 5 +++-- .../src/components/block-list/style.native.scss | 10 ++++++++++ .../src/components/inner-blocks/index.native.js | 2 -- packages/block-library/src/columns/edit.native.js | 1 - packages/block-library/src/columns/editor.native.scss | 6 ------ 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 9f3095307dbfe4..7979f56cfb6361 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -100,7 +100,6 @@ export class BlockList extends Component { isRootList, horizontal, scrollEnabled, - contentContainerStyle, style, shouldShowInsertionPointBefore, shouldShowInsertionPointAfter, @@ -143,7 +142,9 @@ export class BlockList extends Component { } } horizontal={ horizontal } scrollEnabled={ scrollEnabled } - contentContainerStyle={ contentContainerStyle } + contentContainerStyle={ + horizontal && styles.horizontalContentContainer + } style={ style } data={ blockClientIds } keyExtractor={ identity } diff --git a/packages/block-editor/src/components/block-list/style.native.scss b/packages/block-editor/src/components/block-list/style.native.scss index 903401f3bedf30..c7a83cb3f6a427 100644 --- a/packages/block-editor/src/components/block-list/style.native.scss +++ b/packages/block-editor/src/components/block-list/style.native.scss @@ -4,6 +4,16 @@ background-color: #fff; } +.horizontalContentContainer { + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; + align-items: stretch; + max-width: $content-width; + overflow: visible; + width: 100%; +} + .switch { flex-direction: row; justify-content: flex-start; diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index 34478158554780..03a35ef1b5c26f 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -115,7 +115,6 @@ class InnerBlocks extends Component { __experimentalMoverDirection, horizontal, scrollEnabled, - contentContainerStyle, style, contentResizeMode, contentStyle, @@ -138,7 +137,6 @@ class InnerBlocks extends Component { __experimentalMoverDirection={ __experimentalMoverDirection } - contentContainerStyle={ contentContainerStyle } style={ style } horizontal={ horizontal } scrollEnabled={ scrollEnabled } diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 8c50c6fb65e85b..27058725cf6f11 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -142,7 +142,6 @@ function ColumnsEditContainer( { __experimentalMoverDirection={ columnsInRow > 1 ? 'horizontal' : undefined } - contentContainerStyle={ styles.columnsContainer } style={ styles.innerBlocks } horizontal={ true } scrollEnabled={ false } diff --git a/packages/block-library/src/columns/editor.native.scss b/packages/block-library/src/columns/editor.native.scss index 9e5e978c9ed958..e6413ae59f0414 100644 --- a/packages/block-library/src/columns/editor.native.scss +++ b/packages/block-library/src/columns/editor.native.scss @@ -1,11 +1,5 @@ .columnsContainer { - flex-direction: row; - flex-wrap: wrap; - justify-content: space-between; - align-items: stretch; max-width: $content-width; - overflow: visible; - width: 100%; } .innerBlocks { From e55092016f0bc6578d57a4a8f036ab5a3a2139dc Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Mon, 6 Apr 2020 11:00:41 +0200 Subject: [PATCH 174/183] adjust movers translation --- .../components/block-mover/index.native.js | 71 ++++++++++--------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/packages/block-editor/src/components/block-mover/index.native.js b/packages/block-editor/src/components/block-mover/index.native.js index a2feee71157a6a..2a2050188e9a13 100644 --- a/packages/block-editor/src/components/block-mover/index.native.js +++ b/packages/block-editor/src/components/block-mover/index.native.js @@ -19,9 +19,6 @@ const horizontalMover = { secondButtonHint: __( 'Double tap to move the block to the right' ), firstBlockTitle: __( 'Move block left' ), lastBlockTitle: __( 'Move block right' ), - firstButtonDirection: __( 'left' ), - secondButtonDirection: __( 'right' ), - location: 'position', }; const verticalMover = { @@ -31,9 +28,6 @@ const verticalMover = { secondButtonHint: __( 'Double tap to move the block down' ), firstBlockTitle: __( 'Move block up' ), lastBlockTitle: __( 'Move block down' ), - firstButtonDirection: __( 'up' ), - secondButtonDirection: __( 'down' ), - location: 'row', }; const BlockMover = ( { @@ -53,31 +47,53 @@ const BlockMover = ( { secondButtonHint, firstBlockTitle, lastBlockTitle, - firstButtonDirection, - secondButtonDirection, - location, } = horizontalDirection ? horizontalMover : verticalMover; if ( isLocked || ( isFirst && isLast && ! rootClientId ) ) { return null; } + const getFirstMoverButtonTitle = () => { + if ( horizontalDirection ) { + return sprintf( + /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ + __( 'Move block left from position %1$s to position %2$s' ), + firstIndex + 1, + firstIndex + ); + } + + return sprintf( + /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ + __( 'Move block up from row %1$s to row %2$s' ), + firstIndex + 1, + firstIndex + ); + }; + + const getSecondMoverButtonTitle = () => { + if ( horizontalDirection ) { + return sprintf( + /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ + __( 'Move block right from position %1$s to position %2$s' ), + firstIndex + 1, + firstIndex + 2 + ); + } + + return sprintf( + /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ + __( 'Move block down from row %1$s to row %2$s' ), + firstIndex + 1, + firstIndex + 2 + ); + }; + return ( <> <ToolbarButton title={ - ! isFirst - ? sprintf( - /* translators: accessibility text. %1: block moving direction (string). %2: location of block - row or order number (string). %3: current block position (number). %4: next block position (number) */ - __( - 'Move block %2$s from %1$s %3$s to %1$s %4$s' - ), - location, - firstButtonDirection, - firstIndex + 1, - firstIndex - ) - : firstBlockTitle + ! isFirst ? getFirstMoverButtonTitle() : firstBlockTitle } isDisabled={ isFirst } onClick={ onMoveUp } @@ -87,18 +103,7 @@ const BlockMover = ( { <ToolbarButton title={ - ! isLast - ? sprintf( - /* translators: accessibility text. %1: block moving direction (string). %2: location of block - row or order number (string). %3: current block position (number). %4: next block position (number) */ - __( - 'Move block %2$s from %1$s %3$s to %1$s %4$s' - ), - location, - secondButtonDirection, - firstIndex + 1, - firstIndex + 2 - ) - : lastBlockTitle + ! isLast ? getSecondMoverButtonTitle() : lastBlockTitle } isDisabled={ isLast } onClick={ onMoveDown } From e610e5ee8c17fe553f170de2c9d4eecff1fdd23d Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Mon, 6 Apr 2020 11:12:38 +0200 Subject: [PATCH 175/183] refactor movers translation --- .../components/block-mover/index.native.js | 62 ++++++++++--------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/packages/block-editor/src/components/block-mover/index.native.js b/packages/block-editor/src/components/block-mover/index.native.js index 2a2050188e9a13..099a9a1c84e613 100644 --- a/packages/block-editor/src/components/block-mover/index.native.js +++ b/packages/block-editor/src/components/block-mover/index.native.js @@ -54,39 +54,41 @@ const BlockMover = ( { } const getFirstMoverButtonTitle = () => { - if ( horizontalDirection ) { - return sprintf( - /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ - __( 'Move block left from position %1$s to position %2$s' ), - firstIndex + 1, - firstIndex - ); - } - - return sprintf( - /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ - __( 'Move block up from row %1$s to row %2$s' ), - firstIndex + 1, - firstIndex - ); + const fromIndex = firstIndex + 1; + const toIndex = firstIndex; + return horizontalDirection + ? sprintf( + /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ + __( 'Move block left from position %1$s to position %2$s' ), + fromIndex, + toIndex + ) + : sprintf( + /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ + __( 'Move block up from row %1$s to row %2$s' ), + fromIndex, + toIndex + ); }; const getSecondMoverButtonTitle = () => { - if ( horizontalDirection ) { - return sprintf( - /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ - __( 'Move block right from position %1$s to position %2$s' ), - firstIndex + 1, - firstIndex + 2 - ); - } - - return sprintf( - /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ - __( 'Move block down from row %1$s to row %2$s' ), - firstIndex + 1, - firstIndex + 2 - ); + const fromIndex = firstIndex + 1; + const toIndex = firstIndex + 2; + return horizontalDirection + ? sprintf( + /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ + __( + 'Move block right from position %1$s to position %2$s' + ), + fromIndex, + toIndex + ) + : sprintf( + /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ + __( 'Move block down from row %1$s to row %2$s' ), + fromIndex, + toIndex + ); }; return ( From 8bfde1e2c583956a8e35584564a09fa5d5310da6 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Mon, 6 Apr 2020 12:38:27 +0200 Subject: [PATCH 176/183] refactor on passing styles from Columns --- .../src/components/block-list/index.native.js | 11 +++++------ .../src/components/block-list/style.native.scss | 4 ++++ .../src/components/inner-blocks/index.native.js | 2 -- packages/block-library/src/column/edit.native.js | 1 - packages/block-library/src/column/editor.native.scss | 4 ---- packages/block-library/src/columns/edit.native.js | 1 - packages/block-library/src/columns/editor.native.scss | 4 ---- 7 files changed, 9 insertions(+), 18 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index 7979f56cfb6361..830f92559b5cea 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -100,7 +100,6 @@ export class BlockList extends Component { isRootList, horizontal, scrollEnabled, - style, shouldShowInsertionPointBefore, shouldShowInsertionPointAfter, marginVertical = styles.defaultBlock.marginTop, @@ -136,16 +135,16 @@ export class BlockList extends Component { } inputAccessoryViewHeight={ headerToolbar.height } keyboardShouldPersistTaps="always" - scrollViewStyle={ { - flex: isRootList ? 1 : 0, - ...( ! isRootList && { overflow: 'visible' } ), - } } + scrollViewStyle={ [ + { flex: isRootList ? 1 : 0 }, + ! isRootList && styles.overflowVisible, + ] } horizontal={ horizontal } scrollEnabled={ scrollEnabled } contentContainerStyle={ horizontal && styles.horizontalContentContainer } - style={ style } + style={ ! isRootList && styles.overflowVisible } data={ blockClientIds } keyExtractor={ identity } extraData={ forceRefresh } diff --git a/packages/block-editor/src/components/block-list/style.native.scss b/packages/block-editor/src/components/block-list/style.native.scss index c7a83cb3f6a427..78994455ca0f30 100644 --- a/packages/block-editor/src/components/block-list/style.native.scss +++ b/packages/block-editor/src/components/block-list/style.native.scss @@ -80,3 +80,7 @@ .blockToolbar { height: $mobile-block-toolbar-height; } + +.overflowVisible { + overflow: visible; +} diff --git a/packages/block-editor/src/components/inner-blocks/index.native.js b/packages/block-editor/src/components/inner-blocks/index.native.js index 03a35ef1b5c26f..d462fa40d04865 100644 --- a/packages/block-editor/src/components/inner-blocks/index.native.js +++ b/packages/block-editor/src/components/inner-blocks/index.native.js @@ -115,7 +115,6 @@ class InnerBlocks extends Component { __experimentalMoverDirection, horizontal, scrollEnabled, - style, contentResizeMode, contentStyle, onAddBlock, @@ -137,7 +136,6 @@ class InnerBlocks extends Component { __experimentalMoverDirection={ __experimentalMoverDirection } - style={ style } horizontal={ horizontal } scrollEnabled={ scrollEnabled } contentResizeMode={ contentResizeMode } diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index 60770c03918c01..d10d2374d0fb77 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -65,7 +65,6 @@ function ColumnEdit( { > <InnerBlocks scrollEnabled={ false } - style={ styles.innerBlocks } renderAppender={ isSelected && InnerBlocks.ButtonBlockAppender } diff --git a/packages/block-library/src/column/editor.native.scss b/packages/block-library/src/column/editor.native.scss index 4657eab7166228..ec9579c63234b1 100644 --- a/packages/block-library/src/column/editor.native.scss +++ b/packages/block-library/src/column/editor.native.scss @@ -15,10 +15,6 @@ border: $border-width dashed $gray-70; } -.innerBlocks { - overflow: visible; -} - .innerBlocksBottomSpace { margin-bottom: $block-selected-to-content; } diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index 27058725cf6f11..e1cc669a414c11 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -142,7 +142,6 @@ function ColumnsEditContainer( { __experimentalMoverDirection={ columnsInRow > 1 ? 'horizontal' : undefined } - style={ styles.innerBlocks } horizontal={ true } scrollEnabled={ false } allowedBlocks={ ALLOWED_BLOCKS } diff --git a/packages/block-library/src/columns/editor.native.scss b/packages/block-library/src/columns/editor.native.scss index e6413ae59f0414..991655a13f3278 100644 --- a/packages/block-library/src/columns/editor.native.scss +++ b/packages/block-library/src/columns/editor.native.scss @@ -2,10 +2,6 @@ max-width: $content-width; } -.innerBlocks { - overflow: visible; -} - .innerBlocksSelected { margin-bottom: $block-edge-to-content; } From 40289503c173e7e2201747f4bffc67959c33a604 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Tue, 7 Apr 2020 15:30:23 +0200 Subject: [PATCH 177/183] refactor movers logic --- .../components/block-mover/index.native.js | 99 +++++++++---------- 1 file changed, 44 insertions(+), 55 deletions(-) diff --git a/packages/block-editor/src/components/block-mover/index.native.js b/packages/block-editor/src/components/block-mover/index.native.js index 099a9a1c84e613..23b911f4a4d259 100644 --- a/packages/block-editor/src/components/block-mover/index.native.js +++ b/packages/block-editor/src/components/block-mover/index.native.js @@ -13,21 +13,33 @@ import { withInstanceId, compose } from '@wordpress/compose'; import { arrowUp, arrowDown, arrowLeft, arrowRight } from '@wordpress/icons'; const horizontalMover = { - firstButtonIcon: arrowLeft, - secondButtonIcon: arrowRight, - firstButtonHint: __( 'Double tap to move the block to the left' ), - secondButtonHint: __( 'Double tap to move the block to the right' ), + backwardButtonIcon: arrowLeft, + forwardButtonIcon: arrowRight, + backwardButtonHint: __( 'Double tap to move the block to the left' ), + forwardButtonHint: __( 'Double tap to move the block to the right' ), firstBlockTitle: __( 'Move block left' ), lastBlockTitle: __( 'Move block right' ), + /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ + backwardButtonTitle: __( + 'Move block left from position %1$s to position %2$s' + ), + /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ + forwardButtonTitle: __( 'Move block up from row %1$s to row %2$s' ), }; const verticalMover = { - firstButtonIcon: arrowUp, - secondButtonIcon: arrowDown, - firstButtonHint: __( 'Double tap to move the block up' ), - secondButtonHint: __( 'Double tap to move the block down' ), + backwardButtonIcon: arrowUp, + forwardButtonIcon: arrowDown, + backwardButtonHint: __( 'Double tap to move the block up' ), + forwardButtonHint: __( 'Double tap to move the block down' ), firstBlockTitle: __( 'Move block up' ), lastBlockTitle: __( 'Move block down' ), + /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ + backwardButtonTitle: __( + 'Move block right from position %1$s to position %2$s' + ), + /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ + forwardButtonTitle: __( 'Move block down from row %1$s to row %2$s' ), }; const BlockMover = ( { @@ -41,10 +53,10 @@ const BlockMover = ( { horizontalDirection, } ) => { const { - firstButtonIcon, - secondButtonIcon, - firstButtonHint, - secondButtonHint, + backwardButtonIcon, + forwardButtonIcon, + backwardButtonHint, + forwardButtonHint, firstBlockTitle, lastBlockTitle, } = horizontalDirection ? horizontalMover : verticalMover; @@ -53,65 +65,42 @@ const BlockMover = ( { return null; } - const getFirstMoverButtonTitle = () => { - const fromIndex = firstIndex + 1; - const toIndex = firstIndex; - return horizontalDirection - ? sprintf( - /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ - __( 'Move block left from position %1$s to position %2$s' ), - fromIndex, - toIndex - ) - : sprintf( - /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ - __( 'Move block up from row %1$s to row %2$s' ), - fromIndex, - toIndex - ); - }; + const getMoverButtonTitle = ( isBackwardButton ) => { + const fromIndex = firstIndex + 1; // current position based on index + // for backwardButton decrease index (move left/up) for forwardButton increase index (move right/down) + const direction = isBackwardButton ? -1 : 1; + const toIndex = fromIndex + direction; // position after move + + const { backwardButtonTitle, forwardButtonTitle } = horizontalDirection + ? horizontalMover + : verticalMover; + + const buttonTitle = isBackwardButton + ? backwardButtonTitle + : forwardButtonTitle; - const getSecondMoverButtonTitle = () => { - const fromIndex = firstIndex + 1; - const toIndex = firstIndex + 2; - return horizontalDirection - ? sprintf( - /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ - __( - 'Move block right from position %1$s to position %2$s' - ), - fromIndex, - toIndex - ) - : sprintf( - /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ - __( 'Move block down from row %1$s to row %2$s' ), - fromIndex, - toIndex - ); + return sprintf( buttonTitle, fromIndex, toIndex ); }; return ( <> <ToolbarButton title={ - ! isFirst ? getFirstMoverButtonTitle() : firstBlockTitle + ! isFirst ? getMoverButtonTitle( true ) : firstBlockTitle } isDisabled={ isFirst } onClick={ onMoveUp } - icon={ firstButtonIcon } - extraProps={ { hint: firstButtonHint } } + icon={ backwardButtonIcon } + extraProps={ { hint: backwardButtonHint } } /> <ToolbarButton - title={ - ! isLast ? getSecondMoverButtonTitle() : lastBlockTitle - } + title={ ! isLast ? getMoverButtonTitle() : lastBlockTitle } isDisabled={ isLast } onClick={ onMoveDown } - icon={ secondButtonIcon } + icon={ forwardButtonIcon } extraProps={ { - hint: secondButtonHint, + hint: forwardButtonHint, } } /> </> From 4ca51b03c6008715f202794b31e2eaa850c13e9b Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Wed, 8 Apr 2020 12:45:22 +0200 Subject: [PATCH 178/183] handle proper RTL arrow direction --- .../components/block-mover/index.native.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-mover/index.native.js b/packages/block-editor/src/components/block-mover/index.native.js index 23b911f4a4d259..bb954b506136c6 100644 --- a/packages/block-editor/src/components/block-mover/index.native.js +++ b/packages/block-editor/src/components/block-mover/index.native.js @@ -1,6 +1,7 @@ /** * External dependencies */ +import { I18nManager } from 'react-native'; import { first, last, partial, castArray } from 'lodash'; /** @@ -45,6 +46,7 @@ const verticalMover = { const BlockMover = ( { isFirst, isLast, + isRTL, isLocked, onMoveDown, onMoveUp, @@ -82,6 +84,18 @@ const BlockMover = ( { return sprintf( buttonTitle, fromIndex, toIndex ); }; + const getArrowIcon = ( isBackwardButton ) => { + if ( isRTL && horizontalDirection ) { + // for RTL and horizontal direction switch the icons between forward and backward button + if ( isBackwardButton ) { + return forwardButtonIcon; // set forwardButtonIcon for backward button + } + return backwardButtonIcon; // set backwardButtonIcon for forward button + } + + return isBackwardButton ? backwardButtonIcon : forwardButtonIcon; + }; + return ( <> <ToolbarButton @@ -90,7 +104,7 @@ const BlockMover = ( { } isDisabled={ isFirst } onClick={ onMoveUp } - icon={ backwardButtonIcon } + icon={ getArrowIcon( true ) } extraProps={ { hint: backwardButtonHint } } /> @@ -98,7 +112,7 @@ const BlockMover = ( { title={ ! isLast ? getMoverButtonTitle() : lastBlockTitle } isDisabled={ isLast } onClick={ onMoveDown } - icon={ forwardButtonIcon } + icon={ getArrowIcon() } extraProps={ { hint: forwardButtonHint, } } @@ -129,6 +143,7 @@ export default compose( firstIndex, isFirst: firstIndex === 0, isLast: lastIndex === blockOrder.length - 1, + isRTL: I18nManager.isRTL, isLocked: getTemplateLock( rootClientId ) === 'all', rootClientId, }; From a0d0423543c2281f7b4325418660ebffa60ceb78 Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Wed, 8 Apr 2020 12:48:29 +0200 Subject: [PATCH 179/183] fix a11y messages for movers --- .../src/components/block-mover/index.native.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/block-mover/index.native.js b/packages/block-editor/src/components/block-mover/index.native.js index bb954b506136c6..ccf08521c03f6f 100644 --- a/packages/block-editor/src/components/block-mover/index.native.js +++ b/packages/block-editor/src/components/block-mover/index.native.js @@ -25,7 +25,9 @@ const horizontalMover = { 'Move block left from position %1$s to position %2$s' ), /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ - forwardButtonTitle: __( 'Move block up from row %1$s to row %2$s' ), + forwardButtonTitle: __( + 'Move block right from position %1$s to position %2$s' + ), }; const verticalMover = { @@ -36,9 +38,7 @@ const verticalMover = { firstBlockTitle: __( 'Move block up' ), lastBlockTitle: __( 'Move block down' ), /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ - backwardButtonTitle: __( - 'Move block right from position %1$s to position %2$s' - ), + backwardButtonTitle: __( 'Move block up from row %1$s to row %2$s' ), /* translators: accessibility text. %1: current block position (number). %2: next block position (number) */ forwardButtonTitle: __( 'Move block down from row %1$s to row %2$s' ), }; From 4d4d3eb7920941a02b07246a29125576420b771c Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Wed, 8 Apr 2020 12:52:29 +0200 Subject: [PATCH 180/183] fix a11y VoiceOver for movers --- .../src/components/block-list/block.native.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index 6b08593d49f4db..918c414ba833ce 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -101,6 +101,7 @@ class BlockListBlock extends Component { getStylesFromColorScheme, marginVertical, marginHorizontal, + isInnerBlockSelected, } = this.props; const accessibilityLabel = getAccessibleBlockLabel( @@ -109,10 +110,12 @@ class BlockListBlock extends Component { order + 1 ); + const accessible = ! ( isSelected || isInnerBlockSelected ); + return ( <TouchableWithoutFeedback onPress={ this.onFocus } - accessible={ ! isSelected } + accessible={ accessible } accessibilityRole={ 'button' } > <View @@ -200,10 +203,12 @@ export default compose( [ getBlockRootClientId, getLowestCommonAncestorWithSelectedBlock, getBlockParents, + hasSelectedInnerBlock, } = select( 'core/block-editor' ); const order = getBlockIndex( clientId, rootClientId ); const isSelected = isBlockSelected( clientId ); + const isInnerBlockSelected = hasSelectedInnerBlock( clientId ); const block = __unstableGetBlockWithoutInnerBlocks( clientId ); const { name, attributes, isValid } = block || {}; @@ -266,6 +271,7 @@ export default compose( [ attributes, blockType, isSelected, + isInnerBlockSelected, isValid, parentId, isParentSelected, From d3c2601df00028ea3c29c6cd31f6d0cb002683df Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Wed, 8 Apr 2020 14:24:56 +0200 Subject: [PATCH 181/183] extract common logic to switchButtonPropIfRTL function --- .../components/block-mover/index.native.js | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/packages/block-editor/src/components/block-mover/index.native.js b/packages/block-editor/src/components/block-mover/index.native.js index ccf08521c03f6f..5ca80153bee052 100644 --- a/packages/block-editor/src/components/block-mover/index.native.js +++ b/packages/block-editor/src/components/block-mover/index.native.js @@ -67,6 +67,22 @@ const BlockMover = ( { return null; } + const switchButtonPropIfRTL = ( + isBackwardButton, + forwardButtonProp, + backwardButtonProp + ) => { + if ( isRTL && horizontalDirection ) { + // for RTL and horizontal direction switch prop between forward and backward button + if ( isBackwardButton ) { + return forwardButtonProp; // set forwardButtonProp for backward button + } + return backwardButtonProp; // set backwardButtonProp for forward button + } + + return isBackwardButton ? backwardButtonProp : forwardButtonProp; + }; + const getMoverButtonTitle = ( isBackwardButton ) => { const fromIndex = firstIndex + 1; // current position based on index // for backwardButton decrease index (move left/up) for forwardButton increase index (move right/down) @@ -77,24 +93,21 @@ const BlockMover = ( { ? horizontalMover : verticalMover; - const buttonTitle = isBackwardButton - ? backwardButtonTitle - : forwardButtonTitle; + const buttonTitle = switchButtonPropIfRTL( + isBackwardButton, + forwardButtonTitle, + backwardButtonTitle + ); return sprintf( buttonTitle, fromIndex, toIndex ); }; - const getArrowIcon = ( isBackwardButton ) => { - if ( isRTL && horizontalDirection ) { - // for RTL and horizontal direction switch the icons between forward and backward button - if ( isBackwardButton ) { - return forwardButtonIcon; // set forwardButtonIcon for backward button - } - return backwardButtonIcon; // set backwardButtonIcon for forward button - } - - return isBackwardButton ? backwardButtonIcon : forwardButtonIcon; - }; + const getArrowIcon = ( isBackwardButton ) => + switchButtonPropIfRTL( + isBackwardButton, + forwardButtonIcon, + backwardButtonIcon + ); return ( <> From 938fa1f484f37bd86b91c2a87bcf7299904fc4ba Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Wed, 8 Apr 2020 16:47:32 +0200 Subject: [PATCH 182/183] remove extra space --- packages/base-styles/_variables.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/base-styles/_variables.scss b/packages/base-styles/_variables.scss index eb55cb62123799..1d33712cc44e65 100644 --- a/packages/base-styles/_variables.scss +++ b/packages/base-styles/_variables.scss @@ -93,6 +93,7 @@ $block-selected-padding: 0; $block-selected-child-margin: 5px; $block-selected-to-content: $block-edge-to-content - $block-selected-margin - $block-selected-border-width; + /** * Border radii. */ From 26982506bed42e298f7e89bc065883831d2a67ee Mon Sep 17 00:00:00 2001 From: Jakub Binda <jakub.binda@gmail.com> Date: Wed, 8 Apr 2020 16:50:42 +0200 Subject: [PATCH 183/183] remove devOnly flag from Columns/Column block --- packages/block-library/src/index.native.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/index.native.js b/packages/block-library/src/index.native.js index 947a23637cc64f..914cff5f9a3ea8 100644 --- a/packages/block-library/src/index.native.js +++ b/packages/block-library/src/index.native.js @@ -148,8 +148,8 @@ export const registerCoreBlocks = () => { mediaText, preformatted, gallery, - devOnly( columns ), - devOnly( column ), + columns, + column, group, button, spacer,