UNPKG

@wordpress/block-editor

Version:
1,540 lines (1,539 loc) 53.8 kB
// packages/block-editor/src/store/selectors.js import { getBlockType, getBlockTypes, getBlockVariations, hasBlockSupport, getPossibleBlockTransformations, switchToBlockType, store as blocksStore, privateApis as blocksPrivateApis } from "@wordpress/blocks"; import { Platform } from "@wordpress/element"; import { applyFilters } from "@wordpress/hooks"; import { symbol } from "@wordpress/icons"; import { create, remove, toHTMLString } from "@wordpress/rich-text"; import deprecated from "@wordpress/deprecated"; import { createSelector, createRegistrySelector } from "@wordpress/data"; import { isFiltered, checkAllowListRecursive, checkAllowList, getAllPatternsDependants, getInsertBlockTypeDependants, getParsedPattern, getGrammar, mapUserPattern } from "./utils"; import { orderBy } from "../utils/sorting"; import { STORE_NAME } from "./constants"; import { unlock } from "../lock-unlock"; import { getContentLockingParent, getEditedContentOnlySection, getSectionRootClientId, isSectionBlock, getParentSectionBlock, isZoomOut, isContainerInsertableToInContentOnlyMode } from "./private-selectors"; var { isContentBlock } = unlock(blocksPrivateApis); var MILLISECONDS_PER_HOUR = 3600 * 1e3; var MILLISECONDS_PER_DAY = 24 * 3600 * 1e3; var MILLISECONDS_PER_WEEK = 7 * 24 * 3600 * 1e3; var EMPTY_ARRAY = []; var EMPTY_SET = /* @__PURE__ */ new Set(); var DEFAULT_INSERTER_OPTIONS = { [isFiltered]: true }; function getBlockName(state, clientId) { const block = state.blocks.byClientId.get(clientId); const socialLinkName = "core/social-link"; if (Platform.OS !== "web" && block?.name === socialLinkName) { const attributes = state.blocks.attributes.get(clientId); const { service } = attributes ?? {}; return service ? `${socialLinkName}-${service}` : socialLinkName; } return block ? block.name : null; } function isBlockValid(state, clientId) { const block = state.blocks.byClientId.get(clientId); return !!block && block.isValid; } function getBlockAttributes(state, clientId) { const block = state.blocks.byClientId.get(clientId); if (!block) { return null; } return state.blocks.attributes.get(clientId); } function getBlock(state, clientId) { if (!state.blocks.byClientId.has(clientId)) { return null; } return state.blocks.tree.get(clientId); } var __unstableGetBlockWithoutInnerBlocks = createSelector( (state, clientId) => { const block = state.blocks.byClientId.get(clientId); if (!block) { return null; } return { ...block, attributes: getBlockAttributes(state, clientId) }; }, (state, clientId) => [ state.blocks.byClientId.get(clientId), state.blocks.attributes.get(clientId) ] ); function getBlocks(state, rootClientId) { const treeKey = !rootClientId || !areInnerBlocksControlled(state, rootClientId) ? rootClientId || "" : "controlled||" + rootClientId; return state.blocks.tree.get(treeKey)?.innerBlocks || EMPTY_ARRAY; } var __unstableGetClientIdWithClientIdsTree = createSelector( (state, clientId) => { deprecated( "wp.data.select( 'core/block-editor' ).__unstableGetClientIdWithClientIdsTree", { since: "6.3", version: "6.5" } ); return { clientId, innerBlocks: __unstableGetClientIdsTree(state, clientId) }; }, (state) => [state.blocks.order] ); var __unstableGetClientIdsTree = createSelector( (state, rootClientId = "") => { deprecated( "wp.data.select( 'core/block-editor' ).__unstableGetClientIdsTree", { since: "6.3", version: "6.5" } ); return getBlockOrder(state, rootClientId).map( (clientId) => __unstableGetClientIdWithClientIdsTree(state, clientId) ); }, (state) => [state.blocks.order] ); var getClientIdsOfDescendants = createSelector( (state, rootIds) => { rootIds = Array.isArray(rootIds) ? [...rootIds] : [rootIds]; const ids = []; for (const rootId of rootIds) { const order = state.blocks.order.get(rootId); if (order) { ids.push(...order); } } let index = 0; while (index < ids.length) { const id = ids[index]; const order = state.blocks.order.get(id); if (order) { ids.splice(index + 1, 0, ...order); } index++; } return ids; }, (state) => [state.blocks.order] ); var getClientIdsWithDescendants = (state) => getClientIdsOfDescendants(state, ""); var getGlobalBlockCount = createSelector( (state, blockName) => { const clientIds = getClientIdsWithDescendants(state); if (!blockName) { return clientIds.length; } let count = 0; for (const clientId of clientIds) { const block = state.blocks.byClientId.get(clientId); if (block.name === blockName) { count++; } } return count; }, (state) => [state.blocks.order, state.blocks.byClientId] ); var getBlocksByName = createSelector( (state, blockName) => { if (!blockName) { return EMPTY_ARRAY; } const blockNames = Array.isArray(blockName) ? blockName : [blockName]; const clientIds = getClientIdsWithDescendants(state); const foundBlocks = clientIds.filter((clientId) => { const block = state.blocks.byClientId.get(clientId); return blockNames.includes(block.name); }); return foundBlocks.length > 0 ? foundBlocks : EMPTY_ARRAY; }, (state) => [state.blocks.order, state.blocks.byClientId] ); function __experimentalGetGlobalBlocksByName(state, blockName) { deprecated( "wp.data.select( 'core/block-editor' ).__experimentalGetGlobalBlocksByName", { since: "6.5", alternative: `wp.data.select( 'core/block-editor' ).getBlocksByName` } ); return getBlocksByName(state, blockName); } var getBlocksByClientId = createSelector( (state, clientIds) => (Array.isArray(clientIds) ? clientIds : [clientIds]).map( (clientId) => getBlock(state, clientId) ), (state, clientIds) => (Array.isArray(clientIds) ? clientIds : [clientIds]).map( (clientId) => state.blocks.tree.get(clientId) ) ); var getBlockNamesByClientId = createSelector( (state, clientIds) => getBlocksByClientId(state, clientIds).filter(Boolean).map((block) => block.name), (state, clientIds) => getBlocksByClientId(state, clientIds) ); function getBlockCount(state, rootClientId) { return getBlockOrder(state, rootClientId).length; } function getSelectionStart(state) { return state.selection.selectionStart; } function getSelectionEnd(state) { return state.selection.selectionEnd; } function getBlockSelectionStart(state) { return state.selection.selectionStart.clientId; } function getBlockSelectionEnd(state) { return state.selection.selectionEnd.clientId; } function getSelectedBlockCount(state) { const multiSelectedBlockCount = getMultiSelectedBlockClientIds(state).length; if (multiSelectedBlockCount) { return multiSelectedBlockCount; } return state.selection.selectionStart.clientId ? 1 : 0; } function hasSelectedBlock(state) { const { selectionStart, selectionEnd } = state.selection; return !!selectionStart.clientId && selectionStart.clientId === selectionEnd.clientId; } function getSelectedBlockClientId(state) { const { selectionStart, selectionEnd } = state.selection; const { clientId } = selectionStart; if (!clientId || clientId !== selectionEnd.clientId) { return null; } return clientId; } function getSelectedBlock(state) { const clientId = getSelectedBlockClientId(state); return clientId ? getBlock(state, clientId) : null; } function getBlockRootClientId(state, clientId) { return state.blocks.parents.get(clientId) ?? null; } var getBlockParents = createSelector( (state, clientId, ascending = false) => { const parents = []; let current = clientId; while (current = state.blocks.parents.get(current)) { parents.push(current); } if (!parents.length) { return EMPTY_ARRAY; } return ascending ? parents : parents.reverse(); }, (state) => [state.blocks.parents] ); var getBlockParentsByBlockName = createSelector( (state, clientId, blockName, ascending = false) => { const parents = getBlockParents(state, clientId, ascending); const hasName = Array.isArray(blockName) ? (name) => blockName.includes(name) : (name) => blockName === name; return parents.filter((id) => hasName(getBlockName(state, id))); }, (state) => [state.blocks.parents] ); function getBlockHierarchyRootClientId(state, clientId) { let current = clientId; let parent; do { parent = current; current = state.blocks.parents.get(current); } while (current); return parent; } 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; } function getAdjacentBlockClientId(state, startClientId, modifier = 1) { if (startClientId === void 0) { startClientId = getSelectedBlockClientId(state); } if (startClientId === void 0) { if (modifier < 0) { startClientId = getFirstMultiSelectedBlockClientId(state); } else { startClientId = getLastMultiSelectedBlockClientId(state); } } if (!startClientId) { return null; } const rootClientId = getBlockRootClientId(state, startClientId); if (rootClientId === null) { return null; } const { order } = state.blocks; const orderSet = order.get(rootClientId); const index = orderSet.indexOf(startClientId); const nextIndex = index + 1 * modifier; if (nextIndex < 0) { return null; } if (nextIndex === orderSet.length) { return null; } return orderSet[nextIndex]; } function getPreviousBlockClientId(state, startClientId) { return getAdjacentBlockClientId(state, startClientId, -1); } function getNextBlockClientId(state, startClientId) { return getAdjacentBlockClientId(state, startClientId, 1); } function getSelectedBlocksInitialCaretPosition(state) { return state.initialPosition; } var getSelectedBlockClientIds = createSelector( (state) => { const { selectionStart, selectionEnd } = state.selection; if (!selectionStart.clientId || !selectionEnd.clientId) { return EMPTY_ARRAY; } if (selectionStart.clientId === selectionEnd.clientId) { return [selectionStart.clientId]; } 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 ] ); function getMultiSelectedBlockClientIds(state) { const { selectionStart, selectionEnd } = state.selection; if (selectionStart.clientId === selectionEnd.clientId) { return EMPTY_ARRAY; } return getSelectedBlockClientIds(state); } var getMultiSelectedBlocks = createSelector( (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 ] ); function getFirstMultiSelectedBlockClientId(state) { return getMultiSelectedBlockClientIds(state)[0] || null; } function getLastMultiSelectedBlockClientId(state) { const selectedClientIds = getMultiSelectedBlockClientIds(state); return selectedClientIds[selectedClientIds.length - 1] || null; } function isFirstMultiSelectedBlock(state, clientId) { return getFirstMultiSelectedBlockClientId(state) === clientId; } function isBlockMultiSelected(state, clientId) { return getMultiSelectedBlockClientIds(state).indexOf(clientId) !== -1; } var isAncestorMultiSelected = createSelector( (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 ] ); function getMultiSelectedBlocksStartClientId(state) { const { selectionStart, selectionEnd } = state.selection; if (selectionStart.clientId === selectionEnd.clientId) { return null; } return selectionStart.clientId || null; } function getMultiSelectedBlocksEndClientId(state) { const { selectionStart, selectionEnd } = state.selection; if (selectionStart.clientId === selectionEnd.clientId) { return null; } return selectionEnd.clientId || null; } function __unstableIsFullySelected(state) { const selectionAnchor = getSelectionStart(state); const selectionFocus = getSelectionEnd(state); return !selectionAnchor.attributeKey && !selectionFocus.attributeKey && typeof selectionAnchor.offset === "undefined" && typeof selectionFocus.offset === "undefined"; } function __unstableIsSelectionCollapsed(state) { const selectionAnchor = getSelectionStart(state); const selectionFocus = getSelectionEnd(state); return !!selectionAnchor && !!selectionFocus && selectionAnchor.clientId === selectionFocus.clientId && selectionAnchor.attributeKey === selectionFocus.attributeKey && selectionAnchor.offset === selectionFocus.offset; } function __unstableSelectionHasUnmergeableBlock(state) { return getSelectedBlockClientIds(state).some((clientId) => { const blockName = getBlockName(state, clientId); const blockType = getBlockType(blockName); return !blockType.merge; }); } function __unstableIsSelectionMergeable(state, isForward) { const selectionAnchor = getSelectionStart(state); const selectionFocus = getSelectionEnd(state); if (selectionAnchor.clientId === selectionFocus.clientId) { return false; } if (!selectionAnchor.attributeKey || !selectionFocus.attributeKey || typeof selectionAnchor.offset === "undefined" || typeof selectionFocus.offset === "undefined") { return false; } const anchorRootClientId = getBlockRootClientId( state, selectionAnchor.clientId ); const focusRootClientId = getBlockRootClientId( state, selectionFocus.clientId ); if (anchorRootClientId !== focusRootClientId) { return false; } const blockOrder = getBlockOrder(state, anchorRootClientId); const anchorIndex = blockOrder.indexOf(selectionAnchor.clientId); const focusIndex = blockOrder.indexOf(selectionFocus.clientId); let selectionStart, selectionEnd; if (anchorIndex > focusIndex) { selectionStart = selectionFocus; selectionEnd = selectionAnchor; } else { selectionStart = selectionAnchor; selectionEnd = selectionFocus; } const targetBlockClientId = isForward ? selectionEnd.clientId : selectionStart.clientId; const blockToMergeClientId = isForward ? selectionStart.clientId : selectionEnd.clientId; const targetBlockName = getBlockName(state, targetBlockClientId); const targetBlockType = getBlockType(targetBlockName); if (!targetBlockType.merge) { return false; } const blockToMerge = getBlock(state, blockToMergeClientId); if (blockToMerge.name === targetBlockName) { return true; } const blocksToMerge = switchToBlockType(blockToMerge, targetBlockName); return blocksToMerge && blocksToMerge.length; } var __unstableGetSelectedBlocksWithPartialSelection = (state) => { const selectionAnchor = getSelectionStart(state); const selectionFocus = getSelectionEnd(state); if (selectionAnchor.clientId === selectionFocus.clientId) { return EMPTY_ARRAY; } if (!selectionAnchor.attributeKey || !selectionFocus.attributeKey || typeof selectionAnchor.offset === "undefined" || typeof selectionFocus.offset === "undefined") { return EMPTY_ARRAY; } const anchorRootClientId = getBlockRootClientId( state, selectionAnchor.clientId ); const focusRootClientId = getBlockRootClientId( state, selectionFocus.clientId ); if (anchorRootClientId !== focusRootClientId) { return EMPTY_ARRAY; } const blockOrder = getBlockOrder(state, anchorRootClientId); const anchorIndex = blockOrder.indexOf(selectionAnchor.clientId); const focusIndex = blockOrder.indexOf(selectionFocus.clientId); const [selectionStart, selectionEnd] = anchorIndex > focusIndex ? [selectionFocus, selectionAnchor] : [selectionAnchor, selectionFocus]; const blockA = getBlock(state, selectionStart.clientId); const blockB = getBlock(state, selectionEnd.clientId); const htmlA = blockA.attributes[selectionStart.attributeKey]; const htmlB = blockB.attributes[selectionEnd.attributeKey]; let valueA = create({ html: htmlA }); let valueB = create({ html: htmlB }); valueA = remove(valueA, 0, selectionStart.offset); valueB = remove(valueB, selectionEnd.offset, valueB.text.length); return [ { ...blockA, attributes: { ...blockA.attributes, [selectionStart.attributeKey]: toHTMLString({ value: valueA }) } }, { ...blockB, attributes: { ...blockB.attributes, [selectionEnd.attributeKey]: toHTMLString({ value: valueB }) } } ]; }; function getBlockOrder(state, rootClientId) { return state.blocks.order.get(rootClientId || "") || EMPTY_ARRAY; } function getBlockIndex(state, clientId) { const rootClientId = getBlockRootClientId(state, clientId); return getBlockOrder(state, rootClientId).indexOf(clientId); } function isBlockSelected(state, clientId) { const { selectionStart, selectionEnd } = state.selection; if (selectionStart.clientId !== selectionEnd.clientId) { return false; } return selectionStart.clientId === clientId; } function hasSelectedInnerBlock(state, clientId, deep = false) { const selectedBlockClientIds = getSelectedBlockClientIds(state); if (!selectedBlockClientIds.length) { return false; } if (deep) { return selectedBlockClientIds.some( (id) => ( // Pass true because we don't care about order and it's more // performant. getBlockParents(state, id, true).includes(clientId) ) ); } return selectedBlockClientIds.some( (id) => getBlockRootClientId(state, id) === clientId ); } function hasDraggedInnerBlock(state, clientId, deep = false) { return getBlockOrder(state, clientId).some( (innerClientId) => isBlockBeingDragged(state, innerClientId) || deep && hasDraggedInnerBlock(state, innerClientId, deep) ); } function isBlockWithinSelection(state, clientId) { if (!clientId) { return false; } const clientIds = getMultiSelectedBlockClientIds(state); const index = clientIds.indexOf(clientId); return index > -1 && index < clientIds.length - 1; } function hasMultiSelection(state) { const { selectionStart, selectionEnd } = state.selection; return selectionStart.clientId !== selectionEnd.clientId; } function isMultiSelecting(state) { return state.isMultiSelecting; } function isSelectionEnabled(state) { return state.isSelectionEnabled; } function getBlockMode(state, clientId) { return state.blocksMode[clientId] || "visual"; } function isTyping(state) { return state.isTyping; } function isDraggingBlocks(state) { return !!state.draggedBlocks.length; } function getDraggedBlockClientIds(state) { return state.draggedBlocks; } function isBlockBeingDragged(state, clientId) { return state.draggedBlocks.includes(clientId); } function isAncestorBeingDragged(state, clientId) { if (!isDraggingBlocks(state)) { return false; } const parents = getBlockParents(state, clientId); return parents.some( (parentClientId) => isBlockBeingDragged(state, parentClientId) ); } function isCaretWithinFormattedText() { deprecated( 'wp.data.select( "core/block-editor" ).isCaretWithinFormattedText', { since: "6.1", version: "6.3" } ); return false; } var getBlockInsertionPoint = createSelector( (state) => { let rootClientId, index; const { insertionCue, selection: { selectionEnd } } = state; if (insertionCue !== null) { return insertionCue; } const { clientId } = selectionEnd; if (clientId) { rootClientId = getBlockRootClientId(state, clientId) || void 0; index = getBlockIndex(state, selectionEnd.clientId) + 1; } else { index = getBlockOrder(state).length; } return { rootClientId, index }; }, (state) => [ state.insertionCue, state.selection.selectionEnd.clientId, state.blocks.parents, state.blocks.order ] ); function isBlockInsertionPointVisible(state) { return state.insertionCue !== null; } function isValidTemplate(state) { return state.template.isValid; } function getTemplate(state) { return state.settings.template; } function getTemplateLock(state, rootClientId) { if (!rootClientId) { return state.settings.templateLock ?? false; } const blockListTemplateLock = getBlockListSettings( state, rootClientId )?.templateLock; if (blockListTemplateLock === "contentOnly" && state.editedContentOnlySection === rootClientId) { return false; } return blockListTemplateLock ?? false; } var isBlockVisibleInTheInserter = (state, blockNameOrType, rootClientId = null) => { let blockType; let blockName; if (blockNameOrType && "object" === typeof blockNameOrType) { blockType = blockNameOrType; blockName = blockNameOrType.name; } else { blockType = getBlockType(blockNameOrType); blockName = blockNameOrType; } if (!blockType) { return false; } const { allowedBlockTypes } = getSettings(state); const isBlockAllowedInEditor = checkAllowList( allowedBlockTypes, blockName, true ); if (!isBlockAllowedInEditor) { return false; } const parents = (Array.isArray(blockType.parent) ? blockType.parent : []).concat(Array.isArray(blockType.ancestor) ? blockType.ancestor : []); if (parents.length > 0) { if (parents.includes("core/post-content")) { return true; } let current = rootClientId; let hasParent = false; do { if (parents.includes(getBlockName(state, current))) { hasParent = true; break; } current = state.blocks.parents.get(current); } while (current); return hasParent; } return true; }; var canInsertBlockTypeUnmemoized = (state, blockName, rootClientId = null) => { if (!isBlockVisibleInTheInserter(state, blockName, rootClientId)) { return false; } let blockType; if (blockName && "object" === typeof blockName) { blockType = blockName; blockName = blockType.name; } else { blockType = getBlockType(blockName); } const rootTemplateLock = getTemplateLock(state, rootClientId); if (rootTemplateLock && rootTemplateLock !== "contentOnly") { return false; } const blockEditingMode = getBlockEditingMode(state, rootClientId ?? ""); if (blockEditingMode === "disabled") { return false; } const parentBlockListSettings = getBlockListSettings(state, rootClientId); if (rootClientId && parentBlockListSettings === void 0) { return false; } const isContentRoleBlock = isContentBlock(blockName); const isParentSectionBlock = !!isSectionBlock(state, rootClientId); const isBlockWithinSection = !!getParentSectionBlock( state, rootClientId ); if ((isParentSectionBlock || isBlockWithinSection) && !isContentRoleBlock) { return false; } if ((isParentSectionBlock || blockEditingMode === "contentOnly") && !isContainerInsertableToInContentOnlyMode( state, blockName, rootClientId )) { return false; } const parentName = getBlockName(state, rootClientId); const parentBlockType = getBlockType(parentName); const parentAllowedChildBlocks = parentBlockType?.allowedBlocks; let hasParentAllowedBlock = checkAllowList( parentAllowedChildBlocks, blockName ); if (hasParentAllowedBlock !== false) { const parentAllowedBlocks = parentBlockListSettings?.allowedBlocks; const hasParentListAllowedBlock = checkAllowList( parentAllowedBlocks, blockName ); if (hasParentListAllowedBlock !== null) { hasParentAllowedBlock = hasParentListAllowedBlock; } } const blockAllowedParentBlocks = blockType.parent; const hasBlockAllowedParent = checkAllowList( blockAllowedParentBlocks, parentName ); let hasBlockAllowedAncestor = true; const blockAllowedAncestorBlocks = blockType.ancestor; if (blockAllowedAncestorBlocks) { const ancestors = [ rootClientId, ...getBlockParents(state, rootClientId) ]; hasBlockAllowedAncestor = ancestors.some( (ancestorClientId) => checkAllowList( blockAllowedAncestorBlocks, getBlockName(state, ancestorClientId) ) ); } const canInsert = hasBlockAllowedAncestor && (hasParentAllowedBlock === null && hasBlockAllowedParent === null || hasParentAllowedBlock === true || hasBlockAllowedParent === true); if (!canInsert) { return canInsert; } return 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 ) } ); }; var canInsertBlockType = createRegistrySelector( (select) => createSelector( canInsertBlockTypeUnmemoized, (state, blockName, rootClientId) => getInsertBlockTypeDependants(select)(state, rootClientId) ) ); function canInsertBlocks(state, clientIds, rootClientId = null) { return clientIds.every( (id) => canInsertBlockType(state, getBlockName(state, id), rootClientId) ); } function canRemoveBlock(state, clientId) { const attributes = getBlockAttributes(state, clientId); if (attributes === null) { return true; } if (attributes.lock?.remove !== void 0) { return !attributes.lock.remove; } const rootClientId = getBlockRootClientId(state, clientId); const rootTemplateLock = getTemplateLock(state, rootClientId); if (rootTemplateLock && rootTemplateLock !== "contentOnly") { return false; } const isBlockWithinSection = !!getParentSectionBlock(state, clientId); const isContentRoleBlock = isContentBlock( getBlockName(state, clientId) ); if (isBlockWithinSection && !isContentRoleBlock) { return false; } const isParentSectionBlock = !!isSectionBlock(state, rootClientId); const rootBlockEditingMode = getBlockEditingMode(state, rootClientId); if ((isParentSectionBlock || rootBlockEditingMode === "contentOnly") && !isContainerInsertableToInContentOnlyMode( state, getBlockName(state, clientId), rootClientId )) { return false; } return rootBlockEditingMode !== "disabled"; } function canRemoveBlocks(state, clientIds) { return clientIds.every((clientId) => canRemoveBlock(state, clientId)); } function canMoveBlock(state, clientId) { const attributes = getBlockAttributes(state, clientId); if (attributes === null) { return true; } if (attributes.lock?.move !== void 0) { return !attributes.lock.move; } const rootClientId = getBlockRootClientId(state, clientId); const rootTemplateLock = getTemplateLock(state, rootClientId); if (rootTemplateLock === "all") { return false; } const isBlockWithinSection = !!getParentSectionBlock(state, clientId); const isContentRoleBlock = isContentBlock( getBlockName(state, clientId) ); if (isBlockWithinSection && !isContentRoleBlock) { return false; } const isParentSectionBlock = !!isSectionBlock(state, rootClientId); const rootBlockEditingMode = getBlockEditingMode(state, rootClientId); if ((isParentSectionBlock || rootBlockEditingMode === "contentOnly") && !isContainerInsertableToInContentOnlyMode( state, getBlockName(state, clientId), rootClientId )) { return false; } return getBlockEditingMode(state, rootClientId) !== "disabled"; } function canMoveBlocks(state, clientIds) { return clientIds.every((clientId) => canMoveBlock(state, clientId)); } function canEditBlock(state, clientId) { const attributes = getBlockAttributes(state, clientId); if (attributes === null) { return true; } const { lock } = attributes; return !lock?.edit; } function canLockBlockType(state, nameOrType) { if (!hasBlockSupport(nameOrType, "lock", true)) { return false; } return !!state.settings?.canLockBlocks; } function getInsertUsage(state, id) { return state.preferences.insertUsage?.[id] ?? null; } var canIncludeBlockTypeInInserter = (state, blockType, rootClientId) => { if (!hasBlockSupport(blockType, "inserter", true)) { return false; } return canInsertBlockTypeUnmemoized(state, blockType.name, rootClientId); }; var getItemFromVariation = (state, item) => (variation) => { const variationId = `${item.id}/${variation.name}`; const { time, count = 0 } = getInsertUsage(state, variationId) || {}; return { ...item, id: variationId, icon: variation.icon || item.icon, title: variation.title || item.title, description: variation.description || item.description, category: variation.category || item.category, // If `example` is explicitly undefined for the variation, the preview will not be shown. example: variation.hasOwnProperty("example") ? variation.example : item.example, initialAttributes: { ...item.initialAttributes, ...variation.attributes }, innerBlocks: variation.innerBlocks, keywords: variation.keywords || item.keywords, frecency: calculateFrecency(time, count) }; }; var calculateFrecency = (time, count) => { if (!time) { return count; } const duration = Date.now() - time; switch (true) { case duration < MILLISECONDS_PER_HOUR: return count * 4; case duration < MILLISECONDS_PER_DAY: return count * 2; case duration < MILLISECONDS_PER_WEEK: return count / 2; default: return count / 4; } }; var buildBlockTypeItem = (state, { buildScope = "inserter" }) => (blockType) => { const id = blockType.name; let isDisabled = false; if (!hasBlockSupport(blockType.name, "multiple", true)) { isDisabled = getBlocksByClientId( state, getClientIdsWithDescendants(state) ).some(({ name }) => name === blockType.name); } const { time, count = 0 } = getInsertUsage(state, id) || {}; const blockItemBase = { id, name: blockType.name, title: blockType.title, icon: blockType.icon, isDisabled, frecency: calculateFrecency(time, count) }; if (buildScope === "transform") { return blockItemBase; } const inserterVariations = getBlockVariations( blockType.name, "inserter" ); return { ...blockItemBase, initialAttributes: {}, description: blockType.description, category: blockType.category, keywords: blockType.keywords, parent: blockType.parent, ancestor: blockType.ancestor, variations: inserterVariations, example: blockType.example, utility: 1 // Deprecated. }; }; var getInserterItems = createRegistrySelector( (select) => createSelector( (state, rootClientId = null, options = DEFAULT_INSERTER_OPTIONS) => { const buildReusableBlockInserterItem = (reusableBlock) => { const icon = !reusableBlock.wp_pattern_sync_status ? { src: symbol, foreground: "var(--wp-block-synced-color)" } : symbol; const userPattern = mapUserPattern(reusableBlock); const { time, count = 0 } = getInsertUsage(state, userPattern.name) || {}; const frecency = calculateFrecency(time, count); return { id: userPattern.name, name: "core/block", initialAttributes: { ref: reusableBlock.id }, title: userPattern.title, icon, category: "reusable", keywords: ["reusable"], isDisabled: false, utility: 1, // Deprecated. frecency, content: userPattern.content, get blocks() { return getParsedPattern(userPattern).blocks; }, syncStatus: userPattern.syncStatus }; }; const patternInserterItems = canInsertBlockTypeUnmemoized( state, "core/block", rootClientId ) ? unlock(select(STORE_NAME)).getReusableBlocks().map(buildReusableBlockInserterItem) : []; const buildBlockTypeInserterItem = buildBlockTypeItem(state, { buildScope: "inserter" }); let blockTypeInserterItems = getBlockTypes().filter( (blockType) => hasBlockSupport(blockType, "inserter", true) ).map(buildBlockTypeInserterItem); if (options[isFiltered] !== false) { blockTypeInserterItems = blockTypeInserterItems.filter( (blockType) => canIncludeBlockTypeInInserter( state, blockType, rootClientId ) ); } else { blockTypeInserterItems = blockTypeInserterItems.filter( (blockType) => isBlockVisibleInTheInserter( state, blockType, rootClientId ) ).map((blockType) => ({ ...blockType, isAllowedInCurrentRoot: canIncludeBlockTypeInInserter( state, blockType, rootClientId ) })); } const stretchVariations = []; const items = blockTypeInserterItems.reduce( (accumulator, item) => { const { variations = [] } = item; if (!variations.some(({ isDefault }) => isDefault)) { accumulator.push(item); } if (variations.length) { const variationMapper = getItemFromVariation( state, item ); variations.map(variationMapper).forEach((variation) => { if (variation.id === "core/paragraph/stretchy-paragraph" || variation.id === "core/heading/stretchy-heading") { stretchVariations.push(variation); } else { accumulator.push(variation); } }); } return accumulator; }, [] ); items.push(...stretchVariations); const groupByType = (blocks, block) => { const { core, noncore } = blocks; const type = block.name.startsWith("core/") ? core : noncore; type.push(block); return blocks; }; const { core: coreItems, noncore: nonCoreItems } = items.reduce( groupByType, { core: [], noncore: [] } ); const sortedBlockTypes = [...coreItems, ...nonCoreItems]; return [...sortedBlockTypes, ...patternInserterItems]; }, (state, rootClientId) => [ getBlockTypes(), unlock(select(STORE_NAME)).getReusableBlocks(), state.blocks.order, state.preferences.insertUsage, ...getInsertBlockTypeDependants(select)(state, rootClientId) ] ) ); var getBlockTransformItems = createRegistrySelector( (select) => createSelector( (state, blocks, rootClientId = null) => { const normalizedBlocks = Array.isArray(blocks) ? blocks : [blocks]; const buildBlockTypeTransformItem = buildBlockTypeItem(state, { buildScope: "transform" }); const blockTypeTransformItems = getBlockTypes().filter( (blockType) => canIncludeBlockTypeInInserter( state, blockType, rootClientId ) ).map(buildBlockTypeTransformItem); const itemsByName = Object.fromEntries( Object.entries(blockTypeTransformItems).map( ([, value]) => [value.name, value] ) ); const possibleTransforms = getPossibleBlockTransformations( normalizedBlocks ).reduce((accumulator, block) => { if (itemsByName[block?.name]) { accumulator.push(itemsByName[block.name]); } return accumulator; }, []); return orderBy( possibleTransforms, (block) => itemsByName[block.name].frecency, "desc" ); }, (state, blocks, rootClientId) => [ getBlockTypes(), state.preferences.insertUsage, ...getInsertBlockTypeDependants(select)(state, rootClientId) ] ) ); var hasInserterItems = (state, rootClientId = null) => { const hasBlockType = getBlockTypes().some( (blockType) => canIncludeBlockTypeInInserter(state, blockType, rootClientId) ); if (hasBlockType) { return true; } const hasReusableBlock = canInsertBlockTypeUnmemoized( state, "core/block", rootClientId ); return hasReusableBlock; }; var getAllowedBlocks = createRegistrySelector( (select) => createSelector( (state, rootClientId = null) => { if (!rootClientId) { return; } const blockTypes = getBlockTypes().filter( (blockType) => canIncludeBlockTypeInInserter(state, blockType, rootClientId) ); const hasReusableBlock = canInsertBlockTypeUnmemoized( state, "core/block", rootClientId ); if (hasReusableBlock) { blockTypes.push("core/block"); } return blockTypes; }, (state, rootClientId) => [ getBlockTypes(), ...getInsertBlockTypeDependants(select)(state, rootClientId) ] ) ); var __experimentalGetAllowedBlocks = createSelector( (state, rootClientId = null) => { deprecated( 'wp.data.select( "core/block-editor" ).__experimentalGetAllowedBlocks', { alternative: 'wp.data.select( "core/block-editor" ).getAllowedBlocks', since: "6.2", version: "6.4" } ); return getAllowedBlocks(state, rootClientId); }, (state, rootClientId) => getAllowedBlocks.getDependants(state, rootClientId) ); function getDirectInsertBlock(state, rootClientId = null) { if (!rootClientId) { return; } const { defaultBlock, directInsert } = state.blockListSettings[rootClientId] ?? {}; if (!defaultBlock || !directInsert) { return; } return defaultBlock; } function __experimentalGetDirectInsertBlock(state, rootClientId = null) { deprecated( 'wp.data.select( "core/block-editor" ).__experimentalGetDirectInsertBlock', { alternative: 'wp.data.select( "core/block-editor" ).getDirectInsertBlock', since: "6.3", version: "6.4" } ); return getDirectInsertBlock(state, rootClientId); } var __experimentalGetParsedPattern = createRegistrySelector( (select) => (state, patternName) => { const pattern = unlock(select(STORE_NAME)).getPatternBySlug( patternName ); return pattern ? getParsedPattern(pattern) : null; } ); var getAllowedPatternsDependants = (select) => (state, rootClientId) => [ ...getAllPatternsDependants(select)(state), ...getInsertBlockTypeDependants(select)(state, rootClientId) ]; var patternsWithParsedBlocks = /* @__PURE__ */ new WeakMap(); function enhancePatternWithParsedBlocks(pattern) { let enhancedPattern = patternsWithParsedBlocks.get(pattern); if (!enhancedPattern) { enhancedPattern = { ...pattern, get blocks() { return getParsedPattern(pattern).blocks; } }; patternsWithParsedBlocks.set(pattern, enhancedPattern); } return enhancedPattern; } var __experimentalGetAllowedPatterns = createRegistrySelector( (select) => { return createSelector( (state, rootClientId = null, options = DEFAULT_INSERTER_OPTIONS) => { const { getAllPatterns } = unlock(select(STORE_NAME)); const patterns = getAllPatterns(); const { allowedBlockTypes } = getSettings(state); const parsedPatterns = patterns.filter(({ inserter = true }) => !!inserter).map(enhancePatternWithParsedBlocks); const availableParsedPatterns = parsedPatterns.filter( (pattern) => checkAllowListRecursive( getGrammar(pattern), allowedBlockTypes ) ); const patternsAllowed = availableParsedPatterns.filter( (pattern) => getGrammar(pattern).every( ({ blockName: name }) => options[isFiltered] !== false ? canInsertBlockType( state, name, rootClientId ) : isBlockVisibleInTheInserter( state, name, rootClientId ) ) ); return patternsAllowed; }, getAllowedPatternsDependants(select) ); } ); var getPatternsByBlockTypes = createRegistrySelector( (select) => createSelector( (state, blockNames, rootClientId = null) => { if (!blockNames) { return EMPTY_ARRAY; } const patterns = select(STORE_NAME).__experimentalGetAllowedPatterns( rootClientId ); const normalizedBlockNames = Array.isArray(blockNames) ? blockNames : [blockNames]; const filteredPatterns = patterns.filter( (pattern) => pattern?.blockTypes?.some?.( (blockName) => normalizedBlockNames.includes(blockName) ) ); if (filteredPatterns.length === 0) { return EMPTY_ARRAY; } return filteredPatterns; }, (state, blockNames, rootClientId) => getAllowedPatternsDependants(select)(state, rootClientId) ) ); var __experimentalGetPatternsByBlockTypes = createRegistrySelector( (select) => { deprecated( 'wp.data.select( "core/block-editor" ).__experimentalGetPatternsByBlockTypes', { alternative: 'wp.data.select( "core/block-editor" ).getPatternsByBlockTypes', since: "6.2", version: "6.4" } ); return select(STORE_NAME).getPatternsByBlockTypes; } ); var __experimentalGetPatternTransformItems = createRegistrySelector( (select) => createSelector( (state, blocks, rootClientId = null) => { if (!blocks) { return EMPTY_ARRAY; } if (blocks.some( ({ clientId, innerBlocks }) => innerBlocks.length || areInnerBlocksControlled(state, clientId) )) { return EMPTY_ARRAY; } const selectedBlockNames = Array.from( new Set(blocks.map(({ name }) => name)) ); return select(STORE_NAME).getPatternsByBlockTypes( selectedBlockNames, rootClientId ); }, (state, blocks, rootClientId) => getAllowedPatternsDependants(select)(state, rootClientId) ) ); function getBlockListSettings(state, clientId) { return state.blockListSettings[clientId]; } function getSettings(state) { return state.settings; } function isLastBlockChangePersistent(state) { return state.blocks.isPersistentChange; } var __experimentalGetBlockListSettingsForBlocks = createSelector( (state, clientIds = []) => { return clientIds.reduce((blockListSettingsForBlocks, clientId) => { if (!state.blockListSettings[clientId]) { return blockListSettingsForBlocks; } return { ...blockListSettingsForBlocks, [clientId]: state.blockListSettings[clientId] }; }, {}); }, (state) => [state.blockListSettings] ); var __experimentalGetReusableBlockTitle = createRegistrySelector( (select) => createSelector( (state, ref) => { deprecated( "wp.data.select( 'core/block-editor' ).__experimentalGetReusableBlockTitle", { since: "6.6", version: "6.8" } ); const reusableBlock = unlock(select(STORE_NAME)).getReusableBlocks().find((block) => block.id === ref); if (!reusableBlock) { return null; } return reusableBlock.title?.raw; }, () => [unlock(select(STORE_NAME)).getReusableBlocks()] ) ); function __unstableIsLastBlockChangeIgnored(state) { return state.blocks.isIgnoredChange; } function __experimentalGetLastBlockAttributeChanges(state) { return state.lastBlockAttributesChange; } function hasBlockMovingClientId() { deprecated( 'wp.data.select( "core/block-editor" ).hasBlockMovingClientId', { since: "6.7", hint: "Block moving mode feature has been removed" } ); return false; } function didAutomaticChange(state) { return !!state.automaticChangeStatus; } function isBlockHighlighted(state, clientId) { return state.highlightedBlock === clientId; } function areInnerBlocksControlled(state, clientId) { return !!state.blocks.controlledInnerBlocks[clientId]; } var __experimentalGetActiveBlockIdByBlockNames = createSelector( (state, validBlockNames) => { if (!validBlockNames.length) { return null; } const selectedBlockClientId = getSelectedBlockClientId(state); if (validBlockNames.includes( getBlockName(state, selectedBlockClientId) )) { return selectedBlockClientId; } const multiSelectedBlockClientIds = getMultiSelectedBlockClientIds(state); const entityAreaParents = getBlockParentsByBlockName( state, selectedBlockClientId || multiSelectedBlockClientIds[0], validBlockNames ); if (entityAreaParents) { return entityAreaParents[entityAreaParents.length - 1]; } return null; }, (state, validBlockNames) => [ state.selection.selectionStart.clientId, state.selection.selectionEnd.clientId, validBlockNames ] ); function wasBlockJustInserted(state, clientId, source) { const { lastBlockInserted } = state; return lastBlockInserted.clientIds?.includes(clientId) && lastBlockInserted.source === source; } function isBlockVisible(state, clientId) { return state.blockVisibility?.[clientId] ?? true; } function getHoveredBlockClientId() { deprecated( "wp.data.select( 'core/block-editor' ).getHoveredBlockClientId", { since: "6.9", version: "7.1" } ); return void 0; } var __unstableGetVisibleBlocks = createSelector( (state) => { const visibleBlocks = new Set( Object.keys(state.blockVisibility).filter( (key) => state.blockVisibility[key] ) ); if (visibleBlocks.size === 0) { return EMPTY_SET; } return visibleBlocks; }, (state) => [state.blockVisibility] ); function __unstableHasActiveBlockOverlayActive(state, clientId) { if (getBlockEditingMode(state, clientId) !== "default") { return false; } if (!canEditBlock(state, clientId)) { return true; } if (isZoomOut(state)) { const sectionRootClientId = getSectionRootClientId(state); if (sectionRootClientId) { const sectionClientIds = getBlockOrder( state, sectionRootClientId ); if (sectionClientIds?.includes(clientId)) { return true; } } else if (clientId && !getBlockRootClientId(state, clientId)) { return true; } } const blockSupportDisable = hasBlockSupport( getBlockName(state, clientId), "__experimentalDisableBlockOverlay", false ); const shouldEnableIfUnselected = blockSupportDisable ? false : areInnerBlocksControlled(state, clientId); return shouldEnableIfUnselected && !isBlockSelected(state, clientId) && !hasSelectedInnerBlock(state, clientId, true); } function __unstableIsWithinBlockOverlay(state, clientId) { let parent = state.blocks.parents.get(clientId); while (!!parent) { if (__unstableHasActiveBlockOverlayActive(state, parent)) { return true; } parent = state.blocks.parents.get(parent); } return false; } function getBlockEditingMode(state, clientId = "") { if (clientId === null) { clientId = ""; } if (state.derivedBlockEditingModes?.has(clientId)) { return state.derivedBlockEditingModes.get(clientId); } if (state.blockEditingModes.has(clientId)) { return state.blockEditingModes.get(clientId); } return "default"; } var isUngroupable = createRegistrySelector( (select) => (state, clientId = "") => { const _clientId = clientId || getSelectedBlockClientId(state); if (!_clientId) { return false; } if (isSectionBlock(state, _clientId)) { return false; } const { getGroupingBlockName } = select(blocksStore); const block = getBlock(state, _clientId); const groupingBlockName = getGroupingBlockName(); const _isUngroupable = block && (block.name === groupingBlockName || getBlockType(block.name)?.transforms?.ungroup) && !!block.innerBlocks.length; return _isUngroupable && canRemoveBlock(state, _clientId); } ); var isGroupable = createRegistrySelector( (select) => (state, clientIds = EMPTY_ARRAY) => { const { getGroupingBlockName } = select(blocksStore); const groupingBlockName = getGroupingBlockName(); const _clientIds = clientIds?.length ? clientIds : getSelectedBlockClientIds(state); const rootClientId = _clientIds?.length ? getBlockRootClientId(state, _clientIds[0]) : void 0; const groupingBlockAvailable = canInsertBlockType(