From 09d4368f2a81c604baaecca06a7b31e0cd777ba1 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Fri, 20 May 2022 12:28:28 +0200 Subject: [PATCH] [RNMobile] Fix drag mode not being enabled when long-pressing over Shortcode block (#41155) * Add prop for disabling suggestions button * Use allowed formats in format types calculation * Add RichText version to PlainText component * Use experimental version of PlainText in Shortcode block * Add disableAutocorrection prop to RichText * Disable autocorrection in Shortcode block * Update PlainText props in Shortcode block * Use pre as tagName in PlainText * Rename replaceLineBreaks function * Update shortcode block unit tests * Prevent text input focus when selecting Shortcode block * Force text color in Shortcode block * Remove tagName prop from PlainText component --- .../src/components/plain-text/index.native.js | 72 ++++++++++++-- .../src/components/rich-text/index.js | 2 + .../src/components/rich-text/index.native.js | 4 + .../src/shortcode/edit.native.js | 44 ++++++--- .../src/shortcode/style.native.scss | 15 ++- .../test/__snapshots__/edit.native.js.snap | 9 ++ .../src/shortcode/test/edit.native.js | 98 +++++++++++-------- .../ReactNativeAztec/ReactAztecManager.java | 11 +++ .../ios/RNTAztecView/RCTAztecView.swift | 6 ++ .../ios/RNTAztecView/RCTAztecViewManager.m | 1 + .../rich-text/src/component/index.native.js | 27 +++-- test/native/__mocks__/styleMock.js | 3 + 12 files changed, 217 insertions(+), 75 deletions(-) create mode 100644 packages/block-library/src/shortcode/test/__snapshots__/edit.native.js.snap 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 13d2bbf851837b..6c8259ceae1600 100644 --- a/packages/block-editor/src/components/plain-text/index.native.js +++ b/packages/block-editor/src/components/plain-text/index.native.js @@ -7,7 +7,7 @@ import { TextInput, Platform, Dimensions } from 'react-native'; * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { getPxFromCssUnit } from '@wordpress/block-editor'; +import { RichText, getPxFromCssUnit } from '@wordpress/block-editor'; /** * Internal dependencies @@ -18,6 +18,9 @@ export default class PlainText extends Component { constructor() { super( ...arguments ); this.isAndroid = Platform.OS === 'android'; + + this.onChangeTextInput = this.onChangeTextInput.bind( this ); + this.onChangeRichText = this.onChangeRichText.bind( this ); } componentDidMount() { @@ -44,7 +47,7 @@ export default class PlainText extends Component { componentDidUpdate( prevProps ) { if ( ! this.props.isSelected && prevProps.isSelected ) { - this._input.blur(); + this._input?.blur(); } } @@ -55,11 +58,11 @@ export default class PlainText extends Component { } focus() { - this._input.focus(); + this._input?.focus(); } blur() { - this._input.blur(); + this._input?.blur(); } getFontSize() { @@ -79,20 +82,73 @@ export default class PlainText extends Component { }; } + replaceLineBreakTags( value ) { + return value?.replace( RegExp( '
', 'gim' ), '\n' ); + } + + onChangeTextInput( event ) { + const { onChange } = this.props; + onChange( event.nativeEvent.text ); + } + + onChangeRichText( value ) { + const { onChange } = this.props; + // The
tags have to be replaced with new line characters + // as the content of plain text shouldn't contain HTML tags. + onChange( this.replaceLineBreakTags( value ) ); + } + render() { - const { style } = this.props; + const { + style, + __experimentalVersion, + onFocus, + ...otherProps + } = this.props; const textStyles = [ style || styles[ 'block-editor-plain-text' ], this.getFontSize(), ]; + if ( __experimentalVersion === 2 ) { + const disableFormattingProps = { + withoutInteractiveFormatting: true, + disableEditingMenu: true, + __unstableDisableFormats: true, + disableSuggestions: true, + }; + + const forcePlainTextProps = { + preserveWhiteSpace: true, + __unstablePastePlainText: true, + multiline: false, + }; + + const fontProps = { + fontFamily: style?.fontFamily, + fontSize: style?.fontSize, + fontWeight: style?.fontWeight, + }; + + return ( + + ); + } + return ( ( this._input = x ) } - onChange={ ( event ) => { - this.props.onChange( event.nativeEvent.text ); - } } + onChange={ this.onChangeTextInput } onFocus={ this.props.onFocus } // Always assign onFocus as a props. onBlur={ this.props.onBlur } // Always assign onBlur as a props. fontFamily={ diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index d30fa8eae5e92d..eacd225eeb091e 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -76,6 +76,8 @@ function removeNativeProps( props ) { 'minWidth', 'maxWidth', 'setRef', + 'disableSuggestions', + 'disableAutocorrection', ] ); } 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 2c24022c8e9749..0232b68a2a671c 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -111,6 +111,8 @@ function RichTextWrapper( maxWidth, onBlur, setRef, + disableSuggestions, + disableAutocorrection, ...props }, forwardedRef @@ -635,6 +637,8 @@ function RichTextWrapper( maxWidth={ maxWidth } onBlur={ onBlur } setRef={ setRef } + disableSuggestions={ disableSuggestions } + disableAutocorrection={ disableAutocorrection } // Props to be set on the editable container are destructured on the // element itself for web (see below), but passed through rich text // for native. diff --git a/packages/block-library/src/shortcode/edit.native.js b/packages/block-library/src/shortcode/edit.native.js index 778254784682f1..044ad1d23877f8 100644 --- a/packages/block-library/src/shortcode/edit.native.js +++ b/packages/block-library/src/shortcode/edit.native.js @@ -9,6 +9,7 @@ import { View, Text } from 'react-native'; import { __ } from '@wordpress/i18n'; import { PlainText } from '@wordpress/block-editor'; import { withPreferredColorScheme } from '@wordpress/compose'; +import { useCallback } from '@wordpress/element'; /** * Internal dependencies @@ -23,11 +24,16 @@ export function ShortcodeEdit( props ) { onFocus, onBlur, getStylesFromColorScheme, + blockWidth, } = props; const titleStyle = getStylesFromColorScheme( styles.blockTitle, styles.blockTitleDark ); + const shortcodeContainerStyle = getStylesFromColorScheme( + styles.blockShortcodeContainer, + styles.blockShortcodeContainerDark + ); const shortcodeStyle = getStylesFromColorScheme( styles.blockShortcode, styles.blockShortcodeDark @@ -37,24 +43,32 @@ export function ShortcodeEdit( props ) { styles.placeholderDark ); + const maxWidth = + blockWidth - + shortcodeContainerStyle.paddingLeft + + shortcodeContainerStyle.paddingRight; + + const onChange = useCallback( ( text ) => setAttributes( { text } ), [ + setAttributes, + ] ); + return ( { __( 'Shortcode' ) } - setAttributes( { text } ) } - placeholder={ __( 'Add a shortcode…' ) } - aria-label={ __( 'Shortcode' ) } - isSelected={ props.isSelected } - onFocus={ onFocus } - onBlur={ onBlur } - autoCorrect={ false } - autoComplete="off" - placeholderTextColor={ placeholderStyle.color } - /> + <View style={ shortcodeContainerStyle }> + <PlainText + __experimentalVersion={ 2 } + value={ attributes.text } + style={ shortcodeStyle } + onChange={ onChange } + placeholder={ __( 'Add a shortcode…' ) } + onFocus={ onFocus } + onBlur={ onBlur } + placeholderTextColor={ placeholderStyle.color } + maxWidth={ maxWidth } + disableAutocorrection + /> + </View> </View> ); } diff --git a/packages/block-library/src/shortcode/style.native.scss b/packages/block-library/src/shortcode/style.native.scss index ed7d235960abdd..b828e7ff28f7a9 100644 --- a/packages/block-library/src/shortcode/style.native.scss +++ b/packages/block-library/src/shortcode/style.native.scss @@ -11,18 +11,25 @@ color: $gray-50; } +.blockShortcodeContainer { + padding: 12px; + border-radius: 4px; + background-color: $gray-light; +} + +.blockShortcodeContainerDark { + background-color: $gray-100; +} + .blockShortcode { font-family: $default-monospace-font; font-weight: 400; font-size: 14px; - padding: 12px; - border-radius: 4px; - background-color: $gray-light; + color: $gray-900; } .blockShortcodeDark { color: $white; - background-color: $gray-100; } .placeholder { diff --git a/packages/block-library/src/shortcode/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/shortcode/test/__snapshots__/edit.native.js.snap new file mode 100644 index 00000000000000..12d5722a68c39d --- /dev/null +++ b/packages/block-library/src/shortcode/test/__snapshots__/edit.native.js.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Shortcode block edits content 1`] = ` +"<!-- wp:shortcode --> +[youtube https://www.youtube.com/watch?v=ssfHW5lwFZg] +<!-- /wp:shortcode -->" +`; + +exports[`Shortcode block inserts block 1`] = `"<!-- wp:shortcode /-->"`; diff --git a/packages/block-library/src/shortcode/test/edit.native.js b/packages/block-library/src/shortcode/test/edit.native.js index c6aa9261b6d9f5..f6c9ce705f5ad3 100644 --- a/packages/block-library/src/shortcode/test/edit.native.js +++ b/packages/block-library/src/shortcode/test/edit.native.js @@ -1,58 +1,76 @@ /** * External dependencies */ -import renderer from 'react-test-renderer'; -import { TextInput } from 'react-native'; +import { + getEditorHtml, + initializeEditor, + fireEvent, + waitFor, +} from 'test/helpers'; /** * WordPress dependencies */ -import { BlockEdit } from '@wordpress/block-editor'; -import { registerBlockType, unregisterBlockType } from '@wordpress/blocks'; +import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; +import { registerCoreBlocks } from '@wordpress/block-library'; -/** - * Internal dependencies - */ -import { metadata, settings, name } from '../index'; +beforeAll( () => { + // Register all core blocks + registerCoreBlocks(); +} ); + +afterAll( () => { + // Clean up registered blocks + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); + } ); +} ); + +describe( 'Shortcode block', () => { + it( 'inserts block', async () => { + const { + getByA11yLabel, + getByTestId, + getByText, + } = await initializeEditor(); -const Shortcode = ( { clientId, ...props } ) => ( - <BlockEdit name={ name } clientId={ clientId || 0 } { ...props } /> -); + fireEvent.press( getByA11yLabel( 'Add block' ) ); -describe( 'Shortcode', () => { - beforeAll( () => { - registerBlockType( name, { - ...metadata, - ...settings, + const blockList = getByTestId( 'InserterUI-Blocks' ); + // onScroll event used to force the FlatList to render all items + fireEvent.scroll( blockList, { + nativeEvent: { + contentOffset: { y: 0, x: 0 }, + contentSize: { width: 100, height: 100 }, + layoutMeasurement: { width: 100, height: 100 }, + }, } ); - } ); - afterAll( () => { - unregisterBlockType( name ); - } ); + fireEvent.press( await waitFor( () => getByText( 'Shortcode' ) ) ); - it( 'renders without crashing', () => { - const component = renderer.create( - <Shortcode attributes={ { text: '' } } /> - ); - const rendered = component.toJSON(); - expect( rendered ).toBeTruthy(); + expect( getByA11yLabel( /Shortcode Block\. Row 1/ ) ).toBeVisible(); + expect( getEditorHtml() ).toMatchSnapshot(); } ); - it( 'renders given text without crashing', () => { - 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]' + it( 'edits content', async () => { + const { getByA11yLabel, getByPlaceholderText } = await initializeEditor( + { + initialHtml: '<!-- wp:shortcode /-->', + } ); + const shortcodeBlock = getByA11yLabel( /Shortcode Block\. Row 1/ ); + fireEvent.press( shortcodeBlock ); + + const textField = getByPlaceholderText( 'Add a shortcode…' ); + fireEvent( textField, 'focus' ); + fireEvent( textField, 'onChange', { + nativeEvent: { + eventCount: 1, + target: undefined, + text: '[youtube https://www.youtube.com/watch?v=ssfHW5lwFZg]', + }, + } ); + + expect( getEditorHtml() ).toMatchSnapshot(); } ); } ); diff --git a/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java b/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java index 52dac695bc9973..1e3e90c3a48442 100644 --- a/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java +++ b/packages/react-native-aztec/android/src/main/java/org/wordpress/mobile/ReactNativeAztec/ReactAztecManager.java @@ -13,6 +13,7 @@ import android.os.Handler; import android.os.Looper; import android.text.Editable; +import android.text.InputType; import android.text.Layout; import android.text.TextUtils; import android.text.TextWatcher; @@ -617,6 +618,16 @@ public void setShouldDeleteEnter(final ReactAztecText view, boolean shouldDelete view.shouldDeleteEnter = shouldDeleteEnter; } + @ReactProp(name = "disableAutocorrection", defaultBoolean = false) + public void disableAutocorrection(final ReactAztecText view, boolean disable) { + if (disable) { + view.setInputType((view.getInputType() & ~InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); + } + else { + view.setInputType((view.getInputType() & ~InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); + } + } + @Override public Map<String, Integer> getCommandsMap() { return MapBuilder.<String, Integer>builder() diff --git a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift index 5413041b6ac6ff..cc87ef29e9704b 100644 --- a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift +++ b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecView.swift @@ -31,6 +31,12 @@ class RCTAztecView: Aztec.TextView { } } + @objc var disableAutocorrection: Bool = false { + didSet { + autocorrectionType = disableAutocorrection ? .no : .default + } + } + override var textAlignment: NSTextAlignment { set { super.textAlignment = newValue diff --git a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecViewManager.m b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecViewManager.m index bad6dd8abf7680..e8038ab17a7044 100644 --- a/packages/react-native-aztec/ios/RNTAztecView/RCTAztecViewManager.m +++ b/packages/react-native-aztec/ios/RNTAztecView/RCTAztecViewManager.m @@ -31,6 +31,7 @@ @interface RCT_EXTERN_MODULE(RCTAztecViewManager, NSObject) RCT_EXPORT_VIEW_PROPERTY(lineHeight, CGFloat) RCT_EXPORT_VIEW_PROPERTY(disableEditingMenu, BOOL) +RCT_EXPORT_VIEW_PROPERTY(disableAutocorrection, BOOL) RCT_REMAP_VIEW_PROPERTY(textAlign, textAlignment, NSTextAlignment) RCT_REMAP_VIEW_PROPERTY(selectionColor, tintColor, UIColor) diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js index f460019df8b3ce..4fadc90b8d4baa 100644 --- a/packages/rich-text/src/component/index.native.js +++ b/packages/rich-text/src/component/index.native.js @@ -1050,6 +1050,7 @@ export class RichText extends Component { baseGlobalStyles, selectionStart, selectionEnd, + disableSuggestions, } = this.props; const { currentFontSize } = this.state; @@ -1220,6 +1221,7 @@ export class RichText extends Component { minWidth={ minWidth } id={ this.props.id } selectionColor={ this.props.selectionColor } + disableAutocorrection={ this.props.disableAutocorrection } /> { isSelected && ( <> @@ -1230,11 +1232,13 @@ export class RichText extends Component { onChange={ this.onFormatChange } onFocus={ () => {} } /> - <BlockFormatControls> - <ToolbarButtonWithOptions - options={ this.suggestionOptions() } - /> - </BlockFormatControls> + { ! disableSuggestions && ( + <BlockFormatControls> + <ToolbarButtonWithOptions + options={ this.suggestionOptions() } + /> + </BlockFormatControls> + ) } </> ) } </View> @@ -1249,10 +1253,17 @@ RichText.defaultProps = { }; const withFormatTypes = ( WrappedComponent ) => ( props ) => { + const { + clientId, + identifier, + withoutInteractiveFormatting, + allowedFormats, + } = props; const { formatTypes } = useFormatTypes( { - clientId: props.clientId, - identifier: props.identifier, - withoutInteractiveFormatting: props.withoutInteractiveFormatting, + clientId, + identifier, + withoutInteractiveFormatting, + allowedFormats, } ); return <WrappedComponent { ...props } formatTypes={ formatTypes } />; diff --git a/test/native/__mocks__/styleMock.js b/test/native/__mocks__/styleMock.js index 4723663203954f..11f58d79b1afcd 100644 --- a/test/native/__mocks__/styleMock.js +++ b/test/native/__mocks__/styleMock.js @@ -162,4 +162,7 @@ module.exports = { 'dropping-insertion-point': { height: 3, }, + blockShortcodeContainer: { + padding: 12, + }, };