diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 3e0adca5bf779..71358a58596ef 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -361,6 +361,7 @@ function RichTextWrapper( __unstableAllowPrefixTransformations, formatTypes, onReplace, + selectionChange, } ), useRemoveBrowserShortcuts(), useShortcuts( keyboardShortcuts ), diff --git a/packages/block-editor/src/components/rich-text/use-input-rules.js b/packages/block-editor/src/components/rich-text/use-input-rules.js index 3174fafa256e3..719bba1caa274 100644 --- a/packages/block-editor/src/components/rich-text/use-input-rules.js +++ b/packages/block-editor/src/components/rich-text/use-input-rules.js @@ -1,9 +1,14 @@ +/** + * External dependencies + */ +import { findKey } from 'lodash'; + /** * WordPress dependencies */ import { useRef } from '@wordpress/element'; import { useRefEffect } from '@wordpress/compose'; -import { slice, toHTMLString } from '@wordpress/rich-text'; +import { insert, toHTMLString } from '@wordpress/rich-text'; import { getBlockTransforms, findTransform } from '@wordpress/blocks'; import { useDispatch } from '@wordpress/data'; @@ -13,6 +18,37 @@ import { useDispatch } from '@wordpress/data'; import { store as blockEditorStore } from '../../store'; import { preventEventDiscovery } from './prevent-event-discovery'; +// A robust way to retain selection position through various +// transforms is to insert a special character at the position and +// then recover it. +const START_OF_SELECTED_AREA = '\u0086'; + +function findSelection( blocks ) { + let i = blocks.length; + + while ( i-- ) { + const attributeKey = findKey( + blocks[ i ].attributes, + ( v ) => + typeof v === 'string' && + v.indexOf( START_OF_SELECTED_AREA ) !== -1 + ); + + if ( attributeKey ) { + blocks[ i ].attributes[ attributeKey ] = blocks[ i ].attributes[ + attributeKey + ].replace( START_OF_SELECTED_AREA, '' ); + return blocks[ i ].clientId; + } + + const nestedSelection = findSelection( blocks[ i ].innerBlocks ); + + if ( nestedSelection ) { + return nestedSelection; + } + } +} + export function useInputRules( props ) { const { __unstableMarkLastChangeAsPersistent, @@ -22,7 +58,7 @@ export function useInputRules( props ) { propsRef.current = props; return useRefEffect( ( element ) => { function inputRule() { - const { value, onReplace } = propsRef.current; + const { value, onReplace, selectionChange } = propsRef.current; if ( ! onReplace ) { return; @@ -52,10 +88,11 @@ export function useInputRules( props ) { } const content = toHTMLString( { - value: slice( value, start, text.length ), + value: insert( value, START_OF_SELECTED_AREA, 0, start ), } ); const block = transformation.transform( content ); + selectionChange( findSelection( [ block ] ) ); onReplace( [ block ] ); __unstableMarkAutomaticChange(); }