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 274c2896d98a9..0c8189a32baa3 100644 --- a/packages/block-editor/src/components/block-compare/block-view.js +++ b/packages/block-editor/src/components/block-compare/block-view.js @@ -6,19 +6,19 @@ import { Button } from '@wordpress/components'; const BlockView = ( { title, rawContent, renderedContent, action, actionText, className } ) => { return (
-
-

{ title }

+
+

{ title }

-
+
{ rawContent }
-
+
{ renderedContent }
-
+
diff --git a/packages/block-editor/src/components/block-compare/test/__snapshots__/block-view.js.snap b/packages/block-editor/src/components/block-compare/test/__snapshots__/block-view.js.snap index f28df1bbb5548..5177c366d7006 100644 --- a/packages/block-editor/src/components/block-compare/test/__snapshots__/block-view.js.snap +++ b/packages/block-editor/src/components/block-compare/test/__snapshots__/block-view.js.snap @@ -5,26 +5,26 @@ exports[`BlockView should match snapshot 1`] = ` className="class" >

title

raw
render
+ { ! isReusable && ( + + { __( 'Add to Reusable Blocks' ) } + + ) } + { isReusable && ( + + { __( 'Convert to Regular Block' ) } + + ) } + + ); +} + +export default compose( [ + withSelect( ( select, { clientIds } ) => { + const { + getBlocksByClientId, + canInsertBlockType, + } = select( 'core/block-editor' ); + const { + __experimentalGetReusableBlock: getReusableBlock, + } = select( 'core/editor' ); + const { canUser } = select( 'core' ); + + const blocks = getBlocksByClientId( clientIds ); + + const isReusable = ( + blocks.length === 1 && + blocks[ 0 ] && + isReusableBlock( blocks[ 0 ] ) && + !! getReusableBlock( blocks[ 0 ].attributes.ref ) + ); + + // Show 'Convert to Regular Block' when selected block is a reusable block + 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' ) + ); + + return { + isReusable, + isVisible, + }; + } ), + withDispatch( ( dispatch, { clientIds, onToggle = noop } ) => { + const { + __experimentalConvertBlockToReusable: convertBlockToReusable, + __experimentalConvertBlockToStatic: convertBlockToStatic, + } = dispatch( 'core/editor' ); + + return { + onConvertToStatic() { + if ( clientIds.length !== 1 ) { + return; + } + convertBlockToStatic( clientIds[ 0 ] ); + onToggle(); + }, + onConvertToReusable() { + convertBlockToReusable( clientIds ); + onToggle(); + }, + }; + } ), +] )( ReusableBlockConvertButton ); diff --git a/packages/block-editor/src/components/block-settings-menu/reusable-block-delete-button.js b/packages/block-editor/src/components/block-settings-menu/reusable-block-delete-button.js new file mode 100644 index 0000000000000..8c9abb9589c7d --- /dev/null +++ b/packages/block-editor/src/components/block-settings-menu/reusable-block-delete-button.js @@ -0,0 +1,71 @@ +/** + * External dependencies + */ +import { noop } from 'lodash'; + +/** + * WordPress dependencies + */ +import { compose } from '@wordpress/compose'; +import { MenuItem } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { isReusableBlock } from '@wordpress/blocks'; +import { withSelect, withDispatch } from '@wordpress/data'; + +export function ReusableBlockDeleteButton( { isVisible, isDisabled, onDelete } ) { + if ( ! isVisible ) { + return null; + } + + return ( + onDelete() } + > + { __( 'Remove from Reusable Blocks' ) } + + ); +} + +export default compose( [ + withSelect( ( select, { clientId } ) => { + const { getBlock } = select( 'core/block-editor' ); + const { canUser } = select( 'core' ); + const { __experimentalGetReusableBlock: getReusableBlock } = select( 'core/editor' ); + const block = getBlock( clientId ); + + const reusableBlock = block && isReusableBlock( block ) ? + getReusableBlock( block.attributes.ref ) : + null; + + return { + isVisible: !! reusableBlock && !! canUser( 'delete', 'blocks', reusableBlock.id ), + isDisabled: reusableBlock && reusableBlock.isTemporary, + }; + } ), + withDispatch( ( dispatch, { clientId, onToggle = noop }, { select } ) => { + const { + __experimentalDeleteReusableBlock: deleteReusableBlock, + } = dispatch( 'core/editor' ); + const { getBlock } = select( 'core/block-editor' ); + + return { + onDelete() { + // TODO: Make this a 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.' + ) ); + + if ( hasConfirmed ) { + const block = getBlock( clientId ); + deleteReusableBlock( block.attributes.ref ); + onToggle(); + } + }, + }; + } ), +] )( ReusableBlockDeleteButton ); diff --git a/packages/block-editor/src/components/block-settings-menu/test/__snapshots__/reusable-block-delete-button.js.snap b/packages/block-editor/src/components/block-settings-menu/test/__snapshots__/reusable-block-delete-button.js.snap new file mode 100644 index 0000000000000..4d7c78b869765 --- /dev/null +++ b/packages/block-editor/src/components/block-settings-menu/test/__snapshots__/reusable-block-delete-button.js.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ReusableBlockDeleteButton matches the snapshot 1`] = ` + + Remove from Reusable Blocks + +`; diff --git a/packages/block-editor/src/components/block-settings-menu/test/reusable-block-convert-button.js b/packages/block-editor/src/components/block-settings-menu/test/reusable-block-convert-button.js new file mode 100644 index 0000000000000..c6fba313e31b3 --- /dev/null +++ b/packages/block-editor/src/components/block-settings-menu/test/reusable-block-convert-button.js @@ -0,0 +1,56 @@ +/** + * External dependencies + */ +import ShallowRenderer from 'react-test-renderer/shallow'; + +/** + * Internal dependencies + */ +import { ReusableBlockConvertButton } from '../reusable-block-convert-button'; + +describe( 'ReusableBlockConvertButton', () => { + function getShallowRenderOutput( element ) { + const renderer = new ShallowRenderer(); + renderer.render( element ); + return renderer.getRenderOutput(); + } + + it( 'should not render when isVisible false', () => { + const wrapper = getShallowRenderOutput( + + ); + expect( wrapper ).toBe( null ); + } ); + + it( 'should allow converting a static block to a reusable block', () => { + const onConvert = jest.fn(); + const wrapper = getShallowRenderOutput( + + ); + expect( wrapper.props.children[ 1 ] ).toBeFalsy(); + const button = wrapper.props.children[ 0 ]; + expect( button.props.children ).toBe( 'Add to Reusable Blocks' ); + button.props.onClick(); + expect( onConvert ).toHaveBeenCalled(); + } ); + + it( 'should allow converting a reusable block to static', () => { + const onConvert = jest.fn(); + const wrapper = getShallowRenderOutput( + + ); + expect( wrapper.props.children[ 0 ] ).toBeFalsy(); + const button = wrapper.props.children[ 1 ]; + expect( button.props.children ).toBe( 'Convert to Regular Block' ); + button.props.onClick(); + expect( onConvert ).toHaveBeenCalled(); + } ); +} ); diff --git a/packages/block-editor/src/components/block-settings-menu/test/reusable-block-delete-button.js b/packages/block-editor/src/components/block-settings-menu/test/reusable-block-delete-button.js new file mode 100644 index 0000000000000..39299becf29c2 --- /dev/null +++ b/packages/block-editor/src/components/block-settings-menu/test/reusable-block-delete-button.js @@ -0,0 +1,53 @@ +/** + * External dependencies + */ +import ShallowRenderer from 'react-test-renderer/shallow'; +import { noop } from 'lodash'; + +/** + * Internal dependencies + */ +import { ReusableBlockDeleteButton } from '../reusable-block-delete-button'; + +describe( 'ReusableBlockDeleteButton', () => { + function getShallowRenderOutput( element ) { + const renderer = new ShallowRenderer(); + renderer.render( element ); + return renderer.getRenderOutput(); + } + + it( 'should not render when isVisible is false', () => { + const wrapper = getShallowRenderOutput( + + ); + + expect( wrapper ).toBe( null ); + } ); + + it( 'matches the snapshot', () => { + const wrapper = getShallowRenderOutput( + + ); + + expect( wrapper ).toMatchSnapshot(); + } ); + + it( 'should allow deleting a reusable block', () => { + const onDelete = jest.fn(); + const wrapper = getShallowRenderOutput( + + ); + + wrapper.props.onClick(); + expect( onDelete ).toHaveBeenCalled(); + } ); +} ); diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss index e0fbc6601ee36..77b15329ad1e2 100644 --- a/packages/block-editor/src/components/block-toolbar/style.scss +++ b/packages/block-editor/src/components/block-toolbar/style.scss @@ -1,43 +1,25 @@ -.block-editor-block-toolbar { +.editor-block-toolbar { display: flex; flex-grow: 1; width: 100%; overflow: auto; // Allow horizontal scrolling on mobile. position: relative; - transition: border-color 0.1s linear, box-shadow 0.1s linear; - border-left: $border-width solid $light-gray-800; + // Allow overflow on desktop. @include break-small() { - // Allow overflow on desktop. overflow: inherit; - - // Show a left border on the parent container. - border-left: none; - box-shadow: -$block-left-border-width 0 0 0 $dark-gray-500; - - // Show a lighter version for dark themes. - .is-dark-theme & { - box-shadow: -$block-left-border-width 0 0 0 $light-gray-600; - } } + // Show a left border on the parent container. + border-left: $border-width solid $light-gray-500; + // The component is born with a border, but we only need some of them. .components-toolbar { border: 0; - border-top: $border-width solid $light-gray-800; - border-bottom: $border-width solid $light-gray-800; + border-top: $border-width solid $light-gray-500; + border-bottom: $border-width solid $light-gray-500; // Add a right border to show as separator in the block toolbar. - border-right: $border-width solid $light-gray-800; - } - - // Add a left border and adjust the color for Top Toolbar mode. - .has-fixed-toolbar & { - box-shadow: none; - border-left: $border-width solid $light-gray-500; - - .components-toolbar { - border-color: $light-gray-500; - } + border-right: $border-width solid $light-gray-500; } } diff --git a/packages/block-editor/src/components/color-palette/control.scss b/packages/block-editor/src/components/color-palette/control.scss index 249fc32607be1..c82865028658f 100644 --- a/packages/block-editor/src/components/color-palette/control.scss +++ b/packages/block-editor/src/components/color-palette/control.scss @@ -1,4 +1,4 @@ -.block-editor-color-palette-control__color-palette { +.editor-color-palette-control__color-palette { display: inline-block; margin-top: 0.6rem; } diff --git a/packages/block-editor/src/components/contrast-checker/style.scss b/packages/block-editor/src/components/contrast-checker/style.scss index b3b08d6230d05..b484e47d48738 100644 --- a/packages/block-editor/src/components/contrast-checker/style.scss +++ b/packages/block-editor/src/components/contrast-checker/style.scss @@ -1,3 +1,3 @@ -.block-editor-contrast-checker > .components-notice { +.editor-contrast-checker > .components-notice { margin: 0; } diff --git a/packages/block-editor/src/components/inner-blocks/style.scss b/packages/block-editor/src/components/inner-blocks/style.scss index f4218ef0667eb..36ffe038223e4 100644 --- a/packages/block-editor/src/components/inner-blocks/style.scss +++ b/packages/block-editor/src/components/inner-blocks/style.scss @@ -1,9 +1,9 @@ -.block-editor-inner-blocks.has-overlay::after { +.editor-inner-blocks.has-overlay::after { content: ""; position: absolute; top: 0; right: 0; bottom: 0; left: 0; - z-index: z-index(".block-editor-inner-blocks__small-screen-overlay:after"); + z-index: z-index(".editor-inner-blocks__small-screen-overlay:after"); } diff --git a/packages/block-editor/src/components/media-placeholder/index.js b/packages/block-editor/src/components/media-placeholder/index.js index 7b6837588cd99..2270b87762ea3 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, noop, startsWith, defaultTo } from 'lodash'; import classnames from 'classnames'; /** @@ -36,11 +30,11 @@ import URLPopover from '../url-popover'; const InsertFromURLPopover = ( { src, onChange, onSubmit, onClose } ) => (
( value={ src } /> { - onSelect( currentValue.concat( newMedia ) ); - }; - } else { - setMedia = onSelect; - } - } else { - setMedia = ( [ media ] ) => onSelect( media ); - } + const { onSelect, multiple, onError, allowedTypes, mediaUpload } = this.props; + const setMedia = multiple ? onSelect : ( [ media ] ) => onSelect( media ); mediaUpload( { allowedTypes, filesList: files, @@ -147,44 +121,66 @@ export class MediaPlaceholder extends Component { this.setState( { isURLInputVisible: false } ); } - renderPlaceholder( content, onClick ) { + render() { const { - allowedTypes = [], - className, + accept, icon, - isAppender, + className, labels = {}, - notices, + onSelect, + value = {}, onSelectURL, + onHTMLDrop = noop, + multiple = false, + notices, + allowedTypes = [], + hasUploadPermissions, mediaUpload, } = this.props; - let instructions = labels.instructions; - let title = labels.title; + const { + isURLInputVisible, + src, + } = this.state; + + let instructions = labels.instructions || ''; + let title = labels.title || ''; - if ( ! mediaUpload && ! onSelectURL ) { + if ( ! hasUploadPermissions && ! onSelectURL ) { instructions = __( 'To edit this block, you need permission to upload media.' ); } - if ( instructions === undefined || title === undefined ) { + if ( ! instructions || ! title ) { const isOneType = 1 === allowedTypes.length; const isAudio = isOneType && 'audio' === allowedTypes[ 0 ]; const isImage = isOneType && 'image' === allowedTypes[ 0 ]; const isVideo = isOneType && 'video' === allowedTypes[ 0 ]; - if ( instructions === undefined && mediaUpload ) { - instructions = __( 'Drag a media file, upload a new one or select a file from your library.' ); - - if ( isAudio ) { - instructions = __( 'Drag an audio, upload a new one or select a file from your library.' ); - } else if ( isImage ) { - instructions = __( 'Drag an image, upload a new one or select a file from your library.' ); - } else if ( isVideo ) { - instructions = __( 'Drag a video, upload a new one or select a file from your library.' ); + if ( ! instructions ) { + if ( hasUploadPermissions ) { + instructions = __( 'Drag a media file, upload a new one or select a file from your library.' ); + + if ( isAudio ) { + instructions = __( 'Drag an audio, upload a new one or select a file from your library.' ); + } else if ( isImage ) { + instructions = __( 'Drag an image, upload a new one or select a file from your library.' ); + } else if ( isVideo ) { + instructions = __( 'Drag a video, upload a new one or select a file from your library.' ); + } + } else if ( ! hasUploadPermissions && onSelectURL ) { + instructions = __( 'Given your current role, you can only link a media file, you cannot upload.' ); + + if ( isAudio ) { + instructions = __( 'Given your current role, you can only link an audio, you cannot upload.' ); + } else if ( isImage ) { + instructions = __( 'Given your current role, you can only link an image, you cannot upload.' ); + } else if ( isVideo ) { + instructions = __( 'Given your current role, you can only link a video, you cannot upload.' ); + } } } - if ( title === undefined ) { + if ( ! title ) { title = __( 'Media' ); if ( isAudio ) { @@ -197,199 +193,80 @@ export class MediaPlaceholder extends Component { } } - const placeholderClassName = classnames( - 'block-editor-media-placeholder', - 'editor-media-placeholder', - className, - { 'is-appender': isAppender } - ); - return ( - { content } - - ); - } - - renderDropZone() { - const { onHTMLDrop = noop } = this.props; - return ( - - ); - } - - renderUrlSelectionUI() { - const { - onSelectURL, - } = this.props; - if ( ! onSelectURL ) { - return null; - } - const { - isURLInputVisible, - src, - } = this.state; - return ( -
- - { isURLInputVisible && ( - + { !! mediaUpload && ( + + + + { __( 'Upload' ) } + + + ) } + ( + + ) } /> - ) } -
- ); - } - - renderMediaUploadChecked() { - const { - accept, - addToGallery, - allowedTypes = [], - isAppender, - mediaUpload, - multiple = false, - onSelect, - value = {}, - } = this.props; - - const mediaLibraryButton = ( - id ) : - value.id - } - render={ ( { open } ) => { - return ( + + { onSelectURL && ( +
- ); - } } - /> - ); - - if ( mediaUpload && isAppender ) { - return ( - - { this.renderDropZone() } - { - const content = ( - - - { __( 'Upload' ) } - - { mediaLibraryButton } - { this.renderUrlSelectionUI() } - - ); - return this.renderPlaceholder( content, openFileDialog ); - } } - /> - - ); - } - if ( mediaUpload ) { - const content = ( - - { this.renderDropZone() } - ) } - onChange={ this.onUpload } - accept={ accept } - multiple={ multiple } - > - { __( 'Upload' ) } - - { mediaLibraryButton } - { this.renderUrlSelectionUI() } - - ); - return this.renderPlaceholder( content ); - } - return this.renderPlaceholder( mediaLibraryButton ); - } - - render() { - const { - dropZoneUIOnly, - } = this.props; - - if ( dropZoneUIOnly ) { - return ( - - { this.renderDropZone() } - - ); - } - - return ( - - { this.renderMediaUploadChecked() } - +
+ ) } + ); } } const applyWithSelect = withSelect( ( select ) => { + const { canUser } = select( 'core' ); const { getSettings } = select( 'core/block-editor' ); return { + hasUploadPermissions: defaultTo( canUser( 'create', 'media' ), true ), mediaUpload: getSettings().__experimentalMediaUpload, }; } ); diff --git a/packages/block-editor/src/components/media-placeholder/style.scss b/packages/block-editor/src/components/media-placeholder/style.scss index d18b42ce5e57a..526399f385c04 100644 --- a/packages/block-editor/src/components/media-placeholder/style.scss +++ b/packages/block-editor/src/components/media-placeholder/style.scss @@ -1,17 +1,17 @@ -.block-editor-media-placeholder__url-input-container { +.editor-media-placeholder__url-input-container { width: 100%; // Reset the margin to ensure the url popover is adjacent to the button. - .block-editor-media-placeholder__button { + .editor-media-placeholder__button { margin-bottom: 0; } } -.block-editor-media-placeholder__url-input-form { +.editor-media-placeholder__url-input-form { display: flex; // Selector requires a lot of specificity to override base styles. - input[type="url"].block-editor-media-placeholder__url-input-field { + input[type="url"].editor-media-placeholder__url-input-field { width: 100%; @include break-small() { width: 300px; @@ -25,11 +25,11 @@ } } -.block-editor-media-placeholder__url-input-submit-button { +.editor-media-placeholder__url-input-submit-button { flex-shrink: 1; } -.block-editor-media-placeholder__button { +.editor-media-placeholder__button { margin-bottom: 0.5rem; .dashicon { @@ -42,33 +42,6 @@ } } -.components-form-file-upload .block-editor-media-placeholder__button { +.components-form-file-upload .editor-media-placeholder__button { margin-right: $grid-size-small; } - -.block-editor-media-placeholder.is-appender { - min-height: 100px; - outline: $border-width dashed $dark-gray-150; - - &:hover { - outline: $border-width dashed $dark-gray-500; - cursor: pointer; - } - - .is-dark-theme & { - - &:hover { - outline: $border-width dashed $white; - } - } - - .block-editor-media-placeholder__upload-button { - margin-right: $grid-size-small; - &.components-button:hover, - &.components-button:focus { - box-shadow: none; - border: $border-width solid $dark-gray-500; - } - } - -} diff --git a/packages/block-editor/src/components/rich-text/format-toolbar/style.scss b/packages/block-editor/src/components/rich-text/format-toolbar/style.scss index dc493c3b0c845..c9f2d0de0cc91 100644 --- a/packages/block-editor/src/components/rich-text/format-toolbar/style.scss +++ b/packages/block-editor/src/components/rich-text/format-toolbar/style.scss @@ -1,13 +1,13 @@ -.block-editor-format-toolbar { +.editor-format-toolbar { display: flex; flex-shrink: 0; } -.block-editor-format-toolbar__selection-position { +.editor-format-toolbar__selection-position { position: absolute; transform: translateX(-50%); } -.block-editor-format-toolbar .components-dropdown-menu__toggle .components-dropdown-menu__indicator::after { +.editor-format-toolbar .components-dropdown-menu__toggle .components-dropdown-menu__indicator::after { margin: 7px; } diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js index a4761ca33f121..60916e8d0cde3 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -31,6 +31,8 @@ import { isURL } from '@wordpress/url'; */ import FormatEdit from './format-edit'; import FormatToolbar from './format-toolbar'; +import { withBlockEditContext } from '../block-edit/context'; +import { ListEdit } from './list-edit'; import styles from './style.scss'; @@ -70,8 +72,19 @@ const gutenbergFormatNamesToAztec = { }; export class RichText extends Component { - constructor() { + constructor( { multiline } ) { super( ...arguments ); + + this.isMultiline = false; + if ( multiline === true || multiline === 'p' || multiline === 'li' ) { + this.multilineTag = multiline === true ? 'p' : multiline; + this.isMultiline = true; + } + + if ( this.multilineTag === 'li' ) { + this.multilineWrapperTags = [ 'ul', 'ol' ]; + } + this.isIOS = Platform.OS === 'ios'; this.onChange = this.onChange.bind( this ); this.onEnter = this.onEnter.bind( this ); @@ -501,6 +514,7 @@ export class RichText extends Component { style, formattingControls, isSelected, + onTagNameChange, } = this.props; const record = this.getRecord(); @@ -522,6 +536,14 @@ export class RichText extends Component { return ( + { isSelected && this.multilineTag === 'li' && ( + + ) } { isSelected && ( @@ -561,6 +583,7 @@ export class RichText extends Component { fontWeight={ this.props.fontWeight } fontStyle={ this.props.fontStyle } disableEditingMenu={ this.props.disableEditingMenu } + isMultiline={ this.isMultiline } /> { isSelected && } @@ -582,13 +605,46 @@ const RichTextContainer = compose( [ formatTypes: getFormatTypes(), }; } ), + withBlockEditContext( ( context, ownProps ) => { + // When explicitly set as not selected, do nothing. + if ( ownProps.isSelected === false ) { + return { + clientId: context.clientId, + }; + } + // When explicitly set as selected, use the value stored in the context instead. + if ( ownProps.isSelected === true ) { + return { + isSelected: context.isSelected, + clientId: context.clientId, + }; + } + + // Ensures that only one RichText component can be focused. + return { + clientId: context.clientId, + isSelected: context.isSelected, + onFocus: context.onFocus, + }; + } ), ] )( RichText ); -RichTextContainer.Content = ( { value, format, tagName: Tag, ...props } ) => { +RichTextContainer.Content = ( { value, format, tagName: Tag, multiline, ...props } ) => { let content; + let html = value; + let MultilineTag; + + if ( multiline === true || multiline === 'p' || multiline === 'li' ) { + MultilineTag = multiline === true ? 'p' : multiline; + } + + if ( ! html && MultilineTag ) { + html = `<${ MultilineTag }>`; + } + switch ( format ) { case 'string': - content = { value }; + content = { html }; break; } diff --git a/packages/block-editor/src/components/rich-text/list-edit.native.js b/packages/block-editor/src/components/rich-text/list-edit.native.js new file mode 100644 index 0000000000000..bd3c9982d3757 --- /dev/null +++ b/packages/block-editor/src/components/rich-text/list-edit.native.js @@ -0,0 +1,93 @@ +/** + * WordPress dependencies + */ + +import { Toolbar } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { + indentListItems, + outdentListItems, + changeListType, +} from '@wordpress/rich-text'; + +/** + * Internal dependencies + */ + +import BlockFormatControls from '../block-format-controls'; + +/** + * Whether or not the root list is selected. + * + * @return {boolean} True if the root list or nothing is selected, false if an + * inner list is selected. + */ +function isListRootSelected() { + // Consider the root list selected if nothing is selected. + return true; +} + +/** + * Wether or not the selected list has the given tag name. + * + * @param {string} tagName The tag name the list should have. + * @param {string} rootTagName The current root tag name, to compare with in + * case nothing is selected. + * + * @return {boolean} [description] + */ +function isActiveListType( tagName, rootTagName ) { + return tagName === rootTagName; +} + +export const ListEdit = ( { + onTagNameChange, + tagName, + value, + onChange, +} ) => ( + + { + onChange( outdentListItems( value ) ); + }, + }, + { + icon: 'editor-indent', + title: __( 'Indent list item' ), + onClick: () => { + onChange( indentListItems( value, { type: tagName } ) ); + }, + }, + ].filter( Boolean ) } + /> + +); diff --git a/packages/block-editor/src/components/warning/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/warning/test/__snapshots__/index.js.snap index de9e8358fdb80..5e2a8231c7dfd 100644 --- a/packages/block-editor/src/components/warning/test/__snapshots__/index.js.snap +++ b/packages/block-editor/src/components/warning/test/__snapshots__/index.js.snap @@ -2,13 +2,13 @@ exports[`Warning should match snapshot 1`] = `

error

diff --git a/packages/block-library/src/index.native.js b/packages/block-library/src/index.native.js index a1bd0b0d2f4f8..5090d1f18bb3a 100644 --- a/packages/block-library/src/index.native.js +++ b/packages/block-library/src/index.native.js @@ -15,6 +15,7 @@ import * as more from './more'; import * as paragraph from './paragraph'; import * as image from './image'; import * as nextpage from './nextpage'; +import * as list from './list'; export const registerCoreBlocks = () => { [ @@ -24,6 +25,7 @@ export const registerCoreBlocks = () => { more, image, nextpage, + list, ].forEach( ( { name, settings } ) => { registerBlockType( name, settings ); } ); diff --git a/packages/editor/src/components/rich-text/list-edit.native.js b/packages/editor/src/components/rich-text/list-edit.native.js new file mode 100644 index 0000000000000..74b0ce08bbda9 --- /dev/null +++ b/packages/editor/src/components/rich-text/list-edit.native.js @@ -0,0 +1,77 @@ +/** + * WordPress dependencies + */ + +import { Toolbar } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { + changeListType, +} from '@wordpress/rich-text'; + +/** + * Internal dependencies + */ + +import BlockFormatControls from '../block-format-controls'; + +/** + * Whether or not the root list is selected. + * + * @return {boolean} True if the root list or nothing is selected, false if an + * inner list is selected. + */ +function isListRootSelected() { + // Consider the root list selected if nothing is selected. + return true; +} + +/** + * Wether or not the selected list has the given tag name. + * + * @param {string} tagName The tag name the list should have. + * @param {string} rootTagName The current root tag name, to compare with in + * case nothing is selected. + * + * @return {boolean} [description] + */ +function isActiveListType( tagName, rootTagName ) { + return tagName === rootTagName; +} + +export const ListEdit = ( { + onTagNameChange, + tagName, + value, + onChange, +} ) => ( + + + +);