@gechiui/block-editor
Version:
1,546 lines (1,302 loc) • 79.8 kB
JavaScript
"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