From 8b4032d4f122dc0234a4a46d25cce3a3ee5edef2 Mon Sep 17 00:00:00 2001 From: Gerardo Date: Fri, 14 Jun 2024 13:04:05 +0200 Subject: [PATCH 1/7] RichText - Event Listeners: Export findSelection to be reused with the native functionality --- .../src/components/rich-text/event-listeners/input-rules.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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-- ) { From 232d1d3f550b670420fb50af1b5446b8669272d6 Mon Sep 17 00:00:00 2001 From: Gerardo Date: Fri, 14 Jun 2024 13:04:24 +0200 Subject: [PATCH 2/7] API - Factory - Add support for prefix type in native --- packages/blocks/src/api/factory.js | 4 ++++ 1 file changed, 4 insertions(+) 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; } From 9fce82d64270e61a4cff94049c4758a5dfcb686a Mon Sep 17 00:00:00 2001 From: Gerardo Date: Fri, 14 Jun 2024 13:05:35 +0200 Subject: [PATCH 3/7] Native RichText - Add suppot for inputRules --- .../src/components/rich-text/index.native.js | 25 +++++++++++-------- .../rich-text/native/index.native.js | 17 +++++++++++++ 2 files changed, 32 insertions(+), 10 deletions(-) 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..8000bf5b5c7b64 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -7,7 +7,7 @@ import clsx from 'clsx'; * WordPress dependencies */ import { Platform, useRef, useCallback, forwardRef } from '@wordpress/element'; -import { useDispatch, useSelect } from '@wordpress/data'; +import { useDispatch, useSelect, useRegistry } from '@wordpress/data'; import { pasteHandler, children as childrenSource, @@ -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'; @@ -104,6 +105,7 @@ export function RichTextWrapper( providedRef ) { const instanceId = useInstanceId( RichTextWrapper ); + const registry = useRegistry(); identifier = identifier || instanceId; @@ -502,7 +504,7 @@ export function RichTextWrapper( ); const inputRule = useCallback( - ( value, valueToFormat ) => { + ( value ) => { if ( ! onReplace ) { return; } @@ -518,7 +520,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 +535,18 @@ 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 ] ); - __unstableMarkAutomaticChange(); + selectionChange( ...currentSelection ); + registry + .dispatch( blockEditorStore ) + .__unstableMarkAutomaticChange(); }, - [ onReplace, __unstableMarkAutomaticChange ] + [ onReplace, start, selectionChange, registry ] ); 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; From 904391ff361bd6f0942b37627751cb92154f157d Mon Sep 17 00:00:00 2001 From: Gerardo Date: Fri, 14 Jun 2024 13:07:41 +0200 Subject: [PATCH 4/7] Native - Add prefix support tests --- .../test/__snapshots__/edit.native.js.snap | 30 ++++++ .../src/paragraph/test/edit.native.js | 99 +++++++++++++++++++ 2 files changed, 129 insertions(+) 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(); From eac1559dd5858bed5b603c8687ec988bca8ccbd0 Mon Sep 17 00:00:00 2001 From: Gerardo Date: Fri, 14 Jun 2024 13:10:34 +0200 Subject: [PATCH 5/7] Remove useRegistry --- .../src/components/rich-text/index.native.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) 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 8000bf5b5c7b64..4899d49b8c8eb0 100644 --- a/packages/block-editor/src/components/rich-text/index.native.js +++ b/packages/block-editor/src/components/rich-text/index.native.js @@ -7,7 +7,7 @@ import clsx from 'clsx'; * WordPress dependencies */ import { Platform, useRef, useCallback, forwardRef } from '@wordpress/element'; -import { useDispatch, useSelect, useRegistry } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; import { pasteHandler, children as childrenSource, @@ -105,7 +105,6 @@ export function RichTextWrapper( providedRef ) { const instanceId = useInstanceId( RichTextWrapper ); - const registry = useRegistry(); identifier = identifier || instanceId; @@ -542,11 +541,9 @@ export function RichTextWrapper( const currentSelection = findSelection( [ block ] ); onReplace( [ block ] ); selectionChange( ...currentSelection ); - registry - .dispatch( blockEditorStore ) - .__unstableMarkAutomaticChange(); + __unstableMarkAutomaticChange(); }, - [ onReplace, start, selectionChange, registry ] + [ onReplace, start, selectionChange, __unstableMarkAutomaticChange ] ); const mergedRef = useMergeRefs( [ providedRef, fallbackRef ] ); From aa24f93fc71e73185c54a065bac0793d3c38849a Mon Sep 17 00:00:00 2001 From: Gerardo Date: Fri, 14 Jun 2024 13:53:09 +0200 Subject: [PATCH 6/7] Update Changelog --- packages/react-native-editor/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 07dbe7a2459958..dc4c05e4394d25 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] - [*] RichText - Fix undefined onDelete callback [#62486] +- [**] Add support prefix transforms [#62576] ## 1.120.0 - [*] Prevent deleting content when backspacing in the first Paragraph block [#62069] From cb79239c923471a9117a7cf192c69c50c38c2497 Mon Sep 17 00:00:00 2001 From: Gerardo Date: Wed, 19 Jun 2024 13:11:20 +0200 Subject: [PATCH 7/7] Update Changelog --- packages/react-native-editor/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index c8cdc9664c7df8..7b81a5c2f86267 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -13,10 +13,10 @@ 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] -- [**] Add support prefix transforms [#62576] ## 1.120.0 - [*] Prevent deleting content when backspacing in the first Paragraph block [#62069]