@wordpress/editor
Version:
Enhanced block editor for WordPress posts.
1,283 lines (1,174 loc) • 32.4 kB
JavaScript
/**
* WordPress dependencies
*/
import { speak } from '@wordpress/a11y';
import apiFetch from '@wordpress/api-fetch';
import deprecated from '@wordpress/deprecated';
import {
parse,
synchronizeBlocksWithTemplate,
__unstableSerializeAndClean,
} from '@wordpress/blocks';
import { store as noticesStore } from '@wordpress/notices';
import { store as coreStore } from '@wordpress/core-data';
import { store as blockEditorStore } from '@wordpress/block-editor';
import {
applyFilters,
applyFiltersAsync,
doActionAsync,
} from '@wordpress/hooks';
import { store as preferencesStore } from '@wordpress/preferences';
import { __, sprintf } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { localAutosaveSet } from './local-autosave';
import {
getNotificationArgumentsForSaveSuccess,
getNotificationArgumentsForSaveFail,
getNotificationArgumentsForTrashFail,
} from './utils/notice-builder';
import { unlock } from '../lock-unlock';
/**
* Returns an action generator used in signalling that editor has initialized with
* the specified post object and editor settings.
*
* @param {Object} post Post object.
* @param {Object} edits Initial edited attributes object.
* @param {Array} [template] Block Template.
*/
export const setupEditor =
( post, edits, template ) =>
( { dispatch } ) => {
dispatch.setEditedPost( post.type, post.id );
// Apply a template for new posts only, if exists.
const isNewPost = post.status === 'auto-draft';
if ( isNewPost && template ) {
// In order to ensure maximum of a single parse during setup, edits are
// included as part of editor setup action. Assume edited content as
// canonical if provided, falling back to post.
let content;
if ( 'content' in edits ) {
content = edits.content;
} else {
content = post.content.raw;
}
let blocks = parse( content );
blocks = synchronizeBlocksWithTemplate( blocks, template );
dispatch.resetEditorBlocks( blocks, {
__unstableShouldCreateUndoLevel: false,
} );
}
if (
edits &&
Object.entries( edits ).some(
( [ key, edit ] ) =>
edit !== ( post[ key ]?.raw ?? post[ key ] )
)
) {
dispatch.editPost( edits );
}
};
/**
* Returns an action object signalling that the editor is being destroyed and
* that any necessary state or side-effect cleanup should occur.
*
* @deprecated
*
* @return {Object} Action object.
*/
export function __experimentalTearDownEditor() {
deprecated(
"wp.data.dispatch( 'core/editor' ).__experimentalTearDownEditor",
{
since: '6.5',
}
);
return { type: 'DO_NOTHING' };
}
/**
* Returns an action object used in signalling that the latest version of the
* post has been received, either by initialization or save.
*
* @deprecated Since WordPress 6.0.
*/
export function resetPost() {
deprecated( "wp.data.dispatch( 'core/editor' ).resetPost", {
since: '6.0',
version: '6.3',
alternative: 'Initialize the editor with the setupEditorState action',
} );
return { type: 'DO_NOTHING' };
}
/**
* Returns an action object used in signalling that a patch of updates for the
* latest version of the post have been received.
*
* @return {Object} Action object.
* @deprecated since Gutenberg 9.7.0.
*/
export function updatePost() {
deprecated( "wp.data.dispatch( 'core/editor' ).updatePost", {
since: '5.7',
alternative: 'Use the core entities store instead',
} );
return {
type: 'DO_NOTHING',
};
}
/**
* Setup the editor state.
*
* @deprecated
*
* @param {Object} post Post object.
*/
export function setupEditorState( post ) {
deprecated( "wp.data.dispatch( 'core/editor' ).setupEditorState", {
since: '6.5',
alternative: "wp.data.dispatch( 'core/editor' ).setEditedPost",
} );
return setEditedPost( post.type, post.id );
}
/**
* Returns an action that sets the current post Type and post ID.
*
* @param {string} postType Post Type.
* @param {string} postId Post ID.
*
* @return {Object} Action object.
*/
export function setEditedPost( postType, postId ) {
return {
type: 'SET_EDITED_POST',
postType,
postId,
};
}
/**
* 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 the edit.
*
* @example
* ```js
* // Update the post title
* wp.data.dispatch( 'core/editor' ).editPost( { title: `${ newTitle }` } );
* ```
*
* @return {Object} Action object
*/
export const editPost =
( edits, options ) =>
( { select, registry } ) => {
const { id, type } = select.getCurrentPost();
registry
.dispatch( coreStore )
.editEntityRecord( 'postType', type, id, edits, options );
};
/**
* Action for saving the current post in the editor.
*
* @param {Object} [options]
*/
export const savePost =
( options = {} ) =>
async ( { select, dispatch, registry } ) => {
if ( ! select.isEditedPostSaveable() ) {
return;
}
const content = select.getEditedPostContent();
dispatch.editPost( { content }, { undoIgnore: true } );
const previousRecord = select.getCurrentPost();
let edits = {
id: previousRecord.id,
...registry
.select( coreStore )
.getEntityRecordNonTransientEdits(
'postType',
previousRecord.type,
previousRecord.id
),
content,
};
dispatch( { type: 'REQUEST_POST_UPDATE_START', options } );
let error = false;
try {
edits = await applyFiltersAsync(
'editor.preSavePost',
edits,
options
);
} catch ( err ) {
error = err;
}
if ( ! error ) {
try {
await registry
.dispatch( coreStore )
.saveEntityRecord(
'postType',
previousRecord.type,
edits,
options
);
} catch ( err ) {
error =
err.message && err.code !== 'unknown_error'
? err.message
: __( 'An error occurred while updating.' );
}
}
if ( ! error ) {
error = registry
.select( coreStore )
.getLastEntitySaveError(
'postType',
previousRecord.type,
previousRecord.id
);
}
// Run the hook with legacy unstable name for backward compatibility
if ( ! error ) {
try {
await applyFilters(
'editor.__unstableSavePost',
Promise.resolve(),
options
);
} catch ( err ) {
error = err;
}
}
if ( ! error ) {
try {
await doActionAsync(
'editor.savePost',
{ id: previousRecord.id, type: previousRecord.type },
options
);
} catch ( err ) {
error = err;
}
}
dispatch( { type: 'REQUEST_POST_UPDATE_FINISH', options } );
if (
typeof window !== 'undefined' &&
window.__experimentalTemplateActivate &&
! options.isAutosave &&
previousRecord.type === 'wp_template' &&
( typeof previousRecord.id === 'number' ||
/^\d+$/.test( previousRecord.id ) )
) {
templateActivationNotice( { select, dispatch, registry } );
}
if ( error ) {
const args = getNotificationArgumentsForSaveFail( {
post: previousRecord,
edits,
error,
} );
if ( args.length ) {
registry.dispatch( noticesStore ).createErrorNotice( ...args );
}
} else {
const updatedRecord = select.getCurrentPost();
const args = getNotificationArgumentsForSaveSuccess( {
previousPost: previousRecord,
post: updatedRecord,
postType: await registry
.resolveSelect( coreStore )
.getPostType( updatedRecord.type ),
options,
} );
if ( args.length ) {
registry
.dispatch( noticesStore )
.createSuccessNotice( ...args );
}
// Make sure that any edits after saving create an undo level and are
// considered for change detection.
if ( ! options.isAutosave ) {
registry
.dispatch( blockEditorStore )
.__unstableMarkLastChangeAsPersistent();
}
}
};
async function templateActivationNotice( { select, registry } ) {
const editorSettings = select.getEditorSettings();
// Don't open for focused entity.
if ( editorSettings.onNavigateToPreviousEntityRecord ) {
return;
}
const { id, slug } = select.getCurrentPost();
const site = await registry
.select( coreStore )
.getEntityRecord( 'root', 'site' );
// Already active.
if ( site.active_templates[ slug ] === id ) {
return;
}
const currentTheme = await registry
.resolveSelect( coreStore )
.getCurrentTheme();
const templateType = currentTheme?.default_template_types.find(
( type ) => type.slug === slug
);
await registry.dispatch( noticesStore ).createNotice(
'info',
sprintf(
// translators: %s: The name (or slug) of the type of template.
__( 'Do you want to activate this "%s" template?' ),
templateType?.title ?? slug
),
{
id: 'template-activate-notice',
actions: [
{
label: __( 'Activate' ),
onClick: async () => {
await registry
.dispatch( noticesStore )
.createNotice(
'info',
__( 'Activating template…' ),
{ id: 'template-activate-notice' }
);
try {
const currentSite = await registry
.select( coreStore )
.getEntityRecord( 'root', 'site' );
await registry
.dispatch( coreStore )
.saveEntityRecord(
'root',
'site',
{
active_templates: {
...currentSite.active_templates,
[ slug ]: id,
},
},
{ throwOnError: true }
);
await registry
.dispatch( noticesStore )
.createSuccessNotice(
__( 'Template activated.' ),
{ id: 'template-activate-notice' }
);
} catch ( error ) {
await registry
.dispatch( noticesStore )
.createErrorNotice(
__( 'Template activation failed.' ),
{ id: 'template-activate-notice' }
);
// Rethrow for debugging.
throw error;
}
},
},
],
}
);
}
/**
* Action for refreshing the current post.
*
* @deprecated Since WordPress 6.0.
*/
export function refreshPost() {
deprecated( "wp.data.dispatch( 'core/editor' ).refreshPost", {
since: '6.0',
version: '6.3',
alternative: 'Use the core entities store instead',
} );
return { type: 'DO_NOTHING' };
}
/**
* Action for trashing the current post in the editor.
*/
export const trashPost =
() =>
async ( { select, dispatch, registry } ) => {
const postTypeSlug = select.getCurrentPostType();
const postType = await registry
.resolveSelect( coreStore )
.getPostType( postTypeSlug );
const { rest_base: restBase, rest_namespace: restNamespace = 'wp/v2' } =
postType;
dispatch( { type: 'REQUEST_POST_DELETE_START' } );
try {
const post = select.getCurrentPost();
await apiFetch( {
path: `/${ restNamespace }/${ restBase }/${ post.id }`,
method: 'DELETE',
} );
await dispatch.savePost();
} catch ( error ) {
registry
.dispatch( noticesStore )
.createErrorNotice(
...getNotificationArgumentsForTrashFail( { error } )
);
}
dispatch( { type: 'REQUEST_POST_DELETE_FINISH' } );
};
/**
* Action that autosaves the current post. This
* includes server-side autosaving (default) and client-side (a.k.a. local)
* autosaving (e.g. on the Web, the post might be committed to Session
* Storage).
*
* @param {Object} [options] Extra flags to identify the autosave.
* @param {boolean} [options.local] Whether to perform a local autosave.
*/
export const autosave =
( { local = false, ...options } = {} ) =>
async ( { select, dispatch } ) => {
const post = select.getCurrentPost();
if ( local ) {
const isPostNew = select.isEditedPostNew();
const title = select.getEditedPostAttribute( 'title' );
const content = select.getEditedPostAttribute( 'content' );
const excerpt = select.getEditedPostAttribute( 'excerpt' );
localAutosaveSet( post.id, isPostNew, title, content, excerpt );
} else {
await dispatch.savePost( { isAutosave: true, ...options } );
}
};
/**
* Save for preview.
*
* @param {Object} options Options object.
* @param {boolean} options.forceIsAutosaveable Whether to force the post to be autosaveable.
*
* @return {Function} Thunk that saves for preview and returns the preview link.
*/
export const __unstableSaveForPreview =
( { forceIsAutosaveable } = {} ) =>
async ( { select, dispatch } ) => {
if (
( forceIsAutosaveable || select.isEditedPostAutosaveable() ) &&
! select.isPostLocked()
) {
const isDraft = [ 'draft', 'auto-draft' ].includes(
select.getEditedPostAttribute( 'status' )
);
if ( isDraft ) {
await dispatch.savePost( { isPreview: true } );
} else {
await dispatch.autosave( { isPreview: true } );
}
}
return select.getEditedPostPreviewLink();
};
/**
* Action that restores last popped state in undo history.
*/
export const redo =
() =>
( { registry } ) => {
registry.dispatch( coreStore ).redo();
};
/**
* Action that pops a record from undo history and undoes the edit.
*/
export const undo =
() =>
( { registry } ) => {
registry.dispatch( coreStore ).undo();
};
/**
* Action that creates an undo history record.
*
* @deprecated Since WordPress 6.0
*/
export function createUndoLevel() {
deprecated( "wp.data.dispatch( 'core/editor' ).createUndoLevel", {
since: '6.0',
version: '6.3',
alternative: 'Use the core entities store instead',
} );
return { type: 'DO_NOTHING' };
}
/**
* Action that locks the editor.
*
* @param {Object} lock Details about the post lock status, user, and nonce.
* @return {Object} Action object.
*/
export function updatePostLock( lock ) {
return {
type: 'UPDATE_POST_LOCK',
lock,
};
}
/**
* Enable the publish sidebar.
*/
export const enablePublishSidebar =
() =>
( { registry } ) => {
registry
.dispatch( preferencesStore )
.set( 'core', 'isPublishSidebarEnabled', true );
};
/**
* Disables the publish sidebar.
*/
export const disablePublishSidebar =
() =>
( { registry } ) => {
registry
.dispatch( preferencesStore )
.set( 'core', 'isPublishSidebarEnabled', false );
};
/**
* Action that locks post saving.
*
* @param {string} lockName The lock name.
*
* @example
* ```
* const { subscribe } = wp.data;
*
* const initialPostStatus = wp.data.select( 'core/editor' ).getEditedPostAttribute( 'status' );
*
* // Only allow publishing posts that are set to a future date.
* if ( 'publish' !== initialPostStatus ) {
*
* // Track locking.
* let locked = false;
*
* // Watch for the publish event.
* let unssubscribe = subscribe( () => {
* const currentPostStatus = wp.data.select( 'core/editor' ).getEditedPostAttribute( 'status' );
* if ( 'publish' !== currentPostStatus ) {
*
* // Compare the post date to the current date, lock the post if the date isn't in the future.
* const postDate = new Date( wp.data.select( 'core/editor' ).getEditedPostAttribute( 'date' ) );
* const currentDate = new Date();
* if ( postDate.getTime() <= currentDate.getTime() ) {
* if ( ! locked ) {
* locked = true;
* wp.data.dispatch( 'core/editor' ).lockPostSaving( 'futurelock' );
* }
* } else {
* if ( locked ) {
* locked = false;
* wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'futurelock' );
* }
* }
* }
* } );
* }
* ```
*
* @return {Object} Action object
*/
export function lockPostSaving( lockName ) {
return {
type: 'LOCK_POST_SAVING',
lockName,
};
}
/**
* Action that unlocks post saving.
*
* @param {string} lockName The lock name.
*
* @example
* ```
* // Unlock post saving with the lock key `mylock`:
* wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'mylock' );
* ```
*
* @return {Object} Action object
*/
export function unlockPostSaving( lockName ) {
return {
type: 'UNLOCK_POST_SAVING',
lockName,
};
}
/**
* Action that locks post autosaving.
*
* @param {string} lockName The lock name.
*
* @example
* ```
* // Lock post autosaving with the lock key `mylock`:
* wp.data.dispatch( 'core/editor' ).lockPostAutosaving( 'mylock' );
* ```
*
* @return {Object} Action object
*/
export function lockPostAutosaving( lockName ) {
return {
type: 'LOCK_POST_AUTOSAVING',
lockName,
};
}
/**
* Action that unlocks post autosaving.
*
* @param {string} lockName The lock name.
*
* @example
* ```
* // Unlock post saving with the lock key `mylock`:
* wp.data.dispatch( 'core/editor' ).unlockPostAutosaving( 'mylock' );
* ```
*
* @return {Object} Action object
*/
export function unlockPostAutosaving( lockName ) {
return {
type: 'UNLOCK_POST_AUTOSAVING',
lockName,
};
}
/**
* Returns an action object used to signal that the blocks have been updated.
*
* @param {Array} blocks Block Array.
* @param {Object} [options] Optional options.
*/
export const resetEditorBlocks =
( blocks, options = {} ) =>
( { select, dispatch, registry } ) => {
const { __unstableShouldCreateUndoLevel, selection } = options;
const edits = { blocks, selection };
if ( __unstableShouldCreateUndoLevel !== false ) {
const { id, type } = select.getCurrentPost();
const noChange =
registry
.select( coreStore )
.getEditedEntityRecord( 'postType', type, id ).blocks ===
edits.blocks;
if ( noChange ) {
registry
.dispatch( coreStore )
.__unstableCreateUndoLevel( 'postType', type, id );
return;
}
// We create a new function here on every persistent edit
// to make sure the edit makes the post dirty and creates
// a new undo level.
edits.content = ( { blocks: blocksForSerialization = [] } ) =>
__unstableSerializeAndClean( blocksForSerialization );
}
dispatch.editPost( edits );
};
/*
* Returns an action object used in signalling that the post editor settings have been updated.
*
* @param {Object} settings Updated settings
*
* @return {Object} Action object
*/
export function updateEditorSettings( settings ) {
return {
type: 'UPDATE_EDITOR_SETTINGS',
settings,
};
}
/**
* Returns an action used to set the rendering mode of the post editor. We support multiple rendering modes:
*
* - `post-only`: This mode extracts the post blocks from the template and renders only those. The idea is to allow the user to edit the post/page in isolation without the wrapping template.
* - `template-locked`: This mode renders both the template and the post blocks but the template blocks are locked and can't be edited. The post blocks are editable.
*
* @param {string} mode Mode (one of 'post-only' or 'template-locked').
*/
export const setRenderingMode =
( mode ) =>
( { dispatch, registry, select } ) => {
if (
select.__unstableIsEditorReady() &&
! select.getEditorSettings().isPreviewMode
) {
registry.dispatch( blockEditorStore ).clearSelectedBlock();
}
dispatch( {
type: 'SET_RENDERING_MODE',
mode,
} );
};
/**
* Action that changes the width of the editing canvas.
*
* @param {string} deviceType
*
* @return {Object} Action object.
*/
export function setDeviceType( deviceType ) {
return {
type: 'SET_DEVICE_TYPE',
deviceType,
};
}
/**
* Returns an action object used to enable or disable a panel in the editor.
*
* @param {string} panelName A string that identifies the panel to enable or disable.
*
* @return {Object} Action object.
*/
export const toggleEditorPanelEnabled =
( panelName ) =>
( { registry } ) => {
const inactivePanels =
registry
.select( preferencesStore )
.get( 'core', 'inactivePanels' ) ?? [];
const isPanelInactive = !! inactivePanels?.includes( panelName );
// If the panel is inactive, remove it to enable it, else add it to
// make it inactive.
let updatedInactivePanels;
if ( isPanelInactive ) {
updatedInactivePanels = inactivePanels.filter(
( invactivePanelName ) => invactivePanelName !== panelName
);
} else {
updatedInactivePanels = [ ...inactivePanels, panelName ];
}
registry
.dispatch( preferencesStore )
.set( 'core', 'inactivePanels', updatedInactivePanels );
};
/**
* Opens a closed panel and closes an open panel.
*
* @param {string} panelName A string that identifies the panel to open or close.
*/
export const toggleEditorPanelOpened =
( panelName ) =>
( { registry } ) => {
const openPanels =
registry.select( preferencesStore ).get( 'core', 'openPanels' ) ??
[];
const isPanelOpen = !! openPanels?.includes( panelName );
// If the panel is open, remove it to close it, else add it to
// make it open.
let updatedOpenPanels;
if ( isPanelOpen ) {
updatedOpenPanels = openPanels.filter(
( openPanelName ) => openPanelName !== panelName
);
} else {
updatedOpenPanels = [ ...openPanels, panelName ];
}
registry
.dispatch( preferencesStore )
.set( 'core', 'openPanels', updatedOpenPanels );
};
/**
* Returns an action object used to remove a panel from the editor.
*
* @param {string} panelName A string that identifies the panel to remove.
*
* @return {Object} Action object.
*/
export function removeEditorPanel( panelName ) {
return {
type: 'REMOVE_PANEL',
panelName,
};
}
/**
* Returns an action object used to open/close the inserter.
*
* @param {boolean|Object} value Whether the inserter should be
* opened (true) or closed (false).
* To specify an insertion point,
* use an object.
* @param {string} value.rootClientId The root client ID to insert at.
* @param {number} value.insertionIndex The index to insert at.
* @param {string} value.filterValue A query to filter the inserter results.
* @param {Function} value.onSelect A callback when an item is selected.
* @param {string} value.tab The tab to open in the inserter.
* @param {string} value.category The category to initialize in the inserter.
*
* @return {Object} Action object.
*/
export const setIsInserterOpened =
( value ) =>
( { dispatch, registry } ) => {
if (
typeof value === 'object' &&
value.hasOwnProperty( 'rootClientId' ) &&
value.hasOwnProperty( 'insertionIndex' )
) {
unlock( registry.dispatch( blockEditorStore ) ).setInsertionPoint( {
rootClientId: value.rootClientId,
index: value.insertionIndex,
} );
}
dispatch( {
type: 'SET_IS_INSERTER_OPENED',
value,
} );
};
/**
* Returns an action object used to open/close the list view.
*
* @param {boolean} isOpen A boolean representing whether the list view should be opened or closed.
* @return {Object} Action object.
*/
export function setIsListViewOpened( isOpen ) {
return {
type: 'SET_IS_LIST_VIEW_OPENED',
isOpen,
};
}
/**
* Action that toggles Distraction free mode.
* Distraction free mode expects there are no sidebars, as due to the
* z-index values set, you can't close sidebars.
*
* @param {Object} [options={}] Optional configuration object
* @param {boolean} [options.createNotice=true] Whether to create a notice
*/
export const toggleDistractionFree =
( { createNotice = true } = {} ) =>
( { dispatch, registry } ) => {
const isDistractionFree = registry
.select( preferencesStore )
.get( 'core', 'distractionFree' );
if ( isDistractionFree ) {
registry
.dispatch( preferencesStore )
.set( 'core', 'fixedToolbar', false );
}
if ( ! isDistractionFree ) {
registry.batch( () => {
registry
.dispatch( preferencesStore )
.set( 'core', 'fixedToolbar', true );
dispatch.setIsInserterOpened( false );
dispatch.setIsListViewOpened( false );
unlock(
registry.dispatch( blockEditorStore )
).resetZoomLevel();
} );
}
registry.batch( () => {
registry
.dispatch( preferencesStore )
.set( 'core', 'distractionFree', ! isDistractionFree );
if ( createNotice ) {
registry
.dispatch( noticesStore )
.createInfoNotice(
isDistractionFree
? __( 'Distraction free mode deactivated.' )
: __( 'Distraction free mode activated.' ),
{
id: 'core/editor/distraction-free-mode/notice',
type: 'snackbar',
actions: [
{
label: __( 'Undo' ),
onClick: () => {
registry.batch( () => {
registry
.dispatch( preferencesStore )
.set(
'core',
'fixedToolbar',
isDistractionFree
);
registry
.dispatch( preferencesStore )
.toggle(
'core',
'distractionFree'
);
} );
},
},
],
}
);
}
} );
};
/**
* Action that toggles the Spotlight Mode view option.
*/
export const toggleSpotlightMode =
() =>
( { registry } ) => {
registry.dispatch( preferencesStore ).toggle( 'core', 'focusMode' );
const isFocusMode = registry
.select( preferencesStore )
.get( 'core', 'focusMode' );
registry
.dispatch( noticesStore )
.createInfoNotice(
isFocusMode
? __( 'Spotlight mode activated.' )
: __( 'Spotlight mode deactivated.' ),
{
id: 'core/editor/toggle-spotlight-mode/notice',
type: 'snackbar',
actions: [
{
label: __( 'Undo' ),
onClick: () => {
registry
.dispatch( preferencesStore )
.toggle( 'core', 'focusMode' );
},
},
],
}
);
};
/**
* Action that toggles the Top Toolbar view option.
*/
export const toggleTopToolbar =
() =>
( { registry } ) => {
registry.dispatch( preferencesStore ).toggle( 'core', 'fixedToolbar' );
const isTopToolbar = registry
.select( preferencesStore )
.get( 'core', 'fixedToolbar' );
registry
.dispatch( noticesStore )
.createInfoNotice(
isTopToolbar
? __( 'Top toolbar activated.' )
: __( 'Top toolbar deactivated.' ),
{
id: 'core/editor/toggle-top-toolbar/notice',
type: 'snackbar',
actions: [
{
label: __( 'Undo' ),
onClick: () => {
registry
.dispatch( preferencesStore )
.toggle( 'core', 'fixedToolbar' );
},
},
],
}
);
};
/**
* Triggers an action used to switch editor mode.
*
* @param {string} mode The editor mode.
*/
export const switchEditorMode =
( mode ) =>
( { dispatch, registry } ) => {
registry.dispatch( preferencesStore ).set( 'core', 'editorMode', mode );
if ( mode !== 'visual' ) {
// Unselect blocks when we switch to a non visual mode.
registry.dispatch( blockEditorStore ).clearSelectedBlock();
// Exit zoom out state when switching to a non visual mode.
unlock( registry.dispatch( blockEditorStore ) ).resetZoomLevel();
}
if ( mode === 'visual' ) {
speak( __( 'Visual editor selected' ), 'assertive' );
} else if ( mode === 'text' ) {
const isDistractionFree = registry
.select( preferencesStore )
.get( 'core', 'distractionFree' );
if ( isDistractionFree ) {
dispatch.toggleDistractionFree();
}
speak( __( 'Code editor selected' ), 'assertive' );
}
};
/**
* Returns an action object used in signalling that the user opened the publish
* sidebar.
*
* @return {Object} Action object
*/
export function openPublishSidebar() {
return {
type: 'OPEN_PUBLISH_SIDEBAR',
};
}
/**
* Returns an action object used in signalling that the user closed the
* publish sidebar.
*
* @return {Object} Action object.
*/
export function closePublishSidebar() {
return {
type: 'CLOSE_PUBLISH_SIDEBAR',
};
}
/**
* Returns an action object used in signalling that the user toggles the publish sidebar.
*
* @return {Object} Action object
*/
export function togglePublishSidebar() {
return {
type: 'TOGGLE_PUBLISH_SIDEBAR',
};
}
/**
* Backward compatibility
*/
const getBlockEditorAction =
( name ) =>
( ...args ) =>
( { registry } ) => {
deprecated( "`wp.data.dispatch( 'core/editor' )." + name + '`', {
since: '5.3',
alternative:
"`wp.data.dispatch( 'core/block-editor' )." + name + '`',
version: '6.2',
} );
registry.dispatch( blockEditorStore )[ name ]( ...args );
};
/**
* @see resetBlocks in core/block-editor store.
*/
export const resetBlocks = getBlockEditorAction( 'resetBlocks' );
/**
* @see receiveBlocks in core/block-editor store.
*/
export const receiveBlocks = getBlockEditorAction( 'receiveBlocks' );
/**
* @see updateBlock in core/block-editor store.
*/
export const updateBlock = getBlockEditorAction( 'updateBlock' );
/**
* @see updateBlockAttributes in core/block-editor store.
*/
export const updateBlockAttributes = getBlockEditorAction(
'updateBlockAttributes'
);
/**
* @see selectBlock in core/block-editor store.
*/
export const selectBlock = getBlockEditorAction( 'selectBlock' );
/**
* @see startMultiSelect in core/block-editor store.
*/
export const startMultiSelect = getBlockEditorAction( 'startMultiSelect' );
/**
* @see stopMultiSelect in core/block-editor store.
*/
export const stopMultiSelect = getBlockEditorAction( 'stopMultiSelect' );
/**
* @see multiSelect in core/block-editor store.
*/
export const multiSelect = getBlockEditorAction( 'multiSelect' );
/**
* @see clearSelectedBlock in core/block-editor store.
*/
export const clearSelectedBlock = getBlockEditorAction( 'clearSelectedBlock' );
/**
* @see toggleSelection in core/block-editor store.
*/
export const toggleSelection = getBlockEditorAction( 'toggleSelection' );
/**
* @see replaceBlocks in core/block-editor store.
*/
export const replaceBlocks = getBlockEditorAction( 'replaceBlocks' );
/**
* @see replaceBlock in core/block-editor store.
*/
export const replaceBlock = getBlockEditorAction( 'replaceBlock' );
/**
* @see moveBlocksDown in core/block-editor store.
*/
export const moveBlocksDown = getBlockEditorAction( 'moveBlocksDown' );
/**
* @see moveBlocksUp in core/block-editor store.
*/
export const moveBlocksUp = getBlockEditorAction( 'moveBlocksUp' );
/**
* @see moveBlockToPosition in core/block-editor store.
*/
export const moveBlockToPosition = getBlockEditorAction(
'moveBlockToPosition'
);
/**
* @see insertBlock in core/block-editor store.
*/
export const insertBlock = getBlockEditorAction( 'insertBlock' );
/**
* @see insertBlocks in core/block-editor store.
*/
export const insertBlocks = getBlockEditorAction( 'insertBlocks' );
/**
* @see showInsertionPoint in core/block-editor store.
*/
export const showInsertionPoint = getBlockEditorAction( 'showInsertionPoint' );
/**
* @see hideInsertionPoint in core/block-editor store.
*/
export const hideInsertionPoint = getBlockEditorAction( 'hideInsertionPoint' );
/**
* @see setTemplateValidity in core/block-editor store.
*/
export const setTemplateValidity = getBlockEditorAction(
'setTemplateValidity'
);
/**
* @see synchronizeTemplate in core/block-editor store.
*/
export const synchronizeTemplate = getBlockEditorAction(
'synchronizeTemplate'
);
/**
* @see mergeBlocks in core/block-editor store.
*/
export const mergeBlocks = getBlockEditorAction( 'mergeBlocks' );
/**
* @see removeBlocks in core/block-editor store.
*/
export const removeBlocks = getBlockEditorAction( 'removeBlocks' );
/**
* @see removeBlock in core/block-editor store.
*/
export const removeBlock = getBlockEditorAction( 'removeBlock' );
/**
* @see toggleBlockMode in core/block-editor store.
*/
export const toggleBlockMode = getBlockEditorAction( 'toggleBlockMode' );
/**
* @see startTyping in core/block-editor store.
*/
export const startTyping = getBlockEditorAction( 'startTyping' );
/**
* @see stopTyping in core/block-editor store.
*/
export const stopTyping = getBlockEditorAction( 'stopTyping' );
/**
* @see enterFormattedText in core/block-editor store.
*/
export const enterFormattedText = getBlockEditorAction( 'enterFormattedText' );
/**
* @see exitFormattedText in core/block-editor store.
*/
export const exitFormattedText = getBlockEditorAction( 'exitFormattedText' );
/**
* @see insertDefaultBlock in core/block-editor store.
*/
export const insertDefaultBlock = getBlockEditorAction( 'insertDefaultBlock' );
/**
* @see updateBlockListSettings in core/block-editor store.
*/
export const updateBlockListSettings = getBlockEditorAction(
'updateBlockListSettings'
);