From 595bd4f9b3a9143f39422631cd7a14b2e133776e Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler Date: Wed, 7 Nov 2018 11:28:35 +0100 Subject: [PATCH 01/14] Connect App and AppContainer to the gutenberg store as well --- src/app/App.js | 18 ++++++++++++- src/app/AppContainer.js | 58 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/app/App.js b/src/app/App.js index 3a720ef2eb..a3ec4e4985 100644 --- a/src/app/App.js +++ b/src/app/App.js @@ -8,9 +8,13 @@ import { Provider } from 'react-redux'; import { setupStore, html2State } from '../store'; import AppContainer from './AppContainer'; import { Store } from 'redux'; +import { withDispatch, withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; +import { BlockEdit } from '@wordpress/editor'; import initialHtml from './initial-html'; + type PropsType = { initialData: string | Store, }; @@ -18,7 +22,7 @@ type StateType = { store: Store, }; -export default class AppProvider extends React.Component { +class AppProvider extends React.Component { state: StateType; constructor( props: PropsType ) { @@ -30,6 +34,9 @@ export default class AppProvider extends React.Component { props.initialData : setupStore( html2State( props.initialData !== undefined ? props.initialData : initialHtml ) ), }; + + // initialize gutenberg store with local store + props.onResetBlocks( this.state.store.getState().blocks ); } render() { @@ -40,3 +47,12 @@ export default class AppProvider extends React.Component { ); } } + +export default withDispatch( ( dispatch ) => { + const { + resetBlocks, + } = dispatch( 'core/editor' ); + return { + onResetBlocks: resetBlocks, + }; +} )( AppProvider ); \ No newline at end of file diff --git a/src/app/AppContainer.js b/src/app/AppContainer.js index 6979c35773..70fd5e615d 100644 --- a/src/app/AppContainer.js +++ b/src/app/AppContainer.js @@ -15,6 +15,12 @@ import { } from '../store/actions'; import MainApp from './MainApp'; +import { parse, serialize } from '@wordpress/blocks'; +import { withDispatch, withSelect } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; +import { BlockEdit } from '@wordpress/editor'; +import RNReactNativeGutenbergBridge from 'react-native-gutenberg-bridge'; + const mapStateToProps = ( state ) => ( { ...state, } ); @@ -24,33 +30,83 @@ const mapDispatchToProps = ( dispatch, ownProps ) => { ...ownProps, onChange: ( clientId, attributes ) => { dispatch( updateBlockAttributes( clientId, attributes ) ); + ownProps.onAttributesUpdate( clientId, attributes ); }, focusBlockAction: ( clientId ) => { dispatch( focusBlockAction( clientId ) ); + ownProps.onSelect( clientId ); }, moveBlockUpAction: ( clientId ) => { dispatch( moveBlockUpAction( clientId ) ); + ownProps.onMoveUp( clientId ); }, moveBlockDownAction: ( clientId ) => { dispatch( moveBlockDownAction( clientId ) ); + ownProps.onMoveDown( clientId ); }, deleteBlockAction: ( clientId ) => { dispatch( deleteBlockAction( clientId ) ); + ownProps.onRemove( clientId ); }, createBlockAction: ( clientId, block, clientIdAbove ) => { dispatch( createBlockAction( clientId, block, clientIdAbove ) ); + ownProps.onInsertBlock( block, ownProps.selectedBlockIndex + 1 ); }, parseBlocksAction: ( html ) => { dispatch( parseBlocksAction( html ) ); + const parsed = parse( html ); + ownProps.onResetBlocks( parsed ); }, serializeToNativeAction: () => { dispatch( serializeToNativeAction() ); + const html = serialize( ownProps.blocks ); + RNReactNativeGutenbergBridge.provideToNative_Html( html ); }, mergeBlocksAction: ( blockOneClientId, blockTwoClientId, block ) => { dispatch( mergeBlocksAction( blockOneClientId, blockTwoClientId, block ) ); + ownProps.onMerge( blockOneClientId, blockTwoClientId ); }, }; }; const AppContainer = connect( mapStateToProps, mapDispatchToProps )( MainApp ); -export default AppContainer; + +export default compose( [ + withSelect( ( select, ownProps ) => { + const { + getBlockIndex, + getBlocks, + getSelectedBlockClientId, + } = select( 'core/editor' ); + const { rootClientId } = ownProps; + const selectedBlockClientId = getSelectedBlockClientId(); + + return { + selectedBlockIndex: getBlockIndex( selectedBlockClientId, rootClientId ), + blocks: getBlocks(), + }; + } ), + withDispatch( ( dispatch ) => { + const { + insertBlock, + mergeBlocks, + moveBlocksDown, + moveBlocksUp, + removeBlock, + resetBlocks, + selectBlock, + updateBlockAttributes, + } = dispatch( 'core/editor' ); + + return { + onInsertBlock: insertBlock, + onMerge: mergeBlocks, + onMoveDown: moveBlocksDown, + onMoveUp: moveBlocksUp, + onRemove: removeBlock, + onResetBlocks: resetBlocks, + onSelect: selectBlock, + onAttributesUpdate: updateBlockAttributes + }; + } ), +] )( AppContainer ); From ab76265e04e5020779a6c1dfe85c6be8f9da4353 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler Date: Wed, 7 Nov 2018 12:58:33 +0100 Subject: [PATCH 02/14] Replace local store with gutenberg store --- src/app/App.js | 2 +- src/app/AppContainer.js | 40 ++++++++++----------------- src/block-management/block-holder.js | 15 +--------- src/block-management/block-manager.js | 30 ++++++-------------- 4 files changed, 24 insertions(+), 63 deletions(-) diff --git a/src/app/App.js b/src/app/App.js index a3ec4e4985..9b242f0b7f 100644 --- a/src/app/App.js +++ b/src/app/App.js @@ -8,7 +8,7 @@ import { Provider } from 'react-redux'; import { setupStore, html2State } from '../store'; import AppContainer from './AppContainer'; import { Store } from 'redux'; -import { withDispatch, withSelect } from '@wordpress/data'; +import { withDispatch } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { BlockEdit } from '@wordpress/editor'; diff --git a/src/app/AppContainer.js b/src/app/AppContainer.js index 70fd5e615d..e35e5324d0 100644 --- a/src/app/AppContainer.js +++ b/src/app/AppContainer.js @@ -2,17 +2,6 @@ * @format */ import { connect } from 'react-redux'; -import { - updateBlockAttributes, - focusBlockAction, - moveBlockUpAction, - moveBlockDownAction, - deleteBlockAction, - createBlockAction, - parseBlocksAction, - serializeToNativeAction, - mergeBlocksAction, -} from '../store/actions'; import MainApp from './MainApp'; import { parse, serialize } from '@wordpress/blocks'; @@ -21,49 +10,46 @@ import { compose } from '@wordpress/compose'; import { BlockEdit } from '@wordpress/editor'; import RNReactNativeGutenbergBridge from 'react-native-gutenberg-bridge'; -const mapStateToProps = ( state ) => ( { - ...state, -} ); +const mapStateToProps = ( state, ownProps ) => { + return { + blocks: ownProps.blocks.map( block => { + block.focused = ownProps.isBlockSelected( block.clientId ); + return block; + } ), + refresh: ! ownProps.refresh + } +}; const mapDispatchToProps = ( dispatch, ownProps ) => { return { ...ownProps, onChange: ( clientId, attributes ) => { - dispatch( updateBlockAttributes( clientId, attributes ) ); ownProps.onAttributesUpdate( clientId, attributes ); }, focusBlockAction: ( clientId ) => { - dispatch( focusBlockAction( clientId ) ); ownProps.onSelect( clientId ); }, moveBlockUpAction: ( clientId ) => { - dispatch( moveBlockUpAction( clientId ) ); ownProps.onMoveUp( clientId ); }, moveBlockDownAction: ( clientId ) => { - dispatch( moveBlockDownAction( clientId ) ); ownProps.onMoveDown( clientId ); }, deleteBlockAction: ( clientId ) => { - dispatch( deleteBlockAction( clientId ) ); ownProps.onRemove( clientId ); }, - createBlockAction: ( clientId, block, clientIdAbove ) => { - dispatch( createBlockAction( clientId, block, clientIdAbove ) ); + createBlockAction: ( clientId, block ) => { ownProps.onInsertBlock( block, ownProps.selectedBlockIndex + 1 ); }, parseBlocksAction: ( html ) => { - dispatch( parseBlocksAction( html ) ); const parsed = parse( html ); ownProps.onResetBlocks( parsed ); }, serializeToNativeAction: () => { - dispatch( serializeToNativeAction() ); const html = serialize( ownProps.blocks ); RNReactNativeGutenbergBridge.provideToNative_Html( html ); }, - mergeBlocksAction: ( blockOneClientId, blockTwoClientId, block ) => { - dispatch( mergeBlocksAction( blockOneClientId, blockTwoClientId, block ) ); + mergeBlocksAction: ( blockOneClientId, blockTwoClientId ) => { ownProps.onMerge( blockOneClientId, blockTwoClientId ); }, }; @@ -77,11 +63,13 @@ export default compose( [ getBlockIndex, getBlocks, getSelectedBlockClientId, + isBlockSelected, } = select( 'core/editor' ); const { rootClientId } = ownProps; const selectedBlockClientId = getSelectedBlockClientId(); return { + isBlockSelected, selectedBlockIndex: getBlockIndex( selectedBlockClientId, rootClientId ), blocks: getBlocks(), }; @@ -106,7 +94,7 @@ export default compose( [ onRemove: removeBlock, onResetBlocks: resetBlocks, onSelect: selectBlock, - onAttributesUpdate: updateBlockAttributes + onAttributesUpdate: updateBlockAttributes, }; } ), ] )( AppContainer ); diff --git a/src/block-management/block-holder.js b/src/block-management/block-holder.js index 0abf103617..426a72de91 100644 --- a/src/block-management/block-holder.js +++ b/src/block-management/block-holder.js @@ -23,20 +23,7 @@ type PropsType = BlockType & { mergeBlocks: ( forward: boolean ) => void, }; -type StateType = { - selected: boolean, - focused: boolean, -}; - -export default class BlockHolder extends React.Component { - constructor( props: PropsType ) { - super( props ); - this.state = { - selected: false, - focused: false, - }; - } - +export default class BlockHolder extends React.Component { renderToolbarIfBlockFocused() { if ( this.props.focused ) { return ( diff --git a/src/block-management/block-manager.js b/src/block-management/block-manager.js index ebc27d1612..827a086d8e 100644 --- a/src/block-management/block-manager.js +++ b/src/block-management/block-manager.js @@ -48,7 +48,8 @@ export default class BlockManager extends React.Component constructor( props: PropsType ) { super( props ); this.state = { - dataSource: new DataSource( this.props.blocks, ( item: BlockType ) => item.clientId ), + blocks: [], + dataSource: null, showHtml: false, inspectBlocks: false, blockTypePickerVisible: false, @@ -125,12 +126,14 @@ export default class BlockManager extends React.Component } static getDerivedStateFromProps( props: PropsType, state: StateType ) { - if ( props.fullparse === true ) { + if ( state.blocks !== props.blocks ) { return { ...state, + blocks: props.blocks, dataSource: new DataSource( props.blocks, ( item: BlockType ) => item.clientId ), }; } + // no state change necessary return null; } @@ -230,24 +233,7 @@ export default class BlockManager extends React.Component return; } - // Calling the merge to update the attributes and remove the block to be merged - const updatedAttributes = blockType.merge( - blockA.attributes, - blocksWithTheSameType[ 0 ].attributes - ); - - const newBlock = { - ...blockA, - attributes: { - ...blockA.attributes, - ...updatedAttributes, - }, - }; - newBlock.focused = true; - - // set it into the datasource, and use the same object instance to send it to props/redux - this.state.dataSource.splice( this.getDataSourceIndexFromClientId( blockA.clientId ), 2, newBlock ); - this.props.mergeBlocksAction( blockA.clientId, blockB.clientId, newBlock ); + this.props.mergeBlocksAction( blockA.clientId, blockB.clientId ); } onChange( clientId: string, attributes: mixed ) { @@ -291,8 +277,8 @@ export default class BlockManager extends React.Component list = ( item.clientId } renderItem={ this.renderItem.bind( this ) } /> From d95d6a2a4f8245196e484e2d6009205887d42c8a Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler Date: Wed, 7 Nov 2018 17:47:36 +0100 Subject: [PATCH 03/14] Cleanup and optimize blocks change --- src/app/App.js | 8 +++----- src/app/AppContainer.js | 26 ++++++++++++++++++-------- src/block-management/block-manager.js | 3 ++- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/app/App.js b/src/app/App.js index 9b242f0b7f..cdd427de36 100644 --- a/src/app/App.js +++ b/src/app/App.js @@ -5,17 +5,15 @@ import '../globals'; import React from 'react'; import { Provider } from 'react-redux'; -import { setupStore, html2State } from '../store'; +import { type BlockType, setupStore, html2State } from '../store'; import AppContainer from './AppContainer'; import { Store } from 'redux'; import { withDispatch } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; -import { BlockEdit } from '@wordpress/editor'; import initialHtml from './initial-html'; - type PropsType = { + onResetBlocks: Array => mixed, initialData: string | Store, }; type StateType = { @@ -55,4 +53,4 @@ export default withDispatch( ( dispatch ) => { return { onResetBlocks: resetBlocks, }; -} )( AppProvider ); \ No newline at end of file +} )( AppProvider ); diff --git a/src/app/AppContainer.js b/src/app/AppContainer.js index e35e5324d0..3c4e410572 100644 --- a/src/app/AppContainer.js +++ b/src/app/AppContainer.js @@ -2,22 +2,32 @@ * @format */ import { connect } from 'react-redux'; -import MainApp from './MainApp'; +import { isEqual } from 'lodash'; +import MainApp from './MainApp'; import { parse, serialize } from '@wordpress/blocks'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; -import { BlockEdit } from '@wordpress/editor'; import RNReactNativeGutenbergBridge from 'react-native-gutenberg-bridge'; const mapStateToProps = ( state, ownProps ) => { - return { - blocks: ownProps.blocks.map( block => { - block.focused = ownProps.isBlockSelected( block.clientId ); - return block; - } ), - refresh: ! ownProps.refresh + let blocks = ownProps.blocks; + let refresh = !! ownProps.refresh; + + const newBlocks = ownProps.blocks.map( ( block ) => { + block.focused = ownProps.isBlockSelected( block.clientId ); + return block; + } ); + + if ( ! isEqual( blocks, newBlocks ) ) { + blocks = newBlocks; + refresh = ! ownProps.refresh; } + + return { + blocks, + refresh, + }; }; const mapDispatchToProps = ( dispatch, ownProps ) => { diff --git a/src/block-management/block-manager.js b/src/block-management/block-manager.js index 827a086d8e..2afec99ea4 100644 --- a/src/block-management/block-manager.js +++ b/src/block-management/block-manager.js @@ -26,7 +26,7 @@ export type BlockListType = { createBlockAction: ( string, BlockType, string ) => mixed, parseBlocksAction: string => mixed, serializeToNativeAction: void => void, - mergeBlocksAction: ( string, string, BlockType ) => mixed, + mergeBlocksAction: ( string, string ) => mixed, blocks: Array, aztechtml: string, refresh: boolean, @@ -38,6 +38,7 @@ type StateType = { showHtml: boolean, inspectBlocks: boolean, blockTypePickerVisible: boolean, + blocks: Array, selectedBlockType: string, }; From 48127431682c5aad76ab366d18e8ffdf2b548191 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler Date: Thu, 8 Nov 2018 17:49:01 +0100 Subject: [PATCH 04/14] Mock react-native-recyclerview-list --- jest/setup.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jest/setup.js b/jest/setup.js index 7f94c1d3d3..89f4855be3 100644 --- a/jest/setup.js +++ b/jest/setup.js @@ -5,3 +5,5 @@ jest.mock( '../react-native-gutenberg-bridge', () => { subscribeParentGetHtml: jest.fn(), }; } ); + +jest.mock( 'react-native-recyclerview-list' ); From f4c3bc0c6b87f606d1dab5d4f3e6c9f31d32c9c5 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler Date: Fri, 9 Nov 2018 10:57:15 +0100 Subject: [PATCH 05/14] Intialize refresh to false by default --- src/app/AppContainer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/AppContainer.js b/src/app/AppContainer.js index 3c4e410572..27695a2d62 100644 --- a/src/app/AppContainer.js +++ b/src/app/AppContainer.js @@ -12,7 +12,7 @@ import RNReactNativeGutenbergBridge from 'react-native-gutenberg-bridge'; const mapStateToProps = ( state, ownProps ) => { let blocks = ownProps.blocks; - let refresh = !! ownProps.refresh; + let refresh = false; const newBlocks = ownProps.blocks.map( ( block ) => { block.focused = ownProps.isBlockSelected( block.clientId ); From 3f3787cb54382209921f9895fe69da79ef82924b Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler Date: Fri, 9 Nov 2018 17:57:25 +0100 Subject: [PATCH 06/14] Add jest-preset-default as dependency to ensure it's loaded when gutenberg tests run --- package.json | 3 +- yarn.lock | 455 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 448 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index f8b72c905e..667483c6fa 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@babel/plugin-transform-runtime": "^7.1.0", "@wordpress/babel-preset-default": "^1.1.2", "@wordpress/block-serialization-spec-parser": "^1.0.0", + "@wordpress/jest-preset-default": "^3.0.2", "babel-core": "^7.0.0-bridge.0", "babel-eslint": "^8.2.2", "babel-jest": "^23.6.0", @@ -113,4 +114,4 @@ "tinycolor2": "^1.4.1", "turbo-combine-reducers": "^1.0.2" } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index f87c9b0a4e..af798c1c38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -779,6 +779,26 @@ resolved "https://registry.yarnpkg.com/@wordpress/browserslist-config/-/browserslist-config-2.2.0.tgz#7fcc77db40d4d846dbb158e485a6badc143c76d2" integrity sha512-hwWyvpMyETINyUCO44GqS7/I+KK8Yd12mDwKz8kP2Tf0fGI+p+Tc20bk2ibjfVed8sGAwKA3jfYfKmHE11B3jA== +"@wordpress/jest-console@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@wordpress/jest-console/-/jest-console-2.0.6.tgz#df4315ee4ae7ebeae65e9d113e27b2558ceb6f8f" + integrity sha512-WWDhFHljezit3+4QJWy40NufWj2a52264KJ9E0/WZ4IEH2O45q3iu21Ip6ckEfzXVXxnV0vMXrJTLzTXqdsxEA== + dependencies: + "@babel/runtime" "^7.0.0" + jest-matcher-utils "^22.4.3" + lodash "^4.17.10" + +"@wordpress/jest-preset-default@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@wordpress/jest-preset-default/-/jest-preset-default-3.0.2.tgz#671bbff5eb28ab753764b79245cafd15f9e16443" + integrity sha512-DFw/BvWhbowyj2z9NIxq1m5zq3zEuxkYal0L768UJlEr+ipSIAb4bs+sM9NJ6vle4/RSrbxBj2oRThXubSsjFQ== + dependencies: + "@wordpress/jest-console" "^2.0.6" + babel-jest "^23.4.2" + enzyme "^3.7.0" + enzyme-adapter-react-16 "^1.6.0" + jest-enzyme "^6.0.2" + abab@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" @@ -1071,6 +1091,15 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= +array.prototype.flat@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.1.tgz#812db8f02cad24d3fab65dd67eabe3b8903494a4" + integrity sha512-rVqIs330nLJvfC7JqYvEWwqVr5QjYF1ib02i3YJtR/fICO6527Tjpc/e4Mvmxh3GIePPreRXMdaGyC99YphWEw== + dependencies: + define-properties "^1.1.2" + es-abstract "^1.10.0" + function-bind "^1.1.1" + arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -1395,7 +1424,7 @@ babel-jest@^22.4.4: babel-plugin-istanbul "^4.1.5" babel-preset-jest "^22.4.4" -babel-jest@^23.6.0: +babel-jest@^23.4.2, babel-jest@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-23.6.0.tgz#a644232366557a2240a0c083da6b25786185a2f1" integrity sha512-lqKGG6LYXYu+DQh/slrQ8nxXQkEkhugdXsU6St7GmhVS7Ilc/22ArwqXNJrf0QaOBjZB0360qZMwXqDYQHXaew== @@ -2060,6 +2089,11 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== +boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + boolify@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/boolify/-/boolify-1.0.1.tgz#b5c09e17cacd113d11b7bb3ed384cc012994d86b" @@ -2409,6 +2443,18 @@ charenc@~0.0.1: resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= +cheerio@^1.0.0-rc.2: + version "1.0.0-rc.2" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" + integrity sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs= + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.0" + entities "~1.1.1" + htmlparser2 "^3.9.1" + lodash "^4.15.0" + parse5 "^3.0.1" + chownr@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" @@ -2427,6 +2473,11 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" +circular-json-es6@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/circular-json-es6/-/circular-json-es6-2.0.2.tgz#e4f4a093e49fb4b6aba1157365746112a78bd344" + integrity sha512-ODYONMMNb3p658Zv+Pp+/XPa5s6q7afhz3Tzyvo+VRh9WIrJ64J76ZC4GQxnlye/NesTn09jvOiuE8+xxfpwhQ== + circular-json@^0.3.1: version "0.3.3" resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" @@ -2557,6 +2608,11 @@ color@^2.0.1: color-convert "^1.9.1" color-string "^1.5.2" +colors@0.5.x: + version "0.5.1" + resolved "https://registry.yarnpkg.com/colors/-/colors-0.5.1.tgz#7d0023eaeb154e8ee9fce75dcb923d0ed1667774" + integrity sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q= + combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" @@ -2833,6 +2889,16 @@ css-mediaquery@^0.1.2: resolved "https://registry.yarnpkg.com/css-mediaquery/-/css-mediaquery-0.1.2.tgz#6a2c37344928618631c54bd33cedd301da18bea0" integrity sha1-aiw3NEkoYYYxxUvTPO3TAdoYvqA= +css-select@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" + integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= + dependencies: + boolbase "~1.0.0" + css-what "2.1" + domutils "1.5.1" + nth-check "~1.0.1" + css-to-react-native-transform@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/css-to-react-native-transform/-/css-to-react-native-transform-1.7.0.tgz#1e12deb806644be88583ac3751fad68b0e321437" @@ -2851,6 +2917,11 @@ css-to-react-native@^2.2.1: fbjs "^0.8.5" postcss-value-parser "^3.3.0" +css-what@2.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.2.tgz#c0876d9d0480927d7d4920dcd72af3595649554d" + integrity sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ== + css@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/css/-/css-2.2.3.tgz#f861f4ba61e79bedc962aa548e5780fd95cbc6be" @@ -2935,6 +3006,13 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +deep-equal-ident@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/deep-equal-ident/-/deep-equal-ident-1.1.1.tgz#06f4b89e53710cd6cea4a7781c7a956642de8dc9" + integrity sha1-BvS4nlNxDNbOpKd4HHqVZkLejck= + dependencies: + lodash.isequal "^3.0" + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -3077,6 +3155,11 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +discontinuous-range@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" + integrity sha1-44Mx8IRLukm5qctxx3FYWqsbxlo= + dlv@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.2.tgz#270f6737b30d25b6657a7e962c784403f85137e5" @@ -3094,7 +3177,7 @@ dom-react@^2.2.1: resolved "https://registry.yarnpkg.com/dom-react/-/dom-react-2.2.1.tgz#3e500e140374e2b2bd51ecc4e986fea1403e46f9" integrity sha512-kqvoG+Q5oiJMQzQi245ZVA/X2Py2lBCebGcQzQeR51jOJqVghWBodKoJcitX8VRV+e6ku+9hRS+Bev/zmlSPsg== -dom-serializer@0: +dom-serializer@0, dom-serializer@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" integrity sha1-BzxpdUbOB4DOI75KKOKT5AvDDII= @@ -3136,6 +3219,14 @@ domhandler@^2.3.0: dependencies: domelementtype "1" +domutils@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" + integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= + dependencies: + dom-serializer "0" + domelementtype "1" + domutils@^1.5.1, domutils@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" @@ -3243,6 +3334,69 @@ envinfo@^5.7.0: resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-5.10.0.tgz#503a9774ae15b93ea68bdfae2ccd6306624ea6df" integrity sha512-rXbzXWvnQxy+TcqZlARbWVQwgGVVouVJgFZhLVN5htjLxl1thstrP2ZGi0pXC309AbK7gVOPU+ulz/tmpCI7iw== +enzyme-adapter-react-16@^1.6.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.7.0.tgz#90344395a89624edbe7f0e443bc19fef62bf1f9f" + integrity sha512-rDr0xlnnFPffAPYrvG97QYJaRl9unVDslKee33wTStsBEwZTkESX1H7VHGT5eUc6ifNzPgOJGvSh2zpHT4gXjA== + dependencies: + enzyme-adapter-utils "^1.9.0" + function.prototype.name "^1.1.0" + object.assign "^4.1.0" + object.values "^1.0.4" + prop-types "^15.6.2" + react-is "^16.6.1" + react-test-renderer "^16.0.0-0" + +enzyme-adapter-utils@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.9.0.tgz#3997c20f3387fdcd932b155b3740829ea10aa86c" + integrity sha512-uMe4xw4l/Iloh2Fz+EO23XUYMEQXj5k/5ioLUXCNOUCI8Dml5XQMO9+QwUq962hBsY5qftfHHns+d990byWHvg== + dependencies: + function.prototype.name "^1.1.0" + object.assign "^4.1.0" + prop-types "^15.6.2" + semver "^5.6.0" + +enzyme-matchers@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/enzyme-matchers/-/enzyme-matchers-6.1.2.tgz#cd70e45dbcff34d47406797998d55d06ceb822de" + integrity sha512-cP9p+HMOZ1ZXQ+k2H4dCkxmTZzIvpEy5zv0ZjgoBl6D0U43v+bJGH5IeWHdIovCzgJ0dVcMCKJ6lNu83lYUCAA== + dependencies: + circular-json-es6 "^2.0.1" + deep-equal-ident "^1.1.1" + +enzyme-to-json@^3.3.0: + version "3.3.4" + resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.3.4.tgz#67c6040e931182f183418af2eb9f4323258aa77f" + integrity sha1-Z8YEDpMRgvGDQYry659DIyWKp38= + dependencies: + lodash "^4.17.4" + +enzyme@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.7.0.tgz#9b499e8ca155df44fef64d9f1558961ba1385a46" + integrity sha512-QLWx+krGK6iDNyR1KlH5YPZqxZCQaVF6ike1eDJAOg0HvSkSCVImPsdWaNw6v+VrnK92Kg8jIOYhuOSS9sBpyg== + dependencies: + array.prototype.flat "^1.2.1" + cheerio "^1.0.0-rc.2" + function.prototype.name "^1.1.0" + has "^1.0.3" + is-boolean-object "^1.0.0" + is-callable "^1.1.4" + is-number-object "^1.0.3" + is-string "^1.0.4" + is-subset "^0.1.1" + lodash.escape "^4.0.1" + lodash.isequal "^4.5.0" + object-inspect "^1.6.0" + object-is "^1.0.1" + object.assign "^4.1.0" + object.entries "^1.0.4" + object.values "^1.0.4" + raf "^3.4.0" + rst-selector-parser "^2.2.3" + string.prototype.trim "^1.1.2" + equivalent-key-map@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/equivalent-key-map/-/equivalent-key-map-0.2.0.tgz#10acd3f327a1a07ff3eba39e40887ef9aad01004" @@ -3263,7 +3417,7 @@ errorhandler@^1.5.0: accepts "~1.3.3" escape-html "~1.0.3" -es-abstract@^1.5.1, es-abstract@^1.7.0: +es-abstract@^1.10.0, es-abstract@^1.5.0, es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0: version "1.12.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" integrity sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA== @@ -3959,11 +4113,20 @@ fstream@^1.0.0, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" -function-bind@^1.1.1: +function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function.prototype.name@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.0.tgz#8bd763cc0af860a859cc5d49384d74b932cd2327" + integrity sha512-Bs0VRrTz4ghD8pTmbJQD1mZ8A/mN0ur/jGz+A6FBxPDUPkm1tNfF6bhTYPA7i7aF4lZJVr+OXTNNrnnIl58Wfg== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + is-callable "^1.1.3" + functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" @@ -4230,6 +4393,11 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -4361,6 +4529,18 @@ htmlparser2-without-node-native@^3.9.2: inherits "^2.0.1" readable-stream "^2.0.2" +htmlparser2@^3.9.1: + version "3.10.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464" + integrity sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ== + dependencies: + domelementtype "^1.3.0" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^3.0.6" + http-errors@~1.6.2: version "1.6.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" @@ -4572,6 +4752,11 @@ is-arrayish@^0.3.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-boolean-object@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93" + integrity sha1-mPiygDBoQhmpXzdc+9iM40Bd/5M= + is-buffer@^1.1.4, is-buffer@^1.1.5, is-buffer@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -4584,7 +4769,7 @@ is-builtin-module@^1.0.0: dependencies: builtin-modules "^1.0.0" -is-callable@^1.1.1, is-callable@^1.1.3: +is-callable@^1.1.1, is-callable@^1.1.3, is-callable@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== @@ -4708,6 +4893,11 @@ is-hexadecimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz#b6e710d7d07bb66b98cb8cece5c9b4921deeb835" integrity sha512-but/G3sapV3MNyqiDBLrOi4x8uCIw0RY3o/Vb5GT0sMFHrVV7731wFSVy41T5FO1og7G0gXLJh0MkgPRouko/A== +is-number-object@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.3.tgz#f265ab89a9f445034ef6aff15a8f00b00f551799" + integrity sha1-8mWrian0RQNO9q/xWo8AsA9VF5k= + is-number@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" @@ -4790,6 +4980,16 @@ is-stream@^1.0.1, is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-string@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.4.tgz#cc3a9b69857d621e963725a24caeec873b826e64" + integrity sha1-zDqbaYV9Yh6WNyWiTK7shzuCbmQ= + +is-subset@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" + integrity sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY= + is-symbol@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" @@ -5042,6 +5242,13 @@ jest-docblock@^22.4.0, jest-docblock@^22.4.3: dependencies: detect-newline "^2.1.0" +jest-environment-enzyme@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/jest-environment-enzyme/-/jest-environment-enzyme-6.1.2.tgz#2612805a3cc0fa6934f9c3e62cf6255077d1b16b" + integrity sha512-WHeBKgBYOdryuOTEoK55lJwjg7Raery1OgXHLwukI3mSYgOkm2UrCDDT+vneqVgy7F8KuRHyStfD+TC/m2b7Kg== + dependencies: + jest-environment-jsdom "^22.4.1" + jest-environment-jsdom@^22.4.1: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-22.4.3.tgz#d67daa4155e33516aecdd35afd82d4abf0fa8a1e" @@ -5059,6 +5266,15 @@ jest-environment-node@^22.4.1: jest-mock "^22.4.3" jest-util "^22.4.3" +jest-enzyme@^6.0.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/jest-enzyme/-/jest-enzyme-6.1.2.tgz#89da43da19b4d8ddbfb49e153c2884f22954d330" + integrity sha512-+ds7r2ru3QkNJxelQ2tnC6d33pjUSsZHPD3v4TlnHlNMuGX3UKdxm5C46yZBvJICYBvIF+RFKBhLMM4evNM95Q== + dependencies: + enzyme-matchers "^6.1.2" + enzyme-to-json "^3.3.0" + jest-environment-enzyme "^6.1.2" + jest-get-type@^21.0.2: version "21.2.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-21.2.0.tgz#f6376ab9db4b60d81e39f30749c6c466f40d4a23" @@ -5584,6 +5800,25 @@ lodash-es@^4.17.5, lodash-es@^4.2.1: resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05" integrity sha512-iesFYPmxYYGTcmQK0sL8bX3TGHyM6b2qREaB4kamHfQyfPJP0xgoGxp19nsH16nsfquLdiyKyX3mQkfiSGV8Rg== +lodash._baseisequal@^3.0.0: + version "3.0.7" + resolved "https://registry.yarnpkg.com/lodash._baseisequal/-/lodash._baseisequal-3.0.7.tgz#d8025f76339d29342767dcc887ce5cb95a5b51f1" + integrity sha1-2AJfdjOdKTQnZ9zIh85cuVpbUfE= + dependencies: + lodash.isarray "^3.0.0" + lodash.istypedarray "^3.0.0" + lodash.keys "^3.0.0" + +lodash._bindcallback@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" + integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4= + +lodash._getnative@^3.0.0: + version "3.9.1" + resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" + integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= + lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" @@ -5594,6 +5829,53 @@ lodash.clonedeep@^4.3.2: resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= +lodash.escape@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" + integrity sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg= + +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= + +lodash.isarguments@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo= + +lodash.isarray@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" + integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U= + +lodash.isequal@^3.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-3.0.4.tgz#1c35eb3b6ef0cd1ff51743e3ea3cf7fdffdacb64" + integrity sha1-HDXrO27wzR/1F0Pj6jz3/f/ay2Q= + dependencies: + lodash._baseisequal "^3.0.0" + lodash._bindcallback "^3.0.0" + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + +lodash.istypedarray@^3.0.0: + version "3.0.6" + resolved "https://registry.yarnpkg.com/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz#c9a477498607501d8e8494d283b87c39281cef62" + integrity sha1-yaR3SYYHUB2OhJTSg7h8OSgc72I= + +lodash.keys@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" + integrity sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo= + dependencies: + lodash._getnative "^3.0.0" + lodash.isarguments "^3.0.0" + lodash.isarray "^3.0.0" + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -5644,6 +5926,11 @@ lodash@^4.0.0, lodash@^4.13.1, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.4, resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg== +lodash@^4.15.0: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + loglevel-colored-level-prefix@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz#6a40218fdc7ae15fc76c3d0f3e676c465388603e" @@ -6160,6 +6447,11 @@ moment-timezone@^0.5.16: resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y= +moo@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/moo/-/moo-0.4.3.tgz#3f847a26f31cf625a956a87f2b10fbc013bfd10e" + integrity sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw== + morgan@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.0.tgz#d01fa6c65859b76fcf31b3cb53a3821a311d8051" @@ -6213,6 +6505,17 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +nearley@^2.7.10: + version "2.15.1" + resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.15.1.tgz#965e4e6ec9ed6b80fc81453e161efbcebb36d247" + integrity sha512-8IUY/rUrKz2mIynUGh8k+tul1awMKEjeHHC5G3FHvvyAW6oq4mQfNp2c0BMea+sYZJvYcrrM6GmZVIle/GRXGw== + dependencies: + moo "^0.4.3" + nomnom "~1.6.2" + railroad-diagrams "^1.0.0" + randexp "0.4.6" + semver "^5.4.1" + needle@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.1.tgz#b5e325bd3aae8c2678902fa296f729455d1d3a7d" @@ -6352,6 +6655,14 @@ node-sass@^4.8.3: stdout-stream "^1.4.0" "true-case-path" "^1.0.2" +nomnom@~1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.6.2.tgz#84a66a260174408fc5b77a18f888eccc44fb6971" + integrity sha1-hKZqJgF0QI/Ft3oY+IjszET7aXE= + dependencies: + colors "0.5.x" + underscore "~1.4.4" + "nopt@2 || 3", nopt@~3.0.1, nopt@~3.0.6: version "3.0.6" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" @@ -6423,6 +6734,13 @@ npmlog@^2.0.4: are-we-there-yet "~1.1.2" gauge "~1.2.5" +nth-check@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== + dependencies: + boolbase "~1.0.0" + nullthrows@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.0.tgz#832bb19ef7fedab989f81675c846e2858a3917a2" @@ -6462,7 +6780,17 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-keys@^1.0.8: +object-inspect@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b" + integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ== + +object-is@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6" + integrity sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY= + +object-keys@^1.0.11, object-keys@^1.0.8: version "1.0.12" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag== @@ -6474,6 +6802,26 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.entries@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.0.4.tgz#1bf9a4dd2288f5b33f3a993d257661f05d161a5f" + integrity sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8= + dependencies: + define-properties "^1.1.2" + es-abstract "^1.6.1" + function-bind "^1.1.0" + has "^1.0.1" + object.getownpropertydescriptors@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" @@ -6497,6 +6845,16 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" +object.values@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.0.4.tgz#e524da09b4f66ff05df457546ec72ac99f13069a" + integrity sha1-5STaCbT2b/Bd9FdUbscqyZ8TBpo= + dependencies: + define-properties "^1.1.2" + es-abstract "^1.6.1" + function-bind "^1.1.0" + has "^1.0.1" + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -6678,7 +7036,7 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" -parse5@3.0.3: +parse5@3.0.3, parse5@^3.0.1: version "3.0.3" resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" integrity sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA== @@ -7181,6 +7539,26 @@ quick-lru@^1.0.0: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" integrity sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g= +raf@^3.4.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + +railroad-diagrams@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" + integrity sha1-635iZ1SN3t+4mcG5Dlc3RVnN234= + +randexp@0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" + integrity sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ== + dependencies: + discontinuous-range "1.0.0" + ret "~0.1.10" + randomatic@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.0.0.tgz#d35490030eb4f7578de292ce6dfb04a91a128923" @@ -7245,6 +7623,11 @@ react-is@^16.5.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3" integrity sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ== +react-is@^16.6.1: + version "16.6.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.6.1.tgz#f77b1c3d901be300abe8d58645b7a59e794e5982" + integrity sha512-wOKsGtvTMYs7WAscmwwdM8sfRRvE17Ym30zFj3n37Qx5tHRfhenPKEPILHaHob6WoLFADmQm1ZNrE5xMCM6sCw== + react-native-animatable@^1.2.4: version "1.3.0" resolved "https://registry.yarnpkg.com/react-native-animatable/-/react-native-animatable-1.3.0.tgz#b5c3940fc758cfd9b2fe54613a457c4b6962b46e" @@ -7398,6 +7781,16 @@ react-test-renderer@16.5.0: react-is "^16.5.0" schedule "^0.3.0" +react-test-renderer@^16.0.0-0: + version "16.6.1" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.6.1.tgz#8ea357652be3cf81cbd6b2f686e74ebe67c17b78" + integrity sha512-sgZwJZYIgQptRi2qk5+gB8FBQGk4gLSs0gmKZPMfhd3dLkdxIUwVLHteLN3Bnj4LokIZd3U+V2NEJUqeV2PT2w== + dependencies: + object-assign "^4.1.1" + prop-types "^15.6.2" + react-is "^16.6.1" + scheduler "^0.11.0" + react-timer-mixin@^0.13.2: version "0.13.4" resolved "https://registry.yarnpkg.com/react-timer-mixin/-/react-timer-mixin-0.13.4.tgz#75a00c3c94c13abe29b43d63b4c65a88fc8264d3" @@ -7476,6 +7869,15 @@ readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.0.6.tgz#351302e4c68b5abd6a2ed55376a7f9a25be3057a" + integrity sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + realpath-native@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.0.1.tgz#07f40a0cce8f8261e2e8b7ebebf5c95965d7b633" @@ -7915,6 +8317,14 @@ rn-host-detect@^1.0.1: resolved "https://registry.yarnpkg.com/rn-host-detect/-/rn-host-detect-1.1.3.tgz#242d76e2fa485c48d751416e65b7cce596969e91" integrity sha1-JC124vpIXEjXUUFuZbfM5ZaWnpE= +rst-selector-parser@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" + integrity sha1-gbIw6i/MYGbInjRy3nlChdmwPZE= + dependencies: + lodash.flattendeep "^4.4.0" + nearley "^2.7.10" + rsvp@^3.3.3: version "3.6.2" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a" @@ -8045,6 +8455,14 @@ schedule@^0.3.0: dependencies: object-assign "^4.1.1" +scheduler@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.11.0.tgz#def1f1bfa6550cc57981a87106e65e8aea41a6b5" + integrity sha512-MAYbBfmiEHxF0W+c4CxMpEqMYK+rYF584VP/qMKSiHM6lTkBKKYOJaDiSILpJHla6hBOsVd6GucPL46o2Uq3sg== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scss-tokenizer@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" @@ -8068,6 +8486,11 @@ semver@^5.5.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" integrity sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw== +semver@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== + semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -8507,7 +8930,16 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string_decoder@^1.0.3, string_decoder@~1.1.1: +string.prototype.trim@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea" + integrity sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo= + dependencies: + define-properties "^1.1.2" + es-abstract "^1.5.0" + function-bind "^1.0.2" + +string_decoder@^1.0.3, string_decoder@^1.1.1, string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== @@ -8900,6 +9332,11 @@ ultron@~1.1.0: resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== +underscore@~1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604" + integrity sha1-YaajIBBiKvoHljvzJSA88SI51gQ= + unherit@^1.0.4: version "1.1.1" resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.1.tgz#132748da3e88eab767e08fabfbb89c5e9d28628c" @@ -9019,7 +9456,7 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= From 7ce9175a1062891e215b47f0b80f97b002023103 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler Date: Mon, 12 Nov 2018 10:50:17 +0100 Subject: [PATCH 07/14] Do not overwrite dataSource but instead keep it in sync using the ui events --- src/app/AppContainer.js | 5 +++-- src/block-management/block-manager.js | 26 +++++++++++++++++--------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/app/AppContainer.js b/src/app/AppContainer.js index 27695a2d62..3b52c49a85 100644 --- a/src/app/AppContainer.js +++ b/src/app/AppContainer.js @@ -21,7 +21,7 @@ const mapStateToProps = ( state, ownProps ) => { if ( ! isEqual( blocks, newBlocks ) ) { blocks = newBlocks; - refresh = ! ownProps.refresh; + refresh = true; } return { @@ -49,7 +49,7 @@ const mapDispatchToProps = ( dispatch, ownProps ) => { ownProps.onRemove( clientId ); }, createBlockAction: ( clientId, block ) => { - ownProps.onInsertBlock( block, ownProps.selectedBlockIndex + 1 ); + ownProps.onInsertBlock( block, ownProps.selectedBlockIndex + 1, ownProps.rootClientId ); }, parseBlocksAction: ( html ) => { const parsed = parse( html ); @@ -79,6 +79,7 @@ export default compose( [ const selectedBlockClientId = getSelectedBlockClientId(); return { + rootClientId, isBlockSelected, selectedBlockIndex: getBlockIndex( selectedBlockClientId, rootClientId ), blocks: getBlocks(), diff --git a/src/block-management/block-manager.js b/src/block-management/block-manager.js index 2afec99ea4..af24c8e058 100644 --- a/src/block-management/block-manager.js +++ b/src/block-management/block-manager.js @@ -50,7 +50,7 @@ export default class BlockManager extends React.Component super( props ); this.state = { blocks: [], - dataSource: null, + dataSource: new DataSource( this.props.blocks, ( item: BlockType ) => item.clientId ), showHtml: false, inspectBlocks: false, blockTypePickerVisible: false, @@ -59,13 +59,12 @@ export default class BlockManager extends React.Component } onBlockHolderPressed( clientId: string ) { - this.focusDataSourceItem( clientId ); this.props.focusBlockAction( clientId ); } - focusDataSourceItem( clientId: string ) { - for ( let i = 0; i < this.state.dataSource.size(); ++i ) { - const block = this.state.dataSource.get( i ); + static focusDataSourceItem( dataSource: DataSource, clientId: string ) { + for ( let i = 0; i < dataSource.size(); ++i ) { + const block = dataSource.get( i ); if ( block.clientId === clientId ) { block.focused = true; } else { @@ -108,30 +107,39 @@ export default class BlockManager extends React.Component if ( focusedItemIndex === -1 ) { focusedItemIndex = this.state.dataSource.size() - 1; } - const clientIdFocused = this.state.dataSource.get( focusedItemIndex ).clientId; // create an empty block of the selected type const newBlock = createBlock( itemValue, { content: 'new test text for a ' + itemValue + ' block' } ); - newBlock.focused = false; // set it into the datasource, and use the same object instance to send it to props/redux this.state.dataSource.splice( focusedItemIndex + 1, 0, newBlock ); + if ( this.scrollTo ) { this.scrollTo( focusedItemIndex + 1 ); } - this.props.createBlockAction( newBlock.clientId, newBlock, clientIdFocused ); + this.props.createBlockAction( newBlock.clientId, newBlock ); // now set the focus this.props.focusBlockAction( newBlock.clientId ); } static getDerivedStateFromProps( props: PropsType, state: StateType ) { + if ( props.fullparse === true ) { + return { + ...state, + dataSource: new DataSource( props.blocks, ( item: BlockType ) => item.clientId ), + }; + } + if ( state.blocks !== props.blocks ) { + const blockFocused = props.blocks.find( block => block.focused ); + if ( blockFocused ) { + BlockManager.focusDataSourceItem( state.dataSource, blockFocused.clientId ); + } return { ...state, blocks: props.blocks, - dataSource: new DataSource( props.blocks, ( item: BlockType ) => item.clientId ), }; } From afe12a288741f705b94ecae6613edaf0fd53f352 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler Date: Mon, 12 Nov 2018 13:54:30 +0100 Subject: [PATCH 08/14] More reliable way to update focus, do not share object references with dataSource and use react lifecycle methods to update instead --- src/app/AppContainer.js | 28 ++++++++++++--------------- src/block-management/block-manager.js | 16 +++++++++------ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/app/AppContainer.js b/src/app/AppContainer.js index 3b52c49a85..a5ac62d9a5 100644 --- a/src/app/AppContainer.js +++ b/src/app/AppContainer.js @@ -2,7 +2,7 @@ * @format */ import { connect } from 'react-redux'; -import { isEqual } from 'lodash'; +import { cloneDeep } from 'lodash'; import MainApp from './MainApp'; import { parse, serialize } from '@wordpress/blocks'; @@ -11,22 +11,14 @@ import { compose } from '@wordpress/compose'; import RNReactNativeGutenbergBridge from 'react-native-gutenberg-bridge'; const mapStateToProps = ( state, ownProps ) => { - let blocks = ownProps.blocks; - let refresh = false; - - const newBlocks = ownProps.blocks.map( ( block ) => { - block.focused = ownProps.isBlockSelected( block.clientId ); - return block; + const blocks = ownProps.blocksFromState.map( block => { + const newBlock = cloneDeep( block ); + newBlock.focused = ownProps.isBlockSelected( block.clientId ); + return newBlock; } ); - if ( ! isEqual( blocks, newBlocks ) ) { - blocks = newBlocks; - refresh = true; - } - return { - blocks, - refresh, + blocks }; }; @@ -82,11 +74,12 @@ export default compose( [ rootClientId, isBlockSelected, selectedBlockIndex: getBlockIndex( selectedBlockClientId, rootClientId ), - blocks: getBlocks(), + blocksFromState: getBlocks(), }; } ), withDispatch( ( dispatch ) => { const { + clearSelectedBlock, insertBlock, mergeBlocks, moveBlocksDown, @@ -104,7 +97,10 @@ export default compose( [ onMoveUp: moveBlocksUp, onRemove: removeBlock, onResetBlocks: resetBlocks, - onSelect: selectBlock, + onSelect: ( clientId ) => { + clearSelectedBlock(); + selectBlock( clientId ); + }, onAttributesUpdate: updateBlockAttributes, }; } ), diff --git a/src/block-management/block-manager.js b/src/block-management/block-manager.js index af24c8e058..3be18e591c 100644 --- a/src/block-management/block-manager.js +++ b/src/block-management/block-manager.js @@ -4,6 +4,7 @@ */ import React from 'react'; +import { isEqual } from 'lodash'; import { Platform, Switch, Text, View, FlatList, KeyboardAvoidingView } from 'react-native'; import RecyclerViewList, { DataSource } from 'react-native-recyclerview-list'; import BlockHolder from './block-holder'; @@ -29,7 +30,6 @@ export type BlockListType = { mergeBlocksAction: ( string, string ) => mixed, blocks: Array, aztechtml: string, - refresh: boolean, }; type PropsType = BlockListType; @@ -40,6 +40,7 @@ type StateType = { blockTypePickerVisible: boolean, blocks: Array, selectedBlockType: string, + refresh: boolean, }; export default class BlockManager extends React.Component { @@ -55,6 +56,7 @@ export default class BlockManager extends React.Component inspectBlocks: false, blockTypePickerVisible: false, selectedBlockType: 'core/paragraph', // just any valid type to start from + refresh: false, }; } @@ -132,14 +134,16 @@ export default class BlockManager extends React.Component }; } - if ( state.blocks !== props.blocks ) { + if ( ! isEqual( state.blocks, props.blocks ) ) { const blockFocused = props.blocks.find( block => block.focused ); if ( blockFocused ) { BlockManager.focusDataSourceItem( state.dataSource, blockFocused.clientId ); } + // TODO: handle attributes change here return { ...state, blocks: props.blocks, + refresh: ! state.refresh, }; } @@ -287,7 +291,7 @@ export default class BlockManager extends React.Component item.clientId } renderItem={ this.renderItem.bind( this ) } /> @@ -352,7 +356,7 @@ export default class BlockManager extends React.Component this.setState( { inspectBlocks } ); } - renderItem( value: { item: BlockType, clientId: string } ) { + renderItem( value: { item: BlockType } ) { const insertHere = ( @@ -364,13 +368,13 @@ export default class BlockManager extends React.Component return ( this.insertBlocksAfter.bind( this )( value.item.clientId, blocks ) } From 0357cfe70dbcbc8e28df84a3087e0edcc846f51c Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler Date: Mon, 12 Nov 2018 17:17:56 +0100 Subject: [PATCH 09/14] Refactor App and AppContainer to not depend on the gb-mobile store anymore --- gutenberg | 2 +- react-native-aztec | 2 +- src/app/App.js | 63 +++-------- src/app/App.test.js | 20 ---- src/app/AppContainer.js | 131 +++++++++++++--------- src/block-management/block-holder.js | 2 +- src/block-management/block-manager.js | 51 ++++----- src/block-types/unsupported-block/edit.js | 2 +- 8 files changed, 124 insertions(+), 149 deletions(-) diff --git a/gutenberg b/gutenberg index f712410cfc..cb585754f9 160000 --- a/gutenberg +++ b/gutenberg @@ -1 +1 @@ -Subproject commit f712410cfc20b4438b54d3ad100549fb9d6aaebb +Subproject commit cb585754f92beee040af6da3e394916f0255f85b diff --git a/react-native-aztec b/react-native-aztec index 93825e4c24..f17210d6bf 160000 --- a/react-native-aztec +++ b/react-native-aztec @@ -1 +1 @@ -Subproject commit 93825e4c2434df80ad28eabda2e2d706e4eeaace +Subproject commit f17210d6bfa91faff282b7534353a40c6332e946 diff --git a/src/app/App.js b/src/app/App.js index cdd427de36..9ddbba8b6e 100644 --- a/src/app/App.js +++ b/src/app/App.js @@ -4,53 +4,22 @@ import '../globals'; import React from 'react'; -import { Provider } from 'react-redux'; -import { type BlockType, setupStore, html2State } from '../store'; -import AppContainer from './AppContainer'; -import { Store } from 'redux'; -import { withDispatch } from '@wordpress/data'; +// Gutenberg imports +import { registerCoreBlocks } from '@wordpress/block-library'; +import { registerBlockType, setUnregisteredTypeHandlerName } from '@wordpress/blocks'; + +import AppContainer from './AppContainer'; import initialHtml from './initial-html'; -type PropsType = { - onResetBlocks: Array => mixed, - initialData: string | Store, -}; -type StateType = { - store: Store, -}; - -class AppProvider extends React.Component { - state: StateType; - - constructor( props: PropsType ) { - super( props ); - - this.state = { - store: - typeof props.initialData === 'object' ? - props.initialData : - setupStore( html2State( props.initialData !== undefined ? props.initialData : initialHtml ) ), - }; - - // initialize gutenberg store with local store - props.onResetBlocks( this.state.store.getState().blocks ); - } - - render() { - return ( - - - - ); - } -} - -export default withDispatch( ( dispatch ) => { - const { - resetBlocks, - } = dispatch( 'core/editor' ); - return { - onResetBlocks: resetBlocks, - }; -} )( AppProvider ); +import * as UnsupportedBlock from '../block-types/unsupported-block/'; + +registerCoreBlocks(); +registerBlockType( UnsupportedBlock.name, UnsupportedBlock.settings ); +setUnregisteredTypeHandlerName( UnsupportedBlock.name ); + +const AppProvider = () => ( + +); + +export default AppProvider; diff --git a/src/app/App.test.js b/src/app/App.test.js index ac6e8fd4cd..ab0e0b097e 100644 --- a/src/app/App.test.js +++ b/src/app/App.test.js @@ -3,8 +3,6 @@ import renderer from 'react-test-renderer'; import App from './App'; -import initialHtml from './initial-html'; -import { html2State, setupStore } from '../store'; import BlockHolder from '../block-management/block-holder'; describe( 'App', () => { @@ -14,24 +12,6 @@ describe( 'App', () => { expect( rendered ).toBeTruthy(); } ); - it( 'renders without crashing with a block focused', () => { - // construct a state object with the first block focused - const state = html2State( initialHtml ); - const block0 = { ...state.blocks[ 0 ] }; - block0.focused = true; - state.blocks[ 0 ] = block0; - - // create a Store with the state object - const store = setupStore( state ); - - // render an App using the specified Store - const app = renderer.create( ); - const rendered = app.toJSON(); - - // App should be rendered OK - expect( rendered ).toBeTruthy(); - } ); - it( 'Code block is a TextInput', () => { renderer .create( ) diff --git a/src/app/AppContainer.js b/src/app/AppContainer.js index a5ac62d9a5..ab421495a5 100644 --- a/src/app/AppContainer.js +++ b/src/app/AppContainer.js @@ -1,80 +1,108 @@ /** @flow * @format */ -import { connect } from 'react-redux'; -import { cloneDeep } from 'lodash'; - import MainApp from './MainApp'; +import React from 'react'; import { parse, serialize } from '@wordpress/blocks'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import RNReactNativeGutenbergBridge from 'react-native-gutenberg-bridge'; +import type { BlockType } from '../store/types'; + +type PropsType = { + rootClientId: string, + isBlockSelected: string => boolean, + selectedBlockIndex: number, + blocks: Array, + onInsertBlock: ( BlockType, number, string ) => mixed, + onMerge: ( string, string ) => mixed, + onMoveDown: string => mixed, + onMoveUp: string => mixed, + onRemove: string => mixed, + onResetBlocks: Array => mixed, + onSelect: string => mixed, + onAttributesUpdate: ( string, mixed ) => mixed, + initialHtml: string, +}; -const mapStateToProps = ( state, ownProps ) => { - const blocks = ownProps.blocksFromState.map( block => { - const newBlock = cloneDeep( block ); - newBlock.focused = ownProps.isBlockSelected( block.clientId ); - return newBlock; - } ); +class AppContainer extends React.Component { + constructor( props: PropsType ) { + super( props ); - return { - blocks + this.parseBlocksAction( props.initialHtml ); + } + + onChange = ( clientId, attributes ) => { + this.props.onAttributesUpdate( clientId, attributes ); }; -}; -const mapDispatchToProps = ( dispatch, ownProps ) => { - return { - ...ownProps, - onChange: ( clientId, attributes ) => { - ownProps.onAttributesUpdate( clientId, attributes ); - }, - focusBlockAction: ( clientId ) => { - ownProps.onSelect( clientId ); - }, - moveBlockUpAction: ( clientId ) => { - ownProps.onMoveUp( clientId ); - }, - moveBlockDownAction: ( clientId ) => { - ownProps.onMoveDown( clientId ); - }, - deleteBlockAction: ( clientId ) => { - ownProps.onRemove( clientId ); - }, - createBlockAction: ( clientId, block ) => { - ownProps.onInsertBlock( block, ownProps.selectedBlockIndex + 1, ownProps.rootClientId ); - }, - parseBlocksAction: ( html ) => { - const parsed = parse( html ); - ownProps.onResetBlocks( parsed ); - }, - serializeToNativeAction: () => { - const html = serialize( ownProps.blocks ); - RNReactNativeGutenbergBridge.provideToNative_Html( html ); - }, - mergeBlocksAction: ( blockOneClientId, blockTwoClientId ) => { - ownProps.onMerge( blockOneClientId, blockTwoClientId ); - }, + focusBlockAction = ( clientId ) => { + this.props.onSelect( clientId ); }; -}; -const AppContainer = connect( mapStateToProps, mapDispatchToProps )( MainApp ); + moveBlockUpAction = ( clientId ) => { + this.props.onMoveUp( clientId ); + }; + + moveBlockDownAction = ( clientId ) => { + this.props.onMoveDown( clientId ); + }; + + deleteBlockAction = ( clientId ) => { + this.props.onRemove( clientId ); + }; + + createBlockAction = ( clientId, block ) => { + this.props.onInsertBlock( block, this.props.selectedBlockIndex + 1, this.props.rootClientId ); + }; + + parseBlocksAction = ( html = '' ) => { + const parsed = parse( html ); + this.props.onResetBlocks( parsed ); + }; + + serializeToNativeAction = () => { + const html = serialize( this.props.blocks ); + RNReactNativeGutenbergBridge.provideToNative_Html( html ); + }; + + mergeBlocksAction = ( blockOneClientId, blockTwoClientId ) => { + this.props.onMerge( blockOneClientId, blockTwoClientId ); + }; + + render() { + return ( + + ); + } +} export default compose( [ - withSelect( ( select, ownProps ) => { + withSelect( ( select ) => { const { getBlockIndex, getBlocks, getSelectedBlockClientId, isBlockSelected, } = select( 'core/editor' ); - const { rootClientId } = ownProps; const selectedBlockClientId = getSelectedBlockClientId(); return { - rootClientId, isBlockSelected, - selectedBlockIndex: getBlockIndex( selectedBlockClientId, rootClientId ), - blocksFromState: getBlocks(), + selectedBlockIndex: getBlockIndex( selectedBlockClientId ), + blocks: getBlocks(), }; } ), withDispatch( ( dispatch ) => { @@ -91,6 +119,7 @@ export default compose( [ } = dispatch( 'core/editor' ); return { + clearSelectedBlock, onInsertBlock: insertBlock, onMerge: mergeBlocks, onMoveDown: moveBlocksDown, diff --git a/src/block-management/block-holder.js b/src/block-management/block-holder.js index 426a72de91..6c50c8e3d1 100644 --- a/src/block-management/block-holder.js +++ b/src/block-management/block-holder.js @@ -7,7 +7,7 @@ import React from 'react'; import { View, Text, TouchableWithoutFeedback } from 'react-native'; import InlineToolbar from './inline-toolbar'; -import type { BlockType } from '../store/'; +import type { BlockType } from '../store/types'; import styles from './block-holder.scss'; diff --git a/src/block-management/block-manager.js b/src/block-management/block-manager.js index 3be18e591c..1b38dde77f 100644 --- a/src/block-management/block-manager.js +++ b/src/block-management/block-manager.js @@ -4,12 +4,13 @@ */ import React from 'react'; -import { isEqual } from 'lodash'; +import { cloneDeep, isEqual } from 'lodash'; + import { Platform, Switch, Text, View, FlatList, KeyboardAvoidingView } from 'react-native'; import RecyclerViewList, { DataSource } from 'react-native-recyclerview-list'; import BlockHolder from './block-holder'; import { InlineToolbarButton } from './constants'; -import type { BlockType } from '../store/'; +import type { BlockType } from '../store/types'; import styles from './block-manager.scss'; import BlockPicker from './block-picker'; import HTMLTextInput from '../components/html-text-input'; @@ -24,12 +25,12 @@ export type BlockListType = { moveBlockUpAction: string => mixed, moveBlockDownAction: string => mixed, deleteBlockAction: string => mixed, - createBlockAction: ( string, BlockType, string ) => mixed, + createBlockAction: ( string, BlockType ) => mixed, parseBlocksAction: string => mixed, serializeToNativeAction: void => void, mergeBlocksAction: ( string, string ) => mixed, blocks: Array, - aztechtml: string, + isBlockSelected: string => boolean, }; type PropsType = BlockListType; @@ -49,9 +50,16 @@ export default class BlockManager extends React.Component constructor( props: PropsType ) { super( props ); + + const blocks = props.blocks.map( ( block ) => { + const newBlock = cloneDeep( block ); + newBlock.focused = props.isBlockSelected( block.clientId ); + return newBlock; + } ); + this.state = { - blocks: [], - dataSource: new DataSource( this.props.blocks, ( item: BlockType ) => item.clientId ), + blocks: blocks, + dataSource: new DataSource( blocks, ( item: BlockType ) => item.clientId ), showHtml: false, inspectBlocks: false, blockTypePickerVisible: false, @@ -64,17 +72,6 @@ export default class BlockManager extends React.Component this.props.focusBlockAction( clientId ); } - static focusDataSourceItem( dataSource: DataSource, clientId: string ) { - for ( let i = 0; i < dataSource.size(); ++i ) { - const block = dataSource.get( i ); - if ( block.clientId === clientId ) { - block.focused = true; - } else { - block.focused = false; - } - } - } - getDataSourceIndexFromClientId( clientId: string ) { for ( let i = 0; i < this.state.dataSource.size(); ++i ) { const block = this.state.dataSource.get( i ); @@ -134,15 +131,16 @@ export default class BlockManager extends React.Component }; } - if ( ! isEqual( state.blocks, props.blocks ) ) { - const blockFocused = props.blocks.find( block => block.focused ); - if ( blockFocused ) { - BlockManager.focusDataSourceItem( state.dataSource, blockFocused.clientId ); - } - // TODO: handle attributes change here + const blocks = props.blocks.map( ( block ) => { + const newBlock = cloneDeep( block ); + newBlock.focused = props.isBlockSelected( block.clientId ); + return newBlock; + } ); + + if ( ! isEqual( state.blocks, blocks ) ) { return { ...state, - blocks: props.blocks, + blocks, refresh: ! state.refresh, }; } @@ -182,14 +180,13 @@ export default class BlockManager extends React.Component const focusedItemIndex = this.getDataSourceIndexFromClientId( clientId ); const newBlock = blocks[ 0 ]; - newBlock.focused = true; // set it into the datasource, and use the same object instance to send it to props/redux this.state.dataSource.splice( focusedItemIndex + 1, 0, newBlock ); - this.props.createBlockAction( newBlock.clientId, newBlock, clientId ); + this.props.createBlockAction( newBlock.clientId, newBlock ); // now set the focus - this.props.focusBlockAction( newBlock.clientId ); // this not working atm + this.props.focusBlockAction( newBlock.clientId ); } mergeBlocks( clientId: string, forward: boolean ) { diff --git a/src/block-types/unsupported-block/edit.js b/src/block-types/unsupported-block/edit.js index b3ca363d6c..3f3237805c 100644 --- a/src/block-types/unsupported-block/edit.js +++ b/src/block-types/unsupported-block/edit.js @@ -5,7 +5,7 @@ import React from 'react'; import { View, Text } from 'react-native'; -import type { BlockType } from '../../store/'; +import type { BlockType } from '../../store/types'; type PropsType = BlockType & { onChange: ( clientId: string, attributes: mixed ) => void, From 4ad24dc01a059078edc46b67e472b50b87551ef9 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler Date: Tue, 13 Nov 2018 11:56:31 +0100 Subject: [PATCH 10/14] Remove old store --- src/store/actions/ActionTypes.js | 18 --- src/store/actions/index.js | 86 ------------ src/store/actions/test.js | 52 -------- src/store/gutenbergBridgeMiddleware.js | 27 ---- src/store/index.js | 34 ----- src/store/reducers/index.js | 136 ------------------- src/store/reducers/test.js | 176 ------------------------- src/store/utils.js | 35 ----- 8 files changed, 564 deletions(-) delete mode 100644 src/store/actions/ActionTypes.js delete mode 100644 src/store/actions/index.js delete mode 100644 src/store/actions/test.js delete mode 100644 src/store/gutenbergBridgeMiddleware.js delete mode 100644 src/store/index.js delete mode 100644 src/store/reducers/index.js delete mode 100644 src/store/reducers/test.js delete mode 100644 src/store/utils.js diff --git a/src/store/actions/ActionTypes.js b/src/store/actions/ActionTypes.js deleted file mode 100644 index 478fb303b4..0000000000 --- a/src/store/actions/ActionTypes.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @format - * @flow - */ - -export default { - BLOCK: { - UPDATE_ATTRIBUTES: 'UPDATE_BLOCK_ATTRIBUTES', - FOCUS: 'BLOCK_FOCUS_ACTION', - MOVE_UP: 'BLOCK_MOVE_UP_ACTION', - MOVE_DOWN: 'BLOCK_MOVE_DOWN_ACTION', - DELETE: 'BLOCK_DELETE_ACTION', - CREATE: 'BLOCK_CREATE_ACTION', - PARSE: 'BLOCK_PARSE_ACTION', - SERIALIZE_ALL: 'SERIALIZE_ALL_BLOCKS_ACTION', - MERGE: 'BLOCKS_MERGE_ACTION', - }, -}; diff --git a/src/store/actions/index.js b/src/store/actions/index.js deleted file mode 100644 index 586c4abe61..0000000000 --- a/src/store/actions/index.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * @format - * @flow - */ - -import ActionTypes from './ActionTypes'; -import type { BlockType } from '../'; - -export type BlockActionType = string => { - type: $Values, - clientId: string, -}; - -export type CreateActionType = ( string, BlockType, string ) => { - type: $Values, - clientId: string, - block: BlockType, - clientIdAbove: string, -}; - -export type ParseActionType = string => { - type: $Values, - html: string, -}; - -export type serializeToNativeActionType = void => { - type: $Values, -}; - -export type BlocksActionType = ( string, string, BlockType ) => { - type: $Values, - blockOneClientId: string, - blockTwoClientId: string, - block: BlockType, -}; - -export function updateBlockAttributes( clientId: string, attributes: mixed ) { - return { - type: ActionTypes.BLOCK.UPDATE_ATTRIBUTES, - clientId, - attributes, - }; -} - -export const focusBlockAction: BlockActionType = ( clientId ) => ( { - type: ActionTypes.BLOCK.FOCUS, - clientId, -} ); - -export const moveBlockUpAction: BlockActionType = ( clientId ) => ( { - type: ActionTypes.BLOCK.MOVE_UP, - clientId, -} ); - -export const moveBlockDownAction: BlockActionType = ( clientId ) => ( { - type: ActionTypes.BLOCK.MOVE_DOWN, - clientId, -} ); - -export const deleteBlockAction: BlockActionType = ( clientId ) => ( { - type: ActionTypes.BLOCK.DELETE, - clientId, -} ); - -export const createBlockAction: CreateActionType = ( clientId, block, clientIdAbove ) => ( { - type: ActionTypes.BLOCK.CREATE, - clientId, - block: block, - clientIdAbove, -} ); - -export const parseBlocksAction: ParseActionType = ( html ) => ( { - type: ActionTypes.BLOCK.PARSE, - html, -} ); - -export const serializeToNativeAction: serializeToNativeActionType = () => ( { - type: ActionTypes.BLOCK.SERIALIZE_ALL, -} ); - -export const mergeBlocksAction: BlocksActionType = ( blockOneClientId, blockTwoClientId, block ) => ( { - type: ActionTypes.BLOCK.MERGE, - blockOneClientId, - blockTwoClientId, - block, -} ); diff --git a/src/store/actions/test.js b/src/store/actions/test.js deleted file mode 100644 index f5f788542d..0000000000 --- a/src/store/actions/test.js +++ /dev/null @@ -1,52 +0,0 @@ -/** @format */ -import '../../globals'; -import * as actions from './'; -import ActionTypes from './ActionTypes'; -// Gutenberg imports -import { createBlock } from '@wordpress/blocks'; -import { registerCoreBlocks } from '@wordpress/block-library'; - -describe( 'Store', () => { - describe( 'actions', () => { - beforeAll( () => { - registerCoreBlocks(); - } ); - - it( 'should create an action to focus a block', () => { - const action = actions.focusBlockAction( '1' ); - expect( action.type ).toBeDefined(); - expect( action.type ).toEqual( ActionTypes.BLOCK.FOCUS ); - expect( action.clientId ).toEqual( '1' ); - } ); - - it( 'should create an action to move block up', () => { - const action = actions.moveBlockUpAction( '1' ); - expect( action.type ).toBeDefined(); - expect( action.type ).toEqual( ActionTypes.BLOCK.MOVE_UP ); - expect( action.clientId ).toEqual( '1' ); - } ); - - it( 'should create an action to move block down', () => { - const action = actions.moveBlockDownAction( '1' ); - expect( action.type ).toBeDefined(); - expect( action.type ).toEqual( ActionTypes.BLOCK.MOVE_DOWN ); - expect( action.clientId ).toEqual( '1' ); - } ); - - it( 'should create an action to delete a block', () => { - const action = actions.deleteBlockAction( '1' ); - expect( action.type ).toBeDefined(); - expect( action.type ).toEqual( ActionTypes.BLOCK.DELETE ); - expect( action.clientId ).toEqual( '1' ); - } ); - - it( 'should create an action to create a block', () => { - const newBlock = createBlock( 'core/code', { content: 'new test text for a core/code block' } ); - const action = actions.createBlockAction( '1', newBlock, '0' ); - expect( action.type ).toEqual( ActionTypes.BLOCK.CREATE ); - expect( action.clientId ).toEqual( '1' ); - expect( action.block ).toEqual( newBlock ); - expect( action.clientIdAbove ).toEqual( '0' ); - } ); - } ); -} ); diff --git a/src/store/gutenbergBridgeMiddleware.js b/src/store/gutenbergBridgeMiddleware.js deleted file mode 100644 index 8119b5e507..0000000000 --- a/src/store/gutenbergBridgeMiddleware.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @format - * @flow - */ - -import md5 from 'md5'; - -import { store2html } from './utils'; - -import type { Store, Dispatch } from 'redux'; - -import ActionTypes from './actions/ActionTypes'; -import type { BlockActionType } from './actions'; - -import RNReactNativeGutenbergBridge from 'react-native-gutenberg-bridge'; -export default ( store: Store ) => ( next: Dispatch ) => ( action: BlockActionType ) => { - switch ( action.type ) { - case ActionTypes.BLOCK.SERIALIZE_ALL: { - const html = store2html( store ); - const hash = md5( html ); - const oldHash = store.getState().initialHtmlHash; - RNReactNativeGutenbergBridge.provideToNative_Html( html, oldHash !== hash ); - } - } - - return next( action ); -}; diff --git a/src/store/index.js b/src/store/index.js deleted file mode 100644 index cf71c7720f..0000000000 --- a/src/store/index.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @format - * @flow - */ - -// Gutenberg imports -import { registerCoreBlocks } from '@wordpress/block-library'; -import { registerBlockType, setUnregisteredTypeHandlerName } from '@wordpress/blocks'; - -import { createStore, applyMiddleware } from 'redux'; -import { reducer } from './reducers'; -import { html2State } from './utils'; - -import * as UnsupportedBlock from '../block-types/unsupported-block/'; - -import gutenbergBridgeMiddleware from './gutenbergBridgeMiddleware'; - -import type { StateType } from './types'; - -export * from './utils'; -export * from './types'; - -registerCoreBlocks(); -registerBlockType( UnsupportedBlock.name, UnsupportedBlock.settings ); -setUnregisteredTypeHandlerName( UnsupportedBlock.name ); - -// const devToolsEnhancer = -// // ( 'development' === process.env.NODE_ENV && require( 'remote-redux-devtools' ).default ) || -// () => {}; - -export function setupStore( state: StateType = html2State( '' ) ) { - const store = createStore( reducer, state, applyMiddleware( gutenbergBridgeMiddleware ) ); - return store; -} diff --git a/src/store/reducers/index.js b/src/store/reducers/index.js deleted file mode 100644 index ae094d8d16..0000000000 --- a/src/store/reducers/index.js +++ /dev/null @@ -1,136 +0,0 @@ -/** - * @format - * @flow - */ - -import { find, findIndex, reduce } from 'lodash'; - -import { html2State } from '../utils'; - -import ActionTypes from '../actions/ActionTypes'; -import type { StateType } from '../'; -import type { BlockActionType } from '../actions'; - -function findBlock( blocks, clientId: string ) { - return find( blocks, ( obj ) => { - return obj.clientId === clientId; - } ); -} - -function findBlockIndex( blocks, clientId: string ) { - return findIndex( blocks, ( obj ) => { - return obj.clientId === clientId; - } ); -} - -/* - * insert block into blocks[], below / after block having clientIdAbove -*/ -function insertBlock( blocks, block, clientIdAbove ) { - // TODO we need to set focused: true and search for the currently focused block and - // set that one to `focused: false`. - blocks.splice( findBlockIndex( blocks, clientIdAbove ) + 1, 0, block ); -} - -export const reducer = ( - state: StateType = { blocks: [], initialHtmlHash: '', refresh: false, fullparse: false }, - action: BlockActionType -) => { - const blocks = [ ...state.blocks ]; - switch ( action.type ) { - case ActionTypes.BLOCK.UPDATE_ATTRIBUTES: { - const block = findBlock( blocks, action.clientId ); - - // Ignore updates if block isn't known - if ( ! block ) { - return state; - } - - // Consider as updates only changed values - const nextAttributes = reduce( - action.attributes, - ( result, value, key ) => { - if ( value !== result[ key ] ) { - // Avoid mutating original block by creating shallow clone - if ( result === findBlock( blocks, action.clientId ).attributes ) { - result = { ...result }; - } - - result[ key ] = value; - } - - return result; - }, - findBlock( blocks, action.clientId ).attributes - ); - - // Skip update if nothing has been changed. The reference will - // match the original block if `reduce` had no changed values. - if ( nextAttributes === block.attributes ) { - return state; - } - - // Otherwise merge attributes into state - block.attributes = nextAttributes; - - return { ...state, blocks: blocks, refresh: ! state.refresh }; - } - case ActionTypes.BLOCK.FOCUS: { - const destBlock = findBlock( blocks, action.clientId ); - - // Deselect all blocks - for ( const block of blocks ) { - block.focused = false; - } - - destBlock.focused = true; - return { ...state, blocks: blocks, refresh: ! state.refresh }; - } - case ActionTypes.BLOCK.MOVE_UP: { - if ( blocks[ 0 ].clientId === action.clientId ) { - return state; - } - - const index = findBlockIndex( blocks, action.clientId ); - const tmp = blocks[ index ]; - blocks[ index ] = blocks[ index - 1 ]; - blocks[ index - 1 ] = tmp; - return { ...state, blocks: blocks, refresh: ! state.refresh }; - } - case ActionTypes.BLOCK.MOVE_DOWN: { - if ( blocks[ blocks.length - 1 ].clientId === action.clientId ) { - return state; - } - - const index = findBlockIndex( blocks, action.clientId ); - const tmp = blocks[ index ]; - blocks[ index ] = blocks[ index + 1 ]; - blocks[ index + 1 ] = tmp; - return { ...state, blocks: blocks, refresh: ! state.refresh }; - } - case ActionTypes.BLOCK.DELETE: { - const index = findBlockIndex( blocks, action.clientId ); - blocks.splice( index, 1 ); - return { ...state, blocks: blocks, refresh: ! state.refresh }; - } - case ActionTypes.BLOCK.CREATE: { - // TODO we need to set focused: true and search for the currently focused block and - // set that one to `focused: false`. - insertBlock( blocks, action.block, action.clientIdAbove ); - return { ...state, blocks: blocks, refresh: ! state.refresh }; - } - case ActionTypes.BLOCK.PARSE: { - const newState = html2State( action.html ); - newState.refresh = ! state.refresh; - newState.fullparse = true; - return newState; - } - case ActionTypes.BLOCK.MERGE: { - const index = findBlockIndex( blocks, action.blockOneClientId ); - blocks.splice( index, 2, action.block ); - return { ...state, blocks: blocks, refresh: ! state.refresh }; - } - default: - return state; - } -}; diff --git a/src/store/reducers/test.js b/src/store/reducers/test.js deleted file mode 100644 index d9e87bf0be..0000000000 --- a/src/store/reducers/test.js +++ /dev/null @@ -1,176 +0,0 @@ -/** - * @format - */ - -import '../../globals'; - -import { reducer } from './'; -import * as actions from '../actions/'; -import { registerCoreBlocks } from '@wordpress/block-library'; - -describe( 'Store', () => { - describe( 'reducer', () => { - // use scoped variables. See https://github.com/facebook/jest/issues/3553#issuecomment-300851842 - let __iniState; - let initialState; - - beforeAll( () => { - registerCoreBlocks(); - - __iniState = { - blocks: [ - { - clientId: '0', - blockType: 'title', - attributes: { - content: 'Hello World', - }, - focused: false, - }, - { - clientId: '1', - blockType: 'paragraph', - attributes: { - content: 'paragraph content', - }, - focused: false, - }, - ], - refresh: false, - }; - } ); - - beforeEach( () => { - initialState = { ...__iniState }; - } ); - - afterEach( () => { - expect( initialState ).toEqual( __iniState ); - } ); - - // eslint-disable-next-line quotes - it( "should mutate block's content", () => { - const newState = reducer( - initialState, - actions.updateBlockAttributes( '1', { content: 'new content' } ) - ); - - // the title block should still be there at the top - expect( newState.blocks[ 1 ].attributes.content ).toEqual( 'new content' ); - } ); - - it( 'should focus a block', () => { - let newState = reducer( initialState, actions.focusBlockAction( '0' ) ); - - // the focused block should have its variable set to true - expect( newState.blocks[ 0 ].focused ).toEqual( true ); - - // the other block should have its variable set to false - expect( newState.blocks[ 1 ].focused ).toEqual( false ); - - // let's focus on the other block - newState = reducer( initialState, actions.focusBlockAction( '1' ) ); - - // the focused block should have its variable set to true - expect( newState.blocks[ 1 ].focused ).toEqual( true ); - - // the other block should have its variable set to false - expect( newState.blocks[ 0 ].focused ).toEqual( false ); - } ); - - it( 'should not be able to move top block up', () => { - const newState = reducer( initialState, actions.moveBlockUpAction( '0' ) ); - - // blocks should still be in the same places - expect( newState.blocks[ 0 ].blockType ).toEqual( 'title' ); - expect( newState.blocks[ 1 ].blockType ).toEqual( 'paragraph' ); - } ); - - it( 'should move a block up', () => { - const newState = reducer( initialState, actions.moveBlockUpAction( '1' ) ); - - // the paragraph block should have moved up - expect( newState.blocks[ 0 ].blockType ).toEqual( 'paragraph' ); - - // the block below it should be the title now - expect( newState.blocks[ 1 ].blockType ).toEqual( 'title' ); - } ); - - it( 'should not be able to move bottom block down', () => { - const newState = reducer( initialState, actions.moveBlockDownAction( '1' ) ); - - // blocks should still be in the same places - expect( newState.blocks[ 0 ].blockType ).toEqual( 'title' ); - expect( newState.blocks[ 1 ].blockType ).toEqual( 'paragraph' ); - } ); - - it( 'should move a block down', () => { - const newState = reducer( initialState, actions.moveBlockDownAction( '0' ) ); - - // the paragraph block should be at the top now - expect( newState.blocks[ 0 ].blockType ).toEqual( 'paragraph' ); - - // the title block should have moved down - expect( newState.blocks[ 1 ].blockType ).toEqual( 'title' ); - } ); - - it( 'should delete top block', () => { - const newState = reducer( initialState, actions.deleteBlockAction( '0' ) ); - - // only one block should be left - expect( newState.blocks ).toHaveLength( 1 ); - - // the paragraph block should be at the top now - expect( newState.blocks[ 0 ].blockType ).toEqual( 'paragraph' ); - } ); - - it( 'should delete bottom block', () => { - const newState = reducer( initialState, actions.deleteBlockAction( '1' ) ); - - // only one block should be left - expect( newState.blocks ).toHaveLength( 1 ); - - // the title block should still be there at the top - expect( newState.blocks[ 0 ].blockType ).toEqual( 'title' ); - } ); - - it( 'should delete middle block', () => { - // add a third block so there's a middle one to remove - const extraState = { - ...initialState, - blocks: [ - ...initialState.blocks, - { - key: '2', - blockType: 'core/code', - attributes: { - content: 'Hello code', - }, - focused: false, - }, - ], - }; - const newState = reducer( extraState, actions.deleteBlockAction( '1' ) ); - - // only two blocks should be left - expect( newState.blocks ).toHaveLength( 2 ); - - // the title block should still be there at the top - expect( newState.blocks[ 0 ].blockType ).toEqual( 'title' ); - - // the code block should be at the bottom - expect( newState.blocks[ 1 ].blockType ).toEqual( 'core/code' ); - } ); - - it( 'parses the html string into a new array of blocks', () => { - const htmlContent = ''; - const html = '' + htmlContent + ''; - - const newState = reducer( initialState, actions.parseBlocksAction( html ) ); - - expect( newState.blocks ).toHaveLength( 1 ); - expect( newState.blocks[ 0 ].originalContent ).toEqual( htmlContent ); - expect( newState.blocks[ 0 ].name ).toEqual( 'core/more' ); - } ); - } ); -} ); diff --git a/src/store/utils.js b/src/store/utils.js deleted file mode 100644 index 37b8aeda66..0000000000 --- a/src/store/utils.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @format - * @flow - */ - -// Gutenberg imports -import { parse, serialize } from '@wordpress/blocks'; - -import md5 from 'md5'; -import { Store } from 'redux'; - -import type { BlockType, StateType } from './'; - -export function html2State( html: string ) { - const blocksFromHtml = parse( html ); - const reserialized = blocks2html( blocksFromHtml ); - const hash = md5( reserialized ); - const state: StateType = { - // TODO: get blocks list block state should be externalized (shared with Gutenberg at some point?). - // If not it should be created from a string parsing (commented HTML to json). - blocks: blocksFromHtml.map( ( block ) => ( { ...block, focused: false } ) ), - initialHtmlHash: hash, - refresh: false, - fullparse: false, - }; - return state; -} - -export function blocks2html( blocks: Array ) { - return blocks.map( serialize ).join( '\n\n' ); -} - -export function store2html( store: Store ) { - return blocks2html( store.getState().blocks ); -} From ec35d6a6d341df4fe964cf74a52796283854bd2a Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler Date: Tue, 13 Nov 2018 12:38:06 +0100 Subject: [PATCH 11/14] Fix merging blocks using the new store --- src/block-management/block-manager.js | 59 +++++++-------------------- 1 file changed, 14 insertions(+), 45 deletions(-) diff --git a/src/block-management/block-manager.js b/src/block-management/block-manager.js index 1b38dde77f..ec7d66920d 100644 --- a/src/block-management/block-manager.js +++ b/src/block-management/block-manager.js @@ -17,7 +17,7 @@ import HTMLTextInput from '../components/html-text-input'; import BlockToolbar from './block-toolbar'; // Gutenberg imports -import { createBlock, getBlockType, switchToBlockType } from '@wordpress/blocks'; +import { createBlock } from '@wordpress/blocks'; export type BlockListType = { onChange: ( clientId: string, attributes: mixed ) => void, @@ -189,62 +189,33 @@ export default class BlockManager extends React.Component this.props.focusBlockAction( newBlock.clientId ); } - mergeBlocks( clientId: string, forward: boolean ) { + mergeBlocks = ( forward: boolean = false ) => { // find currently focused block - const focusedItemIndex = this.getDataSourceIndexFromClientId( clientId ); + const focusedItemIndex = this.state.blocks.findIndex( ( block ) => block.focused ); if ( focusedItemIndex === -1 ) { // do nothing if it's not found. // Updates calls from the native side may arrive late, and the block already been deleted return; } - // Do nothing when it's the first block and backspace is pressed - // Do nothing when it's the last block and delete is pressed + const block = this.state.blocks[ focusedItemIndex ]; + const previousBlock = this.state.blocks[ focusedItemIndex - 1 ]; + const nextBlock = this.state.blocks[ focusedItemIndex + 1 ]; + + // Do nothing when it's the first block. if ( - ( ! forward && focusedItemIndex === 0 ) || - ( forward && ! focusedItemIndex === this.state.dataSource.size() - 1 ) + ( ! forward && ! previousBlock ) || + ( forward && ! nextBlock ) ) { return; } - let blockA = null; - let blockB = null; if ( forward ) { - blockA = this.state.dataSource.get( focusedItemIndex ); - blockB = this.state.dataSource.get( focusedItemIndex + 1 ); + this.props.mergeBlocksAction( block.clientId, nextBlock.clientId ); } else { - blockA = this.state.dataSource.get( focusedItemIndex - 1 ); - blockB = this.state.dataSource.get( focusedItemIndex ); - } - - // Ignore merge if blocks aren't known - if ( ! blockA || ! blockB ) { - // Updates calls from the native side may arrive late, and one of the block - // may not be available - return; - } - - const blockType = getBlockType( blockA.name ); - - // Only focus the previous block if it's not mergeable - if ( ! blockType.merge ) { - // TO DO: move the focus to the prev block - return; - } - - // We can only merge blocks with similar types - // thus, we transform the block to merge first - const blocksWithTheSameType = blockA.name === blockB.name ? - [ blockB ] : - switchToBlockType( blockB, blockA.name ); - - // If the block types can not match, do nothing - if ( ! blocksWithTheSameType || ! blocksWithTheSameType.length ) { - return; + this.props.mergeBlocksAction( previousBlock.clientId, block.clientId ); } - - this.props.mergeBlocksAction( blockA.clientId, blockB.clientId ); - } + }; onChange( clientId: string, attributes: mixed ) { // Update Redux store @@ -375,9 +346,7 @@ export default class BlockManager extends React.Component insertBlocksAfter={ ( blocks ) => this.insertBlocksAfter.bind( this )( value.item.clientId, blocks ) } - mergeBlocks={ ( forward = false ) => - this.mergeBlocks.bind( this )( value.item.clientId, forward ) - } + mergeBlocks={ this.mergeBlocks } { ...value.item } /> { this.state.blockTypePickerVisible && value.item.focused && insertHere } From 34bc0b97c98ba5c2bc8f8836c9a7ef883f5360ba Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler Date: Tue, 13 Nov 2018 13:45:18 +0100 Subject: [PATCH 12/14] Add back the test that modifies the store --- src/app/App.test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/app/App.test.js b/src/app/App.test.js index ab0e0b097e..924526fd3e 100644 --- a/src/app/App.test.js +++ b/src/app/App.test.js @@ -4,6 +4,7 @@ import renderer from 'react-test-renderer'; import App from './App'; import BlockHolder from '../block-management/block-holder'; +import { dispatch, select } from '@wordpress/data'; describe( 'App', () => { it( 'renders without crashing', () => { @@ -12,6 +13,14 @@ describe( 'App', () => { expect( rendered ).toBeTruthy(); } ); + it( 'renders without crashing with a block focused', () => { + const app = renderer.create( ); + const blocks = select( 'core/editor' ).getBlocks(); + dispatch( 'core/editor' ).selectBlock( blocks[ 0 ].clientId ); + const rendered = app.toJSON(); + expect( rendered ).toBeTruthy(); + } ); + it( 'Code block is a TextInput', () => { renderer .create( ) From 0c614ea63172cf039a979f9de08e71cf14279af3 Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler Date: Tue, 13 Nov 2018 15:03:38 +0100 Subject: [PATCH 13/14] Make a shallow copy of blocks instead of deep copy --- src/block-management/block-manager.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/block-management/block-manager.js b/src/block-management/block-manager.js index ec7d66920d..2fa8cec596 100644 --- a/src/block-management/block-manager.js +++ b/src/block-management/block-manager.js @@ -4,7 +4,7 @@ */ import React from 'react'; -import { cloneDeep, isEqual } from 'lodash'; +import { isEqual } from 'lodash'; import { Platform, Switch, Text, View, FlatList, KeyboardAvoidingView } from 'react-native'; import RecyclerViewList, { DataSource } from 'react-native-recyclerview-list'; @@ -52,7 +52,7 @@ export default class BlockManager extends React.Component super( props ); const blocks = props.blocks.map( ( block ) => { - const newBlock = cloneDeep( block ); + const newBlock = { ...block }; newBlock.focused = props.isBlockSelected( block.clientId ); return newBlock; } ); @@ -132,7 +132,7 @@ export default class BlockManager extends React.Component } const blocks = props.blocks.map( ( block ) => { - const newBlock = cloneDeep( block ); + const newBlock = { ...block }; newBlock.focused = props.isBlockSelected( block.clientId ); return newBlock; } ); From d7ea97323f72623ffbb2f2ccc3f351cfc32b29ad Mon Sep 17 00:00:00 2001 From: Tugdual de Kerviler Date: Wed, 14 Nov 2018 19:18:42 +0100 Subject: [PATCH 14/14] Fix updating dataSource from blocks props --- src/block-management/block-manager.js | 35 +++++++++++++++++++++------ 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/block-management/block-manager.js b/src/block-management/block-manager.js index 2fa8cec596..4c62abaa2a 100644 --- a/src/block-management/block-manager.js +++ b/src/block-management/block-manager.js @@ -4,7 +4,7 @@ */ import React from 'react'; -import { isEqual } from 'lodash'; +import { xorBy, isEqual } from 'lodash'; import { Platform, Switch, Text, View, FlatList, KeyboardAvoidingView } from 'react-native'; import RecyclerViewList, { DataSource } from 'react-native-recyclerview-list'; @@ -72,6 +72,17 @@ export default class BlockManager extends React.Component this.props.focusBlockAction( clientId ); } + static focusDataSourceItem( dataSource: DataSource, clientId: string ) { + for ( let i = 0; i < dataSource.size(); ++i ) { + const block = dataSource.get( i ); + if ( block.clientId === clientId ) { + block.focused = true; + } else { + block.focused = false; + } + } + } + getDataSourceIndexFromClientId( clientId: string ) { for ( let i = 0; i < this.state.dataSource.size(); ++i ) { const block = this.state.dataSource.get( i ); @@ -124,20 +135,28 @@ export default class BlockManager extends React.Component } static getDerivedStateFromProps( props: PropsType, state: StateType ) { - if ( props.fullparse === true ) { - return { - ...state, - dataSource: new DataSource( props.blocks, ( item: BlockType ) => item.clientId ), - }; - } - const blocks = props.blocks.map( ( block ) => { const newBlock = { ...block }; newBlock.focused = props.isBlockSelected( block.clientId ); return newBlock; } ); + // if the blocks in the list (not the order or the block attribute) + // have changed without our knowledge, recreate the dataSource + if ( xorBy( state.blocks, blocks, 'clientId' ).length !== 0 ) { + return { + dataSource: new DataSource( blocks, ( item: BlockType ) => item.clientId ), + blocks, + refresh: ! state.refresh, + }; + } + + // if some properties of the blocks have changed, assume it's `focused` and manually update in dataSource if ( ! isEqual( state.blocks, blocks ) ) { + const blockFocused = blocks.find( ( block ) => block.focused ); + if ( blockFocused ) { + BlockManager.focusDataSourceItem( state.dataSource, blockFocused.clientId ); + } return { ...state, blocks,