@wordpress/editor
Version:
Enhanced block editor for WordPress posts.
193 lines (170 loc) • 5.47 kB
JavaScript
/**
* External dependencies
*/
import { once, uniqueId, omit } from 'lodash';
/**
* WordPress dependencies
*/
import { useCallback, useEffect, useRef } from '@wordpress/element';
import { ifCondition, usePrevious } from '@wordpress/compose';
import { useSelect, useDispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import { parse } from '@wordpress/blocks';
import { store as noticesStore } from '@wordpress/notices';
/**
* Internal dependencies
*/
import AutosaveMonitor from '../autosave-monitor';
import { localAutosaveGet, localAutosaveClear } from '../../store/controls';
const requestIdleCallback = window.requestIdleCallback
? window.requestIdleCallback
: window.requestAnimationFrame;
/**
* Function which returns true if the current environment supports browser
* sessionStorage, or false otherwise. The result of this function is cached and
* reused in subsequent invocations.
*/
const hasSessionStorageSupport = once( () => {
try {
// Private Browsing in Safari 10 and earlier will throw an error when
// attempting to set into sessionStorage. The test here is intentional in
// causing a thrown error as condition bailing from local autosave.
window.sessionStorage.setItem( '__wpEditorTestSessionStorage', '' );
window.sessionStorage.removeItem( '__wpEditorTestSessionStorage' );
return true;
} catch ( error ) {
return false;
}
} );
/**
* Custom hook which manages the creation of a notice prompting the user to
* restore a local autosave, if one exists.
*/
function useAutosaveNotice() {
const { postId, isEditedPostNew, hasRemoteAutosave } = useSelect(
( select ) => ( {
postId: select( 'core/editor' ).getCurrentPostId(),
isEditedPostNew: select( 'core/editor' ).isEditedPostNew(),
getEditedPostAttribute: select( 'core/editor' )
.getEditedPostAttribute,
hasRemoteAutosave: !! select( 'core/editor' ).getEditorSettings()
.autosave,
} ),
[]
);
const { getEditedPostAttribute } = useSelect( 'core/editor' );
const { createWarningNotice, removeNotice } = useDispatch( noticesStore );
const { editPost, resetEditorBlocks } = useDispatch( 'core/editor' );
useEffect( () => {
let localAutosave = localAutosaveGet( postId, isEditedPostNew );
if ( ! localAutosave ) {
return;
}
try {
localAutosave = JSON.parse( localAutosave );
} catch ( error ) {
// Not usable if it can't be parsed.
return;
}
const { post_title: title, content, excerpt } = localAutosave;
const edits = { title, content, excerpt };
{
// Only display a notice if there is a difference between what has been
// saved and that which is stored in sessionStorage.
const hasDifference = Object.keys( edits ).some( ( key ) => {
return edits[ key ] !== getEditedPostAttribute( key );
} );
if ( ! hasDifference ) {
// If there is no difference, it can be safely ejected from storage.
localAutosaveClear( postId, isEditedPostNew );
return;
}
}
if ( hasRemoteAutosave ) {
return;
}
const noticeId = uniqueId( 'wpEditorAutosaveRestore' );
createWarningNotice(
__(
'The backup of this post in your browser is different from the version below.'
),
{
id: noticeId,
actions: [
{
label: __( 'Restore the backup' ),
onClick() {
editPost( omit( edits, [ 'content' ] ) );
resetEditorBlocks( parse( edits.content ) );
removeNotice( noticeId );
},
},
],
}
);
}, [ isEditedPostNew, postId ] );
}
/**
* Custom hook which ejects a local autosave after a successful save occurs.
*/
function useAutosavePurge() {
const {
postId,
isEditedPostNew,
isDirty,
isAutosaving,
didError,
} = useSelect(
( select ) => ( {
postId: select( 'core/editor' ).getCurrentPostId(),
isEditedPostNew: select( 'core/editor' ).isEditedPostNew(),
isDirty: select( 'core/editor' ).isEditedPostDirty(),
isAutosaving: select( 'core/editor' ).isAutosavingPost(),
didError: select( 'core/editor' ).didPostSaveRequestFail(),
} ),
[]
);
const lastIsDirty = useRef( isDirty );
const lastIsAutosaving = useRef( isAutosaving );
useEffect( () => {
if (
! didError &&
( ( lastIsAutosaving.current && ! isAutosaving ) ||
( lastIsDirty.current && ! isDirty ) )
) {
localAutosaveClear( postId, isEditedPostNew );
}
lastIsDirty.current = isDirty;
lastIsAutosaving.current = isAutosaving;
}, [ isDirty, isAutosaving, didError ] );
// Once the isEditedPostNew changes from true to false, let's clear the auto-draft autosave.
const wasEditedPostNew = usePrevious( isEditedPostNew );
const prevPostId = usePrevious( postId );
useEffect( () => {
if ( prevPostId === postId && wasEditedPostNew && ! isEditedPostNew ) {
localAutosaveClear( postId, true );
}
}, [ isEditedPostNew, postId ] );
}
function LocalAutosaveMonitor() {
const { autosave } = useDispatch( 'core/editor' );
const deferedAutosave = useCallback( () => {
requestIdleCallback( () => autosave( { local: true } ) );
}, [] );
useAutosaveNotice();
useAutosavePurge();
const { localAutosaveInterval } = useSelect(
( select ) => ( {
localAutosaveInterval: select( 'core/editor' ).getEditorSettings()
.__experimentalLocalAutosaveInterval,
} ),
[]
);
return (
<AutosaveMonitor
interval={ localAutosaveInterval }
autosave={ deferedAutosave }
/>
);
}
export default ifCondition( hasSessionStorageSupport )( LocalAutosaveMonitor );