Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Editor: Sync blocks state to edited post content #9403

Closed
wants to merge 8 commits into from
99 changes: 62 additions & 37 deletions edit-post/editor.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,76 @@
/**
* WordPress dependencies
*/
import { withSelect } from '@wordpress/data';
import { compose } from '@wordpress/compose';
import { withSelect, withDispatch } from '@wordpress/data';
import { EditorProvider, ErrorBoundary } from '@wordpress/editor';
import { StrictMode } from '@wordpress/element';
import { StrictMode, Component } from '@wordpress/element';

/**
* Internal dependencies
*/
import Layout from './components/layout';

function Editor( {
settings,
hasFixedToolbar,
focusMode,
post,
overridePost,
onError,
...props
} ) {
if ( ! post ) {
return null;
class Editor extends Component {
constructor( props ) {
super( ...arguments );

const { initialEdits, editPost } = props;
if ( initialEdits ) {
editPost( initialEdits, { quiet: true } );
}
}

const editorSettings = {
...settings,
hasFixedToolbar,
focusMode,
};

return (
<StrictMode>
<EditorProvider
settings={ editorSettings }
post={ { ...post, ...overridePost } }
{ ...props }
>
<ErrorBoundary onError={ onError }>
<Layout />
</ErrorBoundary>
</EditorProvider>
</StrictMode>
);
render() {
const {
settings,
hasFixedToolbar,
focusMode,
post,
onError,
...props
} = this.props;

if ( ! post ) {
return null;
}

const editorSettings = {
...settings,
hasFixedToolbar,
focusMode,
};

return (
<StrictMode>
<EditorProvider
settings={ editorSettings }
post={ post }
{ ...props }
>
<ErrorBoundary onError={ onError }>
<Layout />
</ErrorBoundary>
</EditorProvider>
</StrictMode>
);
}
}

export default withSelect( ( select, { postId, postType } ) => ( {
hasFixedToolbar: select( 'core/edit-post' ).isFeatureActive( 'fixedToolbar' ),
focusMode: select( 'core/edit-post' ).isFeatureActive( 'focusMode' ),
post: select( 'core' ).getEntityRecord( 'postType', postType, postId ),
} ) )( Editor );
export default compose( [
withSelect( ( select, { postId, postType } ) => {
const { isFeatureActive } = select( 'core/edit-post' );
const { getEntityRecord } = select( 'core' );

return {
hasFixedToolbar: isFeatureActive( 'fixedToolbar' ),
focusMode: isFeatureActive( 'focusMode' ),
post: getEntityRecord( 'postType', postType, postId ),
};
} ),
withDispatch( ( dispatch ) => {
const { editPost } = dispatch( 'core/editor' );

return { editPost };
} ),
] )( Editor );
37 changes: 21 additions & 16 deletions edit-post/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,17 @@ import Editor from './editor';
* an unhandled error occurs, replacing previously mounted editor element using
* an initial state from prior to the crash.
*
* @param {Object} postType Post type of the post to edit.
* @param {Object} postId ID of the post to edit.
* @param {Element} target DOM node in which editor is rendered.
* @param {?Object} settings Editor settings object.
* @param {Object} overridePost Post properties to override.
* @param {Object} postType Post type of the post to edit.
* @param {Object} postId ID of the post to edit.
* @param {Element} target DOM node in which editor is rendered.
* @param {?Object} settings Editor settings object.
*/
export function reinitializeEditor( postType, postId, target, settings, overridePost ) {
export function reinitializeEditor( postType, postId, target, settings ) {
unmountComponentAtNode( target );
const reboot = reinitializeEditor.bind( null, postType, postId, target, settings, overridePost );
const reboot = reinitializeEditor.bind( null, postType, postId, target, settings );

render(
<Editor settings={ settings } onError={ reboot } postId={ postId } postType={ postType } overridePost={ overridePost } recovery />,
<Editor settings={ settings } onError={ reboot } postId={ postId } postType={ postType } recovery />,
target
);
}
Expand All @@ -45,17 +44,17 @@ export function reinitializeEditor( postType, postId, target, settings, override
* The return value of this function is not necessary if we change where we
* call initializeEditor(). This is due to metaBox timing.
*
* @param {string} id Unique identifier for editor instance.
* @param {Object} postType Post type of the post to edit.
* @param {Object} postId ID of the post to edit.
* @param {?Object} settings Editor settings object.
* @param {Object} overridePost Post properties to override.
* @param {string} id Unique identifier for editor instance.
* @param {Object} postType Post type of the post to edit.
* @param {Object} postId ID of the post to edit.
* @param {?Object} settings Editor settings object.
* @param {Object} initialEdits Post properties to override.
*
* @return {Object} Editor interface.
*/
export function initializeEditor( id, postType, postId, settings, overridePost ) {
export function initializeEditor( id, postType, postId, settings, initialEdits ) {
const target = document.getElementById( id );
const reboot = reinitializeEditor.bind( null, postType, postId, target, settings, overridePost );
const reboot = reinitializeEditor.bind( null, postType, postId, target, settings );

registerCoreBlocks();

Expand All @@ -67,7 +66,13 @@ export function initializeEditor( id, postType, postId, settings, overridePost )
] );

render(
<Editor settings={ settings } onError={ reboot } postId={ postId } postType={ postType } overridePost={ overridePost } />,
<Editor
settings={ settings }
onError={ reboot }
postId={ postId }
postType={ postType }
initialEdits={ initialEdits }
/>,
target
);

Expand Down
12 changes: 3 additions & 9 deletions lib/client-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -1331,20 +1331,14 @@ function gutenberg_editor_scripts_and_styles( $hook ) {
$demo_content = ob_get_clean();

$initial_edits = array(
'title' => array(
'raw' => __( 'Welcome to the Gutenberg Editor', 'gutenberg' ),
),
'content' => array(
'raw' => $demo_content,
),
'title' => __( 'Welcome to the Gutenberg Editor', 'gutenberg' ),
'content' => $demo_content,
);
} elseif ( $is_new_post ) {
// Override "(Auto Draft)" new post default title with empty string,
// or filtered value.
$initial_edits = array(
'title' => array(
'raw' => apply_filters( 'the_title', '', $post->ID ),
),
'title' => apply_filters( 'the_title', '', $post->ID ),
);
} else {
$initial_edits = null;
Expand Down
6 changes: 6 additions & 0 deletions packages/editor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@

- `wp.editor.RichTextProvider` flagged for deprecation. Please use `wp.data.select( 'core/editor' )` methods instead.

### Deprecations

- The `setupEditor` action has been deprecated. Note: An editor is kept in sync automatically by its post state, without a predefined start point.
- The `setupEditorState` action has been deprecated. Note: An editor is kept in sync automatically by its post state, without a predefined start point.
- The `checkTemplateValidity` action has been deprecated. Note: Validity is verified automatically upon block reset.

### Bug Fixes

- The `PostTextEditor` component will respect its in-progress state edited value, even if the assigned prop value changes.
7 changes: 3 additions & 4 deletions packages/editor/src/components/post-text-editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import Textarea from 'react-autosize-textarea';
import { __ } from '@wordpress/i18n';
import { decodeEntities } from '@wordpress/html-entities';
import { Component, Fragment } from '@wordpress/element';
import { parse } from '@wordpress/blocks';
import { withSelect, withDispatch } from '@wordpress/data';
import { withInstanceId, compose } from '@wordpress/compose';

Expand Down Expand Up @@ -97,13 +96,13 @@ export default compose( [
};
} ),
withDispatch( ( dispatch ) => {
const { editPost, resetBlocks } = dispatch( 'core/editor' );
const { editPost } = dispatch( 'core/editor' );
return {
onChange( content ) {
editPost( { content } );
editPost( { content }, { skipContentParse: true } );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes me think, this should be a local state and only called when onPersist is called.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps.

Good: It might help us avoid the need for this skipContentParse flag altogether.
Bad: We rely on editPost to trigger the change detection which would alert the user to their changes being unsaved. As noted in the original comment, this isn't entirely perfect as implemented anyways.

},
onPersist( content ) {
resetBlocks( parse( content ) );
editPost( { content }, { skipContentParse: false } );
},
};
} ),
Expand Down
45 changes: 39 additions & 6 deletions packages/editor/src/components/provider/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { compose } from '@wordpress/compose';
import { createElement, Component } from '@wordpress/element';
import { DropZoneProvider, SlotFillProvider } from '@wordpress/components';
import { withDispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
Expand All @@ -21,10 +22,38 @@ class EditorProvider extends Component {
constructor( props ) {
super( ...arguments );

// Assume that we don't need to initialize in the case of an error recovery.
if ( ! props.recovery ) {
this.props.updateEditorSettings( props.settings );
this.props.setupEditor( props.post, props.settings.autosave );
// Assume that we don't need to initialize in the case of an error
// recovery.
//
// TODO: Check to see whether we would ever expect constructor to be
// called even in case of recovery. In recovery, wouldn't the same
// Provider be reused? Suspected dead code.
if ( props.recovery ) {
return;
}

props.updateEditorSettings( props.settings );
props.resetPost( props.post );

const isNewPost = props.post.status === 'auto-draft';
if ( isNewPost ) {
props.synchronizeTemplate();
}

const { autosave } = props.settings;
if ( autosave ) {
const noticeMessage = __( 'There is an autosave of this post that is more recent than the version below.' );
props.createWarningNotice(
<p>
{ noticeMessage }
{ ' ' }
<a href={ autosave.editLink }>{ __( 'View the autosave' ) }</a>
</p>,
{
id: 'autosave-exists',
spokenMessage: noticeMessage,
}
);
}
}

Expand Down Expand Up @@ -106,17 +135,21 @@ class EditorProvider extends Component {

export default withDispatch( ( dispatch ) => {
const {
setupEditor,
resetPost,
createWarningNotice,
updateEditorSettings,
undo,
redo,
createUndoLevel,
synchronizeTemplate,
} = dispatch( 'core/editor' );
return {
setupEditor,
resetPost,
createWarningNotice,
undo,
redo,
createUndoLevel,
updateEditorSettings,
synchronizeTemplate,
};
} )( EditorProvider );
27 changes: 26 additions & 1 deletion packages/editor/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ import deprecated from '@wordpress/deprecated';
* @return {Object} Action object.
*/
export function setupEditor( post, autosaveStatus ) {
deprecated( 'setupEditor', {
version: '4.0',
plugin: 'Gutenberg',
hint: 'An editor is kept in sync automatically by its post state, without a predefined start point.',
} );

return {
type: 'SETUP_EDITOR',
autosave: autosaveStatus,
Expand Down Expand Up @@ -85,6 +91,12 @@ export function updatePost( edits ) {
* @return {Object} Action object.
*/
export function setupEditorState( post, blocks, edits ) {
deprecated( 'setupEditorState', {
version: '4.0',
plugin: 'Gutenberg',
hint: 'An editor is kept in sync automatically by its post state, without a predefined start point.',
} );

return {
type: 'SETUP_EDITOR_STATE',
post,
Expand Down Expand Up @@ -398,10 +410,23 @@ export function synchronizeTemplate() {
};
}

export function editPost( edits ) {
/**
* Returns an action object used in signalling that attributes of the post have
* been edited.
*
* @param {Object} edits Post attributes to edit.
* @param {?Object} options Options for editing.
* @param {?boolean} options.quiet Whether edit is triggered programmatically,
* contrasted with explicit user interaction,
* to bypass change detection and undo history.
*
* @return {Object} Action object.
*/
export function editPost( edits, options = {} ) {
return {
type: 'EDIT_POST',
edits,
options,
};
}

Expand Down
Loading