From 1c966a95dca6e779d07f5ba02ec101943b1be9a8 Mon Sep 17 00:00:00 2001 From: Danilo Ercoli Date: Wed, 1 Aug 2018 16:41:43 +0200 Subject: [PATCH] Introduces RichText component for mobile and ports Para block for mobile (#8231) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Introduce RichText component for mobile, and port the Paragraph block by using the newly introduced RichText component. * Fix lint * Use an edit file to share more common code with the main Paragraph web code. * Fix lint * Remove log calls * Revert renaming of custom-class-name.native.js → custom-class-name-native.js and relevant import statement. * Cleaning the code / Refactoring by "nativizing" `utils.js` (& `index.js`) in raw-handling * Remove any extra P. * Replace

by
* The RichText mobile component now returns plain HTML, and the transformation from HTML to React is made in the mobile paragraph block. * Update code to work with the lastest master changes * Correctly check if minHeight is undefined when setting the style of mobile paragraph block. * Blocks: Extract phrasing content to its own file to allow reuse with React Native * Remove method `setForceUpdate` that is not used. Also remove the conversion of value to boolean in if. JS does it itself. * Remove `aria-label` that is not used in RichText mobile * Convert private instance variable `lastContentSizeHeight`, to local method variable. * Use built-in `forceUpdate` method call, instead of using a private boolean variable used to skip the rendering. * Add comment for P replacement for BR --- core-blocks/index.native.js | 2 + core-blocks/paragraph/edit.js | 243 ++++++++++++++++++ core-blocks/paragraph/edit.native.js | 60 +++++ core-blocks/paragraph/index.js | 221 +--------------- packages/blocks/src/api/index.native.js | 2 + .../raw-handling/figure-content-reducer.js | 2 +- packages/blocks/src/api/raw-handling/index.js | 2 +- .../src/api/raw-handling/index.native.js | 1 + .../src/api/raw-handling/is-inline-content.js | 2 +- .../src/api/raw-handling/normalise-blocks.js | 3 +- .../raw-handling/phrasing-content-reducer.js | 2 +- .../src/api/raw-handling/phrasing-content.js | 50 ++++ .../blocks/src/api/raw-handling/test/utils.js | 3 +- packages/blocks/src/api/raw-handling/utils.js | 51 +--- .../src/components/font-sizes/index.native.js | 1 + .../editor/src/components/index.native.js | 3 + .../src/components/rich-text/index.native.js | 118 +++++++++ 17 files changed, 495 insertions(+), 271 deletions(-) create mode 100644 core-blocks/paragraph/edit.js create mode 100644 core-blocks/paragraph/edit.native.js create mode 100644 packages/blocks/src/api/raw-handling/index.native.js create mode 100644 packages/blocks/src/api/raw-handling/phrasing-content.js create mode 100644 packages/editor/src/components/font-sizes/index.native.js create mode 100644 packages/editor/src/components/rich-text/index.native.js diff --git a/core-blocks/index.native.js b/core-blocks/index.native.js index df6b8b623b2936..ca4f9439317a41 100644 --- a/core-blocks/index.native.js +++ b/core-blocks/index.native.js @@ -10,9 +10,11 @@ import { */ import * as code from './code'; import * as more from './more'; +import * as paragraph from './paragraph'; export const registerCoreBlocks = () => { [ + paragraph, code, more, ].forEach( ( { name, settings } ) => { diff --git a/core-blocks/paragraph/edit.js b/core-blocks/paragraph/edit.js new file mode 100644 index 00000000000000..6e6871045e1552 --- /dev/null +++ b/core-blocks/paragraph/edit.js @@ -0,0 +1,243 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + Component, + Fragment, +} from '@wordpress/element'; +import { + PanelBody, + ToggleControl, + withFallbackStyles, +} from '@wordpress/components'; +import { + withColors, + AlignmentToolbar, + BlockControls, + ContrastChecker, + FontSizePicker, + InspectorControls, + PanelColorSettings, + RichText, + withFontSizes, +} from '@wordpress/editor'; +import { createBlock } from '@wordpress/blocks'; +import { compose } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import './style.scss'; + +const { getComputedStyle } = window; + +const name = 'core/paragraph'; + +const applyFallbackStyles = withFallbackStyles( ( node, ownProps ) => { + const { textColor, backgroundColor, fontSize, customFontSize } = ownProps.attributes; + const editableNode = node.querySelector( '[contenteditable="true"]' ); + //verify if editableNode is available, before using getComputedStyle. + const computedStyles = editableNode ? getComputedStyle( editableNode ) : null; + return { + fallbackBackgroundColor: backgroundColor || ! computedStyles ? undefined : computedStyles.backgroundColor, + fallbackTextColor: textColor || ! computedStyles ? undefined : computedStyles.color, + fallbackFontSize: fontSize || customFontSize || ! computedStyles ? undefined : parseInt( computedStyles.fontSize ) || undefined, + }; +} ); + +class ParagraphBlock extends Component { + constructor() { + super( ...arguments ); + + this.onReplace = this.onReplace.bind( this ); + this.toggleDropCap = this.toggleDropCap.bind( this ); + this.splitBlock = this.splitBlock.bind( this ); + } + + onReplace( blocks ) { + const { attributes, onReplace } = this.props; + onReplace( blocks.map( ( block, index ) => ( + index === 0 && block.name === name ? + { ...block, + attributes: { + ...attributes, + ...block.attributes, + }, + } : + block + ) ) ); + } + + toggleDropCap() { + const { attributes, setAttributes } = this.props; + setAttributes( { dropCap: ! attributes.dropCap } ); + } + + getDropCapHelp( checked ) { + return checked ? __( 'Showing large initial letter.' ) : __( 'Toggle to show a large initial letter.' ); + } + + /** + * Split handler for RichText value, namely when content is pasted or the + * user presses the Enter key. + * + * @param {?Array} before Optional before value, to be used as content + * in place of what exists currently for the + * block. If undefined, the block is deleted. + * @param {?Array} after Optional after value, to be appended in a new + * paragraph block to the set of blocks passed + * as spread. + * @param {...WPBlock} blocks Optional blocks inserted between the before + * and after value blocks. + */ + splitBlock( before, after, ...blocks ) { + const { + attributes, + insertBlocksAfter, + setAttributes, + onReplace, + } = this.props; + + if ( after ) { + // Append "After" content as a new paragraph block to the end of + // any other blocks being inserted after the current paragraph. + blocks.push( createBlock( name, { content: after } ) ); + } + + if ( blocks.length && insertBlocksAfter ) { + insertBlocksAfter( blocks ); + } + + const { content } = attributes; + if ( ! before ) { + // If before content is omitted, treat as intent to delete block. + onReplace( [] ); + } else if ( content !== before ) { + // Only update content if it has in-fact changed. In case that user + // has created a new paragraph at end of an existing one, the value + // of before will be strictly equal to the current content. + setAttributes( { content: before } ); + } + } + + render() { + const { + attributes, + setAttributes, + mergeBlocks, + onReplace, + className, + backgroundColor, + textColor, + setBackgroundColor, + setTextColor, + fallbackBackgroundColor, + fallbackTextColor, + fallbackFontSize, + fontSize, + setFontSize, + } = this.props; + + const { + align, + content, + dropCap, + placeholder, + } = attributes; + + return ( + + + { + setAttributes( { align: nextAlign } ); + } } + /> + + + + + + + + + + + { + setAttributes( { + content: nextContent, + } ); + } } + onSplit={ this.splitBlock } + onMerge={ mergeBlocks } + onReplace={ this.onReplace } + onRemove={ () => onReplace( [] ) } + placeholder={ placeholder || __( 'Add text or type / to add content' ) } + /> + + ); + } +} + +const ParagraphEdit = compose( [ + withColors( 'backgroundColor', { textColor: 'color' } ), + withFontSizes( 'fontSize' ), + applyFallbackStyles, +] )( ParagraphBlock ); + +export default ParagraphEdit; diff --git a/core-blocks/paragraph/edit.native.js b/core-blocks/paragraph/edit.native.js new file mode 100644 index 00000000000000..155d20f54f37ae --- /dev/null +++ b/core-blocks/paragraph/edit.native.js @@ -0,0 +1,60 @@ +/** + * External dependencies + */ +import { View } from 'react-native'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { Component } from '@wordpress/element'; +import { parse } from '@wordpress/blocks'; +import { RichText } from '@wordpress/editor'; + +const minHeight = 50; + +class ParagraphEdit extends Component { + render() { + const { + attributes, + setAttributes, + style, + } = this.props; + + const { + placeholder, + } = attributes; + + return ( + + { + // Create a React Tree from the new HTML + const newParaBlock = parse( '

' + event.content + '

' )[ 0 ]; + setAttributes( { + ...this.props.attributes, + content: newParaBlock.attributes.content, + eventCount: event.eventCount, + } ); + } + } + onContentSizeChange={ ( event ) => { + setAttributes( { + ...this.props.attributes, + aztecHeight: event.aztecHeight, + } ); + } + } + placeholder={ placeholder || __( 'Add text or type / to add content' ) } + /> + + ); + } +} + +export default ParagraphEdit; diff --git a/core-blocks/paragraph/index.js b/core-blocks/paragraph/index.js index f1ebe001709305..1453dbd9d67a70 100644 --- a/core-blocks/paragraph/index.js +++ b/core-blocks/paragraph/index.js @@ -9,236 +9,23 @@ import { isFinite, omit } from 'lodash'; */ import { __ } from '@wordpress/i18n'; import { - Component, - Fragment, RawHTML, } from '@wordpress/element'; -import { - PanelBody, - ToggleControl, - withFallbackStyles, -} from '@wordpress/components'; import { getColorClass, getFontSizeClass, - withColors, - AlignmentToolbar, - BlockControls, - ContrastChecker, - FontSizePicker, - InspectorControls, - PanelColorSettings, RichText, - withFontSizes, } from '@wordpress/editor'; import { - createBlock, getPhrasingContentSchema, children, } from '@wordpress/blocks'; -import { compose } from '@wordpress/compose'; /** * Internal dependencies */ import './style.scss'; - -const { getComputedStyle } = window; - -const FallbackStyles = withFallbackStyles( ( node, ownProps ) => { - const { textColor, backgroundColor, fontSize, customFontSize } = ownProps.attributes; - const editableNode = node.querySelector( '[contenteditable="true"]' ); - //verify if editableNode is available, before using getComputedStyle. - const computedStyles = editableNode ? getComputedStyle( editableNode ) : null; - return { - fallbackBackgroundColor: backgroundColor || ! computedStyles ? undefined : computedStyles.backgroundColor, - fallbackTextColor: textColor || ! computedStyles ? undefined : computedStyles.color, - fallbackFontSize: fontSize || customFontSize || ! computedStyles ? undefined : parseInt( computedStyles.fontSize ) || undefined, - }; -} ); - -class ParagraphBlock extends Component { - constructor() { - super( ...arguments ); - - this.onReplace = this.onReplace.bind( this ); - this.toggleDropCap = this.toggleDropCap.bind( this ); - this.splitBlock = this.splitBlock.bind( this ); - } - - onReplace( blocks ) { - const { attributes, onReplace } = this.props; - onReplace( blocks.map( ( block, index ) => ( - index === 0 && block.name === name ? - { ...block, - attributes: { - ...attributes, - ...block.attributes, - }, - } : - block - ) ) ); - } - - toggleDropCap() { - const { attributes, setAttributes } = this.props; - setAttributes( { dropCap: ! attributes.dropCap } ); - } - - getDropCapHelp( checked ) { - return checked ? __( 'Showing large initial letter.' ) : __( 'Toggle to show a large initial letter.' ); - } - - /** - * Split handler for RichText value, namely when content is pasted or the - * user presses the Enter key. - * - * @param {?Array} before Optional before value, to be used as content - * in place of what exists currently for the - * block. If undefined, the block is deleted. - * @param {?Array} after Optional after value, to be appended in a new - * paragraph block to the set of blocks passed - * as spread. - * @param {...WPBlock} blocks Optional blocks inserted between the before - * and after value blocks. - */ - splitBlock( before, after, ...blocks ) { - const { - attributes, - insertBlocksAfter, - setAttributes, - onReplace, - } = this.props; - - if ( after ) { - // Append "After" content as a new paragraph block to the end of - // any other blocks being inserted after the current paragraph. - blocks.push( createBlock( name, { content: after } ) ); - } - - if ( blocks.length && insertBlocksAfter ) { - insertBlocksAfter( blocks ); - } - - const { content } = attributes; - if ( ! before ) { - // If before content is omitted, treat as intent to delete block. - onReplace( [] ); - } else if ( content !== before ) { - // Only update content if it has in-fact changed. In case that user - // has created a new paragraph at end of an existing one, the value - // of before will be strictly equal to the current content. - setAttributes( { content: before } ); - } - } - - render() { - const { - attributes, - setAttributes, - mergeBlocks, - onReplace, - className, - backgroundColor, - textColor, - setBackgroundColor, - setTextColor, - fallbackBackgroundColor, - fallbackTextColor, - fallbackFontSize, - fontSize, - setFontSize, - } = this.props; - - const { - align, - content, - dropCap, - placeholder, - } = attributes; - - return ( - - - { - setAttributes( { align: nextAlign } ); - } } - /> - - - - - - - - - - - { - setAttributes( { - content: nextContent, - } ); - } } - onSplit={ this.splitBlock } - onMerge={ mergeBlocks } - onReplace={ this.onReplace } - onRemove={ () => onReplace( [] ) } - placeholder={ placeholder || __( 'Add text or type / to add content' ) } - /> - - ); - } -} +import edit from './edit'; const supports = { className: false, @@ -439,11 +226,7 @@ export const settings = { } }, - edit: compose( [ - withColors( 'backgroundColor', { textColor: 'color' } ), - withFontSizes( 'fontSize' ), - FallbackStyles, - ] )( ParagraphBlock ), + edit, save( { attributes } ) { const { diff --git a/packages/blocks/src/api/index.native.js b/packages/blocks/src/api/index.native.js index 2e473a005b3822..5b16c358e3511e 100644 --- a/packages/blocks/src/api/index.native.js +++ b/packages/blocks/src/api/index.native.js @@ -16,3 +16,5 @@ export { getBlockType, hasBlockSupport, } from './registration'; +export { getPhrasingContentSchema } from './raw-handling'; +export { default as children } from './children'; diff --git a/packages/blocks/src/api/raw-handling/figure-content-reducer.js b/packages/blocks/src/api/raw-handling/figure-content-reducer.js index 1fa63b080d1c44..86478176fdb889 100644 --- a/packages/blocks/src/api/raw-handling/figure-content-reducer.js +++ b/packages/blocks/src/api/raw-handling/figure-content-reducer.js @@ -6,7 +6,7 @@ import { has } from 'lodash'; /** * Internal dependencies */ -import { isPhrasingContent } from './utils'; +import { isPhrasingContent } from './phrasing-content'; /** * Whether or not the given node is figure content. diff --git a/packages/blocks/src/api/raw-handling/index.js b/packages/blocks/src/api/raw-handling/index.js index 72cd01fcb9ef3f..23d7cd9d853ce7 100644 --- a/packages/blocks/src/api/raw-handling/index.js +++ b/packages/blocks/src/api/raw-handling/index.js @@ -23,11 +23,11 @@ import figureContentReducer from './figure-content-reducer'; import shortcodeConverter from './shortcode-converter'; import markdownConverter from './markdown-converter'; import iframeRemover from './iframe-remover'; +import { getPhrasingContentSchema } from './phrasing-content'; import { deepFilterHTML, isPlain, removeInvalidHTML, - getPhrasingContentSchema, getBlockContentSchema, } from './utils'; diff --git a/packages/blocks/src/api/raw-handling/index.native.js b/packages/blocks/src/api/raw-handling/index.native.js new file mode 100644 index 00000000000000..fb0890bf2aecd3 --- /dev/null +++ b/packages/blocks/src/api/raw-handling/index.native.js @@ -0,0 +1 @@ +export { getPhrasingContentSchema } from './phrasing-content'; diff --git a/packages/blocks/src/api/raw-handling/is-inline-content.js b/packages/blocks/src/api/raw-handling/is-inline-content.js index 28d9587f0ab861..e0067a4550b0bf 100644 --- a/packages/blocks/src/api/raw-handling/is-inline-content.js +++ b/packages/blocks/src/api/raw-handling/is-inline-content.js @@ -6,7 +6,7 @@ import { difference } from 'lodash'; /** * Internal dependencies */ -import { isPhrasingContent } from './utils'; +import { isPhrasingContent } from './phrasing-content'; /** * Checks if the given node should be considered inline content, optionally diff --git a/packages/blocks/src/api/raw-handling/normalise-blocks.js b/packages/blocks/src/api/raw-handling/normalise-blocks.js index 7bb3b44f2d5711..52ca57c43e047d 100644 --- a/packages/blocks/src/api/raw-handling/normalise-blocks.js +++ b/packages/blocks/src/api/raw-handling/normalise-blocks.js @@ -1,7 +1,8 @@ /** * Internal dependencies */ -import { isPhrasingContent, isEmpty } from './utils'; +import { isEmpty } from './utils'; +import { isPhrasingContent } from './phrasing-content'; /** * Browser dependencies diff --git a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js index c4e2a34ad7380a..0504bbb6e45b81 100644 --- a/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js +++ b/packages/blocks/src/api/raw-handling/phrasing-content-reducer.js @@ -6,7 +6,7 @@ import { unwrap, replaceTag } from '@wordpress/dom'; /** * Internal dependencies */ -import { isPhrasingContent } from './utils'; +import { isPhrasingContent } from './phrasing-content'; function isBlockContent( node, schema = {} ) { return schema.hasOwnProperty( node.nodeName.toLowerCase() ); diff --git a/packages/blocks/src/api/raw-handling/phrasing-content.js b/packages/blocks/src/api/raw-handling/phrasing-content.js new file mode 100644 index 00000000000000..c39b3330f1105d --- /dev/null +++ b/packages/blocks/src/api/raw-handling/phrasing-content.js @@ -0,0 +1,50 @@ +/** + * External dependencies + */ +import { omit } from 'lodash'; + +const phrasingContentSchema = { + strong: {}, + em: {}, + del: {}, + ins: {}, + a: { attributes: [ 'href', 'target', 'rel' ] }, + code: {}, + abbr: { attributes: [ 'title' ] }, + sub: {}, + sup: {}, + br: {}, + '#text': {}, +}; + +// Recursion is needed. +// Possible: strong > em > strong. +// Impossible: strong > strong. +[ 'strong', 'em', 'del', 'ins', 'a', 'code', 'abbr', 'sub', 'sup' ].forEach( ( tag ) => { + phrasingContentSchema[ tag ].children = omit( phrasingContentSchema, tag ); +} ); + +/** + * Get schema of possible paths for phrasing content. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Phrasing_content + * + * @return {Object} Schema. + */ +export function getPhrasingContentSchema() { + return phrasingContentSchema; +} + +/** + * Find out whether or not the given node is phrasing content. + * + * @see https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Phrasing_content + * + * @param {Element} node The node to test. + * + * @return {boolean} True if phrasing content, false if not. + */ +export function isPhrasingContent( node ) { + const tag = node.nodeName.toLowerCase(); + return getPhrasingContentSchema().hasOwnProperty( tag ) || tag === 'span'; +} diff --git a/packages/blocks/src/api/raw-handling/test/utils.js b/packages/blocks/src/api/raw-handling/test/utils.js index 3cbdc68a5e268e..8f8e02a6520edd 100644 --- a/packages/blocks/src/api/raw-handling/test/utils.js +++ b/packages/blocks/src/api/raw-handling/test/utils.js @@ -1,7 +1,8 @@ /** * Internal dependencies */ -import { isEmpty, isPlain, removeInvalidHTML, getPhrasingContentSchema } from '../utils'; +import { getPhrasingContentSchema } from '../phrasing-content'; +import { isEmpty, isPlain, removeInvalidHTML } from '../utils'; describe( 'isEmpty', () => { function isEmptyHTML( HTML ) { diff --git a/packages/blocks/src/api/raw-handling/utils.js b/packages/blocks/src/api/raw-handling/utils.js index 3aa3e438c2e510..1494c9b3e11a49 100644 --- a/packages/blocks/src/api/raw-handling/utils.js +++ b/packages/blocks/src/api/raw-handling/utils.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { omit, mergeWith, includes, noop } from 'lodash'; +import { mergeWith, includes, noop } from 'lodash'; /** * WordPress dependencies @@ -10,55 +10,14 @@ import { unwrap, insertAfter, remove } from '@wordpress/dom'; import { hasBlockSupport } from '..'; /** - * Browser dependencies + * Internal dependencies */ -const { ELEMENT_NODE, TEXT_NODE } = window.Node; - -const phrasingContentSchema = { - strong: {}, - em: {}, - del: {}, - ins: {}, - a: { attributes: [ 'href', 'target', 'rel' ] }, - code: {}, - abbr: { attributes: [ 'title' ] }, - sub: {}, - sup: {}, - br: {}, - '#text': {}, -}; - -// Recursion is needed. -// Possible: strong > em > strong. -// Impossible: strong > strong. -[ 'strong', 'em', 'del', 'ins', 'a', 'code', 'abbr', 'sub', 'sup' ].forEach( ( tag ) => { - phrasingContentSchema[ tag ].children = omit( phrasingContentSchema, tag ); -} ); +import { isPhrasingContent } from './phrasing-content'; /** - * Get schema of possible paths for phrasing content. - * - * @see https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Phrasing_content - * - * @return {Object} Schema. - */ -export function getPhrasingContentSchema() { - return phrasingContentSchema; -} - -/** - * Find out whether or not the given node is phrasing content. - * - * @see https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Phrasing_content - * - * @param {Element} node The node to test. - * - * @return {boolean} True if phrasing content, false if not. + * Browser dependencies */ -export function isPhrasingContent( node ) { - const tag = node.nodeName.toLowerCase(); - return getPhrasingContentSchema().hasOwnProperty( tag ) || tag === 'span'; -} +const { ELEMENT_NODE, TEXT_NODE } = window.Node; /** * Given raw transforms from blocks, merges all schemas into one. diff --git a/packages/editor/src/components/font-sizes/index.native.js b/packages/editor/src/components/font-sizes/index.native.js new file mode 100644 index 00000000000000..2ebb67cf494ba1 --- /dev/null +++ b/packages/editor/src/components/font-sizes/index.native.js @@ -0,0 +1 @@ +export { getFontSize, getFontSizeClass } from './utils'; diff --git a/packages/editor/src/components/index.native.js b/packages/editor/src/components/index.native.js index 2c2b5eaf2957a1..46734c5bc71263 100644 --- a/packages/editor/src/components/index.native.js +++ b/packages/editor/src/components/index.native.js @@ -1 +1,4 @@ +export * from './colors'; +export * from './font-sizes'; export { default as PlainText } from './plain-text'; +export { default as RichText } from './rich-text'; diff --git a/packages/editor/src/components/rich-text/index.native.js b/packages/editor/src/components/rich-text/index.native.js new file mode 100644 index 00000000000000..ba9833ef0ed8d7 --- /dev/null +++ b/packages/editor/src/components/rich-text/index.native.js @@ -0,0 +1,118 @@ +/** + * External dependencies + */ +import RCTAztecView from 'react-native-aztec'; + +/** + * WordPress dependencies + */ +import { Component, RawHTML, renderToString } from '@wordpress/element'; +import { withInstanceId, compose } from '@wordpress/compose'; +import { children } from '@wordpress/blocks'; + +export class RichText extends Component { + constructor() { + super( ...arguments ); + this.onChange = this.onChange.bind( this ); + this.onContentSizeChange = this.onContentSizeChange.bind( this ); + + this.lastEventCount = 0; + } + + /** + * Handles any case where the content of the AztecRN instance has changed. + */ + + onChange( event ) { + if ( !! this.currentTimer ) { + clearTimeout( this.currentTimer ); + } + this.lastEventCount = event.nativeEvent.eventCount; + // The following method just cleans up any

tags produced by aztec and replaces them with a br tag + // This should be removed on a later version when aztec doesn't return the top tag of the text being edited + const contentWithoutP = event.nativeEvent.text.replace( /

/gi, '' ).replace( /<\/p>/gi, '
' ); + this.lastContent = contentWithoutP; + + this.currentTimer = setTimeout( function() { + this.props.onChange( { + content: this.lastContent, + eventCount: this.lastEventCount, + } ); + }.bind( this ), 1000 ); + } + + /** + * Handles any case where the content of the AztecRN instance has changed in size + */ + + onContentSizeChange( event ) { + const contentHeight = event.nativeEvent.contentSize.height; + this.forceUpdate(); // force re-render the component skipping shouldComponentUpdate() See: https://reactjs.org/docs/react-component.html#forceupdate + this.props.onContentSizeChange( { + aztecHeight: contentHeight, + } + ); + } + + shouldComponentUpdate( nextProps ) { + // The check below allows us to avoid updating the content right after an `onChange` call + if ( nextProps.content.contentTree && + nextProps.content.eventCount && + this.lastContent && // first time the component is drawn with empty content `lastContent` is undefined + this.lastEventCount && + nextProps.content.contentTree.eventCount !== this.lastEventCount ) { + return false; + } + + return true; + } + + render() { + // Save back to HTML from React tree + const html = renderToString( this.props.content.contentTree ); + const { + style, + eventCount, + } = this.props; + + return ( + + ); + } +} + +const RichTextContainer = compose( [ + withInstanceId, +] )( RichText ); + +RichTextContainer.Content = ( { value, format, tagName: Tag, ...props } ) => { + let content; + switch ( format ) { + case 'string': + content = { value }; + break; + + case 'children': + content = { children.toHTML( value ) }; + break; + } + + if ( Tag ) { + return { content }; + } + + return content; +}; + +RichTextContainer.Content.defaultProps = { + format: 'children', +}; + +export default RichTextContainer;