diff --git a/packages/block-library/src/more/edit.native.js b/packages/block-library/src/more/edit.native.js index 5b85da44359d5..6a73a44c73b6a 100644 --- a/packages/block-library/src/more/edit.native.js +++ b/packages/block-library/src/more/edit.native.js @@ -1,12 +1,17 @@ /** * External dependencies */ -import { View, Text } from 'react-native'; +import { View } from 'react-native'; /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { Component } from '@wordpress/element'; +import { + getDefaultBlockName, + createBlock, +} from '@wordpress/blocks'; /** * Internal dependencies @@ -14,28 +19,68 @@ import { __ } from '@wordpress/i18n'; import { PlainText } from '@wordpress/editor'; import styles from './editor.scss'; -export default function MoreEdit( props ) { - const { attributes, setAttributes, onFocus, onBlur } = props; - const { customText } = attributes; - const defaultText = __( 'Read more' ); - const value = customText !== undefined ? customText : defaultText; +export default class MoreEdit extends Component { + constructor() { + super( ...arguments ); + this.onChangeInput = this.onChangeInput.bind( this ); - return ( - - - <!-- + this.state = { + defaultText: __( 'Read more' ), + }; + } + + onChangeInput( newValue ) { + // Detect Enter.key and add new empty block after. + // Note: This is detected after the fact, and the newline could be visible on the block + // for a very small time. This is OK for the alpha, we will revisit the logic later. + // See https://github.com/wordpress-mobile/gutenberg-mobile/issues/324 + if ( newValue.indexOf( '\n' ) !== -1 ) { + const { insertBlocksAfter } = this.props; + insertBlocksAfter( [ createBlock( getDefaultBlockName() ) ] ); + return; + } + // Set defaultText to an empty string, allowing the user to clear/replace the input field's text + this.setState( { + defaultText: '', + } ); + const value = newValue.length === 0 ? undefined : newValue; + this.props.setAttributes( { customText: value } ); + } + + renderLine() { + return ; + } + + renderText() { + const { attributes, onFocus, onBlur } = this.props; + const { customText } = attributes; + const { defaultText } = this.state; + const value = customText !== undefined ? customText : defaultText; + + return ( + setAttributes( { customText: newValue } ) } + onChange={ this.onChangeInput } placeholder={ defaultText } - isSelected={ props.isSelected } + isSelected={ this.props.isSelected } onFocus={ onFocus } onBlur={ onBlur } /> - <Text className={ styles[ 'block-library-more__right-marker' ] }>--&gt;</Text> </View> - </View> ); + ); + } + + render() { + return ( + <View style={ styles[ 'block-library-more__container' ] }> + { this.renderLine() } + { this.renderText() } + { this.renderLine() } + </View> + ); + } } diff --git a/packages/block-library/src/more/editor.native.scss b/packages/block-library/src/more/editor.native.scss index 46d36fe08e321..beb5ef423776f 100644 --- a/packages/block-library/src/more/editor.native.scss +++ b/packages/block-library/src/more/editor.native.scss @@ -2,21 +2,22 @@ .block-library-more__container { align-items: center; - padding-left: 4; - padding-right: 4; - padding-top: 4; - padding-bottom: 4; -} - -.block-library-more__sub-container { - align-items: center; + padding: 4px; flex-direction: row; } -.block-library-more__left-marker { - padding-right: 4; +.block-library-more__line { + background-color: #555d66; + height: 2; + flex: 1; } -.block-library-more__right-marker { - padding-left: 4; +.block-library-more__text { + text-decoration-style: solid; + flex: 0; + width: 200; + text-align: center; + margin-left: 15; + margin-right: 15; + margin-bottom: 5; } diff --git a/packages/block-library/src/nextpage/edit.native.js b/packages/block-library/src/nextpage/edit.native.js index 03cd3bfe3c7d1..413fda53fe217 100644 --- a/packages/block-library/src/nextpage/edit.native.js +++ b/packages/block-library/src/nextpage/edit.native.js @@ -18,7 +18,7 @@ export default function NextPageEdit( { attributes } ) { const { customText = __( 'Page break' ) } = attributes; return ( - <View className={ styles[ 'block-library-nextpage__container' ] }> + <View style={ styles[ 'block-library-nextpage__container' ] }> <Hr text={ customText } textStyle={ styles[ 'block-library-nextpage__text' ] } lineStyle={ styles[ 'block-library-nextpage__line' ] } /> diff --git a/packages/components/src/primitives/svg/index.native.js b/packages/components/src/primitives/svg/index.native.js index 47c49b1bb6128..b0272e6b5a7b9 100644 --- a/packages/components/src/primitives/svg/index.native.js +++ b/packages/components/src/primitives/svg/index.native.js @@ -17,16 +17,8 @@ export { } from 'react-native-svg'; export const SVG = ( props ) => { - // We're using the react-native-classname-to-style plugin, so when a `className` prop is passed it gets converted to `style` here. - // Given it carries a string (as it was originally className) but an object is expected for `style`, - // we need to check whether `style` exists and is a string, and convert it to an object - - let styleValues = {}; - if ( typeof props.style === 'string' ) { - const oneStyle = props.style.split( ' ' ).map( ( element ) => styles[ element ] ).filter( Boolean ); - styleValues = Object.assign( styleValues, ...oneStyle ); - } - + const stylesFromClasses = ( props.className || '' ).split( ' ' ).map( ( element ) => styles[ element ] ).filter( Boolean ); + const styleValues = Object.assign( {}, props.style, ...stylesFromClasses ); const safeProps = { ...props, style: styleValues }; return ( diff --git a/packages/editor/src/components/default-block-appender/index.native.js b/packages/editor/src/components/default-block-appender/index.native.js new file mode 100644 index 0000000000000..436ecc5772b00 --- /dev/null +++ b/packages/editor/src/components/default-block-appender/index.native.js @@ -0,0 +1,75 @@ +/** + * External dependencies + */ +import { TextInput, TouchableWithoutFeedback, View } from 'react-native'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { compose } from '@wordpress/compose'; +import { decodeEntities } from '@wordpress/html-entities'; +import { withSelect, withDispatch } from '@wordpress/data'; + +import styles from './style.scss'; + +export function DefaultBlockAppender( { + isLocked, + isVisible, + onAppend, + placeholder, +} ) { + if ( isLocked || ! isVisible ) { + return null; + } + + const value = decodeEntities( placeholder ) || __( 'Start writing or press \u2295 to add content' ); + + return ( + <TouchableWithoutFeedback + onPress={ onAppend } + > + <View style={ styles.blockHolder } pointerEvents="box-only"> + <View style={ styles.blockContainer }> + <TextInput + style={ styles.textView } + textAlignVertical="top" + multiline + numberOfLines={ 0 } + value={ value } + /> + </View> + </View> + </TouchableWithoutFeedback> + ); +} + +export default compose( + withSelect( ( select, ownProps ) => { + const { getBlockCount, getEditorSettings, getTemplateLock } = select( 'core/editor' ); + + const isEmpty = ! getBlockCount( ownProps.rootClientId ); + const { bodyPlaceholder } = getEditorSettings(); + + return { + isVisible: isEmpty, + isLocked: !! getTemplateLock( ownProps.rootClientId ), + placeholder: bodyPlaceholder, + }; + } ), + withDispatch( ( dispatch, ownProps ) => { + const { + insertDefaultBlock, + startTyping, + } = dispatch( 'core/editor' ); + + return { + onAppend() { + const { rootClientId } = ownProps; + + insertDefaultBlock( undefined, rootClientId ); + startTyping(); + }, + }; + } ), +)( DefaultBlockAppender ); diff --git a/packages/editor/src/components/default-block-appender/style.native.scss b/packages/editor/src/components/default-block-appender/style.native.scss new file mode 100644 index 0000000000000..cc08f6c820ca9 --- /dev/null +++ b/packages/editor/src/components/default-block-appender/style.native.scss @@ -0,0 +1,14 @@ + +.blockHolder { + flex: 1 1 auto; +} + +.blockContainer { + background-color: $white; + padding: 8px; +} + +.textView { + color: #87a6bc; + font-size: 16px; +} diff --git a/packages/editor/src/components/index.native.js b/packages/editor/src/components/index.native.js index dc651f9e7e0a6..229efa5879cc3 100644 --- a/packages/editor/src/components/index.native.js +++ b/packages/editor/src/components/index.native.js @@ -6,5 +6,6 @@ export { default as MediaPlaceholder } from './media-placeholder'; export { default as BlockFormatControls } from './block-format-controls'; export { default as BlockControls } from './block-controls'; export { default as BlockEdit } from './block-edit'; +export { default as DefaultBlockAppender } from './default-block-appender'; export { default as EditorHistoryRedo } from './editor-history/redo'; export { default as EditorHistoryUndo } from './editor-history/undo'; diff --git a/packages/editor/src/components/plain-text/index.native.js b/packages/editor/src/components/plain-text/index.native.js index e94f1939040f5..35ffd2782a133 100644 --- a/packages/editor/src/components/plain-text/index.native.js +++ b/packages/editor/src/components/plain-text/index.native.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { TextInput } from 'react-native'; +import { TextInput, Platform } from 'react-native'; /** * WordPress dependencies @@ -14,6 +14,11 @@ import { Component } from '@wordpress/element'; import styles from './style.scss'; export default class PlainText extends Component { + constructor() { + super( ...arguments ); + this.isIOS = Platform.OS === 'ios'; + } + componentDidMount() { // if isSelected is true, we should request the focus on this TextInput if ( ( this._input.isFocused() === false ) && ( this._input.props.isSelected === true ) ) { @@ -21,6 +26,12 @@ export default class PlainText extends Component { } } + componentDidUpdate( prevProps ) { + if ( ! this.props.isSelected && prevProps.isSelected && this.isIOS ) { + this._input.blur(); + } + } + focus() { this._input.focus(); } @@ -28,12 +39,14 @@ export default class PlainText extends Component { render() { return ( <TextInput + { ...this.props } ref={ ( x ) => this._input = x } className={ [ styles[ 'editor-plain-text' ], this.props.className ] } - onChangeText={ ( text ) => this.props.onChange( text ) } + 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 - { ...this.props } /> ); } diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/editor/src/components/rich-text/index.native.js index 362dc4b359d1a..f6796a97fdbaa 100644 --- a/packages/editor/src/components/rich-text/index.native.js +++ b/packages/editor/src/components/rich-text/index.native.js @@ -2,7 +2,7 @@ * External dependencies */ import RCTAztecView from 'react-native-aztec'; -import { View } from 'react-native'; +import { View, Platform } from 'react-native'; import { forEach, merge, @@ -66,6 +66,7 @@ export function getFormatValue( formatName ) { export class RichText extends Component { constructor() { super( ...arguments ); + this.isIOS = Platform.OS === 'ios'; this.onChange = this.onChange.bind( this ); this.onEnter = this.onEnter.bind( this ); this.onBackspace = this.onBackspace.bind( this ); @@ -274,8 +275,8 @@ export class RichText extends Component { // If the component is changed React side (undo/redo/merging/splitting/custom text actions) // we need to make sure the native is updated as well - if ( nextProps.value && - this.lastContent && + if ( ( typeof nextProps.value !== 'undefined' ) && + ( typeof this.lastContent !== 'undefined' ) && nextProps.value !== this.lastContent ) { this.lastEventCount = undefined; // force a refresh on the native side } @@ -292,6 +293,8 @@ export class RichText extends Component { componentDidUpdate( prevProps ) { if ( this.props.isSelected && ! prevProps.isSelected ) { this._editor.focus(); + } else if ( ! this.props.isSelected && prevProps.isSelected && this.isIOS ) { + this._editor.blur(); } } @@ -370,6 +373,7 @@ export class RichText extends Component { onContentSizeChange={ this.onContentSizeChange } onActiveFormatsChange={ this.onActiveFormatsChange } isSelected={ this.props.isSelected } + blockType={ { tag: tagName } } color={ 'black' } maxImagesWidth={ 200 } style={ style }