UNPKG

@gechiui/block-editor

Version:
1,546 lines (1,302 loc) 79.8 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.__experimentalGetDirectInsertBlock = exports.__experimentalGetBlockListSettingsForBlocks = exports.__experimentalGetAllowedPatterns = exports.__experimentalGetAllowedBlocks = exports.__experimentalGetActiveBlockIdByBlockNames = void 0; exports.__experimentalGetLastBlockAttributeChanges = __experimentalGetLastBlockAttributeChanges; exports.__unstableGetClientIdsTree = exports.__unstableGetClientIdWithClientIdsTree = exports.__unstableGetBlockWithoutInnerBlocks = exports.__experimentalGetReusableBlockTitle = exports.__experimentalGetPatternsByBlockTypes = exports.__experimentalGetPatternTransformItems = exports.__experimentalGetParsedPattern = void 0; exports.__unstableIsLastBlockChangeIgnored = __unstableIsLastBlockChangeIgnored; exports.areInnerBlocksControlled = areInnerBlocksControlled; exports.canInsertBlockType = void 0; exports.canInsertBlocks = canInsertBlocks; exports.canMoveBlock = canMoveBlock; exports.canMoveBlocks = canMoveBlocks; exports.canRemoveBlock = canRemoveBlock; exports.canRemoveBlocks = canRemoveBlocks; exports.didAutomaticChange = didAutomaticChange; exports.getAdjacentBlockClientId = getAdjacentBlockClientId; exports.getBlock = getBlock; exports.getBlockAttributes = getBlockAttributes; exports.getBlockCount = getBlockCount; exports.getBlockHierarchyRootClientId = getBlockHierarchyRootClientId; exports.getBlockIndex = getBlockIndex; exports.getBlockInsertionPoint = getBlockInsertionPoint; exports.getBlockListSettings = getBlockListSettings; exports.getBlockMode = getBlockMode; exports.getBlockName = getBlockName; exports.getBlockOrder = getBlockOrder; exports.getBlockParentsByBlockName = exports.getBlockParents = void 0; exports.getBlockRootClientId = getBlockRootClientId; exports.getBlockSelectionEnd = getBlockSelectionEnd; exports.getBlockSelectionStart = getBlockSelectionStart; exports.getBlockTransformItems = void 0; exports.getBlocks = getBlocks; exports.getClientIdsWithDescendants = exports.getClientIdsOfDescendants = exports.getBlocksByClientId = void 0; exports.getDraggedBlockClientIds = getDraggedBlockClientIds; exports.getFirstMultiSelectedBlockClientId = getFirstMultiSelectedBlockClientId; exports.getInserterItems = exports.getGlobalBlockCount = void 0; exports.getLastMultiSelectedBlockClientId = getLastMultiSelectedBlockClientId; exports.getLowestCommonAncestorWithSelectedBlock = getLowestCommonAncestorWithSelectedBlock; exports.getMultiSelectedBlockClientIds = getMultiSelectedBlockClientIds; exports.getMultiSelectedBlocks = void 0; exports.getMultiSelectedBlocksEndClientId = getMultiSelectedBlocksEndClientId; exports.getMultiSelectedBlocksStartClientId = getMultiSelectedBlocksStartClientId; exports.getNextBlockClientId = getNextBlockClientId; exports.getPreviousBlockClientId = getPreviousBlockClientId; exports.getSelectedBlock = getSelectedBlock; exports.getSelectedBlockClientId = getSelectedBlockClientId; exports.getSelectedBlockClientIds = void 0; exports.getSelectedBlockCount = getSelectedBlockCount; exports.getSelectedBlocksInitialCaretPosition = getSelectedBlocksInitialCaretPosition; exports.getSelectionEnd = getSelectionEnd; exports.getSelectionStart = getSelectionStart; exports.getSettings = getSettings; exports.getTemplate = getTemplate; exports.getTemplateLock = getTemplateLock; exports.hasBlockMovingClientId = hasBlockMovingClientId; exports.hasInserterItems = void 0; exports.hasMultiSelection = hasMultiSelection; exports.hasSelectedBlock = hasSelectedBlock; exports.hasSelectedInnerBlock = hasSelectedInnerBlock; exports.isAncestorBeingDragged = isAncestorBeingDragged; exports.isAncestorMultiSelected = void 0; exports.isBlockBeingDragged = isBlockBeingDragged; exports.isBlockHighlighted = isBlockHighlighted; exports.isBlockInsertionPointVisible = isBlockInsertionPointVisible; exports.isBlockMultiSelected = isBlockMultiSelected; exports.isBlockSelected = isBlockSelected; exports.isBlockValid = isBlockValid; exports.isBlockWithinSelection = isBlockWithinSelection; exports.isCaretWithinFormattedText = isCaretWithinFormattedText; exports.isDraggingBlocks = isDraggingBlocks; exports.isFirstMultiSelectedBlock = isFirstMultiSelectedBlock; exports.isLastBlockChangePersistent = isLastBlockChangePersistent; exports.isMultiSelecting = isMultiSelecting; exports.isNavigationMode = isNavigationMode; exports.isSelectionEnabled = isSelectionEnabled; exports.isTyping = isTyping; exports.isValidTemplate = isValidTemplate; exports.wasBlockJustInserted = wasBlockJustInserted; var _lodash = require("lodash"); var _rememo = _interopRequireDefault(require("rememo")); var _blocks = require("@gechiui/blocks"); var _element = require("@gechiui/element"); var _hooks = require("@gechiui/hooks"); var _icons = require("@gechiui/icons"); /** * External dependencies */ /** * GeChiUI dependencies */ /** * A block selection object. * * @typedef {Object} GCBlockSelection * * @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 `gc.richText.create`. */ // Module constants const MILLISECONDS_PER_HOUR = 3600 * 1000; const MILLISECONDS_PER_DAY = 24 * 3600 * 1000; const MILLISECONDS_PER_WEEK = 7 * 24 * 3600 * 1000; /** * 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. * * @type {Array} */ const EMPTY_ARRAY = []; /** * Returns a block's name given its client ID, or null if no block exists with * the client ID. * * @param {Object} state Editor state. * @param {string} clientId Block client ID. * * @return {string} Block name. */ function getBlockName(state, clientId) { const block = state.blocks.byClientId[clientId]; const socialLinkName = 'core/social-link'; if (_element.Platform.OS !== 'web' && (block === null || block === void 0 ? void 0 : block.name) === socialLinkName) { const attributes = state.blocks.attributes[clientId]; const { service } = attributes; return service ? `${socialLinkName}-${service}` : socialLinkName; } return block ? block.name : null; } /** * Returns whether a block is valid or not. * * @param {Object} state Editor state. * @param {string} clientId Block client ID. * * @return {boolean} Is Valid. */ function isBlockValid(state, clientId) { const block = state.blocks.byClientId[clientId]; return !!block && block.isValid; } /** * Returns a block's attributes given its client ID, or null if no block exists with * the client ID. * * @param {Object} state Editor state. * @param {string} clientId Block client ID. * * @return {Object?} Block attributes. */ function getBlockAttributes(state, clientId) { const block = state.blocks.byClientId[clientId]; if (!block) { return null; } return state.blocks.attributes[clientId]; } /** * Returns a block given its client ID. This is a parsed copy of the block, * containing its `blockName`, `clientId`, and current `attributes` state. This * is not the block's registration settings, which must be retrieved from the * blocks module registration store. * * getBlock recurses through its inner blocks until all its children blocks have * been retrieved. Note that getBlock will not return the child inner blocks of * an inner block controller. This is because an inner block controller syncs * itself with its own entity, and should therefore not be included with the * blocks of a different entity. For example, say you call `getBlocks( TP )` to * get the blocks of a template part. If another template part is a child of TP, * then the nested template part's child blocks will not be returned. This way, * the template block itself is considered part of the parent, but the children * are not. * * @param {Object} state Editor state. * @param {string} clientId Block client ID. * * @return {Object} Parsed block object. */ function getBlock(state, clientId) { const block = state.blocks.byClientId[clientId]; if (!block) { return null; } return state.blocks.tree[clientId]; } const __unstableGetBlockWithoutInnerBlocks = (0, _rememo.default)((state, clientId) => { const block = state.blocks.byClientId[clientId]; if (!block) { return null; } return { ...block, attributes: getBlockAttributes(state, clientId) }; }, (state, clientId) => [state.blocks.byClientId[clientId], state.blocks.attributes[clientId]]); /** * Returns all block objects for the current post being edited as an array in * the order they appear in the post. Note that this will exclude child blocks * of nested inner block controllers. * * @param {Object} state Editor state. * @param {?string} rootClientId Optional root client ID of block list. * * @return {Object[]} Post blocks. */ exports.__unstableGetBlockWithoutInnerBlocks = __unstableGetBlockWithoutInnerBlocks; function getBlocks(state, rootClientId) { var _state$blocks$tree$tr; const treeKey = !rootClientId || !areInnerBlocksControlled(state, rootClientId) ? rootClientId || '' : 'controlled||' + rootClientId; return ((_state$blocks$tree$tr = state.blocks.tree[treeKey]) === null || _state$blocks$tree$tr === void 0 ? void 0 : _state$blocks$tree$tr.innerBlocks) || EMPTY_ARRAY; } /** * Returns a stripped down block object containing only its client ID, * and its inner blocks' client IDs. * * @param {Object} state Editor state. * @param {string} clientId Client ID of the block to get. * * @return {Object} Client IDs of the post blocks. */ const __unstableGetClientIdWithClientIdsTree = (0, _rememo.default)((state, clientId) => ({ clientId, innerBlocks: __unstableGetClientIdsTree(state, clientId) }), state => [state.blocks.order]); /** * Returns the block tree represented in the block-editor store from the * given root, consisting of stripped down block objects containing only * their client IDs, and their inner blocks' client IDs. * * @param {Object} state Editor state. * @param {?string} rootClientId Optional root client ID of block list. * * @return {Object[]} Client IDs of the post blocks. */ exports.__unstableGetClientIdWithClientIdsTree = __unstableGetClientIdWithClientIdsTree; const __unstableGetClientIdsTree = (0, _rememo.default)(function (state) { let rootClientId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; return (0, _lodash.map)(getBlockOrder(state, rootClientId), clientId => __unstableGetClientIdWithClientIdsTree(state, clientId)); }, state => [state.blocks.order]); /** * Returns an array containing the clientIds of all descendants * of the blocks given. * * @param {Object} state Global application state. * @param {Array} clientIds Array of blocks to inspect. * * @return {Array} ids of descendants. */ exports.__unstableGetClientIdsTree = __unstableGetClientIdsTree; const getClientIdsOfDescendants = (state, clientIds) => (0, _lodash.flatMap)(clientIds, clientId => { const descendants = getBlockOrder(state, clientId); return [...descendants, ...getClientIdsOfDescendants(state, descendants)]; }); /** * Returns an array containing the clientIds of the top-level blocks * and their descendants of any depth (for nested blocks). * * @param {Object} state Global application state. * * @return {Array} ids of top-level and descendant blocks. */ exports.getClientIdsOfDescendants = getClientIdsOfDescendants; const getClientIdsWithDescendants = (0, _rememo.default)(state => { const topLevelIds = getBlockOrder(state); return [...topLevelIds, ...getClientIdsOfDescendants(state, topLevelIds)]; }, state => [state.blocks.order]); /** * Returns the total number of blocks, or the total number of blocks with a specific name in a post. * The number returned includes nested blocks. * * @param {Object} state Global application state. * @param {?string} blockName Optional block name, if specified only blocks of that type will be counted. * * @return {number} Number of blocks in the post, or number of blocks with name equal to blockName. */ exports.getClientIdsWithDescendants = getClientIdsWithDescendants; const getGlobalBlockCount = (0, _rememo.default)((state, blockName) => { const clientIds = getClientIdsWithDescendants(state); if (!blockName) { return clientIds.length; } return (0, _lodash.reduce)(clientIds, (accumulator, clientId) => { const block = state.blocks.byClientId[clientId]; return block.name === blockName ? accumulator + 1 : accumulator; }, 0); }, state => [state.blocks.order, state.blocks.byClientId]); /** * Given an array of block client IDs, returns the corresponding array of block * objects. * * @param {Object} state Editor state. * @param {string[]} clientIds Client IDs for which blocks are to be returned. * * @return {GCBlock[]} Block objects. */ exports.getGlobalBlockCount = getGlobalBlockCount; const getBlocksByClientId = (0, _rememo.default)((state, clientIds) => (0, _lodash.map)((0, _lodash.castArray)(clientIds), clientId => getBlock(state, clientId)), (state, clientIds) => (0, _lodash.map)((0, _lodash.castArray)(clientIds), clientId => state.blocks.tree[clientId])); /** * Returns the number of blocks currently present in the post. * * @param {Object} state Editor state. * @param {?string} rootClientId Optional root client ID of block list. * * @return {number} Number of blocks in the post. */ exports.getBlocksByClientId = getBlocksByClientId; function getBlockCount(state, rootClientId) { return getBlockOrder(state, rootClientId).length; } /** * Returns the current selection start block client ID, attribute key and text * offset. * * @param {Object} state Block editor state. * * @return {GCBlockSelection} Selection start information. */ function getSelectionStart(state) { return state.selection.selectionStart; } /** * Returns the current selection end block client ID, attribute key and text * offset. * * @param {Object} state Block editor state. * * @return {GCBlockSelection} Selection end information. */ function getSelectionEnd(state) { return state.selection.selectionEnd; } /** * Returns the current block selection start. This value may be null, and it * may represent either a singular block selection or multi-selection start. * A selection is singular if its start and end match. * * @param {Object} state Global application state. * * @return {?string} Client ID of block selection start. */ function getBlockSelectionStart(state) { return state.selection.selectionStart.clientId; } /** * Returns the current block selection end. This value may be null, and it * may represent either a singular block selection or multi-selection end. * A selection is singular if its start and end match. * * @param {Object} state Global application state. * * @return {?string} Client ID of block selection end. */ function getBlockSelectionEnd(state) { return state.selection.selectionEnd.clientId; } /** * Returns the number of blocks currently selected in the post. * * @param {Object} state Global application state. * * @return {number} Number of blocks selected in the post. */ function getSelectedBlockCount(state) { const multiSelectedBlockCount = getMultiSelectedBlockClientIds(state).length; if (multiSelectedBlockCount) { return multiSelectedBlockCount; } return state.selection.selectionStart.clientId ? 1 : 0; } /** * Returns true if there is a single selected block, or false otherwise. * * @param {Object} state Editor state. * * @return {boolean} Whether a single block is selected. */ function hasSelectedBlock(state) { const { selectionStart, selectionEnd } = state.selection; return !!selectionStart.clientId && selectionStart.clientId === selectionEnd.clientId; } /** * Returns the currently selected block client ID, or null if there is no * selected block. * * @param {Object} state Editor state. * * @return {?string} Selected block client ID. */ function getSelectedBlockClientId(state) { const { selectionStart, selectionEnd } = state.selection; const { clientId } = selectionStart; if (!clientId || clientId !== selectionEnd.clientId) { return null; } return clientId; } /** * Returns the currently selected block, or null if there is no selected block. * * @param {Object} state Global application state. * * @return {?Object} Selected block. */ function getSelectedBlock(state) { const clientId = getSelectedBlockClientId(state); return clientId ? getBlock(state, clientId) : null; } /** * Given a block client ID, returns the root block from which the block is * nested, an empty string for top-level blocks, or null if the block does not * exist. * * @param {Object} state Editor state. * @param {string} clientId Block from which to find root client ID. * * @return {?string} Root client ID, if exists */ function getBlockRootClientId(state, clientId) { return state.blocks.parents[clientId] !== undefined ? state.blocks.parents[clientId] : null; } /** * Given a block client ID, returns the list of all its parents from top to bottom. * * @param {Object} state Editor state. * @param {string} clientId Block from which to find root client ID. * @param {boolean} ascending Order results from bottom to top (true) or top to bottom (false). * * @return {Array} ClientIDs of the parent blocks. */ const getBlockParents = (0, _rememo.default)(function (state, clientId) { let ascending = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; const parents = []; let current = clientId; while (!!state.blocks.parents[current]) { current = state.blocks.parents[current]; parents.push(current); } return ascending ? parents : parents.reverse(); }, state => [state.blocks.parents]); /** * Given a block client ID and a block name, returns the list of all its parents * from top to bottom, filtered by the given name(s). For example, if passed * 'core/group' as the blockName, it will only return parents which are group * blocks. If passed `[ 'core/group', 'core/cover']`, as the blockName, it will * return parents which are group blocks and parents which are cover blocks. * * @param {Object} state Editor state. * @param {string} clientId Block from which to find root client ID. * @param {string|string[]} blockName Block name(s) to filter. * @param {boolean} ascending Order results from bottom to top (true) or top to bottom (false). * * @return {Array} ClientIDs of the parent blocks. */ exports.getBlockParents = getBlockParents; const getBlockParentsByBlockName = (0, _rememo.default)(function (state, clientId, blockName) { let ascending = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; const parents = getBlockParents(state, clientId, ascending); return (0, _lodash.map)((0, _lodash.filter)((0, _lodash.map)(parents, id => ({ id, name: getBlockName(state, id) })), _ref => { let { name } = _ref; if (Array.isArray(blockName)) { return blockName.includes(name); } return name === blockName; }), _ref2 => { let { id } = _ref2; return id; }); }, state => [state.blocks.parents]); /** * Given a block client ID, returns the root of the hierarchy from which the block is nested, return the block itself for root level blocks. * * @param {Object} state Editor state. * @param {string} clientId Block from which to find root client ID. * * @return {string} Root client ID */ exports.getBlockParentsByBlockName = getBlockParentsByBlockName; function getBlockHierarchyRootClientId(state, clientId) { let current = clientId; let parent; do { parent = current; current = state.blocks.parents[current]; } while (current); return parent; } /** * Given a block client ID, returns the lowest common ancestor with selected client ID. * * @param {Object} state Editor state. * @param {string} clientId Block from which to find common ancestor client ID. * * @return {string} Common ancestor client ID or undefined */ function getLowestCommonAncestorWithSelectedBlock(state, clientId) { const selectedId = getSelectedBlockClientId(state); const clientParents = [...getBlockParents(state, clientId), clientId]; const selectedParents = [...getBlockParents(state, selectedId), selectedId]; let lowestCommonAncestor; const maxDepth = Math.min(clientParents.length, selectedParents.length); for (let index = 0; index < maxDepth; index++) { if (clientParents[index] === selectedParents[index]) { lowestCommonAncestor = clientParents[index]; } else { break; } } return lowestCommonAncestor; } /** * Returns the client ID of the block adjacent one at the given reference * startClientId and modifier directionality. Defaults start startClientId to * the selected block, and direction as next block. Returns null if there is no * adjacent block. * * @param {Object} state Editor state. * @param {?string} startClientId Optional client ID of block from which to * search. * @param {?number} modifier Directionality multiplier (1 next, -1 * previous). * * @return {?string} Return the client ID of the block, or null if none exists. */ function getAdjacentBlockClientId(state, startClientId) { let modifier = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1; // Default to selected block. if (startClientId === undefined) { startClientId = getSelectedBlockClientId(state); } // Try multi-selection starting at extent based on modifier. if (startClientId === undefined) { if (modifier < 0) { startClientId = getFirstMultiSelectedBlockClientId(state); } else { startClientId = getLastMultiSelectedBlockClientId(state); } } // Validate working start client ID. if (!startClientId) { return null; } // Retrieve start block root client ID, being careful to allow the falsey // empty string top-level root by explicitly testing against null. const rootClientId = getBlockRootClientId(state, startClientId); if (rootClientId === null) { return null; } const { order } = state.blocks; const orderSet = order[rootClientId]; const index = orderSet.indexOf(startClientId); const nextIndex = index + 1 * modifier; // Block was first in set and we're attempting to get previous. if (nextIndex < 0) { return null; } // Block was last in set and we're attempting to get next. if (nextIndex === orderSet.length) { return null; } // Assume incremented index is within the set. return orderSet[nextIndex]; } /** * Returns the previous block's client ID from the given reference start ID. * Defaults start to the selected block. Returns null if there is no previous * block. * * @param {Object} state Editor state. * @param {?string} startClientId Optional client ID of block from which to * search. * * @return {?string} Adjacent block's client ID, or null if none exists. */ function getPreviousBlockClientId(state, startClientId) { return getAdjacentBlockClientId(state, startClientId, -1); } /** * Returns the next block's client ID from the given reference start ID. * Defaults start to the selected block. Returns null if there is no next * block. * * @param {Object} state Editor state. * @param {?string} startClientId Optional client ID of block from which to * search. * * @return {?string} Adjacent block's client ID, or null if none exists. */ function getNextBlockClientId(state, startClientId) { return getAdjacentBlockClientId(state, startClientId, 1); } /* eslint-disable jsdoc/valid-types */ /** * Returns the initial caret position for the selected block. * This position is to used to position the caret properly when the selected block changes. * If the current block is not a RichText, having initial position set to 0 means "focus block" * * @param {Object} state Global application state. * * @return {0|-1|null} Initial position. */ function getSelectedBlocksInitialCaretPosition(state) { /* eslint-enable jsdoc/valid-types */ return state.initialPosition; } /** * Returns the current selection set of block client IDs (multiselection or single selection). * * @param {Object} state Editor state. * * @return {Array} Multi-selected block client IDs. */ const getSelectedBlockClientIds = (0, _rememo.default)(state => { const { selectionStart, selectionEnd } = state.selection; if (selectionStart.clientId === undefined || selectionEnd.clientId === undefined) { return EMPTY_ARRAY; } if (selectionStart.clientId === selectionEnd.clientId) { return [selectionStart.clientId]; } // Retrieve root client ID to aid in retrieving relevant nested block // order, being careful to allow the falsey empty string top-level root // by explicitly testing against null. const rootClientId = getBlockRootClientId(state, selectionStart.clientId); if (rootClientId === null) { return EMPTY_ARRAY; } const blockOrder = getBlockOrder(state, rootClientId); const startIndex = blockOrder.indexOf(selectionStart.clientId); const endIndex = blockOrder.indexOf(selectionEnd.clientId); if (startIndex > endIndex) { return blockOrder.slice(endIndex, startIndex + 1); } return blockOrder.slice(startIndex, endIndex + 1); }, state => [state.blocks.order, state.selection.selectionStart.clientId, state.selection.selectionEnd.clientId]); /** * Returns the current multi-selection set of block client IDs, or an empty * array if there is no multi-selection. * * @param {Object} state Editor state. * * @return {Array} Multi-selected block client IDs. */ exports.getSelectedBlockClientIds = getSelectedBlockClientIds; function getMultiSelectedBlockClientIds(state) { const { selectionStart, selectionEnd } = state.selection; if (selectionStart.clientId === selectionEnd.clientId) { return EMPTY_ARRAY; } return getSelectedBlockClientIds(state); } /** * Returns the current multi-selection set of blocks, or an empty array if * there is no multi-selection. * * @param {Object} state Editor state. * * @return {Array} Multi-selected block objects. */ const getMultiSelectedBlocks = (0, _rememo.default)(state => { const multiSelectedBlockClientIds = getMultiSelectedBlockClientIds(state); if (!multiSelectedBlockClientIds.length) { return EMPTY_ARRAY; } return multiSelectedBlockClientIds.map(clientId => getBlock(state, clientId)); }, state => [...getSelectedBlockClientIds.getDependants(state), state.blocks.byClientId, state.blocks.order, state.blocks.attributes]); /** * Returns the client ID of the first block in the multi-selection set, or null * if there is no multi-selection. * * @param {Object} state Editor state. * * @return {?string} First block client ID in the multi-selection set. */ exports.getMultiSelectedBlocks = getMultiSelectedBlocks; function getFirstMultiSelectedBlockClientId(state) { return (0, _lodash.first)(getMultiSelectedBlockClientIds(state)) || null; } /** * Returns the client ID of the last block in the multi-selection set, or null * if there is no multi-selection. * * @param {Object} state Editor state. * * @return {?string} Last block client ID in the multi-selection set. */ function getLastMultiSelectedBlockClientId(state) { return (0, _lodash.last)(getMultiSelectedBlockClientIds(state)) || null; } /** * Returns true if a multi-selection exists, and the block corresponding to the * specified client ID is the first block of the multi-selection set, or false * otherwise. * * @param {Object} state Editor state. * @param {string} clientId Block client ID. * * @return {boolean} Whether block is first in multi-selection. */ function isFirstMultiSelectedBlock(state, clientId) { return getFirstMultiSelectedBlockClientId(state) === clientId; } /** * Returns true if the client ID occurs within the block multi-selection, or * false otherwise. * * @param {Object} state Editor state. * @param {string} clientId Block client ID. * * @return {boolean} Whether block is in multi-selection set. */ function isBlockMultiSelected(state, clientId) { return getMultiSelectedBlockClientIds(state).indexOf(clientId) !== -1; } /** * Returns true if an ancestor of the block is multi-selected, or false * otherwise. * * @param {Object} state Editor state. * @param {string} clientId Block client ID. * * @return {boolean} Whether an ancestor of the block is in multi-selection * set. */ const isAncestorMultiSelected = (0, _rememo.default)((state, clientId) => { let ancestorClientId = clientId; let isMultiSelected = false; while (ancestorClientId && !isMultiSelected) { ancestorClientId = getBlockRootClientId(state, ancestorClientId); isMultiSelected = isBlockMultiSelected(state, ancestorClientId); } return isMultiSelected; }, state => [state.blocks.order, state.selection.selectionStart.clientId, state.selection.selectionEnd.clientId]); /** * Returns the client ID of the block which begins the multi-selection set, or * null if there is no multi-selection. * * This is not necessarily the first client ID in the selection. * * @see getFirstMultiSelectedBlockClientId * * @param {Object} state Editor state. * * @return {?string} Client ID of block beginning multi-selection. */ exports.isAncestorMultiSelected = isAncestorMultiSelected; function getMultiSelectedBlocksStartClientId(state) { const { selectionStart, selectionEnd } = state.selection; if (selectionStart.clientId === selectionEnd.clientId) { return null; } return selectionStart.clientId || null; } /** * Returns the client ID of the block which ends the multi-selection set, or * null if there is no multi-selection. * * This is not necessarily the last client ID in the selection. * * @see getLastMultiSelectedBlockClientId * * @param {Object} state Editor state. * * @return {?string} Client ID of block ending multi-selection. */ function getMultiSelectedBlocksEndClientId(state) { const { selectionStart, selectionEnd } = state.selection; if (selectionStart.clientId === selectionEnd.clientId) { return null; } return selectionEnd.clientId || null; } /** * Returns an array containing all block client IDs in the editor in the order * they appear. Optionally accepts a root client ID of the block list for which * the order should be returned, defaulting to the top-level block order. * * @param {Object} state Editor state. * @param {?string} rootClientId Optional root client ID of block list. * * @return {Array} Ordered client IDs of editor blocks. */ function getBlockOrder(state, rootClientId) { return state.blocks.order[rootClientId || ''] || EMPTY_ARRAY; } /** * Returns the index at which the block corresponding to the specified client * ID occurs within the block order, or `-1` if the block does not exist. * * @param {Object} state Editor state. * @param {string} clientId Block client ID. * * @return {number} Index at which block exists in order. */ function getBlockIndex(state, clientId) { const rootClientId = getBlockRootClientId(state, clientId); return getBlockOrder(state, rootClientId).indexOf(clientId); } /** * Returns true if the block corresponding to the specified client ID is * currently selected and no multi-selection exists, or false otherwise. * * @param {Object} state Editor state. * @param {string} clientId Block client ID. * * @return {boolean} Whether block is selected and multi-selection exists. */ function isBlockSelected(state, clientId) { const { selectionStart, selectionEnd } = state.selection; if (selectionStart.clientId !== selectionEnd.clientId) { return false; } return selectionStart.clientId === clientId; } /** * Returns true if one of the block's inner blocks is selected. * * @param {Object} state Editor state. * @param {string} clientId Block client ID. * @param {boolean} deep Perform a deep check. * * @return {boolean} Whether the block as an inner block selected */ function hasSelectedInnerBlock(state, clientId) { let deep = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; return (0, _lodash.some)(getBlockOrder(state, clientId), innerClientId => isBlockSelected(state, innerClientId) || isBlockMultiSelected(state, innerClientId) || deep && hasSelectedInnerBlock(state, innerClientId, deep)); } /** * Returns true if the block corresponding to the specified client ID is * currently selected but isn't the last of the selected blocks. Here "last" * refers to the block sequence in the document, _not_ the sequence of * multi-selection, which is why `state.selectionEnd` isn't used. * * @param {Object} state Editor state. * @param {string} clientId Block client ID. * * @return {boolean} Whether block is selected and not the last in the * selection. */ function isBlockWithinSelection(state, clientId) { if (!clientId) { return false; } const clientIds = getMultiSelectedBlockClientIds(state); const index = clientIds.indexOf(clientId); return index > -1 && index < clientIds.length - 1; } /** * Returns true if a multi-selection has been made, or false otherwise. * * @param {Object} state Editor state. * * @return {boolean} Whether multi-selection has been made. */ function hasMultiSelection(state) { const { selectionStart, selectionEnd } = state.selection; return selectionStart.clientId !== selectionEnd.clientId; } /** * Whether in the process of multi-selecting or not. This flag is only true * while the multi-selection is being selected (by mouse move), and is false * once the multi-selection has been settled. * * @see hasMultiSelection * * @param {Object} state Global application state. * * @return {boolean} True if multi-selecting, false if not. */ function isMultiSelecting(state) { return state.isMultiSelecting; } /** * Selector that returns if multi-selection is enabled or not. * * @param {Object} state Global application state. * * @return {boolean} True if it should be possible to multi-select blocks, false if multi-selection is disabled. */ function isSelectionEnabled(state) { return state.isSelectionEnabled; } /** * Returns the block's editing mode, defaulting to "visual" if not explicitly * assigned. * * @param {Object} state Editor state. * @param {string} clientId Block client ID. * * @return {Object} Block editing mode. */ function getBlockMode(state, clientId) { return state.blocksMode[clientId] || 'visual'; } /** * Returns true if the user is typing, or false otherwise. * * @param {Object} state Global application state. * * @return {boolean} Whether user is typing. */ function isTyping(state) { return state.isTyping; } /** * Returns true if the user is dragging blocks, or false otherwise. * * @param {Object} state Global application state. * * @return {boolean} Whether user is dragging blocks. */ function isDraggingBlocks(state) { return !!state.draggedBlocks.length; } /** * Returns the client ids of any blocks being directly dragged. * * This does not include children of a parent being dragged. * * @param {Object} state Global application state. * * @return {string[]} Array of dragged block client ids. */ function getDraggedBlockClientIds(state) { return state.draggedBlocks; } /** * Returns whether the block is being dragged. * * Only returns true if the block is being directly dragged, * not if the block is a child of a parent being dragged. * See `isAncestorBeingDragged` for child blocks. * * @param {Object} state Global application state. * @param {string} clientId Client id for block to check. * * @return {boolean} Whether the block is being dragged. */ function isBlockBeingDragged(state, clientId) { return state.draggedBlocks.includes(clientId); } /** * Returns whether a parent/ancestor of the block is being dragged. * * @param {Object} state Global application state. * @param {string} clientId Client id for block to check. * * @return {boolean} Whether the block's ancestor is being dragged. */ function isAncestorBeingDragged(state, clientId) { // Return early if no blocks are being dragged rather than // the more expensive check for parents. if (!isDraggingBlocks(state)) { return false; } const parents = getBlockParents(state, clientId); return (0, _lodash.some)(parents, parentClientId => isBlockBeingDragged(state, parentClientId)); } /** * Returns true if the caret is within formatted text, or false otherwise. * * @param {Object} state Global application state. * * @return {boolean} Whether the caret is within formatted text. */ function isCaretWithinFormattedText(state) { return state.isCaretWithinFormattedText; } /** * Returns the insertion point, the index at which the new inserted block would * be placed. Defaults to the last index. * * @param {Object} state Editor state. * * @return {Object} Insertion point object with `rootClientId`, `index`. */ function getBlockInsertionPoint(state) { let rootClientId, index; const { insertionPoint, selection: { selectionEnd } } = state; if (insertionPoint !== null) { return insertionPoint; } const { clientId } = selectionEnd; if (clientId) { rootClientId = getBlockRootClientId(state, clientId) || undefined; index = getBlockIndex(state, selectionEnd.clientId, rootClientId) + 1; } else { index = getBlockOrder(state).length; } return { rootClientId, index }; } /** * Returns true if we should show the block insertion point. * * @param {Object} state Global application state. * * @return {?boolean} Whether the insertion point is visible or not. */ function isBlockInsertionPointVisible(state) { return state.insertionPoint !== null; } /** * Returns whether the blocks matches the template or not. * * @param {boolean} state * @return {?boolean} Whether the template is valid or not. */ function isValidTemplate(state) { return state.template.isValid; } /** * Returns the defined block template * * @param {boolean} state * * @return {?Array} Block Template. */ function getTemplate(state) { return state.settings.template; } /** * Returns the defined block template lock. Optionally accepts a root block * client ID as context, otherwise defaulting to the global context. * * @param {Object} state Editor state. * @param {?string} rootClientId Optional block root client ID. * * @return {?string} Block Template Lock */ function getTemplateLock(state, rootClientId) { if (!rootClientId) { return state.settings.templateLock; } const blockListSettings = getBlockListSettings(state, rootClientId); if (!blockListSettings) { return null; } return blockListSettings.templateLock; } const checkAllowList = function (list, item) { let defaultResult = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; if ((0, _lodash.isBoolean)(list)) { return list; } if ((0, _lodash.isArray)(list)) { // TODO: when there is a canonical way to detect that we are editing a post // the following check should be changed to something like: // if ( list.includes( 'core/post-content' ) && getEditorMode() === 'post-content' && item === null ) if (list.includes('core/post-content') && item === null) { return true; } return list.includes(item); } return defaultResult; }; /** * Determines if the given block type is allowed to be inserted into the block list. * This function is not exported and not memoized because using a memoized selector * inside another memoized selector is just a waste of time. * * @param {Object} state Editor state. * @param {string|Object} blockName The block type object, e.g., the response * from the block directory; or a string name of * an installed block type, e.g.' core/paragraph'. * @param {?string} rootClientId Optional root client ID of block list. * * @return {boolean} Whether the given block type is allowed to be inserted. */ const canInsertBlockTypeUnmemoized = function (state, blockName) { let rootClientId = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; let blockType; if (blockName && 'object' === typeof blockName) { blockType = blockName; blockName = blockType.name; } else { blockType = (0, _blocks.getBlockType)(blockName); } if (!blockType) { return false; } const { allowedBlockTypes } = getSettings(state); const isBlockAllowedInEditor = checkAllowList(allowedBlockTypes, blockName, true); if (!isBlockAllowedInEditor) { return false; } const isLocked = !!getTemplateLock(state, rootClientId); if (isLocked) { return false; } const parentBlockListSettings = getBlockListSettings(state, rootClientId); // The parent block doesn't have settings indicating it doesn't support // inner blocks, return false. if (rootClientId && parentBlockListSettings === undefined) { return false; } const parentAllowedBlocks = parentBlockListSettings === null || parentBlockListSettings === void 0 ? void 0 : parentBlockListSettings.allowedBlocks; const hasParentAllowedBlock = checkAllowList(parentAllowedBlocks, blockName); const blockAllowedParentBlocks = blockType.parent; const parentName = getBlockName(state, rootClientId); const hasBlockAllowedParent = checkAllowList(blockAllowedParentBlocks, parentName); const canInsert = hasParentAllowedBlock === null && hasBlockAllowedParent === null || hasParentAllowedBlock === true || hasBlockAllowedParent === true; if (!canInsert) { return canInsert; } /** * This filter is an ad-hoc solution to prevent adding template parts inside post content. * Conceptually, having a filter inside a selector is bad pattern so this code will be * replaced by a declarative API that doesn't the following drawbacks: * * Filters are not reactive: Upon switching between "template mode" and non "template mode", * the filter and selector won't necessarily be executed again. For now, it doesn't matter much * because you can't switch between the two modes while the inserter stays open. * * Filters are global: Once they're defined, they will affect all editor instances and all registries. * An ideal API would only affect specific editor instances. */ return (0, _hooks.applyFilters)('blockEditor.__unstableCanInsertBlockType', canInsert, blockType, rootClientId, { // Pass bound selectors of the current registry. If we're in a nested // context, the data will differ from the one selected from the root // registry. getBlock: getBlock.bind(null, state), getBlockParentsByBlockName: getBlockParentsByBlockName.bind(null, state) }); }; /** * Determines if the given block type is allowed to be inserted into the block list. * * @param {Object} state Editor state. * @param {string} blockName The name of the block type, e.g.' core/paragraph'. * @param {?string} rootClientId Optional root client ID of block list. * * @return {boolean} Whether the given block type is allowed to be inserted. */ const canInsertBlockType = (0, _rememo.default)(canInsertBlockTypeUnmemoized, (state, blockName, rootClientId) => [state.blockListSettings[rootClientId], state.blocks.byClientId[rootClientId], state.settings.allowedBlockTypes, state.settings.templateLock]); /** * Determines if the given blocks are allowed to be inserted into the block * list. * * @param {Object} state Editor state. * @param {string} clientIds The block client IDs to be inserted. * @param {?string} rootClientId Optional root client ID of block list. * * @return {boolean} Whether the given blocks are allowed to be inserted. */ exports.canInsertBlockType = canInsertBlockType; function canInsertBlocks(state, clientIds) { let rootClientId = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; return clientIds.every(id => canInsertBlockType(state, getBlockName(state, id), rootClientId)); } /** * Determines if the given block is allowed to be deleted. * * @param {Object} state Editor state. * @param {string} clientId The block client Id. * @param {?string} rootClientId Optional root client ID of block list. * * @return {boolean} Whether the given block is allowed to be removed. */ function canRemoveBlock(state, clientId) { let rootClientId = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; const attributes = getBlockAttributes(state, clientId); // attributes can be null if the block is already deleted. if (attributes === null) { return true; } const { lock } = attributes; const parentIsLocked = !!getTemplateLock(state, rootClientId); // If we don't have a lock on the blockType level, we differ to the parent templateLock. if (lock === undefined || (lock === null || lock === void 0 ? void 0 : lock.remove) === undefined) { return !parentIsLocked; } // when remove is true, it means we cannot remove it. return !(lock !== null && lock !== void 0 && lock.remove); } /** * Determines if the given blocks are allowed to be removed. * * @param {Object} state Editor state. * @param {string} clientIds The block client IDs to be removed. * @param {?string} rootClientId Optional root client ID of block list. * * @return {boolean} Whether the given blocks are allowed to be removed. */ function canRemoveBlocks(state, clientIds) { let rootClientId = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; return clientIds.every(clientId => canRemoveBlock(state, clientId, rootClientId)); } /** * Determines if the given block is allowed to be moved. * * @param {Object} state Editor state. * @param {string} clientId The block client Id. * @param {?string} rootClientId Optional root client ID of block list. * * @return {boolean} Whether the given block is allowed to be moved. */ function canMoveBlock(state, clientId) { let rootClientId = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; const attributes = getBlockAttributes(state, clientId); if (attributes === null) { return; } const { lock } = attributes; const parentIsLocked = getTemplateLock(state, rootClientId) === 'all'; // If we don't have a lock on the blockType level, we differ to the parent templateLock. if (lock === undefined || (lock === null || lock === void 0 ? void 0 : lock.move) === undefined) { return !parentIsLocked; } // when move is true, it means we cannot move it. return !(lock !== null && lock !== void 0 && lock.move); } /** * Determines if the given blocks are allowed to be moved. * * @param {Object} state Editor state. * @param {string} clientIds The block client IDs to be moved. * @param {?string} rootClientId Optional root client ID of block list. * * @return {boolean} Whether the given blocks are allowed to be moved. */ function canMoveBlocks(state, clientIds) { let rootClientId = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; return clientIds.every(clientId => canMoveBlock(state, clientId, rootClientId)); } /** * Returns information about how recently and frequently a block has been inserted. * * @param {Object} state Global application state. * @param {string} id A string which identifies the insert, e.g. 'core/block/12' * * @return {?{ time: number, count: number }} An object containing `time` which is when the last * insert occurred as a UNIX epoch, and `count` which is * the number of inserts that have occurred. */ function getInsertUsage(state, id) { var _state$preferences$in, _state$preferences$in2; return (_state$preferences$in = (_state$preferences$in2 = state.preferences.insertUsage) === null || _state$preferences$in2 === void 0 ? void 0 : _state$preferences$in2[id]) !== null && _state$preferences$in !== void 0 ? _state$preferences$in : null; } /** * Returns whether we can show a block type in the inserter * * @param {Object} state Global State * @param {Object} blockType BlockType * @param {?string} rootClientId Optional root client ID of block list. * * @return {boolean} Whether the given block type is allowed to be shown in the inserter. */ const canIncludeBlockTypeInInserter = (state, blockType, rootClientId) => { if (!(0, _blocks.hasBlockSupport)(blockType, 'inserter', true)) { return false; } return canInsertBlockTypeUnmemoized(state, blockType.name, rootClientId); }; /** * Return a function to be used to tranform a block variation to an inserter item * * @param {Object} state Global State * @param {Object} item Denormalized i