UNPKG

@wordpress/editor

Version:
725 lines (645 loc) 19.5 kB
/** * External dependencies */ import { has } from 'lodash'; /** * WordPress dependencies */ import deprecated from '@wordpress/deprecated'; import { controls } from '@wordpress/data'; import { apiFetch } from '@wordpress/data-controls'; import { parse, synchronizeBlocksWithTemplate, __unstableSerializeAndClean } from '@wordpress/blocks'; import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies */ import { STORE_NAME, TRASH_POST_NOTICE_ID } from './constants'; import { getNotificationArgumentsForSaveSuccess, getNotificationArgumentsForSaveFail, getNotificationArgumentsForTrashFail } from './utils/notice-builder'; /** * 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 function* setupEditor(post, edits, 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 (has(edits, ['content'])) { content = edits.content; } else { content = post.content.raw; } let blocks = parse(content); // Apply a template for new posts only, if exists. const isNewPost = post.status === 'auto-draft'; if (isNewPost && template) { blocks = synchronizeBlocksWithTemplate(blocks, template); } yield resetPost(post); yield { type: 'SETUP_EDITOR', post, edits, template }; yield resetEditorBlocks(blocks, { __unstableShouldCreateUndoLevel: false }); yield setupEditorState(post); if (edits && Object.keys(edits).some(key => edits[key] !== (has(post, [key, 'raw']) ? post[key].raw : post[key]))) { yield editPost(edits); } } /** * Returns an action object signalling that the editor is being destroyed and * that any necessary state or side-effect cleanup should occur. * * @return {Object} Action object. */ export function __experimentalTearDownEditor() { return { type: 'TEAR_DOWN_EDITOR' }; } /** * Returns an action object used in signalling that the latest version of the * post has been received, either by initialization or save. * * @param {Object} post Post object. * * @return {Object} Action object. */ export function resetPost(post) { return { type: 'RESET_POST', post }; } /** * Returns an action object used in signalling that the latest autosave of the * post has been received, by initialization or autosave. * * @deprecated since 5.6. Callers should use the `receiveAutosaves( postId, autosave )` * selector from the '@wordpress/core-data' package. * * @param {Object} newAutosave Autosave post object. * * @return {Object} Action object. */ export function* resetAutosave(newAutosave) { deprecated('resetAutosave action (`core/editor` store)', { since: '5.3', alternative: 'receiveAutosaves action (`core` store)' }); const postId = yield controls.select(STORE_NAME, 'getCurrentPostId'); yield controls.dispatch('core', 'receiveAutosaves', postId, newAutosave); return { type: '__INERT__' }; } /** * Action for dispatching that a post update request has started. * * @param {Object} options * * @return {Object} An action object */ export function __experimentalRequestPostUpdateStart(options = {}) { return { type: 'REQUEST_POST_UPDATE_START', options }; } /** * Action for dispatching that a post update request has finished. * * @param {Object} options * * @return {Object} An action object */ export function __experimentalRequestPostUpdateFinish(options = {}) { return { type: 'REQUEST_POST_UPDATE_FINISH', options }; } /** * 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: 'User the core entitires store instead' }); return { type: 'DO_NOTHING' }; } /** * Returns an action object used to setup the editor state when first opening * an editor. * * @param {Object} post Post object. * * @return {Object} Action object. */ export function setupEditorState(post) { return { type: 'SETUP_EDITOR_STATE', post }; } /** * 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. * * @yield {Object} Action object or control. */ export function* editPost(edits, options) { const { id, type } = yield controls.select(STORE_NAME, 'getCurrentPost'); yield controls.dispatch('core', 'editEntityRecord', 'postType', type, id, edits, options); } /** * Action generator for saving the current post in the editor. * * @param {Object} options */ export function* savePost(options = {}) { if (!(yield controls.select(STORE_NAME, 'isEditedPostSaveable'))) { return; } let edits = { content: yield controls.select(STORE_NAME, 'getEditedPostContent') }; if (!options.isAutosave) { yield controls.dispatch(STORE_NAME, 'editPost', edits, { undoIgnore: true }); } yield __experimentalRequestPostUpdateStart(options); const previousRecord = yield controls.select(STORE_NAME, 'getCurrentPost'); edits = { id: previousRecord.id, ...(yield controls.select('core', 'getEntityRecordNonTransientEdits', 'postType', previousRecord.type, previousRecord.id)), ...edits }; yield controls.dispatch('core', 'saveEntityRecord', 'postType', previousRecord.type, edits, options); yield __experimentalRequestPostUpdateFinish(options); const error = yield controls.select('core', 'getLastEntitySaveError', 'postType', previousRecord.type, previousRecord.id); if (error) { const args = getNotificationArgumentsForSaveFail({ post: previousRecord, edits, error }); if (args.length) { yield controls.dispatch(noticesStore, 'createErrorNotice', ...args); } } else { const updatedRecord = yield controls.select(STORE_NAME, 'getCurrentPost'); const args = getNotificationArgumentsForSaveSuccess({ previousPost: previousRecord, post: updatedRecord, postType: yield controls.resolveSelect('core', 'getPostType', updatedRecord.type), options }); if (args.length) { yield controls.dispatch(noticesStore, 'createSuccessNotice', ...args); } // Make sure that any edits after saving create an undo level and are // considered for change detection. if (!options.isAutosave) { yield controls.dispatch('core/block-editor', '__unstableMarkLastChangeAsPersistent'); } } } /** * Action generator for handling refreshing the current post. */ export function* refreshPost() { const post = yield controls.select(STORE_NAME, 'getCurrentPost'); const postTypeSlug = yield controls.select(STORE_NAME, 'getCurrentPostType'); const postType = yield controls.resolveSelect('core', 'getPostType', postTypeSlug); const newPost = yield apiFetch({ // Timestamp arg allows caller to bypass browser caching, which is // expected for this specific function. path: `/wp/v2/${postType.rest_base}/${post.id}` + `?context=edit&_timestamp=${Date.now()}` }); yield controls.dispatch(STORE_NAME, 'resetPost', newPost); } /** * Action generator for trashing the current post in the editor. */ export function* trashPost() { const postTypeSlug = yield controls.select(STORE_NAME, 'getCurrentPostType'); const postType = yield controls.resolveSelect('core', 'getPostType', postTypeSlug); yield controls.dispatch(noticesStore, 'removeNotice', TRASH_POST_NOTICE_ID); try { const post = yield controls.select(STORE_NAME, 'getCurrentPost'); yield apiFetch({ path: `/wp/v2/${postType.rest_base}/${post.id}`, method: 'DELETE' }); yield controls.dispatch(STORE_NAME, 'savePost'); } catch (error) { yield controls.dispatch(noticesStore, 'createErrorNotice', ...getNotificationArgumentsForTrashFail({ error })); } } /** * Action generator used in signalling that the post should autosave. 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. */ export function* autosave({ local = false, ...options } = {}) { if (local) { const post = yield controls.select(STORE_NAME, 'getCurrentPost'); const isPostNew = yield controls.select(STORE_NAME, 'isEditedPostNew'); const title = yield controls.select(STORE_NAME, 'getEditedPostAttribute', 'title'); const content = yield controls.select(STORE_NAME, 'getEditedPostAttribute', 'content'); const excerpt = yield controls.select(STORE_NAME, 'getEditedPostAttribute', 'excerpt'); yield { type: 'LOCAL_AUTOSAVE_SET', postId: post.id, isPostNew, title, content, excerpt }; } else { yield controls.dispatch(STORE_NAME, 'savePost', { isAutosave: true, ...options }); } } /** * Returns an action object used in signalling that undo history should * restore last popped state. * * @yield {Object} Action object. */ export function* redo() { yield controls.dispatch('core', 'redo'); } /** * Returns an action object used in signalling that undo history should pop. * * @yield {Object} Action object. */ export function* undo() { yield controls.dispatch('core', 'undo'); } /** * Returns an action object used in signalling that undo history record should * be created. * * @return {Object} Action object. */ export function createUndoLevel() { return { type: 'CREATE_UNDO_LEVEL' }; } /** * Returns an action object used to lock 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 }; } /** * Returns an action object used in signalling that the user has enabled the * publish sidebar. * * @return {Object} Action object */ export function enablePublishSidebar() { return { type: 'ENABLE_PUBLISH_SIDEBAR' }; } /** * Returns an action object used in signalling that the user has disabled the * publish sidebar. * * @return {Object} Action object */ export function disablePublishSidebar() { return { type: 'DISABLE_PUBLISH_SIDEBAR' }; } /** * Returns an action object used to signal that post saving is locked. * * @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 }; } /** * Returns an action object used to signal that post saving is unlocked. * * @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 }; } /** * Returns an action object used to signal that post autosaving is locked. * * @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 }; } /** * Returns an action object used to signal that post autosaving is unlocked. * * @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. * * @yield {Object} Action object */ export function* resetEditorBlocks(blocks, options = {}) { const { __unstableShouldCreateUndoLevel, selection } = options; const edits = { blocks, selection }; if (__unstableShouldCreateUndoLevel !== false) { const { id, type } = yield controls.select(STORE_NAME, 'getCurrentPost'); const noChange = (yield controls.select('core', 'getEditedEntityRecord', 'postType', type, id)).blocks === edits.blocks; if (noChange) { return yield controls.dispatch('core', '__unstableCreateUndoLevel', 'postType', type, id); } // 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); } yield* 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 }; } /** * Backward compatibility */ const getBlockEditorAction = name => function* (...args) { deprecated("`wp.data.dispatch( 'core/editor' )." + name + '`', { since: '5.3', alternative: "`wp.data.dispatch( 'core/block-editor' )." + name + '`' }); yield controls.dispatch('core/block-editor', 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'); //# sourceMappingURL=actions.js.map