From 648a1b9280a343c4b921781577a9d048db1d7027 Mon Sep 17 00:00:00 2001 From: Danilo Ercoli Date: Thu, 19 Sep 2019 09:47:44 -0400 Subject: [PATCH] [RNMobile] Add autosave to mobile apps (#17329) * [RNMobile] Fix crash when adding separator * Build: remove global install of latest npm since we want to use the paired node/npm version (#17134) * Build: remove global install of latest npm since we want to use the paired node/npm version * Also update travis to remove --latest-npm flag * [RNMobile] Try dark mode (iOS) (#17067) * Adding dark mode component implemented on list and list block * Adding DarkMode handling to RichText, ToolBar and SafeArea * Mobile: Using DarkMode as HOC * iOS DarkMode: Modified colors on block list and block container * iOS DarkMode: Improved Header Toolbar colors * iOS DarkMode: Removing background from buttons * iOS DarkMode warning and unsupported * iOS DarkMode: MediaPlaceholder * iOS DarkMode: BottomSheets * iOS DarkMode: Inserter * iOS DarkMode: DefaultBlockAppender * iOS DarkMode: PostTite * Update hardcoded colors with variables * iOS DarkMode: Fix bottom-sheet cell value color * iOS DarkMode: More - PageBreak - Add Block Here * iOS DarkMode: Better text color * iOS Darkmode: Code block * iOS DarkMode: HTML View * iOS DarkMode: Improve colors on SafeArea * Fix toolbar not avoiding keyboard regression * Fix native unit tests * Fix gutenberg-mobile unit tests * Adding RNDarkMode mocks * RNMobile: Fix crash when viewing HTML on iOS * [RNMobile] Remove toolbar from html view * [RNMobile] Fix MaxListenersExceededWarning caused by dark-mode event emitter (#17186) * Fix MaxListenersExceededWarning caused by dark-mode event emitter * Checking for setMaxListeners trying to avoid CI error * Adding remove listener to DarkMode HOC * DarkMode: Binding this.onModeChanged to `this` * DarkMode: Adding conditional needed to pass UI Tests on CI * Fix focus title on new posts regression (#17180) * BottomSheet: Setting DashIcon color directly when theme is default (light) (#17193) * Add a preliminary version of the AutosaveMonitor for mobile that calls the "bridge" and asks the native side to save the content * Add autosave mock function for tests * Fix merge conflicts * Fix lint * Re-add autosave on mobile that was removed erroneously during import-merge from rnmobile/master * Remove native variant of AutosaveMonitor and introduces changes at editor store level * Default to false for `isEditedPostAutosaveable` on mobile. There was a typo in the returing value on the previous commit. * Make sure to consider edits to the Title when checking if auto-save is needed * Fix lint --- .../src/components/layout/index.native.js | 2 + .../editor/src/components/index.native.js | 1 + packages/editor/src/store/actions.native.js | 13 ++++++ packages/editor/src/store/reducer.native.js | 4 ++ packages/editor/src/store/selectors.native.js | 42 +++++++++++++++++++ test/native/setup.js | 1 + 6 files changed, 63 insertions(+) diff --git a/packages/edit-post/src/components/layout/index.native.js b/packages/edit-post/src/components/layout/index.native.js index 36250cf27eb96..44bef0e3bec51 100644 --- a/packages/edit-post/src/components/layout/index.native.js +++ b/packages/edit-post/src/components/layout/index.native.js @@ -12,6 +12,7 @@ import { Component } from '@wordpress/element'; import { withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { HTMLTextInput, KeyboardAvoidingView, ReadableContentView, withTheme } from '@wordpress/components'; +import { AutosaveMonitor } from '@wordpress/editor'; /** * Internal dependencies @@ -116,6 +117,7 @@ class Layout extends Component { return ( + { isHtmlView ? this.renderHTML() : this.renderVisual() } diff --git a/packages/editor/src/components/index.native.js b/packages/editor/src/components/index.native.js index 69035455d49f1..45156604efe6f 100644 --- a/packages/editor/src/components/index.native.js +++ b/packages/editor/src/components/index.native.js @@ -1,5 +1,6 @@ // Post Related Components +export { default as AutosaveMonitor } from './autosave-monitor'; export { default as PostTitle } from './post-title'; export { default as EditorHistoryRedo } from './editor-history/redo'; export { default as EditorHistoryUndo } from './editor-history/undo'; diff --git a/packages/editor/src/store/actions.native.js b/packages/editor/src/store/actions.native.js index 51741ac9bc3b4..17733a0e47b40 100644 --- a/packages/editor/src/store/actions.native.js +++ b/packages/editor/src/store/actions.native.js @@ -1,3 +1,7 @@ +/** + * External dependencies + */ +import RNReactNativeGutenbergBridge from 'react-native-gutenberg-bridge'; export * from './actions.js'; @@ -14,3 +18,12 @@ export function togglePostTitleSelection( isSelected = true ) { isSelected, }; } + +/** + * Action generator used in signalling that the post should autosave. + * + * @param {Object?} options Extra flags to identify the autosave. + */ +export function* autosave( ) { + RNReactNativeGutenbergBridge.editorDidAutosave(); +} diff --git a/packages/editor/src/store/reducer.native.js b/packages/editor/src/store/reducer.native.js index 82b3689a98ead..0c2c4e7d24e70 100644 --- a/packages/editor/src/store/reducer.native.js +++ b/packages/editor/src/store/reducer.native.js @@ -26,6 +26,10 @@ import { editorSettings, } from './reducer.js'; +import { EDITOR_SETTINGS_DEFAULTS } from './defaults.js'; + +EDITOR_SETTINGS_DEFAULTS.autosaveInterval = 2; // This is a way to override default on mobile + export * from './reducer.js'; /** diff --git a/packages/editor/src/store/selectors.native.js b/packages/editor/src/store/selectors.native.js index 8c6ead8e97ba2..ae84e7b5afe5a 100644 --- a/packages/editor/src/store/selectors.native.js +++ b/packages/editor/src/store/selectors.native.js @@ -1,3 +1,16 @@ +/** + * WordPress dependencies + */ +import { createRegistrySelector } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { + isEditedPostDirty, + isEditedPostSaveable, + hasChangedContent, +} from './selectors.js'; export * from './selectors.js'; @@ -11,3 +24,32 @@ export * from './selectors.js'; export function isPostTitleSelected( state ) { return state.postTitle.isSelected; } + +/** + * Returns true if the post can be autosaved, or false otherwise. + * + * @param {Object} state Global application state. + * @param {Object} autosave A raw autosave object from the REST API. + * + * @return {boolean} Whether the post can be autosaved. + */ +export const isEditedPostAutosaveable = createRegistrySelector( ( ) => function( state ) { + // A post must contain a title, an excerpt, or non-empty content to be valid for autosaving. + if ( ! isEditedPostSaveable( state ) ) { + return false; + } + + // To avoid an expensive content serialization, use the content dirtiness + // flag in place of content field comparison against the known autosave. + // This is not strictly accurate, and relies on a tolerance toward autosave + // request failures for unnecessary saves. + if ( hasChangedContent( state ) ) { + return true; + } + + if ( isEditedPostDirty( state ) ) { + return true; + } + + return false; +} ); diff --git a/test/native/setup.js b/test/native/setup.js index c0e3e0942a918..f34947ed9958c 100644 --- a/test/native/setup.js +++ b/test/native/setup.js @@ -14,6 +14,7 @@ jest.mock( 'react-native-gutenberg-bridge', () => { subscribeUpdateHtml: jest.fn(), subscribeMediaAppend: jest.fn(), editorDidMount: jest.fn(), + editorDidAutosave: jest.fn(), subscribeMediaUpload: jest.fn(), requestMediaPickFromMediaLibrary: jest.fn(), requestMediaPickFromDeviceLibrary: jest.fn(),