UNPKG

@wordpress/editor

Version:
1,549 lines (1,292 loc) 56.3 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.isEditedPostNew = isEditedPostNew; exports.hasChangedContent = hasChangedContent; exports.isCleanNewPost = isCleanNewPost; exports.getCurrentPostType = getCurrentPostType; exports.getCurrentPostId = getCurrentPostId; exports.getCurrentPostRevisionsCount = getCurrentPostRevisionsCount; exports.getCurrentPostLastRevisionId = getCurrentPostLastRevisionId; exports.getCurrentPostAttribute = getCurrentPostAttribute; exports.getEditedPostAttribute = getEditedPostAttribute; exports.getEditedPostVisibility = getEditedPostVisibility; exports.isCurrentPostPending = isCurrentPostPending; exports.isCurrentPostPublished = isCurrentPostPublished; exports.isCurrentPostScheduled = isCurrentPostScheduled; exports.isEditedPostPublishable = isEditedPostPublishable; exports.isEditedPostSaveable = isEditedPostSaveable; exports.isEditedPostEmpty = isEditedPostEmpty; exports.isEditedPostBeingScheduled = isEditedPostBeingScheduled; exports.isEditedPostDateFloating = isEditedPostDateFloating; exports.isAutosavingPost = isAutosavingPost; exports.isPreviewingPost = isPreviewingPost; exports.getEditedPostPreviewLink = getEditedPostPreviewLink; exports.getSuggestedPostFormat = getSuggestedPostFormat; exports.getBlocksForSerialization = getBlocksForSerialization; exports.isPublishingPost = isPublishingPost; exports.isPermalinkEditable = isPermalinkEditable; exports.getPermalink = getPermalink; exports.getEditedPostSlug = getEditedPostSlug; exports.getPermalinkParts = getPermalinkParts; exports.isPostLocked = isPostLocked; exports.isPostSavingLocked = isPostSavingLocked; exports.isPostAutosavingLocked = isPostAutosavingLocked; exports.isPostLockTakeover = isPostLockTakeover; exports.getPostLockUser = getPostLockUser; exports.getActivePostLock = getActivePostLock; exports.canUserUseUnfilteredHTML = canUserUseUnfilteredHTML; exports.isPublishSidebarEnabled = isPublishSidebarEnabled; exports.getEditorBlocks = getEditorBlocks; exports.getEditorSelectionStart = getEditorSelectionStart; exports.getEditorSelectionEnd = getEditorSelectionEnd; exports.getEditorSelection = getEditorSelection; exports.__unstableIsEditorReady = __unstableIsEditorReady; exports.getEditorSettings = getEditorSettings; exports.getStateBeforeOptimisticTransaction = getStateBeforeOptimisticTransaction; exports.inSomeHistory = inSomeHistory; exports.__experimentalGetDefaultTemplateTypes = __experimentalGetDefaultTemplateTypes; exports.__experimentalGetTemplateInfo = __experimentalGetTemplateInfo; exports.__experimentalGetDefaultTemplateType = exports.__experimentalGetDefaultTemplatePartAreas = exports.getBlockListSettings = exports.hasInserterItems = exports.getInserterItems = exports.canInsertBlockType = exports.getTemplateLock = exports.getTemplate = exports.isValidTemplate = exports.isBlockInsertionPointVisible = exports.getBlockInsertionPoint = exports.isCaretWithinFormattedText = exports.isTyping = exports.getBlockMode = exports.isSelectionEnabled = exports.isMultiSelecting = exports.hasMultiSelection = exports.isBlockWithinSelection = exports.hasSelectedInnerBlock = exports.isBlockSelected = exports.getBlockIndex = exports.getBlockOrder = exports.getMultiSelectedBlocksEndClientId = exports.getMultiSelectedBlocksStartClientId = exports.isAncestorMultiSelected = exports.isBlockMultiSelected = exports.isFirstMultiSelectedBlock = exports.getLastMultiSelectedBlockClientId = exports.getFirstMultiSelectedBlockClientId = exports.getMultiSelectedBlocks = exports.getMultiSelectedBlockClientIds = exports.getSelectedBlocksInitialCaretPosition = exports.getNextBlockClientId = exports.getPreviousBlockClientId = exports.getAdjacentBlockClientId = exports.getBlockHierarchyRootClientId = exports.getBlockRootClientId = exports.getSelectedBlock = exports.getSelectedBlockClientId = exports.hasSelectedBlock = exports.getSelectedBlockCount = exports.getBlockSelectionEnd = exports.getBlockSelectionStart = exports.getBlockCount = exports.getBlocksByClientId = exports.getGlobalBlockCount = exports.getClientIdsWithDescendants = exports.getClientIdsOfDescendants = exports.__unstableGetBlockWithoutInnerBlocks = exports.getBlocks = exports.getBlock = exports.getBlockAttributes = exports.isBlockValid = exports.getBlockName = exports.getEditedPostContent = exports.didPostSaveRequestFail = exports.didPostSaveRequestSucceed = exports.isSavingPost = exports.hasAutosave = exports.getAutosave = exports.isEditedPostAutosaveable = exports.getAutosaveAttribute = exports.getReferenceByDistinctEdits = exports.getPostEdits = exports.getCurrentPost = exports.hasNonPostEntityChanges = exports.isEditedPostDirty = exports.hasEditorRedo = exports.hasEditorUndo = void 0; var _lodash = require("lodash"); var _rememo = _interopRequireDefault(require("rememo")); var _blocks = require("@wordpress/blocks"); var _date = require("@wordpress/date"); var _url = require("@wordpress/url"); var _data = require("@wordpress/data"); var _deprecated = _interopRequireDefault(require("@wordpress/deprecated")); var _element = require("@wordpress/element"); var _icons = require("@wordpress/icons"); var _defaults = require("./defaults"); var _constants = require("./constants"); var _reducer = require("./reducer"); var _url2 = require("../utils/url"); var _getTemplatePartIcon = require("./utils/get-template-part-icon"); /** * External dependencies */ /** * WordPress dependencies */ /** * Internal dependencies */ /** * Shared reference to an empty object for cases where it is important to avoid * returning a new object reference on every invocation, as in a connected or * other pure component which performs `shouldComponentUpdate` check on props. * This should be used as a last resort, since the normalized data should be * maintained by the reducer result in state. */ const EMPTY_OBJECT = {}; /** * Shared reference to an empty array for cases where it is important to avoid * returning a new array reference on every invocation, as in a connected or * other pure component which performs `shouldComponentUpdate` check on props. * This should be used as a last resort, since the normalized data should be * maintained by the reducer result in state. */ const EMPTY_ARRAY = []; /** * Returns true if any past editor history snapshots exist, or false otherwise. * * @param {Object} state Global application state. * * @return {boolean} Whether undo history exists. */ const hasEditorUndo = (0, _data.createRegistrySelector)(select => () => { return select('core').hasUndo(); }); /** * Returns true if any future editor history snapshots exist, or false * otherwise. * * @param {Object} state Global application state. * * @return {boolean} Whether redo history exists. */ exports.hasEditorUndo = hasEditorUndo; const hasEditorRedo = (0, _data.createRegistrySelector)(select => () => { return select('core').hasRedo(); }); /** * Returns true if the currently edited post is yet to be saved, or false if * the post has been saved. * * @param {Object} state Global application state. * * @return {boolean} Whether the post is new. */ exports.hasEditorRedo = hasEditorRedo; function isEditedPostNew(state) { return getCurrentPost(state).status === 'auto-draft'; } /** * Returns true if content includes unsaved changes, or false otherwise. * * @param {Object} state Editor state. * * @return {boolean} Whether content includes unsaved changes. */ function hasChangedContent(state) { const edits = getPostEdits(state); return 'blocks' in edits || // `edits` is intended to contain only values which are different from // the saved post, so the mere presence of a property is an indicator // that the value is different than what is known to be saved. While // content in Visual mode is represented by the blocks state, in Text // mode it is tracked by `edits.content`. 'content' in edits; } /** * Returns true if there are unsaved values for the current edit session, or * false if the editing state matches the saved or new post. * * @param {Object} state Global application state. * * @return {boolean} Whether unsaved values exist. */ const isEditedPostDirty = (0, _data.createRegistrySelector)(select => state => { // Edits should contain only fields which differ from the saved post (reset // at initial load and save complete). Thus, a non-empty edits state can be // inferred to contain unsaved values. const postType = getCurrentPostType(state); const postId = getCurrentPostId(state); if (select('core').hasEditsForEntityRecord('postType', postType, postId)) { return true; } return false; }); /** * Returns true if there are unsaved edits for entities other than * the editor's post, and false otherwise. * * @param {Object} state Global application state. * * @return {boolean} Whether there are edits or not. */ exports.isEditedPostDirty = isEditedPostDirty; const hasNonPostEntityChanges = (0, _data.createRegistrySelector)(select => state => { const dirtyEntityRecords = select('core').__experimentalGetDirtyEntityRecords(); const { type, id } = getCurrentPost(state); return (0, _lodash.some)(dirtyEntityRecords, entityRecord => entityRecord.kind !== 'postType' || entityRecord.name !== type || entityRecord.key !== id); }); /** * Returns true if there are no unsaved values for the current edit session and * if the currently edited post is new (has never been saved before). * * @param {Object} state Global application state. * * @return {boolean} Whether new post and unsaved values exist. */ exports.hasNonPostEntityChanges = hasNonPostEntityChanges; function isCleanNewPost(state) { return !isEditedPostDirty(state) && isEditedPostNew(state); } /** * Returns the post currently being edited in its last known saved state, not * including unsaved edits. Returns an object containing relevant default post * values if the post has not yet been saved. * * @param {Object} state Global application state. * * @return {Object} Post object. */ const getCurrentPost = (0, _data.createRegistrySelector)(select => state => { const postId = getCurrentPostId(state); const postType = getCurrentPostType(state); const post = select('core').getRawEntityRecord('postType', postType, postId); if (post) { return post; } // This exists for compatibility with the previous selector behavior // which would guarantee an object return based on the editor reducer's // default empty object state. return EMPTY_OBJECT; }); /** * Returns the post type of the post currently being edited. * * @param {Object} state Global application state. * * @return {string} Post type. */ exports.getCurrentPost = getCurrentPost; function getCurrentPostType(state) { return state.postType; } /** * Returns the ID of the post currently being edited, or null if the post has * not yet been saved. * * @param {Object} state Global application state. * * @return {?number} ID of current post. */ function getCurrentPostId(state) { return state.postId; } /** * Returns the number of revisions of the post currently being edited. * * @param {Object} state Global application state. * * @return {number} Number of revisions. */ function getCurrentPostRevisionsCount(state) { return (0, _lodash.get)(getCurrentPost(state), ['_links', 'version-history', 0, 'count'], 0); } /** * Returns the last revision ID of the post currently being edited, * or null if the post has no revisions. * * @param {Object} state Global application state. * * @return {?number} ID of the last revision. */ function getCurrentPostLastRevisionId(state) { return (0, _lodash.get)(getCurrentPost(state), ['_links', 'predecessor-version', 0, 'id'], null); } /** * Returns any post values which have been changed in the editor but not yet * been saved. * * @param {Object} state Global application state. * * @return {Object} Object of key value pairs comprising unsaved edits. */ const getPostEdits = (0, _data.createRegistrySelector)(select => state => { const postType = getCurrentPostType(state); const postId = getCurrentPostId(state); return select('core').getEntityRecordEdits('postType', postType, postId) || EMPTY_OBJECT; }); /** * Returns a new reference when edited values have changed. This is useful in * inferring where an edit has been made between states by comparison of the * return values using strict equality. * * @deprecated since Gutenberg 6.5.0. * * @example * * ``` * const hasEditOccurred = ( * getReferenceByDistinctEdits( beforeState ) !== * getReferenceByDistinctEdits( afterState ) * ); * ``` * * @param {Object} state Editor state. * * @return {*} A value whose reference will change only when an edit occurs. */ exports.getPostEdits = getPostEdits; const getReferenceByDistinctEdits = (0, _data.createRegistrySelector)(select => () => /* state */ { (0, _deprecated.default)("`wp.data.select( 'core/editor' ).getReferenceByDistinctEdits`", { since: '5.4', alternative: "`wp.data.select( 'core' ).getReferenceByDistinctEdits`" }); return select('core').getReferenceByDistinctEdits(); }); /** * Returns an attribute value of the saved post. * * @param {Object} state Global application state. * @param {string} attributeName Post attribute name. * * @return {*} Post attribute value. */ exports.getReferenceByDistinctEdits = getReferenceByDistinctEdits; function getCurrentPostAttribute(state, attributeName) { switch (attributeName) { case 'type': return getCurrentPostType(state); case 'id': return getCurrentPostId(state); default: const post = getCurrentPost(state); if (!post.hasOwnProperty(attributeName)) { break; } return (0, _reducer.getPostRawValue)(post[attributeName]); } } /** * Returns a single attribute of the post being edited, preferring the unsaved * edit if one exists, but merging with the attribute value for the last known * saved state of the post (this is needed for some nested attributes like meta). * * @param {Object} state Global application state. * @param {string} attributeName Post attribute name. * * @return {*} Post attribute value. */ const getNestedEditedPostProperty = (state, attributeName) => { const edits = getPostEdits(state); if (!edits.hasOwnProperty(attributeName)) { return getCurrentPostAttribute(state, attributeName); } return { ...getCurrentPostAttribute(state, attributeName), ...edits[attributeName] }; }; /** * Returns a single attribute of the post being edited, preferring the unsaved * edit if one exists, but falling back to the attribute for the last known * saved state of the post. * * @param {Object} state Global application state. * @param {string} attributeName Post attribute name. * * @return {*} Post attribute value. */ function getEditedPostAttribute(state, attributeName) { // Special cases switch (attributeName) { case 'content': return getEditedPostContent(state); } // Fall back to saved post value if not edited. const edits = getPostEdits(state); if (!edits.hasOwnProperty(attributeName)) { return getCurrentPostAttribute(state, attributeName); } // Merge properties are objects which contain only the patch edit in state, // and thus must be merged with the current post attribute. if (_constants.EDIT_MERGE_PROPERTIES.has(attributeName)) { return getNestedEditedPostProperty(state, attributeName); } return edits[attributeName]; } /** * Returns an attribute value of the current autosave revision for a post, or * null if there is no autosave for the post. * * @deprecated since 5.6. Callers should use the `getAutosave( postType, postId, userId )` selector * from the '@wordpress/core-data' package and access properties on the returned * autosave object using getPostRawValue. * * @param {Object} state Global application state. * @param {string} attributeName Autosave attribute name. * * @return {*} Autosave attribute value. */ const getAutosaveAttribute = (0, _data.createRegistrySelector)(select => (state, attributeName) => { if (!(0, _lodash.includes)(_constants.AUTOSAVE_PROPERTIES, attributeName) && attributeName !== 'preview_link') { return; } const postType = getCurrentPostType(state); const postId = getCurrentPostId(state); const currentUserId = (0, _lodash.get)(select('core').getCurrentUser(), ['id']); const autosave = select('core').getAutosave(postType, postId, currentUserId); if (autosave) { return (0, _reducer.getPostRawValue)(autosave[attributeName]); } }); /** * Returns the current visibility of the post being edited, preferring the * unsaved value if different than the saved post. The return value is one of * "private", "password", or "public". * * @param {Object} state Global application state. * * @return {string} Post visibility. */ exports.getAutosaveAttribute = getAutosaveAttribute; function getEditedPostVisibility(state) { const status = getEditedPostAttribute(state, 'status'); if (status === 'private') { return 'private'; } const password = getEditedPostAttribute(state, 'password'); if (password) { return 'password'; } return 'public'; } /** * Returns true if post is pending review. * * @param {Object} state Global application state. * * @return {boolean} Whether current post is pending review. */ function isCurrentPostPending(state) { return getCurrentPost(state).status === 'pending'; } /** * Return true if the current post has already been published. * * @param {Object} state Global application state. * @param {Object?} currentPost Explicit current post for bypassing registry selector. * * @return {boolean} Whether the post has been published. */ function isCurrentPostPublished(state, currentPost) { const post = currentPost || getCurrentPost(state); return ['publish', 'private'].indexOf(post.status) !== -1 || post.status === 'future' && !(0, _date.isInTheFuture)(new Date(Number((0, _date.getDate)(post.date)) - _constants.ONE_MINUTE_IN_MS)); } /** * Returns true if post is already scheduled. * * @param {Object} state Global application state. * * @return {boolean} Whether current post is scheduled to be posted. */ function isCurrentPostScheduled(state) { return getCurrentPost(state).status === 'future' && !isCurrentPostPublished(state); } /** * Return true if the post being edited can be published. * * @param {Object} state Global application state. * * @return {boolean} Whether the post can been published. */ function isEditedPostPublishable(state) { const post = getCurrentPost(state); // TODO: Post being publishable should be superset of condition of post // being saveable. Currently this restriction is imposed at UI. // // See: <PostPublishButton /> (`isButtonEnabled` assigned by `isSaveable`) return isEditedPostDirty(state) || ['publish', 'private', 'future'].indexOf(post.status) === -1; } /** * Returns true if the post can be saved, or false otherwise. A post must * contain a title, an excerpt, or non-empty content to be valid for save. * * @param {Object} state Global application state. * * @return {boolean} Whether the post can be saved. */ function isEditedPostSaveable(state) { if (isSavingPost(state)) { return false; } // TODO: Post should not be saveable if not dirty. Cannot be added here at // this time since posts where meta boxes are present can be saved even if // the post is not dirty. Currently this restriction is imposed at UI, but // should be moved here. // // See: `isEditedPostPublishable` (includes `isEditedPostDirty` condition) // See: <PostSavedState /> (`forceIsDirty` prop) // See: <PostPublishButton /> (`forceIsDirty` prop) // See: https://github.com/WordPress/gutenberg/pull/4184 return !!getEditedPostAttribute(state, 'title') || !!getEditedPostAttribute(state, 'excerpt') || !isEditedPostEmpty(state) || _element.Platform.OS === 'native'; } /** * Returns true if the edited post has content. A post has content if it has at * least one saveable block or otherwise has a non-empty content property * assigned. * * @param {Object} state Global application state. * * @return {boolean} Whether post has content. */ function isEditedPostEmpty(state) { // While the condition of truthy content string is sufficient to determine // emptiness, testing saveable blocks length is a trivial operation. Since // this function can be called frequently, optimize for the fast case as a // condition of the mere existence of blocks. Note that the value of edited // content takes precedent over block content, and must fall through to the // default logic. const blocks = getEditorBlocks(state); if (blocks.length) { // Pierce the abstraction of the serializer in knowing that blocks are // joined with with newlines such that even if every individual block // produces an empty save result, the serialized content is non-empty. if (blocks.length > 1) { return false; } // There are two conditions under which the optimization cannot be // assumed, and a fallthrough to getEditedPostContent must occur: // // 1. getBlocksForSerialization has special treatment in omitting a // single unmodified default block. // 2. Comment delimiters are omitted for a freeform or unregistered // block in its serialization. The freeform block specifically may // produce an empty string in its saved output. // // For all other content, the single block is assumed to make a post // non-empty, if only by virtue of its own comment delimiters. const blockName = blocks[0].name; if (blockName !== (0, _blocks.getDefaultBlockName)() && blockName !== (0, _blocks.getFreeformContentHandlerName)()) { return false; } } return !getEditedPostContent(state); } /** * Returns true if the post can be autosaved, or false otherwise. * * @param {Object} state Global application state. * @param {Object} autosave A raw autosave object from the REST API. * * @return {boolean} Whether the post can be autosaved. */ const isEditedPostAutosaveable = (0, _data.createRegistrySelector)(select => state => { // A post must contain a title, an excerpt, or non-empty content to be valid for autosaving. if (!isEditedPostSaveable(state)) { return false; } // A post is not autosavable when there is a post autosave lock. if (isPostAutosavingLocked(state)) { return false; } const postType = getCurrentPostType(state); const postId = getCurrentPostId(state); const hasFetchedAutosave = select('core').hasFetchedAutosaves(postType, postId); const currentUserId = (0, _lodash.get)(select('core').getCurrentUser(), ['id']); // Disable reason - this line causes the side-effect of fetching the autosave // via a resolver, moving below the return would result in the autosave never // being fetched. // eslint-disable-next-line @wordpress/no-unused-vars-before-return const autosave = select('core').getAutosave(postType, postId, currentUserId); // If any existing autosaves have not yet been fetched, this function is // unable to determine if the post is autosaveable, so return false. if (!hasFetchedAutosave) { return false; } // If we don't already have an autosave, the post is autosaveable. if (!autosave) { return true; } // To avoid an expensive content serialization, use the content dirtiness // flag in place of content field comparison against the known autosave. // This is not strictly accurate, and relies on a tolerance toward autosave // request failures for unnecessary saves. if (hasChangedContent(state)) { return true; } // If the title or excerpt has changed, the post is autosaveable. return ['title', 'excerpt'].some(field => (0, _reducer.getPostRawValue)(autosave[field]) !== getEditedPostAttribute(state, field)); }); /** * Returns the current autosave, or null if one is not set (i.e. if the post * has yet to be autosaved, or has been saved or published since the last * autosave). * * @deprecated since 5.6. Callers should use the `getAutosave( postType, postId, userId )` * selector from the '@wordpress/core-data' package. * * @param {Object} state Editor state. * * @return {?Object} Current autosave, if exists. */ exports.isEditedPostAutosaveable = isEditedPostAutosaveable; const getAutosave = (0, _data.createRegistrySelector)(select => state => { (0, _deprecated.default)("`wp.data.select( 'core/editor' ).getAutosave()`", { since: '5.3', alternative: "`wp.data.select( 'core' ).getAutosave( postType, postId, userId )`" }); const postType = getCurrentPostType(state); const postId = getCurrentPostId(state); const currentUserId = (0, _lodash.get)(select('core').getCurrentUser(), ['id']); const autosave = select('core').getAutosave(postType, postId, currentUserId); return (0, _lodash.mapValues)((0, _lodash.pick)(autosave, _constants.AUTOSAVE_PROPERTIES), _reducer.getPostRawValue); }); /** * Returns the true if there is an existing autosave, otherwise false. * * @deprecated since 5.6. Callers should use the `getAutosave( postType, postId, userId )` selector * from the '@wordpress/core-data' package and check for a truthy value. * * @param {Object} state Global application state. * * @return {boolean} Whether there is an existing autosave. */ exports.getAutosave = getAutosave; const hasAutosave = (0, _data.createRegistrySelector)(select => state => { (0, _deprecated.default)("`wp.data.select( 'core/editor' ).hasAutosave()`", { since: '5.3', alternative: "`!! wp.data.select( 'core' ).getAutosave( postType, postId, userId )`" }); const postType = getCurrentPostType(state); const postId = getCurrentPostId(state); const currentUserId = (0, _lodash.get)(select('core').getCurrentUser(), ['id']); return !!select('core').getAutosave(postType, postId, currentUserId); }); /** * Return true if the post being edited is being scheduled. Preferring the * unsaved status values. * * @param {Object} state Global application state. * * @return {boolean} Whether the post has been published. */ exports.hasAutosave = hasAutosave; function isEditedPostBeingScheduled(state) { const date = getEditedPostAttribute(state, 'date'); // Offset the date by one minute (network latency) const checkedDate = new Date(Number((0, _date.getDate)(date)) - _constants.ONE_MINUTE_IN_MS); return (0, _date.isInTheFuture)(checkedDate); } /** * Returns whether the current post should be considered to have a "floating" * date (i.e. that it would publish "Immediately" rather than at a set time). * * Unlike in the PHP backend, the REST API returns a full date string for posts * where the 0000-00-00T00:00:00 placeholder is present in the database. To * infer that a post is set to publish "Immediately" we check whether the date * and modified date are the same. * * @param {Object} state Editor state. * * @return {boolean} Whether the edited post has a floating date value. */ function isEditedPostDateFloating(state) { const date = getEditedPostAttribute(state, 'date'); const modified = getEditedPostAttribute(state, 'modified'); // This should be the status of the persisted post // It shouldn't use the "edited" status otherwise it breaks the // infered post data floating status // See https://github.com/WordPress/gutenberg/issues/28083 const status = getCurrentPost(state).status; if (status === 'draft' || status === 'auto-draft' || status === 'pending') { return date === modified || date === null; } return false; } /** * Returns true if the post is currently being saved, or false otherwise. * * @param {Object} state Global application state. * * @return {boolean} Whether post is being saved. */ const isSavingPost = (0, _data.createRegistrySelector)(select => state => { const postType = getCurrentPostType(state); const postId = getCurrentPostId(state); return select('core').isSavingEntityRecord('postType', postType, postId); }); /** * Returns true if a previous post save was attempted successfully, or false * otherwise. * * @param {Object} state Global application state. * * @return {boolean} Whether the post was saved successfully. */ exports.isSavingPost = isSavingPost; const didPostSaveRequestSucceed = (0, _data.createRegistrySelector)(select => state => { const postType = getCurrentPostType(state); const postId = getCurrentPostId(state); return !select('core').getLastEntitySaveError('postType', postType, postId); }); /** * Returns true if a previous post save was attempted but failed, or false * otherwise. * * @param {Object} state Global application state. * * @return {boolean} Whether the post save failed. */ exports.didPostSaveRequestSucceed = didPostSaveRequestSucceed; const didPostSaveRequestFail = (0, _data.createRegistrySelector)(select => state => { const postType = getCurrentPostType(state); const postId = getCurrentPostId(state); return !!select('core').getLastEntitySaveError('postType', postType, postId); }); /** * Returns true if the post is autosaving, or false otherwise. * * @param {Object} state Global application state. * * @return {boolean} Whether the post is autosaving. */ exports.didPostSaveRequestFail = didPostSaveRequestFail; function isAutosavingPost(state) { if (!isSavingPost(state)) { return false; } return !!(0, _lodash.get)(state.saving, ['options', 'isAutosave']); } /** * Returns true if the post is being previewed, or false otherwise. * * @param {Object} state Global application state. * * @return {boolean} Whether the post is being previewed. */ function isPreviewingPost(state) { if (!isSavingPost(state)) { return false; } return !!state.saving.options.isPreview; } /** * Returns the post preview link * * @param {Object} state Global application state. * * @return {string?} Preview Link. */ function getEditedPostPreviewLink(state) { if (state.saving.pending || isSavingPost(state)) { return; } let previewLink = getAutosaveAttribute(state, 'preview_link'); if (!previewLink) { previewLink = getEditedPostAttribute(state, 'link'); if (previewLink) { previewLink = (0, _url.addQueryArgs)(previewLink, { preview: true }); } } const featuredImageId = getEditedPostAttribute(state, 'featured_media'); if (previewLink && featuredImageId) { return (0, _url.addQueryArgs)(previewLink, { _thumbnail_id: featuredImageId }); } return previewLink; } /** * Returns a suggested post format for the current post, inferred only if there * is a single block within the post and it is of a type known to match a * default post format. Returns null if the format cannot be determined. * * @param {Object} state Global application state. * * @return {?string} Suggested post format. */ function getSuggestedPostFormat(state) { const blocks = getEditorBlocks(state); if (blocks.length > 2) return null; let name; // If there is only one block in the content of the post grab its name // so we can derive a suitable post format from it. if (blocks.length === 1) { name = blocks[0].name; // check for core/embed `video` and `audio` eligible suggestions if (name === 'core/embed') { var _blocks$0$attributes; const provider = (_blocks$0$attributes = blocks[0].attributes) === null || _blocks$0$attributes === void 0 ? void 0 : _blocks$0$attributes.providerNameSlug; if (['youtube', 'vimeo'].includes(provider)) { name = 'core/video'; } else if (['spotify', 'soundcloud'].includes(provider)) { name = 'core/audio'; } } } // If there are two blocks in the content and the last one is a text blocks // grab the name of the first one to also suggest a post format from it. if (blocks.length === 2 && blocks[1].name === 'core/paragraph') { name = blocks[0].name; } // We only convert to default post formats in core. switch (name) { case 'core/image': return 'image'; case 'core/quote': case 'core/pullquote': return 'quote'; case 'core/gallery': return 'gallery'; case 'core/video': return 'video'; case 'core/audio': return 'audio'; default: return null; } } /** * Returns a set of blocks which are to be used in consideration of the post's * generated save content. * * @deprecated since Gutenberg 6.2.0. * * @param {Object} state Editor state. * * @return {WPBlock[]} Filtered set of blocks for save. */ function getBlocksForSerialization(state) { (0, _deprecated.default)('`core/editor` getBlocksForSerialization selector', { since: '5.3', alternative: 'getEditorBlocks', hint: 'Blocks serialization pre-processing occurs at save time' }); const blocks = state.editor.present.blocks.value; // WARNING: Any changes to the logic of this function should be verified // against the implementation of isEditedPostEmpty, which bypasses this // function for performance' sake, in an assumption of this current logic // being irrelevant to the optimized condition of emptiness. // A single unmodified default block is assumed to be equivalent to an // empty post. const isSingleUnmodifiedDefaultBlock = blocks.length === 1 && (0, _blocks.isUnmodifiedDefaultBlock)(blocks[0]); if (isSingleUnmodifiedDefaultBlock) { return []; } return blocks; } /** * Returns the content of the post being edited. * * @param {Object} state Global application state. * * @return {string} Post content. */ const getEditedPostContent = (0, _data.createRegistrySelector)(select => state => { const postId = getCurrentPostId(state); const postType = getCurrentPostType(state); const record = select('core').getEditedEntityRecord('postType', postType, postId); if (record) { if (typeof record.content === 'function') { return record.content(record); } else if (record.blocks) { return (0, _blocks.__unstableSerializeAndClean)(record.blocks); } else if (record.content) { return record.content; } } return ''; }); /** * Returns true if the post is being published, or false otherwise. * * @param {Object} state Global application state. * * @return {boolean} Whether post is being published. */ exports.getEditedPostContent = getEditedPostContent; function isPublishingPost(state) { return isSavingPost(state) && !isCurrentPostPublished(state) && getEditedPostAttribute(state, 'status') === 'publish'; } /** * Returns whether the permalink is editable or not. * * @param {Object} state Editor state. * * @return {boolean} Whether or not the permalink is editable. */ function isPermalinkEditable(state) { const permalinkTemplate = getEditedPostAttribute(state, 'permalink_template'); return _constants.PERMALINK_POSTNAME_REGEX.test(permalinkTemplate); } /** * Returns the permalink for the post. * * @param {Object} state Editor state. * * @return {?string} The permalink, or null if the post is not viewable. */ function getPermalink(state) { const permalinkParts = getPermalinkParts(state); if (!permalinkParts) { return null; } const { prefix, postName, suffix } = permalinkParts; if (isPermalinkEditable(state)) { return prefix + postName + suffix; } return prefix; } /** * Returns the slug for the post being edited, preferring a manually edited * value if one exists, then a sanitized version of the current post title, and * finally the post ID. * * @param {Object} state Editor state. * * @return {string} The current slug to be displayed in the editor */ function getEditedPostSlug(state) { return getEditedPostAttribute(state, 'slug') || (0, _url2.cleanForSlug)(getEditedPostAttribute(state, 'title')) || getCurrentPostId(state); } /** * Returns the permalink for a post, split into it's three parts: the prefix, * the postName, and the suffix. * * @param {Object} state Editor state. * * @return {Object} An object containing the prefix, postName, and suffix for * the permalink, or null if the post is not viewable. */ function getPermalinkParts(state) { const permalinkTemplate = getEditedPostAttribute(state, 'permalink_template'); if (!permalinkTemplate) { return null; } const postName = getEditedPostAttribute(state, 'slug') || getEditedPostAttribute(state, 'generated_slug'); const [prefix, suffix] = permalinkTemplate.split(_constants.PERMALINK_POSTNAME_REGEX); return { prefix, postName, suffix }; } /** * Returns whether the post is locked. * * @param {Object} state Global application state. * * @return {boolean} Is locked. */ function isPostLocked(state) { return state.postLock.isLocked; } /** * Returns whether post saving is locked. * * @param {Object} state Global application state. * * @return {boolean} Is locked. */ function isPostSavingLocked(state) { return Object.keys(state.postSavingLock).length > 0; } /** * Returns whether post autosaving is locked. * * @param {Object} state Global application state. * * @return {boolean} Is locked. */ function isPostAutosavingLocked(state) { return Object.keys(state.postAutosavingLock).length > 0; } /** * Returns whether the edition of the post has been taken over. * * @param {Object} state Global application state. * * @return {boolean} Is post lock takeover. */ function isPostLockTakeover(state) { return state.postLock.isTakeover; } /** * Returns details about the post lock user. * * @param {Object} state Global application state. * * @return {Object} A user object. */ function getPostLockUser(state) { return state.postLock.user; } /** * Returns the active post lock. * * @param {Object} state Global application state. * * @return {Object} The lock object. */ function getActivePostLock(state) { return state.postLock.activePostLock; } /** * Returns whether or not the user has the unfiltered_html capability. * * @param {Object} state Editor state. * * @return {boolean} Whether the user can or can't post unfiltered HTML. */ function canUserUseUnfilteredHTML(state) { return (0, _lodash.has)(getCurrentPost(state), ['_links', 'wp:action-unfiltered-html']); } /** * Returns whether the pre-publish panel should be shown * or skipped when the user clicks the "publish" button. * * @param {Object} state Global application state. * * @return {boolean} Whether the pre-publish panel should be shown or not. */ function isPublishSidebarEnabled(state) { if (state.preferences.hasOwnProperty('isPublishSidebarEnabled')) { return state.preferences.isPublishSidebarEnabled; } return _defaults.PREFERENCES_DEFAULTS.isPublishSidebarEnabled; } /** * Return the current block list. * * @param {Object} state * @return {Array} Block list. */ function getEditorBlocks(state) { return getEditedPostAttribute(state, 'blocks') || EMPTY_ARRAY; } /** * A block selection object. * * @typedef {Object} WPBlockSelection * * @property {string} clientId A block client ID. * @property {string} attributeKey A block attribute key. * @property {number} offset An attribute value offset, based on the rich * text value. See `wp.richText.create`. */ /** * Returns the current selection start. * * @param {Object} state * @return {WPBlockSelection} The selection start. * * @deprecated since Gutenberg 10.0.0. */ function getEditorSelectionStart(state) { var _getEditedPostAttribu; (0, _deprecated.default)("select('core/editor').getEditorSelectionStart", { since: '10.0', plugin: 'Gutenberg', alternative: "select('core/editor').getEditorSelection" }); return (_getEditedPostAttribu = getEditedPostAttribute(state, 'selection')) === null || _getEditedPostAttribu === void 0 ? void 0 : _getEditedPostAttribu.selectionStart; } /** * Returns the current selection end. * * @param {Object} state * @return {WPBlockSelection} The selection end. * * @deprecated since Gutenberg 10.0.0. */ function getEditorSelectionEnd(state) { var _getEditedPostAttribu2; (0, _deprecated.default)("select('core/editor').getEditorSelectionStart", { since: '10.0', plugin: 'Gutenberg', alternative: "select('core/editor').getEditorSelection" }); return (_getEditedPostAttribu2 = getEditedPostAttribute(state, 'selection')) === null || _getEditedPostAttribu2 === void 0 ? void 0 : _getEditedPostAttribu2.selectionEnd; } /** * Returns the current selection. * * @param {Object} state * @return {WPBlockSelection} The selection end. */ function getEditorSelection(state) { return getEditedPostAttribute(state, 'selection'); } /** * Is the editor ready * * @param {Object} state * @return {boolean} is Ready. */ function __unstableIsEditorReady(state) { return state.isReady; } /** * Returns the post editor settings. * * @param {Object} state Editor state. * * @return {Object} The editor settings object. */ function getEditorSettings(state) { return state.editorSettings; } /* * Backward compatibility */ /** * Returns state object prior to a specified optimist transaction ID, or `null` * if the transaction corresponding to the given ID cannot be found. * * @deprecated since Gutenberg 9.7.0. */ function getStateBeforeOptimisticTransaction() { (0, _deprecated.default)("select('core/editor').getStateBeforeOptimisticTransaction", { since: '5.7', hint: 'No state history is kept on this store anymore' }); return null; } /** * Returns true if an optimistic transaction is pending commit, for which the * before state satisfies the given predicate function. * * @deprecated since Gutenberg 9.7.0. */ function inSomeHistory() { (0, _deprecated.default)("select('core/editor').inSomeHistory", { since: '5.7', hint: 'No state history is kept on this store anymore' }); return false; } function getBlockEditorSelector(name) { return (0, _data.createRegistrySelector)(select => (state, ...args) => { (0, _deprecated.default)("`wp.data.select( 'core/editor' )." + name + '`', { since: '5.3', alternative: "`wp.data.select( 'core/block-editor' )." + name + '`' }); return select('core/block-editor')[name](...args); }); } /** * @see getBlockName in core/block-editor store. */ const getBlockName = getBlockEditorSelector('getBlockName'); /** * @see isBlockValid in core/block-editor store. */ exports.getBlockName = getBlockName; const isBlockValid = getBlockEditorSelector('isBlockValid'); /** * @see getBlockAttributes in core/block-editor store. */ exports.isBlockValid = isBlockValid; const getBlockAttributes = getBlockEditorSelector('getBlockAttributes'); /** * @see getBlock in core/block-editor store. */ exports.getBlockAttributes = getBlockAttributes; const getBlock = getBlockEditorSelector('getBlock'); /** * @see getBlocks in core/block-editor store. */ exports.getBlock = getBlock; const getBlocks = getBlockEditorSelector('getBlocks'); /** * @see __unstableGetBlockWithoutInnerBlocks in core/block-editor store. */ exports.getBlocks = getBlocks; const __unstableGetBlockWithoutInnerBlocks = getBlockEditorSelector('__unstableGetBlockWithoutInnerBlocks'); /** * @see getClientIdsOfDescendants in core/block-editor store. */ exports.__unstableGetBlockWithoutInnerBlocks = __unstableGetBlockWithoutInnerBlocks; const getClientIdsOfDescendants = getBlockEditorSelector('getClientIdsOfDescendants'); /** * @see getClientIdsWithDescendants in core/block-editor store. */ exports.getClientIdsOfDescendants = getClientIdsOfDescendants; const getClientIdsWithDescendants = getBlockEditorSelector('getClientIdsWithDescendants'); /** * @see getGlobalBlockCount in core/block-editor store. */ exports.getClientIdsWithDescendants = getClientIdsWithDescendants; const getGlobalBlockCount = getBlockEditorSelector('getGlobalBlockCount'); /** * @see getBlocksByClientId in core/block-editor store. */ exports.getGlobalBlockCount = getGlobalBlockCount; const getBlocksByClientId = getBlockEditorSelector('getBlocksByClientId'); /** * @see getBlockCount in core/block-editor store. */ exports.getBlocksByClientId = getBlocksByClientId; const getBlockCount = getBlockEditorSelector('getBlockCount'); /** * @see getBlockSelectionStart in core/block-editor store. */ exports.getBlockCount = getBlockCount; const getBlockSelectionStart = getBlockEditorSelector('getBlockSelectionStart'); /** * @see getBlockSelectionEnd in core/block-editor store. */ exports.getBlockSelectionStart = getBlockSelectionStart; const getBlockSelectionEnd = getBlockEditorSelector('getBlockSelectionEnd'); /** * @see getSelectedBlockCount in core/block-editor store. */ exports.getBlockSelectionEnd = getBlockSelectionEnd; const getSelectedBlockCount = getBlockEditorSelector('getSelectedBlockCount'); /** * @see hasSelectedBlock in core/block-editor store. */ exports.getSelectedBlockCount = getSelectedBlockCount; const hasSelectedBlock = getBlockEditorSelector('hasSelectedBlock'); /** * @see getSelectedBlockClientId in core/block-editor store. */ exports.hasSelectedBlock = hasSelectedBlock; const getSelectedBlockClientId = getBlockEditorSelector('getSelectedBlockClientId'); /** * @see getSelectedBlock in core/block-editor store. */ exports.getSelectedBlockClientId = getSelectedBlockClientId; const getSelectedBlock = getBlockEditorSelector('getSelectedBlock'); /** * @see getBlockRootClientId in core/block-editor store. */ exports.getSelectedBlock = getSelectedBlock; const getBlockRootClientId = getBlockEditorSelector('getBlockRootClientId'); /** * @see getBlockHierarchyRootClientId in core/block-editor store. */ exports.getBlockRootClientId = getBlockRootClientId; const getBlockHierarchyRootClientId = getBlockEditorSelector('getBlockHierarchyRootClientId'); /** * @see getAdjacentBlockClientId in core/block-editor store. */ exports.getBlockHierarchyRootClientId = getBlockHierarchyRootClientId; const getAdjacentBlockClientId = getBlockEditorSelector('getAdjacentBlockClientId'); /** * @see getPreviousBlockClientId in core/block-editor store. */ exports.getAdjacentBlockClientId = getAdjacentBlockClientId; const getPreviousBlockClientId = getBlockEditorSelector('getPreviousBlockClientId'); /** * @see getNextBlockClientId in core/block-editor store. */ exports.getPreviousBlockClientId = getPreviousBlockClientId; const getNextBlockClientId = getBlockEditorSelector('getNextBlockClientId'); /** * @see getSelectedBlocksInitialCaretPosition in core/block-editor store. */ exports.getNextBlockClientId = getNextBlockClientId; const getSelectedBlocksInitialCaretPosition = getBlockEditorSelector('getSelectedBlocksInitialCaretPosition'); /** * @see getMultiSelectedBlockClientIds in core/block-editor store. */ exports.getSelectedBlocksInitialCaretPosition = getSelectedBlocksInitialCaretPosition; const getMultiSelectedBlockClientIds = getBlockEditorSelector('getMultiSelectedBlockClientIds'); /** * @see getMultiSelectedBlocks in core/block-editor store. */ exports.getMultiSelectedBlockClientIds = getMultiSelectedBlockClientIds; const getMultiSelectedBlocks = getBlockEditorSelector('getMultiSelectedBlocks'); /** * @see getFirstMultiSelectedBlockClientId in core/block-editor store. */ exports.getMultiSelectedBlocks = getMultiSelectedBlocks; const getFirstMultiSelectedBlockClientId = getBlockEditorSelector('getFirstMultiSelectedBlockClientId'); /** * @see getLastMultiSelectedBlockClientId in core/block-editor store. */ exports.getFirstMultiSelectedBlockClientId = getFirstMultiSelectedBlockClientId; const getLastMultiSelectedBlockClientId = getBlockEditorSelector('getLastMultiSelectedBlockClientId'); /** * @see isFirstMultiSelectedBlock in core/block-editor store. */ exports.getLastMultiSelectedBlockClientId = getLastMultiSelectedBlockClientId; const isFirstMultiSelectedBlock = getBlockEditorSelector('isFirstMultiSelectedBlock'); /** * @see isBlockMultiSelected in core/block-editor store. */ exports.isFirstMultiSelectedBlock = isFirstMultiSelectedBlock; const isBlockMultiSelected = getBlockEditorSelector('isBlockMultiSelected'); /** * @see isAncestorMultiSelected in core/block-editor store. */ exports.isBlockMultiSelected = isBlockMultiSelected; const isAncestorMultiSelected = getBlockEditorSelector('isAncestorMultiSelected'); /** * @see getMultiSelectedBlocksStartClientId in core/block-editor store. */ exports.isAncestorMultiSelected = isAncestorMultiSelected; const getMultiSelectedBlocksStartClientId = getBlockEditorSelector('getMultiSelectedBlocksStartClientId'); /** * @see getMultiSelectedBlocksEndClientId in core/block-editor store. */ exports.getMultiSelectedBlocksStartClientId = getMultiSelectedBlocksStartClientId; const getMultiSelectedBlocksEndClientId = getBlockEditorSelector('getMultiSelectedBlocksEndClientId'); /** * @see getBlockOrder in core/block-editor store. */ exports.getMultiSelectedBlocksEndClientId = getMultiSelectedBlocksEndClientId; const getBlockOrder = getBlockEditorSelector('getBlockOrder'); /** * @see getBlockIndex in core/block-editor store. */ exports.getBlockOrder = getBlockOrder; const getBlockIndex = getBlockEditorSelector('getBlockIndex'); /** * @see isBlockSelected in core/block-editor store. */ exports.getBlockIndex = getBlockIndex; const isBlockSelected = getBlockEditorSelector('isBlockSelected'); /** * @see hasSelectedInnerBlock in core/block-editor store. */ exports.isBlockSelected = isBlockSelected; const hasSelectedInnerBlock =