From 35db76a868703b6f5de43e2f0f8f469d2ca06a9f Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco Date: Thu, 20 Jun 2024 11:10:47 +0200 Subject: [PATCH] [Mobile] Support prefix transforms in mobile (#62576) * RichText - Event Listeners: Export findSelection to be reused with the native functionality * API - Factory - Add support for prefix type in native * Native RichText - Add suppot for inputRules * Native - Add prefix support tests * Remove useRegistry * Update Changelog * Update Changelog Co-authored-by: geriux Co-authored-by: twstokes --- .../rich-text/event-listeners/input-rules.js | 2 +- .../src/components/rich-text/index.native.js | 18 ++-- .../rich-text/native/index.native.js | 17 ++++ .../test/__snapshots__/edit.native.js.snap | 30 ++++++ .../src/paragraph/test/edit.native.js | 99 +++++++++++++++++++ packages/blocks/src/api/factory.js | 4 + packages/react-native-editor/CHANGELOG.md | 1 + 7 files changed, 162 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/event-listeners/input-rules.js b/packages/block-editor/src/components/rich-text/event-listeners/input-rules.js index f316348994c85b..4a1e8400e35a17 100644 --- a/packages/block-editor/src/components/rich-text/event-listeners/input-rules.js +++ b/packages/block-editor/src/components/rich-text/event-listeners/input-rules.js @@ -14,7 +14,7 @@ import { START_OF_SELECTED_AREA, } from '../../../utils/selection'; -function findSelection( blocks ) { +export function findSelection( blocks ) { let i = blocks.length; while ( i-- ) { 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 9390c71bdcf0b5..4899d49b8c8eb0 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -24,7 +24,6 @@ import { create, split, toHTMLString, - slice, } from '@wordpress/rich-text'; import { isURL } from '@wordpress/url'; @@ -46,6 +45,8 @@ import EmbedHandlerPicker from './embed-handler-picker'; import { Content } from './content'; import RichText from './native'; import { withDeprecations } from './with-deprecations'; +import { findSelection } from './event-listeners/input-rules'; +import { START_OF_SELECTED_AREA } from '../../utils/selection'; const classes = 'block-editor-rich-text__editable'; @@ -502,7 +503,7 @@ export function RichTextWrapper( ); const inputRule = useCallback( - ( value, valueToFormat ) => { + ( value ) => { if ( ! onReplace ) { return; } @@ -518,7 +519,7 @@ export function RichTextWrapper( return; } - const trimmedTextBefore = text.slice( 0, startPosition ).trim(); + const trimmedTextBefore = text.slice( 0, start ).trim(); const prefixTransforms = getBlockTransforms( 'from' ).filter( ( { type } ) => type === 'prefix' ); @@ -533,15 +534,16 @@ export function RichTextWrapper( return; } - const content = valueToFormat( - slice( value, startPosition, text.length ) - ); + const content = toHTMLString( { + value: insert( value, START_OF_SELECTED_AREA, 0, start ), + } ); const block = transformation.transform( content ); - + const currentSelection = findSelection( [ block ] ); onReplace( [ block ] ); + selectionChange( ...currentSelection ); __unstableMarkAutomaticChange(); }, - [ onReplace, __unstableMarkAutomaticChange ] + [ onReplace, start, selectionChange, __unstableMarkAutomaticChange ] ); const mergedRef = useMergeRefs( [ providedRef, fallbackRef ] ); diff --git a/packages/block-editor/src/components/rich-text/native/index.native.js b/packages/block-editor/src/components/rich-text/native/index.native.js index 4eeaabe6d790aa..f30a56fd1268ce 100644 --- a/packages/block-editor/src/components/rich-text/native/index.native.js +++ b/packages/block-editor/src/components/rich-text/native/index.native.js @@ -316,6 +316,23 @@ export class RichText extends Component { const contentWithoutRootTag = this.removeRootTagsProducedByAztec( event.nativeEvent.text ); + + const { __unstableInputRule } = this.props; + const currentValuePosition = { + end: this.isIOS ? this.selectionEnd : this.selectionEnd + 1, + start: this.isIOS ? this.selectionStart : this.selectionStart + 1, + }; + + if ( + __unstableInputRule && + __unstableInputRule( { + ...currentValuePosition, + ...this.formatToValue( contentWithoutRootTag ), + } ) + ) { + return; + } + // On iOS, onChange can be triggered after selection changes, even though there are no content changes. if ( contentWithoutRootTag === this.value?.toString() ) { return; diff --git a/packages/block-library/src/paragraph/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/paragraph/test/__snapshots__/edit.native.js.snap index 10e599372dfef9..82b6a608814a72 100644 --- a/packages/block-library/src/paragraph/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/paragraph/test/__snapshots__/edit.native.js.snap @@ -1,5 +1,35 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Paragraph block should be able to use a prefix to create a Heading block 1`] = ` +" +

+" +`; + +exports[`Paragraph block should be able to use a prefix to create a List block 1`] = ` +" +
    +
  • +
+" +`; + +exports[`Paragraph block should be able to use a prefix to create a Quote block 1`] = ` +" +
+

+
+" +`; + +exports[`Paragraph block should be able to use a prefix to create a numbered List block 1`] = ` +" +
    +
  1. +
+" +`; + exports[`Paragraph block should prevent deleting the first Paragraph block when pressing backspace at the start 1`] = ` "

A quick brown fox jumps over the lazy dog.

diff --git a/packages/block-library/src/paragraph/test/edit.native.js b/packages/block-library/src/paragraph/test/edit.native.js index df18654c1915dc..c3c7e7ca49d720 100644 --- a/packages/block-library/src/paragraph/test/edit.native.js +++ b/packages/block-library/src/paragraph/test/edit.native.js @@ -90,6 +90,105 @@ describe( 'Paragraph block', () => { expect( getEditorHtml() ).toMatchSnapshot(); } ); + it( 'should be able to use a prefix to create a Heading block', async () => { + const screen = await initializeEditor(); + await addBlock( screen, 'Paragraph' ); + const text = '# '; + + const paragraphBlock = getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + const paragraphTextInput = + within( paragraphBlock ).getByPlaceholderText( 'Start writing…' ); + typeInRichText( paragraphTextInput, text, { + finalSelectionStart: 1, + finalSelectionEnd: 1, + } ); + + fireEvent( paragraphTextInput, 'onChange', { + nativeEvent: { text }, + preventDefault() {}, + } ); + + const headingBlock = getBlock( screen, 'Heading' ); + expect( headingBlock ).toBeVisible(); + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'should be able to use a prefix to create a Quote block', async () => { + const screen = await initializeEditor(); + await addBlock( screen, 'Paragraph' ); + const text = '> '; + + const paragraphBlock = getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + const paragraphTextInput = + within( paragraphBlock ).getByPlaceholderText( 'Start writing…' ); + typeInRichText( paragraphTextInput, text, { + finalSelectionStart: 1, + finalSelectionEnd: 1, + } ); + + fireEvent( paragraphTextInput, 'onChange', { + nativeEvent: { text }, + preventDefault() {}, + } ); + const quoteBlock = getBlock( screen, 'Quote' ); + await triggerBlockListLayout( quoteBlock ); + + expect( quoteBlock ).toBeVisible(); + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'should be able to use a prefix to create a List block', async () => { + const screen = await initializeEditor(); + await addBlock( screen, 'Paragraph' ); + const text = '- '; + + const paragraphBlock = getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + const paragraphTextInput = + within( paragraphBlock ).getByPlaceholderText( 'Start writing…' ); + typeInRichText( paragraphTextInput, text, { + finalSelectionStart: 1, + finalSelectionEnd: 1, + } ); + + fireEvent( paragraphTextInput, 'onChange', { + nativeEvent: { text }, + preventDefault() {}, + } ); + const listBlock = getBlock( screen, 'List' ); + await triggerBlockListLayout( listBlock ); + + expect( listBlock ).toBeVisible(); + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'should be able to use a prefix to create a numbered List block', async () => { + const screen = await initializeEditor(); + await addBlock( screen, 'Paragraph' ); + const text = '1. '; + + const paragraphBlock = getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + const paragraphTextInput = + within( paragraphBlock ).getByPlaceholderText( 'Start writing…' ); + typeInRichText( paragraphTextInput, text, { + finalSelectionStart: 2, + finalSelectionEnd: 2, + } ); + + fireEvent( paragraphTextInput, 'onChange', { + nativeEvent: { text }, + preventDefault() {}, + } ); + const listBlock = getBlock( screen, 'List' ); + await triggerBlockListLayout( listBlock ); + + expect( listBlock ).toBeVisible(); + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + it( 'should bold text', async () => { // Arrange const screen = await initializeEditor(); diff --git a/packages/blocks/src/api/factory.js b/packages/blocks/src/api/factory.js index 53333ef688085f..25bf64ca65dc90 100644 --- a/packages/blocks/src/api/factory.js +++ b/packages/blocks/src/api/factory.js @@ -402,6 +402,10 @@ export function getBlockTransforms( direction, blockTypeOrName ) { return true; } + if ( t.type === 'prefix' ) { + return true; + } + if ( ! t.blocks || ! t.blocks.length ) { return false; } diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 02eb3154d01e06..7b81a5c2f86267 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -13,6 +13,7 @@ For each user feature we should also add a importance categorization label to i - [internal] Fix Inserter items list filtering [#62334] - [*] Prevent hiding the keyboard when creating new list items [#62446] - [*] Fix issue when pasting HTML content [#62588] +- [**] Add support prefix transforms [#62576] ## 1.120.1 - [*] RichText - Fix undefined onDelete callback [#62486]