From a18ba18f2ac7c36310576aa3934b88434c3477bb Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler Date: Fri, 21 Jun 2019 18:55:57 +0200 Subject: [PATCH] Add native support for BlockList to @wordpress/block-editor (Ported from gutenberg mobile) (#16239) * Add native support for BlockList to @wordpress/block-editor (Ported from gutenberg mobile) * Fix tests * Exclude flow check from BlockList --- .../src/components/block-list/block.native.js | 4 +- .../src/components/block-list/index.native.js | 283 ++++++++++++++++++ .../components/block-list/style.native.scss | 75 +++++ .../src/components/index.native.js | 3 +- .../mobile/html-text-input/index.native.js | 7 +- .../html-text-input/test/index.native.js | 8 +- .../components/visual-editor/index.native.js | 102 +++++++ .../visual-editor/style.native.scss | 17 ++ packages/edit-post/src/index.native.js | 2 + 9 files changed, 493 insertions(+), 8 deletions(-) create mode 100644 packages/block-editor/src/components/block-list/index.native.js create mode 100644 packages/block-editor/src/components/block-list/style.native.scss create mode 100644 packages/edit-post/src/components/visual-editor/index.native.js create mode 100644 packages/edit-post/src/components/visual-editor/style.native.scss 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 3d93500ce9d88..d836627337635 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -14,13 +14,15 @@ import { Component } from '@wordpress/element'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { getBlockType } from '@wordpress/blocks'; -import { BlockEdit, BlockInvalidWarning, BlockMobileToolbar } from '@wordpress/block-editor'; import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies */ import styles from './block.scss'; +import BlockEdit from '../block-edit'; +import BlockInvalidWarning from './block-invalid-warning'; +import BlockMobileToolbar from './block-mobile-toolbar'; class BlockListBlock extends Component { constructor() { diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js new file mode 100644 index 0000000000000..07815c1391302 --- /dev/null +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -0,0 +1,283 @@ +/** + * External dependencies + */ +import { identity } from 'lodash'; +import { Text, View, Keyboard, SafeAreaView, Platform } from 'react-native'; +import { subscribeMediaAppend } from 'react-native-gutenberg-bridge'; + +/** + * WordPress dependencies + */ +import { Component, Fragment } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { withDispatch, withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; +import { createBlock, isUnmodifiedDefaultBlock } from '@wordpress/blocks'; +import { HTMLTextInput, KeyboardAvoidingView, KeyboardAwareFlatList, ReadableContentView } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import styles from './style.scss'; +import BlockListBlock from './block'; +import BlockToolbar from '../block-toolbar'; +import DefaultBlockAppender from '../default-block-appender'; +import Inserter from '../inserter'; + +const blockMobileToolbarHeight = 44; +const toolbarHeight = 44; + +export class BlockList extends Component { + constructor() { + super( ...arguments ); + + this.renderItem = this.renderItem.bind( this ); + this.shouldFlatListPreventAutomaticScroll = this.shouldFlatListPreventAutomaticScroll.bind( this ); + this.renderDefaultBlockAppender = this.renderDefaultBlockAppender.bind( this ); + this.onBlockTypeSelected = this.onBlockTypeSelected.bind( this ); + this.keyboardDidShow = this.keyboardDidShow.bind( this ); + this.keyboardDidHide = this.keyboardDidHide.bind( this ); + this.onCaretVerticalPositionChange = this.onCaretVerticalPositionChange.bind( this ); + this.scrollViewInnerRef = this.scrollViewInnerRef.bind( this ); + + this.state = { + blockTypePickerVisible: false, + isKeyboardVisible: false, + }; + } + + // TODO: in the near future this will likely be changed to onShowBlockTypePicker and bound to this.props + // once we move the action to the toolbar + showBlockTypePicker( show ) { + this.setState( { blockTypePickerVisible: show } ); + } + + onBlockTypeSelected( itemValue ) { + this.setState( { blockTypePickerVisible: false } ); + + // create an empty block of the selected type + const newBlock = createBlock( itemValue ); + + this.finishBlockAppendingOrReplacing( newBlock ); + } + + finishBlockAppendingOrReplacing( newBlock ) { + // now determine whether we need to replace the currently selected block (if it's empty) + // or just add a new block as usual + if ( this.isReplaceable( this.props.selectedBlock ) ) { + // do replace here + this.props.replaceBlock( this.props.selectedBlockClientId, newBlock ); + } else { + const indexAfterSelected = this.props.selectedBlockOrder + 1; + const insertionIndex = indexAfterSelected || this.props.blockCount; + this.props.insertBlock( newBlock, insertionIndex ); + } + } + + blockHolderBorderStyle() { + return this.state.isFullyBordered ? styles.blockHolderFullBordered : styles.blockHolderSemiBordered; + } + + componentDidMount() { + this._isMounted = true; + Keyboard.addListener( 'keyboardDidShow', this.keyboardDidShow ); + Keyboard.addListener( 'keyboardDidHide', this.keyboardDidHide ); + + this.subscriptionParentMediaAppend = subscribeMediaAppend( ( payload ) => { + // create an empty media block + const newMediaBlock = createBlock( 'core/' + payload.mediaType ); + + // now set the url and id + if ( payload.mediaType === 'image' ) { + newMediaBlock.attributes.url = payload.mediaUrl; + } else if ( payload.mediaType === 'video' ) { + newMediaBlock.attributes.src = payload.mediaUrl; + } + + newMediaBlock.attributes.id = payload.mediaId; + + // finally append or replace as appropriate + this.finishBlockAppendingOrReplacing( newMediaBlock ); + } ); + } + + componentWillUnmount() { + Keyboard.removeListener( 'keyboardDidShow', this.keyboardDidShow ); + Keyboard.removeListener( 'keyboardDidHide', this.keyboardDidHide ); + + if ( this.subscriptionParentMediaAppend ) { + this.subscriptionParentMediaAppend.remove(); + } + this._isMounted = false; + } + + keyboardDidShow() { + this.setState( { isKeyboardVisible: true } ); + } + + keyboardDidHide() { + this.setState( { isKeyboardVisible: false } ); + } + + onCaretVerticalPositionChange( targetId, caretY, previousCaretY ) { + KeyboardAwareFlatList.handleCaretVerticalPositionChange( this.scrollViewRef, targetId, caretY, previousCaretY ); + } + + scrollViewInnerRef( ref ) { + this.scrollViewRef = ref; + } + + shouldFlatListPreventAutomaticScroll() { + return this.state.blockTypePickerVisible; + } + + renderDefaultBlockAppender() { + return ( + + + + ); + } + + renderList() { + return ( + + + + + + + { + this.showBlockTypePicker( true ); + } } + showKeyboardHideButton={ this.state.isKeyboardVisible } + /> + + + ); + } + + render() { + return ( + + { this.renderList() } + { this.state.blockTypePickerVisible && ( + this.showBlockTypePicker( false ) } + onValueSelected={ this.onBlockTypeSelected } + isReplacement={ this.isReplaceable( this.props.selectedBlock ) } + addExtraBottomPadding={ this.props.safeAreaBottomInset === 0 } + /> + ) } + + ); + } + + isReplaceable( block ) { + if ( ! block ) { + return false; + } + return isUnmodifiedDefaultBlock( block ); + } + + renderItem( { item: clientId } ) { + return ( + + + { this.state.blockTypePickerVisible && this.props.isBlockSelected( clientId ) && ( + + + { __( 'ADD BLOCK HERE' ) } + + + ) } + + ); + } + + renderHTML() { + return ( + + ); + } +} + +export default compose( [ + withSelect( ( select, { rootClientId } ) => { + const { + getBlockCount, + getBlockName, + getBlockIndex, + getBlockOrder, + getSelectedBlock, + getSelectedBlockClientId, + isBlockSelected, + } = select( 'core/block-editor' ); + + const selectedBlockClientId = getSelectedBlockClientId(); + + return { + blockClientIds: getBlockOrder( rootClientId ), + blockCount: getBlockCount( rootClientId ), + getBlockName, + isBlockSelected, + selectedBlock: getSelectedBlock(), + selectedBlockClientId, + selectedBlockOrder: getBlockIndex( selectedBlockClientId ), + }; + } ), + withDispatch( ( dispatch ) => { + const { + insertBlock, + replaceBlock, + clearSelectedBlock, + } = dispatch( 'core/block-editor' ); + + return { + clearSelectedBlock, + insertBlock, + replaceBlock, + }; + } ), +] )( BlockList ); + diff --git a/packages/block-editor/src/components/block-list/style.native.scss b/packages/block-editor/src/components/block-list/style.native.scss new file mode 100644 index 0000000000000..f856893d5160a --- /dev/null +++ b/packages/block-editor/src/components/block-list/style.native.scss @@ -0,0 +1,75 @@ +.container { + flex: 1; + justify-content: flex-start; + background-color: #fff; +} + +.list { + flex: 1; +} + +.switch { + flex-direction: row; + justify-content: flex-start; + align-items: center; + margin: 10px; +} + +.switchLabel { + margin-left: 10px; +} + +.lineStyleAddHere { + flex: 1; + background-color: #0087be; // blue_wordpress + align-self: center; + height: 2px; +} + +.labelStyleAddHere { + flex: 1; + text-align: center; + font-family: $default-monospace-font; + font-size: 12px; + font-weight: bold; +} + +.containerStyleAddHere { + flex: 1; + flex-direction: row; + background-color: $white; +} + +.blockToolbarKeyboardAvoidingView { + position: absolute; + bottom: 0; + right: 0; + left: 0; +} + +.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 { + background-color: $white; + 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; +} diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js index 49038ecd6ef2f..dee7b5fbdedda 100644 --- a/packages/block-editor/src/components/index.native.js +++ b/packages/block-editor/src/components/index.native.js @@ -19,8 +19,7 @@ export { default as URLInput } from './url-input'; export { default as BlockInvalidWarning } from './block-list/block-invalid-warning'; // Content Related Components -export { default as BlockListBlock } from './block-list/block'; -export { default as BlockMobileToolbar } from './block-list/block-mobile-toolbar'; +export { default as BlockList } from './block-list'; export { default as BlockMover } from './block-mover'; export { default as BlockToolbar } from './block-toolbar'; export { default as DefaultBlockAppender } from './default-block-appender'; 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 3811085213dd1..e25f7a1af71c4 100644 --- a/packages/components/src/mobile/html-text-input/index.native.js +++ b/packages/components/src/mobile/html-text-input/index.native.js @@ -70,7 +70,7 @@ export class HTMLTextInput extends Component { style={ styles.htmlViewTitle } value={ this.props.title } placeholder={ __( 'Add title' ) } - onChangeText={ this.props.setTitleAction } + onChangeText={ this.props.editTitle } /> { const { + getEditedPostAttribute, getEditedPostContent, } = select( 'core/editor' ); return { + title: getEditedPostAttribute( 'title' ), value: getEditedPostContent(), }; } ), @@ -103,6 +105,9 @@ export default compose( [ const { resetBlocks } = dispatch( 'core/block-editor' ); const { editPost } = dispatch( 'core/editor' ); return { + editTitle( title ) { + editPost( { title } ); + }, onChange( content ) { editPost( { content } ); }, 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 39153dd945cca..479846d3f6a96 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 @@ -96,11 +96,11 @@ describe( 'HTMLTextInput', () => { } ); it( 'HTMLTextInput propagates title changes to store', () => { - const setTitleAction = jest.fn(); + const editTitle = jest.fn(); const wrapper = shallow( ); @@ -109,8 +109,8 @@ describe( 'HTMLTextInput', () => { textInput.simulate( 'changeText', 'text' ); //Check if the setTitleAction is called - expect( setTitleAction ).toHaveBeenCalledTimes( 1 ); - expect( setTitleAction ).toHaveBeenCalledWith( 'text' ); + expect( editTitle ).toHaveBeenCalledTimes( 1 ); + expect( editTitle ).toHaveBeenCalledWith( 'text' ); } ); } ); diff --git a/packages/edit-post/src/components/visual-editor/index.native.js b/packages/edit-post/src/components/visual-editor/index.native.js new file mode 100644 index 0000000000000..ddc0995123ff9 --- /dev/null +++ b/packages/edit-post/src/components/visual-editor/index.native.js @@ -0,0 +1,102 @@ +/** + * WordPress dependencies + */ +import { Component } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { withDispatch, withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; +import { BlockEditorProvider, BlockList } from '@wordpress/block-editor'; +import { PostTitle } from '@wordpress/editor'; +import { ReadableContentView } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import styles from './style.scss'; + +class VisualEditor extends Component { + renderHeader() { + const { + editTitle, + setTitleRef, + title, + } = this.props; + + return ( + + + + ); + } + + render() { + const { + blocks, + isFullyBordered, + resetEditorBlocks, + resetEditorBlocksWithoutUndoLevel, + rootViewHeight, + safeAreaBottomInset, + } = this.props; + + return ( + + + + ); + } +} + +export default compose( [ + withSelect( ( select ) => { + const { + getEditorBlocks, + getEditedPostAttribute, + } = select( 'core/editor' ); + + return { + blocks: getEditorBlocks(), + title: getEditedPostAttribute( 'title' ), + }; + } ), + withDispatch( ( dispatch ) => { + const { + editPost, + resetEditorBlocks, + } = dispatch( 'core/editor' ); + + return { + editTitle( title ) { + editPost( { title } ); + }, + resetEditorBlocks, + resetEditorBlocksWithoutUndoLevel( blocks ) { + resetEditorBlocks( blocks, { + __unstableShouldCreateUndoLevel: false, + } ); + }, + }; + } ), +] )( VisualEditor ); diff --git a/packages/edit-post/src/components/visual-editor/style.native.scss b/packages/edit-post/src/components/visual-editor/style.native.scss new file mode 100644 index 0000000000000..02b49a1515584 --- /dev/null +++ b/packages/edit-post/src/components/visual-editor/style.native.scss @@ -0,0 +1,17 @@ +.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; +} + +.blockHolderFocused { + border-color: $gray-lighten-30; +} diff --git a/packages/edit-post/src/index.native.js b/packages/edit-post/src/index.native.js index 1abd926f26bc8..6a2d926d2c193 100644 --- a/packages/edit-post/src/index.native.js +++ b/packages/edit-post/src/index.native.js @@ -11,6 +11,8 @@ import { unregisterBlockType } from '@wordpress/blocks'; */ import './store'; +export { default as VisualEditor } from './components/visual-editor'; + /** * Initializes the Editor. */