diff --git a/packages/block-editor/src/components/autocomplete/index.native.js b/packages/block-editor/src/components/autocomplete/index.native.js
new file mode 100644
index 0000000000000..461f67a0a4bcb
--- /dev/null
+++ b/packages/block-editor/src/components/autocomplete/index.native.js
@@ -0,0 +1 @@
+export default () => null;
diff --git a/packages/block-editor/src/components/rich-text/file-paste-handler.js b/packages/block-editor/src/components/rich-text/file-paste-handler.js
new file mode 100644
index 0000000000000..eceeb069b263f
--- /dev/null
+++ b/packages/block-editor/src/components/rich-text/file-paste-handler.js
@@ -0,0 +1,11 @@
+/**
+ * WordPress dependencies
+ */
+import { createBlobURL } from '@wordpress/blob';
+
+export function filePasteHandler( files ) {
+ return files
+ .filter( ( { type } ) => /^image\/(?:jpe?g|png|gif)$/.test( type ) )
+ .map( ( file ) => `` )
+ .join( '' );
+}
diff --git a/packages/block-editor/src/components/rich-text/file-paste-handler.native.js b/packages/block-editor/src/components/rich-text/file-paste-handler.native.js
new file mode 100644
index 0000000000000..41402a0dcda68
--- /dev/null
+++ b/packages/block-editor/src/components/rich-text/file-paste-handler.native.js
@@ -0,0 +1,3 @@
+export function filePasteHandler( files ) {
+ return files.map( ( url ) => `` ).join( '' );
+}
diff --git a/packages/block-editor/src/components/rich-text/format-toolbar-container.js b/packages/block-editor/src/components/rich-text/format-toolbar-container.js
new file mode 100644
index 0000000000000..fc857311706db
--- /dev/null
+++ b/packages/block-editor/src/components/rich-text/format-toolbar-container.js
@@ -0,0 +1,59 @@
+/**
+ * WordPress dependencies
+ */
+import { Popover } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import BlockFormatControls from '../block-format-controls';
+import FormatToolbar from './format-toolbar';
+
+function getAnchorRect( anchorObj ) {
+ const { current } = anchorObj;
+ const rect = current.getBoundingClientRect();
+
+ // Add some space.
+ const buffer = 6;
+
+ // Subtract padding if any.
+ let { paddingTop } = window.getComputedStyle( current );
+
+ paddingTop = parseInt( paddingTop, 10 );
+
+ return {
+ x: rect.left,
+ y: rect.top + paddingTop - buffer,
+ width: rect.width,
+ height: rect.height - paddingTop + buffer,
+ left: rect.left,
+ right: rect.right,
+ top: rect.top + paddingTop - buffer,
+ bottom: rect.bottom,
+ };
+}
+
+const FormatToolbarContainer = ( { inline, anchorObj } ) => {
+ if ( inline ) {
+ // Render in popover
+ return (
+ getAnchorRect( anchorObj ) }
+ className="block-editor-rich-text__inline-format-toolbar"
+ >
+
+
+ );
+ }
+ // Render regular toolbar
+ return (
+
+
+
+ );
+};
+
+export default FormatToolbarContainer;
diff --git a/packages/block-editor/src/components/rich-text/format-toolbar-container.native.js b/packages/block-editor/src/components/rich-text/format-toolbar-container.native.js
new file mode 100644
index 0000000000000..d37fe5bfee0f6
--- /dev/null
+++ b/packages/block-editor/src/components/rich-text/format-toolbar-container.native.js
@@ -0,0 +1,16 @@
+/**
+ * Internal dependencies
+ */
+import BlockFormatControls from '../block-format-controls';
+import FormatToolbar from './format-toolbar';
+
+const FormatToolbarContainer = () => {
+ // Render regular toolbar
+ return (
+
+
+
+ );
+};
+
+export default FormatToolbarContainer;
diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js
index b78e8c3a46ffc..53292f1402d5d 100644
--- a/packages/block-editor/src/components/rich-text/index.js
+++ b/packages/block-editor/src/components/rich-text/index.js
@@ -7,9 +7,9 @@ import { omit } from 'lodash';
/**
* WordPress dependencies
*/
-import { RawHTML, Component, createRef } from '@wordpress/element';
+import { RawHTML, Component, createRef, Platform } from '@wordpress/element';
import { withDispatch, withSelect } from '@wordpress/data';
-import { pasteHandler, children as childrenSource, getBlockTransforms, findTransform } from '@wordpress/blocks';
+import { pasteHandler, children as childrenSource, getBlockTransforms, findTransform, isUnmodifiedDefaultBlock } from '@wordpress/blocks';
import { withInstanceId, compose } from '@wordpress/compose';
import {
__experimentalRichText as RichText,
@@ -25,8 +25,7 @@ import {
toHTMLString,
slice,
} from '@wordpress/rich-text';
-import { withFilters, Popover } from '@wordpress/components';
-import { createBlobURL } from '@wordpress/blob';
+import { withFilters } from '@wordpress/components';
import deprecated from '@wordpress/deprecated';
import { isURL } from '@wordpress/url';
@@ -34,10 +33,10 @@ import { isURL } from '@wordpress/url';
* Internal dependencies
*/
import Autocomplete from '../autocomplete';
-import BlockFormatControls from '../block-format-controls';
-import FormatToolbar from './format-toolbar';
import { withBlockEditContext } from '../block-edit/context';
import { RemoveBrowserShortcuts } from './remove-browser-shortcuts';
+import { filePasteHandler } from './file-paste-handler';
+import FormatToolbarContainer from './format-toolbar-container';
const wrapperClasses = 'editor-rich-text block-editor-rich-text';
const classes = 'editor-rich-text__editable block-editor-rich-text__editable';
@@ -66,7 +65,6 @@ class RichTextWrapper extends Component {
this.onPaste = this.onPaste.bind( this );
this.onDelete = this.onDelete.bind( this );
this.inputRule = this.inputRule.bind( this );
- this.getAnchorRect = this.getAnchorRect.bind( this );
}
onEnter( { value, onChange, shiftKey } ) {
@@ -124,7 +122,7 @@ class RichTextWrapper extends Component {
}
}
- onPaste( { value, onChange, html, plainText, image } ) {
+ onPaste( { value, onChange, html, plainText, files } ) {
const {
onReplace,
onSplit,
@@ -134,16 +132,18 @@ class RichTextWrapper extends Component {
__unstableEmbedURLOnPaste,
} = this.props;
- if ( image && ! html ) {
- const file = image.getAsFile ? image.getAsFile() : image;
+ // Only process file if no HTML is present.
+ // Note: a pasted file may have the URL as plain text.
+ if ( files && files.length && ! html ) {
const content = pasteHandler( {
- HTML: ``,
+ HTML: filePasteHandler( files ),
mode: 'BLOCKS',
tagName,
} );
// Allows us to ask for this information when we get a report.
- window.console.log( 'Received item:\n\n', file );
+ // eslint-disable-next-line no-console
+ window.console.log( 'Received items:\n\n', files );
if ( onReplace && isEmpty( value ) ) {
onReplace( content );
@@ -303,30 +303,6 @@ class RichTextWrapper extends Component {
return formattingControls.map( ( name ) => `core/${ name }` );
}
- getAnchorRect() {
- const { current } = this.ref;
- const rect = current.getBoundingClientRect();
-
- // Add some space.
- const buffer = 6;
-
- // Subtract padding if any.
- let { paddingTop } = window.getComputedStyle( current );
-
- paddingTop = parseInt( paddingTop, 10 );
-
- return {
- x: rect.left,
- y: rect.top + paddingTop - buffer,
- width: rect.width,
- height: rect.height - paddingTop + buffer,
- left: rect.left,
- right: rect.right,
- top: rect.top + paddingTop - buffer,
- bottom: rect.bottom,
- };
- }
-
render() {
const {
children,
@@ -424,22 +400,7 @@ class RichTextWrapper extends Component {
{ ( { isSelected, value, onChange, Editable } ) =>
<>
{ children && children( { value, onChange } ) }
- { isSelected && ! inlineToolbar && hasFormats && (
-
-
-
- ) }
- { isSelected && inlineToolbar && hasFormats && (
-
-
-
- ) }
+ { isSelected && hasFormats && ( ) }
{ isSelected && }
( { clientId } ) ),
+ withBlockEditContext( ( { clientId, onCaretVerticalPositionChange, isSelected }, ownProps ) => {
+ if ( Platform.OS === 'web' ) {
+ return { clientId };
+ }
+ return {
+ clientId,
+ blockIsSelected: ownProps.isSelected !== undefined ? ownProps.isSelected : isSelected,
+ onCaretVerticalPositionChange,
+ };
+ } ),
withSelect( ( select, {
clientId,
instanceId,
@@ -495,6 +465,7 @@ const RichTextContainer = compose( [
getSelectionEnd,
getSettings,
didAutomaticChange,
+ __unstableGetBlockWithoutInnerBlocks,
} = select( 'core/block-editor' );
const selectionStart = getSelectionStart();
@@ -509,6 +480,18 @@ const RichTextContainer = compose( [
isSelected = selectionStart.clientId === clientId;
}
+ let extraProps = {};
+ if ( Platform.OS === 'native' ) {
+ // If the block of this RichText is unmodified then it's a candidate for replacing when adding a new block.
+ // In order to fix https://github.com/wordpress-mobile/gutenberg-mobile/issues/1126, let's blur on unmount in that case.
+ // This apparently assumes functionality the BlockHlder actually
+ const block = clientId && __unstableGetBlockWithoutInnerBlocks( clientId );
+ const shouldBlurOnUnmount = block && isSelected && isUnmodifiedDefaultBlock( block );
+ extraProps = {
+ shouldBlurOnUnmount,
+ };
+ }
+
return {
canUserUseUnfilteredHTML: __experimentalCanUserUseUnfilteredHTML,
isCaretWithinFormattedText: isCaretWithinFormattedText(),
@@ -516,6 +499,7 @@ const RichTextContainer = compose( [
selectionEnd: isSelected ? selectionEnd.offset : undefined,
isSelected,
didAutomaticChange: didAutomaticChange(),
+ ...extraProps,
};
} ),
withDispatch( ( dispatch, {
diff --git a/packages/block-editor/src/components/rich-text/index.native.js b/packages/block-editor/src/components/rich-text/index.native.js
deleted file mode 100644
index feab0f747a922..0000000000000
--- a/packages/block-editor/src/components/rich-text/index.native.js
+++ /dev/null
@@ -1,213 +0,0 @@
-/**
- * External dependencies
- */
-import classnames from 'classnames';
-import { View } from 'react-native';
-
-/**
- * WordPress dependencies
- */
-import { RawHTML } from '@wordpress/element';
-import { withDispatch, withSelect } from '@wordpress/data';
-import { pasteHandler, isUnmodifiedDefaultBlock } from '@wordpress/blocks';
-import { withInstanceId, compose } from '@wordpress/compose';
-import { __experimentalRichText as RichText } from '@wordpress/rich-text';
-
-/**
- * Internal dependencies
- */
-import Autocomplete from '../autocomplete';
-import BlockFormatControls from '../block-format-controls';
-import FormatToolbar from './format-toolbar';
-import { withBlockEditContext } from '../block-edit/context';
-
-const wrapperClasses = 'editor-rich-text block-editor-rich-text';
-const classes = 'editor-rich-text__editable block-editor-rich-text__editable';
-
-function RichTextWraper( {
- children,
- tagName,
- value: originalValue,
- onChange: originalOnChange,
- selectionStart,
- selectionEnd,
- onSelectionChange,
- multiline,
- inlineToolbar,
- wrapperClassName,
- className,
- autocompleters,
- onReplace,
- onRemove,
- onMerge,
- onSplit,
- isCaretWithinFormattedText,
- onEnterFormattedText,
- onExitFormattedText,
- canUserUseUnfilteredHTML,
- isSelected: originalIsSelected,
- onCreateUndoLevel,
- placeholder,
- // From experimental filter.
- ...experimentalProps
-} ) {
- const adjustedValue = originalValue;
- const adjustedOnChange = originalOnChange;
-
- return (
-
- { ( { isSelected, value, onChange } ) =>
-
- { children && children( { value, onChange } ) }
- { isSelected && ! inlineToolbar && (
-
-
-
- ) }
-
- }
-
- );
-}
-
-const RichTextContainer = compose( [
- withInstanceId,
- withBlockEditContext( ( { clientId, onCaretVerticalPositionChange, isSelected }, ownProps ) => {
- return {
- clientId,
- blockIsSelected: ownProps.isSelected !== undefined ? ownProps.isSelected : isSelected,
- onCaretVerticalPositionChange,
- };
- } ),
- withSelect( ( select, {
- clientId,
- instanceId,
- identifier = instanceId,
- isSelected,
- blockIsSelected,
- } ) => {
- const { getFormatTypes } = select( 'core/rich-text' );
- const {
- getSelectionStart,
- getSelectionEnd,
- __unstableGetBlockWithoutInnerBlocks,
- } = select( 'core/block-editor' );
-
- const selectionStart = getSelectionStart();
- const selectionEnd = getSelectionEnd();
-
- if ( isSelected === undefined ) {
- isSelected = (
- selectionStart.clientId === clientId &&
- selectionStart.attributeKey === identifier
- );
- }
-
- // If the block of this RichText is unmodified then it's a candidate for replacing when adding a new block.
- // In order to fix https://github.com/wordpress-mobile/gutenberg-mobile/issues/1126, let's blur on unmount in that case.
- // This apparently assumes functionality the BlockHlder actually
- const block = clientId && __unstableGetBlockWithoutInnerBlocks( clientId );
- const shouldBlurOnUnmount = block && isSelected && isUnmodifiedDefaultBlock( block );
-
- return {
- formatTypes: getFormatTypes(),
- selectionStart: isSelected ? selectionStart.offset : undefined,
- selectionEnd: isSelected ? selectionEnd.offset : undefined,
- isSelected,
- blockIsSelected,
- shouldBlurOnUnmount,
- };
- } ),
- withDispatch( ( dispatch, {
- clientId,
- instanceId,
- identifier = instanceId,
- } ) => {
- const {
- __unstableMarkLastChangeAsPersistent,
- selectionChange,
- } = dispatch( 'core/block-editor' );
-
- return {
- onCreateUndoLevel: __unstableMarkLastChangeAsPersistent,
- onSelectionChange( start, end ) {
- selectionChange( clientId, identifier, start, end );
- },
- };
- } ),
-] )( RichTextWraper );
-
-RichTextContainer.Content = ( { value, format, tagName: Tag, multiline, ...props } ) => {
- let content;
- let html = value;
- let MultilineTag;
-
- if ( multiline === true || multiline === 'p' || multiline === 'li' ) {
- MultilineTag = multiline === true ? 'p' : multiline;
- }
-
- if ( ! html && MultilineTag ) {
- html = `<${ MultilineTag }>${ MultilineTag }>`;
- }
-
- switch ( format ) {
- case 'string':
- content = { html };
- break;
- }
-
- if ( Tag ) {
- return { content };
- }
-
- return content;
-};
-
-RichTextContainer.isEmpty = ( value = '' ) => {
- // Handle deprecated `children` and `node` sources.
- if ( Array.isArray( value ) ) {
- return ! value || value.length === 0;
- }
-
- return value.length === 0;
-};
-
-RichTextContainer.Content.defaultProps = {
- format: 'string',
- value: '',
-};
-
-/**
- * @see https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/rich-text/README.md
- */
-export default RichTextContainer;
-export { RichTextShortcut } from './shortcut';
-export { RichTextToolbarButton } from './toolbar-button';
-export { __unstableRichTextInputEvent } from './input-event';
diff --git a/packages/block-editor/src/components/rich-text/remove-browser-shortcuts.native.js b/packages/block-editor/src/components/rich-text/remove-browser-shortcuts.native.js
new file mode 100644
index 0000000000000..43a4b030646db
--- /dev/null
+++ b/packages/block-editor/src/components/rich-text/remove-browser-shortcuts.native.js
@@ -0,0 +1 @@
+export const RemoveBrowserShortcuts = () => null;
diff --git a/packages/blocks/src/api/index.native.js b/packages/blocks/src/api/index.native.js
deleted file mode 100644
index b2cfd4963d2e8..0000000000000
--- a/packages/blocks/src/api/index.native.js
+++ /dev/null
@@ -1,44 +0,0 @@
-export {
- cloneBlock,
- createBlock,
- switchToBlockType,
-} from './factory';
-export {
- default as parse,
- getBlockAttributes,
- parseWithAttributeSchema,
-} from './parser';
-export {
- default as serialize,
- getBlockContent,
- getBlockDefaultClassName,
- getSaveContent,
-} from './serializer';
-export {
- registerBlockType,
- unregisterBlockType,
- getFreeformContentHandlerName,
- setUnregisteredTypeHandlerName,
- getUnregisteredTypeHandlerName,
- getBlockType,
- getBlockTypes,
- getBlockSupport,
- hasBlockSupport,
- isReusableBlock,
- getChildBlockNames,
- hasChildBlocks,
- hasChildBlocksWithInserterSupport,
- setDefaultBlockName,
- getDefaultBlockName,
- setGroupingBlockName,
-} from './registration';
-export {
- isUnmodifiedDefaultBlock,
- normalizeIconObject,
-} from './utils';
-export {
- doBlocksMatchTemplate,
- synchronizeBlocksWithTemplate,
-} from './templates';
-export { pasteHandler, getPhrasingContentSchema } from './raw-handling';
-export { default as children } from './children';
diff --git a/packages/components/src/index.native.js b/packages/components/src/index.native.js
index 3fada7034deeb..e9aec0ad2fb71 100644
--- a/packages/components/src/index.native.js
+++ b/packages/components/src/index.native.js
@@ -1,5 +1,7 @@
// Components
export * from './primitives';
+export { default as ColorIndicator } from './color-indicator';
+export { default as ColorPalette } from './color-palette';
export { default as Dashicon } from './dashicon';
export { default as Dropdown } from './dropdown';
export { default as Toolbar } from './toolbar';
diff --git a/packages/components/src/keyboard-shortcuts/index.native.js b/packages/components/src/keyboard-shortcuts/index.native.js
new file mode 100644
index 0000000000000..fab7b99261e3a
--- /dev/null
+++ b/packages/components/src/keyboard-shortcuts/index.native.js
@@ -0,0 +1,2 @@
+const KeyboardShortcuts = () => null;
+export default KeyboardShortcuts;
diff --git a/packages/rich-text/src/component/index.js b/packages/rich-text/src/component/index.js
index d6a40914ea0f9..c3112d3337fb7 100644
--- a/packages/rich-text/src/component/index.js
+++ b/packages/rich-text/src/component/index.js
@@ -259,18 +259,32 @@ class RichText extends Component {
}
if ( onPaste ) {
- // Only process file if no HTML is present.
- // Note: a pasted file may have the URL as plain text.
- const image = find( [ ...items, ...files ], ( { type } ) =>
- /^image\/(?:jpe?g|png|gif)$/.test( type )
- );
+ files = Array.from( files );
+
+ Array.from( items ).forEach( ( item ) => {
+ if ( ! item.getAsFile ) {
+ return;
+ }
+
+ const file = item.getAsFile();
+
+ if ( ! file ) {
+ return;
+ }
+
+ const { name, type, size } = file;
+
+ if ( ! find( files, { name, type, size } ) ) {
+ files.push( file );
+ }
+ } );
onPaste( {
value: this.removeEditorOnlyFormats( record ),
onChange: this.onChange,
html,
plainText,
- image,
+ files,
} );
}
}
diff --git a/packages/rich-text/src/component/index.native.js b/packages/rich-text/src/component/index.native.js
index 09ac451ed56d2..34a1b03fe24ef 100644
--- a/packages/rich-text/src/component/index.native.js
+++ b/packages/rich-text/src/component/index.native.js
@@ -30,10 +30,7 @@ import { getActiveFormat } from '../get-active-format';
import { getActiveFormats } from '../get-active-formats';
import { isEmpty, isEmptyLine } from '../is-empty';
import { create } from '../create';
-import { split } from '../split';
import { toHTMLString } from '../to-html-string';
-import { insert } from '../insert';
-import { insertLineSeparator } from '../insert-line-separator';
import { removeLineSeparator } from '../remove-line-separator';
import { isCollapsed } from '../is-collapsed';
import { remove } from '../remove';
@@ -43,28 +40,6 @@ const unescapeSpaces = ( text ) => {
return text.replace( / | /gi, ' ' );
};
-/**
- * Calls {@link pasteHandler} with a fallback to plain text when HTML processing
- * results in errors
- *
- * @param {Function} originalPasteHandler The original handler function
- * @param {Object} [options] The options to pass to {@link pasteHandler}
- *
- * @return {Array|string} A list of blocks or a string, depending on
- * `handlerMode`.
- */
-const saferPasteHandler = ( originalPasteHandler, options ) => {
- try {
- return originalPasteHandler( options );
- } catch ( error ) {
- window.console.log( 'Pasting HTML failed:', error );
- window.console.log( 'HTML:', options.HTML );
- window.console.log( 'Falling back to plain text.' );
- // fallback to plain text
- return originalPasteHandler( { ...options, HTML: '' } );
- }
-};
-
const gutenbergFormatNamesToAztec = {
'core/bold': 'bold',
'core/italic': 'italic',
@@ -72,7 +47,7 @@ const gutenbergFormatNamesToAztec = {
};
export class RichText extends Component {
- constructor( { value, __unstableMultiline: multiline, selectionStart, selectionEnd } ) {
+ constructor( { value, selectionStart, selectionEnd, __unstableMultilineTag: multiline } ) {
super( ...arguments );
this.isMultiline = false;
@@ -84,12 +59,12 @@ export class RichText extends Component {
if ( this.multilineTag === 'li' ) {
this.multilineWrapperTags = [ 'ul', 'ol' ];
}
- this.onSplit = this.onSplit.bind( this );
+
this.isIOS = Platform.OS === 'ios';
this.createRecord = this.createRecord.bind( this );
this.onChange = this.onChange.bind( this );
- this.onEnter = this.onEnter.bind( this );
- this.onBackspace = this.onBackspace.bind( this );
+ this.handleEnter = this.handleEnter.bind( this );
+ this.handleDelete = this.handleDelete.bind( this );
this.onPaste = this.onPaste.bind( this );
this.onFocus = this.onFocus.bind( this );
this.onBlur = this.onBlur.bind( this );
@@ -169,63 +144,6 @@ export class RichText extends Component {
return { ...value, start, end };
}
- /**
- * Signals to the RichText owner that the block can be replaced with two
- * blocks as a result of splitting the block by pressing enter, or with
- * blocks as a result of splitting the block by pasting block content in the
- * instance.
- *
- * @param {Object} record The rich text value to split.
- * @param {Array} pastedBlocks The pasted blocks to insert, if any.
- */
- onSplit( record, pastedBlocks = [] ) {
- const {
- __unstableOnReplace: onReplace,
- __unstableOnSplit: onSplit,
- __unstableOnSplitMiddle: onSplitMiddle,
- } = this.props;
-
- if ( ! onReplace || ! onSplit ) {
- return;
- }
-
- const blocks = [];
- const [ before, after ] = split( record );
- const hasPastedBlocks = pastedBlocks.length > 0;
-
- // Create a block with the content before the caret if there's no pasted
- // blocks, or if there are pasted blocks and the value is not empty.
- // We do not want a leading empty block on paste, but we do if split
- // with e.g. the enter key.
- if ( ! hasPastedBlocks || ! isEmpty( before ) ) {
- blocks.push( onSplit( this.valueToFormat( before ) ) );
- }
-
- if ( hasPastedBlocks ) {
- blocks.push( ...pastedBlocks );
- } else if ( onSplitMiddle ) {
- blocks.push( onSplitMiddle() );
- }
-
- // If there's pasted blocks, append a block with the content after the
- // caret. Otherwise, do append and empty block if there is no
- // `onSplitMiddle` prop, but if there is and the content is empty, the
- // middle block is enough to set focus in.
- if ( hasPastedBlocks || ! onSplitMiddle || ! isEmpty( after ) ) {
- blocks.push( onSplit( this.valueToFormat( after ) ) );
- }
-
- // If there are pasted blocks, set the selection to the last one.
- // Otherwise, set the selection to the second block.
- const indexToSelect = hasPastedBlocks ? blocks.length - 1 : 1;
- // The onSplit event can cause a content update event for this block. Such event should
- // definitely be processed by our native components, since they have no knowledge of
- // how the split works. Setting lastEventCount to undefined forces the native component to
- // always update when provided with new content.
- this.lastEventCount = undefined;
- onReplace( blocks, indexToSelect );
- }
-
valueToFormat( value ) {
// remove the outer root tags
return this.removeRootTagsProduceByAztec( toHTMLString( {
@@ -245,6 +163,7 @@ export class RichText extends Component {
}
onFormatChange( record ) {
+ this.getRecord( record );
const { start, end, activeFormats = [] } = record;
const changeHandlers = pickBy( this.props, ( v, key ) =>
key.startsWith( 'format_on_change_functions_' )
@@ -346,92 +265,67 @@ export class RichText extends Component {
this.lastAztecEventType = 'content size change';
}
- onEnter( event ) {
- if ( this.props.onEnter ) {
- this.props.onEnter();
+ handleEnter( event ) {
+ const { onEnter } = this.props;
+
+ if ( ! onEnter ) {
return;
}
- const {
- __unstableOnReplace: onReplace,
- __unstableOnSplit: onSplit,
- } = this.props;
- this.lastEventCount = event.nativeEvent.eventCount;
- this.comesFromAztec = true;
- this.firedAfterTextChanged = event.nativeEvent.firedAfterTextChanged;
-
- const canSplit = onReplace && onSplit;
- const currentRecord = this.createRecord();
- if ( this.multilineTag ) {
- if ( event.shiftKey ) {
- this.needsSelectionUpdate = true;
- const insertedLineBreak = { ...insert( currentRecord, '\n' ) };
- this.onFormatChange( insertedLineBreak );
- } else if ( canSplit && isEmptyLine( currentRecord ) ) {
- this.onSplit( currentRecord );
- } else {
- this.needsSelectionUpdate = true;
- const insertedLineSeparator = { ...insertLineSeparator( currentRecord ) };
- this.onFormatChange( insertedLineSeparator );
- }
- } else if ( event.shiftKey || ! onSplit ) {
- this.needsSelectionUpdate = true;
- const insertedLineBreak = { ...insert( currentRecord, '\n' ) };
- this.onFormatChange( insertedLineBreak );
- } else {
- this.onSplit( currentRecord );
- }
+ onEnter( {
+ value: this.createRecord(),
+ onChange: this.onFormatChange,
+ shiftKey: event.shiftKey,
+ } );
this.lastAztecEventType = 'input';
}
- onBackspace( event ) {
- const {
- __unstableOnMerge: onMerge,
- __unstableOnRemove: onRemove,
- onChange,
- } = this.props;
- if ( ! onMerge && ! onRemove ) {
- return;
- }
-
+ handleDelete( event ) {
const keyCode = BACKSPACE; // TODO : should we differentiate BACKSPACE and DELETE?
const isReverse = keyCode === BACKSPACE;
+ const { onDelete, __unstableMultilineTag: multilineTag } = this.props;
+ const { activeFormats = [] } = this.state;
this.lastEventCount = event.nativeEvent.eventCount;
this.comesFromAztec = true;
this.firedAfterTextChanged = event.nativeEvent.firedAfterTextChanged;
const value = this.createRecord();
- const { start, end } = value;
+ const { start, end, text } = value;
let newValue;
// Always handle full content deletion ourselves.
- if ( start === 0 && end !== 0 && end >= value.text.length ) {
- newValue = remove( value, start, end );
- onChange( newValue );
+ if ( start === 0 && end !== 0 && end >= text.length ) {
+ newValue = remove( value );
+ this.onFormatChange( newValue );
+ event.preventDefault();
return;
}
- if ( this.multilineTag ) {
- newValue = removeLineSeparator( value, keyCode === BACKSPACE );
+ if ( multilineTag ) {
+ if ( isReverse && value.start === 0 && value.end === 0 && isEmptyLine( value ) ) {
+ newValue = removeLineSeparator( value, ! isReverse );
+ } else {
+ newValue = removeLineSeparator( value, isReverse );
+ }
if ( newValue ) {
this.onFormatChange( newValue );
+ event.preventDefault();
return;
}
}
- const empty = this.isEmpty();
-
- if ( onMerge ) {
- onMerge( ! isReverse );
+ // Only process delete if the key press occurs at an uncollapsed edge.
+ if (
+ ! onDelete ||
+ ! isCollapsed( value ) ||
+ activeFormats.length ||
+ ( isReverse && start !== 0 ) ||
+ ( ! isReverse && end !== text.length )
+ ) {
+ return;
}
- // Only handle remove on Backspace. This serves dual-purpose of being
- // an intentional user interaction distinguishing between Backspace and
- // Delete to remove the empty field, but also to avoid merge & remove
- // causing destruction of two fields (merge, then removed merged).
- if ( onRemove && empty && isReverse ) {
- onRemove( ! isReverse );
- }
+ onDelete( { isReverse, value } );
event.preventDefault();
this.lastAztecEventType = 'input';
@@ -444,10 +338,7 @@ export class RichText extends Component {
*/
onPaste( event ) {
const {
- tagName,
- __unstablePasteHandler: pasteHandler,
- __unstableOnReplace: onReplace,
- __unstableOnSplit: onSplit,
+ onPaste,
onChange,
} = this.props;
@@ -456,30 +347,6 @@ export class RichText extends Component {
event.preventDefault();
- // Only process file if no HTML is present.
- // Note: a pasted file may have the URL as plain text.
- if ( files && files.length > 0 ) {
- const uploadId = Number.MAX_SAFE_INTEGER;
- let html = '';
- files.forEach( ( file ) => {
- html += ``;
- } );
- const content = pasteHandler( {
- HTML: html,
- mode: 'BLOCKS',
- tagName,
- } );
- const shouldReplace = onReplace && this.isEmpty();
-
- if ( shouldReplace ) {
- onReplace( content );
- } else {
- this.onSplit( currentRecord, content );
- }
-
- return;
- }
-
// There is a selection, check if a URL is pasted.
if ( ! isCollapsed( currentRecord ) ) {
const trimmedText = ( pastedHtml || pastedText ).replace( /<[^>]+>/g, '' )
@@ -503,46 +370,14 @@ export class RichText extends Component {
}
}
- const shouldReplace = this.props.onReplace && this.isEmpty();
-
- let mode = 'INLINE';
-
- if ( shouldReplace ) {
- mode = 'BLOCKS';
- } else if ( onSplit ) {
- mode = 'AUTO';
- }
-
- const pastedContent = saferPasteHandler( pasteHandler, {
- HTML: pastedHtml,
- plainText: pastedText,
- mode,
- tagName: this.props.tagName,
- canUserUseUnfilteredHTML: this.props.canUserUseUnfilteredHTML,
- } );
-
- if ( typeof pastedContent === 'string' ) {
- const recordToInsert = create( { html: pastedContent } );
- const resultingRecord = insert( currentRecord, recordToInsert );
- const resultingContent = this.valueToFormat( resultingRecord );
-
- this.lastEventCount = undefined;
- this.value = resultingContent;
-
- // explicitly set selection after inline paste
- this.onSelectionChange( resultingRecord.start, resultingRecord.end );
-
- onChange( this.value );
- } else if ( onSplit ) {
- if ( ! pastedContent.length ) {
- return;
- }
-
- if ( shouldReplace ) {
- onReplace( pastedContent );
- } else {
- this.onSplit( currentRecord, pastedContent );
- }
+ if ( onPaste ) {
+ onPaste( {
+ value: currentRecord,
+ onChange: this.onFormatChange,
+ html: pastedHtml,
+ plainText: pastedText,
+ files,
+ } );
}
}
@@ -759,7 +594,7 @@ export class RichText extends Component {
this.lastEventCount = undefined; // force a refresh on the native side
value = '';
}
- // On android if content is empty we need to send no content or else the placeholder with not show.
+ // On android if content is empty we need to send no content or else the placeholder will not show.
if ( ! this.isIOS && value === '' ) {
return value;
}
@@ -852,8 +687,8 @@ export class RichText extends Component {
onChange={ this.onChange }
onFocus={ this.onFocus }
onBlur={ this.onBlur }
- onEnter={ this.onEnter }
- onBackspace={ this.onBackspace }
+ onEnter={ this.handleEnter }
+ onBackspace={ this.handleDelete }
onPaste={ this.onPaste }
activeFormats={ this.getActiveFormatNames( record ) }
onContentSizeChange={ this.onContentSizeChange }
diff --git a/packages/rich-text/src/component/test/index.native.js b/packages/rich-text/src/component/test/index.native.js
index 6b2bc12f855ff..22ee6b118bb2d 100644
--- a/packages/rich-text/src/component/test/index.native.js
+++ b/packages/rich-text/src/component/test/index.native.js
@@ -1,17 +1,8 @@
-/**
- * External dependencies
- */
-import { shallow } from 'enzyme';
-
/**
* Internal dependencies
*/
import { RichText } from '../index';
-const getStylesFromColorScheme = () => {
- return { color: 'white' };
-};
-
describe( 'RichText Native', () => {
let richText;
@@ -33,29 +24,4 @@ describe( 'RichText Native', () => {
expect( richText.willTrimSpaces( html ) ).toBe( false );
} );
} );
-
- describe( 'Adds new line on Enter', () => {
- let newValue;
- const wrapper = shallow( {
- newValue = value;
- } }
- formatTypes={ [] }
- onSelectionChange={ jest.fn() }
- getStylesFromColorScheme={ getStylesFromColorScheme }
- /> );
-
- const event = {
- nativeEvent: {
- eventCount: 0,
- },
- };
- wrapper.instance().onEnter( event );
-
- it( ' Adds
tag to content after pressing Enter key', () => {
- expect( newValue ).toEqual( '
' );
- } );
- } );
} );