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

Autosaves: Create a separate revision when autosaving a draft #26345

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions client/post-editor/post-editor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import React from 'react';
import ReactDom from 'react-dom';
import page from 'page';
import PropTypes from 'prop-types';
import { debounce, flow, get, partial, throttle } from 'lodash';
import { debounce, flow, get, isEqual, partial, pick, throttle } from 'lodash';
import { connect } from 'react-redux';
import { localize } from 'i18n-calypso';
import classNames from 'classnames';
Expand All @@ -16,7 +16,8 @@ import { v4 as uuid } from 'uuid';
/**
* Internal dependencies
*/
import { autosave, saveEdited } from 'state/posts/actions';
import { POST_REVISION_FIELDS } from 'state/posts/constants';
import { autosave, editPost, saveEdited, saveRevision } from 'state/posts/actions';
import { addSiteFragment } from 'lib/route';
import EditorActionBar from 'post-editor/editor-action-bar';
import FeaturedImage from 'post-editor/editor-featured-image';
Expand Down Expand Up @@ -50,7 +51,6 @@ import {
getEditorLoadingError,
} from 'state/ui/editor/selectors';
import { recordTracksEvent, recordGoogleEvent } from 'state/analytics/actions';
import { editPost } from 'state/posts/actions';
import {
getSitePost,
getEditedPost,
Expand Down Expand Up @@ -123,13 +123,20 @@ export class PostEditor extends React.Component {
};
}

autosaveFlushToRevision = () => {
saveRevision( this.props.siteId, this.props.post );
};

componentWillMount() {
this.debouncedSaveRawContent = debounce( this.saveRawContent, 200 );
this.throttledAutosave = throttle( this.autosave, 20000 );
this.debouncedAutosave = debounce( this.throttledAutosave, 3000 );
this.switchEditorVisualMode = this.switchEditorMode.bind( this, 'tinymce' );
this.switchEditorHtmlMode = this.switchEditorMode.bind( this, 'html' );
this.debouncedCopySelectedText = debounce( this.copySelectedText, 200 );
this.debouncedAutosaveFlushToRevision = debounce( this.autosaveFlushToRevision, 10000, {
leading: true,
} );
this.useDefaultSidebarFocus();
analytics.mc.bumpStat( 'calypso_default_sidebar_mode', this.props.editorSidebarPreference );

Expand Down Expand Up @@ -170,6 +177,8 @@ export class PostEditor extends React.Component {
this.throttledAutosave.cancel();
this.debouncedSaveRawContent.cancel();
this.debouncedCopySelectedText.cancel();
this.debouncedAutosaveFlushToRevision.cancel();
this.autosaveFlushToRevision();
this._previewWindow = null;
clearTimeout( this._switchEditorTimeout );
}
Expand Down Expand Up @@ -255,7 +264,14 @@ export class PostEditor extends React.Component {
const isInvalidURL = this.props.loadingError;

const isTrashed = get( this.props.post, 'status' ) === 'trash';
const hasAutosave = get( this.props.post, 'meta.data.autosave' );
const autosaveMetaData = get( this.props.post, 'meta.data.autosave' );
const hasDifferingAutosave =
autosaveMetaData &&
! this.state.isSaving &&
! isEqual(
pick( autosaveMetaData, POST_REVISION_FIELDS ),
pick( this.props.post, POST_REVISION_FIELDS )
);

const classes = classNames( 'post-editor', {
'is-loading': ! this.state.isEditorInitialized,
Expand Down Expand Up @@ -392,7 +408,7 @@ export class PostEditor extends React.Component {
<VerifyEmailDialog onClose={ this.closeVerifyEmailDialog } />
) : null }
{ isInvalidURL && <InvalidURLDialog onClose={ this.onClose } /> }
{ hasAutosave && this.state.showAutosaveDialog ? (
{ hasDifferingAutosave && this.state.showAutosaveDialog ? (
<RestorePostDialog
onRestore={ this.restoreAutosave }
onClose={ this.closeAutosaveDialog }
Expand Down Expand Up @@ -538,6 +554,9 @@ export class PostEditor extends React.Component {
const saveResult = await this.props.autosave();
if ( ! savingPublishedPost ) {
this.onSaveDraftSuccess( saveResult );

// Create a revision separately from the autosave
this.debouncedAutosaveFlushToRevision();
}
} catch ( error ) {
if ( ! savingPublishedPost ) {
Expand Down
21 changes: 21 additions & 0 deletions client/state/posts/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
POSTS_REQUEST_SUCCESS,
POSTS_REQUEST_FAILURE,
} from 'state/action-types';
import { POST_REVISION_FIELDS } from 'state/posts/constants';
import { getSitePost, getEditedPost, getPostEdits, isEditedPostDirty } from 'state/posts/selectors';
import { recordSaveEvent } from 'state/posts/stats';
import {
Expand Down Expand Up @@ -774,3 +775,23 @@ export const autosave = () => async ( dispatch, getState ) => {

return await dispatch( saveEdited( { recordSaveEvent: false, autosave: true } ) );
};

/**
* Save a revision of the passed-in post. Calls `wpcom.post.add` which initiates a network call.
* @param {int} siteId The site ID for use in the `savePost` call
* @param {object} post post-like object with keys: 'ID', 'content', 'excerpt', 'title'
* @returns {Promise} The return value of `wpcom.post.add`
*/
export const saveRevision = ( siteId, post ) => {
const normalized = normalizePostForApi( {
...pick( post, POST_REVISION_FIELDS ),
parent: post.ID,
status: 'inherit',
type: 'revision',
} );
Copy link
Member

Choose a reason for hiding this comment

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

The savePost action dispatches POST_SAVE, POST_SAVE_SUCCESS and POSTS_RECEIVE while saving the post or revision. That updates the structures in state.posts. And the revision doesn't really belong there.

It doesn't immediately break anything, just adds more entropy to already complex and fragile code.

It would be better to directly call wpcom.site().post().add() in the saveRevision action thunk, without the extra dispatches.


return wpcom
.site( siteId )
.post()
.add( { apiVersion: '1.2' }, normalized );
};
2 changes: 2 additions & 0 deletions client/state/posts/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ export const DEFAULT_NEW_POST_VALUES = {
format: 'default',
featured_image: '',
};

export const POST_REVISION_FIELDS = [ 'content', 'excerpt', 'title' ];