@gechiui/block-editor
Version:
1,714 lines (1,417 loc) • 50.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.automaticChangeStatus = automaticChangeStatus;
exports.blocks = exports.blockListSettings = void 0;
exports.blocksMode = blocksMode;
exports.default = void 0;
exports.draggedBlocks = draggedBlocks;
exports.hasBlockMovingClientId = hasBlockMovingClientId;
exports.hasSameKeys = hasSameKeys;
exports.highlightedBlock = highlightedBlock;
exports.initialPosition = initialPosition;
exports.insertionPoint = insertionPoint;
exports.isCaretWithinFormattedText = isCaretWithinFormattedText;
exports.isMultiSelecting = isMultiSelecting;
exports.isNavigationMode = isNavigationMode;
exports.isSelectionEnabled = isSelectionEnabled;
exports.isTyping = isTyping;
exports.isUpdatingSameBlockAttribute = isUpdatingSameBlockAttribute;
exports.lastBlockAttributesChange = lastBlockAttributesChange;
exports.lastBlockInserted = lastBlockInserted;
exports.preferences = preferences;
exports.selection = selection;
exports.settings = settings;
exports.template = template;
var _lodash = require("lodash");
var _data = require("@gechiui/data");
var _blocks = require("@gechiui/blocks");
var _defaults = require("./defaults");
var _array = require("./array");
/**
* External dependencies
*/
/**
* GeChiUI dependencies
*/
/**
* Internal dependencies
*/
/**
* Given an array of blocks, returns an object where each key is a nesting
* context, the value of which is an array of block client IDs existing within
* that nesting context.
*
* @param {Array} blocks Blocks to map.
* @param {?string} rootClientId Assumed root client ID.
*
* @return {Object} Block order map object.
*/
function mapBlockOrder(blocks) {
let rootClientId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
const result = {
[rootClientId]: []
};
blocks.forEach(block => {
const {
clientId,
innerBlocks
} = block;
result[rootClientId].push(clientId);
Object.assign(result, mapBlockOrder(innerBlocks, clientId));
});
return result;
}
/**
* Given an array of blocks, returns an object where each key contains
* the clientId of the block and the value is the parent of the block.
*
* @param {Array} blocks Blocks to map.
* @param {?string} rootClientId Assumed root client ID.
*
* @return {Object} Block order map object.
*/
function mapBlockParents(blocks) {
let rootClientId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
return blocks.reduce((result, block) => Object.assign(result, {
[block.clientId]: rootClientId
}, mapBlockParents(block.innerBlocks, block.clientId)), {});
}
/**
* Helper method to iterate through all blocks, recursing into inner blocks,
* applying a transformation function to each one.
* Returns a flattened object with the transformed blocks.
*
* @param {Array} blocks Blocks to flatten.
* @param {Function} transform Transforming function to be applied to each block.
*
* @return {Object} Flattened object.
*/
function flattenBlocks(blocks) {
let transform = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _lodash.identity;
const result = {};
const stack = [...blocks];
while (stack.length) {
const {
innerBlocks,
...block
} = stack.shift();
stack.push(...innerBlocks);
result[block.clientId] = transform(block);
}
return result;
}
/**
* Given an array of blocks, returns an object containing all blocks, without
* attributes, recursing into inner blocks. Keys correspond to the block client
* ID, the value of which is the attributes object.
*
* @param {Array} blocks Blocks to flatten.
*
* @return {Object} Flattened block attributes object.
*/
function getFlattenedBlocksWithoutAttributes(blocks) {
return flattenBlocks(blocks, block => (0, _lodash.omit)(block, 'attributes'));
}
/**
* Given an array of blocks, returns an object containing all block attributes,
* recursing into inner blocks. Keys correspond to the block client ID, the
* value of which is the attributes object.
*
* @param {Array} blocks Blocks to flatten.
*
* @return {Object} Flattened block attributes object.
*/
function getFlattenedBlockAttributes(blocks) {
return flattenBlocks(blocks, block => block.attributes);
}
/**
* Returns an object against which it is safe to perform mutating operations,
* given the original object and its current working copy.
*
* @param {Object} original Original object.
* @param {Object} working Working object.
*
* @return {Object} Mutation-safe object.
*/
function getMutateSafeObject(original, working) {
if (original === working) {
return { ...original
};
}
return working;
}
/**
* Returns true if the two object arguments have the same keys, or false
* otherwise.
*
* @param {Object} a First object.
* @param {Object} b Second object.
*
* @return {boolean} Whether the two objects have the same keys.
*/
function hasSameKeys(a, b) {
return (0, _lodash.isEqual)((0, _lodash.keys)(a), (0, _lodash.keys)(b));
}
/**
* Returns true if, given the currently dispatching action and the previously
* dispatched action, the two actions are updating the same block attribute, or
* false otherwise.
*
* @param {Object} action Currently dispatching action.
* @param {Object} lastAction Previously dispatched action.
*
* @return {boolean} Whether actions are updating the same block attribute.
*/
function isUpdatingSameBlockAttribute(action, lastAction) {
return action.type === 'UPDATE_BLOCK_ATTRIBUTES' && lastAction !== undefined && lastAction.type === 'UPDATE_BLOCK_ATTRIBUTES' && (0, _lodash.isEqual)(action.clientIds, lastAction.clientIds) && hasSameKeys(action.attributes, lastAction.attributes);
}
function buildBlockTree(state, blocks) {
const result = {};
const stack = [...blocks];
const flattenedBlocks = [...blocks];
while (stack.length) {
const block = stack.shift();
stack.push(...block.innerBlocks);
flattenedBlocks.push(...block.innerBlocks);
} // Create objects before mutating them, that way it's always defined.
for (const block of flattenedBlocks) {
result[block.clientId] = {};
}
for (const block of flattenedBlocks) {
result[block.clientId] = Object.assign(result[block.clientId], { ...state.byClientId[block.clientId],
attributes: state.attributes[block.clientId],
innerBlocks: block.innerBlocks.map(subBlock => result[subBlock.clientId])
});
}
return result;
}
function updateParentInnerBlocksInTree(state, tree, updatedClientIds) {
let updateChildrenOfUpdatedClientIds = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
const uncontrolledParents = new Set([]);
const controlledParents = new Set();
for (const clientId of updatedClientIds) {
let current = updateChildrenOfUpdatedClientIds ? clientId : state.parents[clientId];
do {
if (state.controlledInnerBlocks[current]) {
// Should stop on controlled blocks.
// If we reach a controlled parent, break out of the loop.
controlledParents.add(current);
break;
} else {
// else continue traversing up through parents.
uncontrolledParents.add(current);
current = state.parents[current];
}
} while (current !== undefined);
} // To make sure the order of assignments doesn't matter,
// we first create empty objects and mutates the inner blocks later.
for (const clientId of uncontrolledParents) {
tree[clientId] = { ...tree[clientId]
};
}
for (const clientId of uncontrolledParents) {
tree[clientId].innerBlocks = (state.order[clientId] || []).map(subClientId => tree[subClientId]);
} // Controlled parent blocks, need a dedicated key for their inner blocks
// to be used when doing getBlocks( controlledBlockClientId ).
for (const clientId of controlledParents) {
tree['controlled||' + clientId] = {
innerBlocks: (state.order[clientId] || []).map(subClientId => tree[subClientId])
};
}
return tree;
}
/**
* Higher-order reducer intended to compute full block objects key for each block in the post.
* This is a denormalization to optimize the performance of the getBlock selectors and avoid
* recomputing the block objects and avoid heavy memoization.
*
* @param {Function} reducer Original reducer function.
*
* @return {Function} Enhanced reducer function.
*/
const withBlockTree = reducer => function () {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let action = arguments.length > 1 ? arguments[1] : undefined;
const newState = reducer(state, action);
if (newState === state) {
return state;
}
newState.tree = state.tree ? state.tree : {};
switch (action.type) {
case 'RECEIVE_BLOCKS':
case 'INSERT_BLOCKS':
{
const subTree = buildBlockTree(newState, action.blocks);
newState.tree = updateParentInnerBlocksInTree(newState, { ...newState.tree,
...subTree
}, action.rootClientId ? [action.rootClientId] : [''], true);
break;
}
case 'UPDATE_BLOCK':
newState.tree = updateParentInnerBlocksInTree(newState, { ...newState.tree,
[action.clientId]: { ...newState.tree[action.clientId],
...newState.byClientId[action.clientId],
attributes: newState.attributes[action.clientId]
}
}, [action.clientId], false);
break;
case 'UPDATE_BLOCK_ATTRIBUTES':
{
const newSubTree = action.clientIds.reduce((result, clientId) => {
result[clientId] = { ...newState.tree[clientId],
attributes: newState.attributes[clientId]
};
return result;
}, {});
newState.tree = updateParentInnerBlocksInTree(newState, { ...newState.tree,
...newSubTree
}, action.clientIds, false);
break;
}
case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN':
{
const subTree = buildBlockTree(newState, action.blocks);
newState.tree = updateParentInnerBlocksInTree(newState, { ...(0, _lodash.omit)(newState.tree, action.replacedClientIds.concat( // Controlled inner blocks are only removed
// if the block doesn't move to another position
// otherwise their content will be lost.
action.replacedClientIds.filter(clientId => !subTree[clientId]).map(clientId => 'controlled||' + clientId))),
...subTree
}, action.blocks.map(b => b.clientId), false); // If there are no replaced blocks, it means we're removing blocks so we need to update their parent.
const parentsOfRemovedBlocks = [];
for (const clientId of action.clientIds) {
if (state.parents[clientId] !== undefined && (state.parents[clientId] === '' || newState.byClientId[state.parents[clientId]])) {
parentsOfRemovedBlocks.push(state.parents[clientId]);
}
}
newState.tree = updateParentInnerBlocksInTree(newState, newState.tree, parentsOfRemovedBlocks, true);
break;
}
case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN':
const parentsOfRemovedBlocks = [];
for (const clientId of action.clientIds) {
if (state.parents[clientId] !== undefined && (state.parents[clientId] === '' || newState.byClientId[state.parents[clientId]])) {
parentsOfRemovedBlocks.push(state.parents[clientId]);
}
}
newState.tree = updateParentInnerBlocksInTree(newState, (0, _lodash.omit)(newState.tree, action.removedClientIds.concat(action.removedClientIds.map(clientId => 'controlled||' + clientId))), parentsOfRemovedBlocks, true);
break;
case 'MOVE_BLOCKS_TO_POSITION':
{
const updatedBlockUids = [];
if (action.fromRootClientId) {
updatedBlockUids.push(action.fromRootClientId);
}
if (action.toRootClientId) {
updatedBlockUids.push(action.toRootClientId);
}
if (!action.fromRootClientId || !action.fromRootClientId) {
updatedBlockUids.push('');
}
newState.tree = updateParentInnerBlocksInTree(newState, newState.tree, updatedBlockUids, true);
break;
}
case 'MOVE_BLOCKS_UP':
case 'MOVE_BLOCKS_DOWN':
{
const updatedBlockUids = [action.rootClientId ? action.rootClientId : ''];
newState.tree = updateParentInnerBlocksInTree(newState, newState.tree, updatedBlockUids, true);
break;
}
case 'SAVE_REUSABLE_BLOCK_SUCCESS':
{
const updatedBlockUids = (0, _lodash.keys)((0, _lodash.omitBy)(newState.attributes, (attributes, clientId) => {
return newState.byClientId[clientId].name !== 'core/block' || attributes.ref !== action.updatedId;
}));
newState.tree = updateParentInnerBlocksInTree(newState, { ...newState.tree,
...updatedBlockUids.reduce((result, clientId) => {
result[clientId] = { ...newState.byClientId[clientId],
attributes: newState.attributes[clientId],
innerBlocks: newState.tree[clientId].innerBlocks
};
return result;
}, {})
}, updatedBlockUids, false);
}
}
return newState;
};
/**
* Higher-order reducer intended to augment the blocks reducer, assigning an
* `isPersistentChange` property value corresponding to whether a change in
* state can be considered as persistent. All changes are considered persistent
* except when updating the same block attribute as in the previous action.
*
* @param {Function} reducer Original reducer function.
*
* @return {Function} Enhanced reducer function.
*/
function withPersistentBlockChange(reducer) {
let lastAction;
let markNextChangeAsNotPersistent = false;
return (state, action) => {
let nextState = reducer(state, action);
const isExplicitPersistentChange = action.type === 'MARK_LAST_CHANGE_AS_PERSISTENT' || markNextChangeAsNotPersistent; // Defer to previous state value (or default) unless changing or
// explicitly marking as persistent.
if (state === nextState && !isExplicitPersistentChange) {
var _state$isPersistentCh;
markNextChangeAsNotPersistent = action.type === 'MARK_NEXT_CHANGE_AS_NOT_PERSISTENT';
const nextIsPersistentChange = (_state$isPersistentCh = state === null || state === void 0 ? void 0 : state.isPersistentChange) !== null && _state$isPersistentCh !== void 0 ? _state$isPersistentCh : true;
if (state.isPersistentChange === nextIsPersistentChange) {
return state;
}
return { ...nextState,
isPersistentChange: nextIsPersistentChange
};
}
nextState = { ...nextState,
isPersistentChange: isExplicitPersistentChange ? !markNextChangeAsNotPersistent : !isUpdatingSameBlockAttribute(action, lastAction)
}; // In comparing against the previous action, consider only those which
// would have qualified as one which would have been ignored or not
// have resulted in a changed state.
lastAction = action;
markNextChangeAsNotPersistent = action.type === 'MARK_NEXT_CHANGE_AS_NOT_PERSISTENT';
return nextState;
};
}
/**
* Higher-order reducer intended to augment the blocks reducer, assigning an
* `isIgnoredChange` property value corresponding to whether a change in state
* can be considered as ignored. A change is considered ignored when the result
* of an action not incurred by direct user interaction.
*
* @param {Function} reducer Original reducer function.
*
* @return {Function} Enhanced reducer function.
*/
function withIgnoredBlockChange(reducer) {
/**
* Set of action types for which a blocks state change should be ignored.
*
* @type {Set}
*/
const IGNORED_ACTION_TYPES = new Set(['RECEIVE_BLOCKS']);
return (state, action) => {
const nextState = reducer(state, action);
if (nextState !== state) {
nextState.isIgnoredChange = IGNORED_ACTION_TYPES.has(action.type);
}
return nextState;
};
}
/**
* Higher-order reducer targeting the combined blocks reducer, augmenting
* block client IDs in remove action to include cascade of inner blocks.
*
* @param {Function} reducer Original reducer function.
*
* @return {Function} Enhanced reducer function.
*/
const withInnerBlocksRemoveCascade = reducer => (state, action) => {
// Gets all children which need to be removed.
const getAllChildren = clientIds => {
let result = clientIds;
for (let i = 0; i < result.length; i++) {
if (!state.order[result[i]] || action.keepControlledInnerBlocks && action.keepControlledInnerBlocks[result[i]]) {
continue;
}
if (result === clientIds) {
result = [...result];
}
result.push(...state.order[result[i]]);
}
return result;
};
if (state) {
switch (action.type) {
case 'REMOVE_BLOCKS':
action = { ...action,
type: 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN',
removedClientIds: getAllChildren(action.clientIds)
};
break;
case 'REPLACE_BLOCKS':
action = { ...action,
type: 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN',
replacedClientIds: getAllChildren(action.clientIds)
};
break;
}
}
return reducer(state, action);
};
/**
* Higher-order reducer which targets the combined blocks reducer and handles
* the `RESET_BLOCKS` action. When dispatched, this action will replace all
* blocks that exist in the post, leaving blocks that exist only in state (e.g.
* reusable blocks and blocks controlled by inner blocks controllers) alone.
*
* @param {Function} reducer Original reducer function.
*
* @return {Function} Enhanced reducer function.
*/
const withBlockReset = reducer => (state, action) => {
if (action.type === 'RESET_BLOCKS') {
const newState = { ...state,
byClientId: getFlattenedBlocksWithoutAttributes(action.blocks),
attributes: getFlattenedBlockAttributes(action.blocks),
order: mapBlockOrder(action.blocks),
parents: mapBlockParents(action.blocks),
controlledInnerBlocks: {}
};
const subTree = buildBlockTree(newState, action.blocks);
newState.tree = { ...subTree,
// Root
'': {
innerBlocks: action.blocks.map(subBlock => subTree[subBlock.clientId])
}
};
return newState;
}
return reducer(state, action);
};
/**
* Higher-order reducer which targets the combined blocks reducer and handles
* the `REPLACE_INNER_BLOCKS` action. When dispatched, this action the state
* should become equivalent to the execution of a `REMOVE_BLOCKS` action
* containing all the child's of the root block followed by the execution of
* `INSERT_BLOCKS` with the new blocks.
*
* @param {Function} reducer Original reducer function.
*
* @return {Function} Enhanced reducer function.
*/
const withReplaceInnerBlocks = reducer => (state, action) => {
if (action.type !== 'REPLACE_INNER_BLOCKS') {
return reducer(state, action);
} // Finds every nested inner block controller. We must check the action blocks
// and not just the block parent state because some inner block controllers
// should be deleted if specified, whereas others should not be deleted. If
// a controlled should not be deleted, then we need to avoid deleting its
// inner blocks from the block state because its inner blocks will not be
// attached to the block in the action.
const nestedControllers = {};
if (Object.keys(state.controlledInnerBlocks).length) {
const stack = [...action.blocks];
while (stack.length) {
const {
innerBlocks,
...block
} = stack.shift();
stack.push(...innerBlocks);
if (!!state.controlledInnerBlocks[block.clientId]) {
nestedControllers[block.clientId] = true;
}
}
} // The `keepControlledInnerBlocks` prop will keep the inner blocks of the
// marked block in the block state so that they can be reattached to the
// marked block when we re-insert everything a few lines below.
let stateAfterBlocksRemoval = state;
if (state.order[action.rootClientId]) {
stateAfterBlocksRemoval = reducer(stateAfterBlocksRemoval, {
type: 'REMOVE_BLOCKS',
keepControlledInnerBlocks: nestedControllers,
clientIds: state.order[action.rootClientId]
});
}
let stateAfterInsert = stateAfterBlocksRemoval;
if (action.blocks.length) {
stateAfterInsert = reducer(stateAfterInsert, { ...action,
type: 'INSERT_BLOCKS',
index: 0
}); // We need to re-attach the block order of the controlled inner blocks.
// Otherwise, an inner block controller's blocks will be deleted entirely
// from its entity..
stateAfterInsert.order = { ...stateAfterInsert.order,
...(0, _lodash.reduce)(nestedControllers, (result, value, key) => {
if (state.order[key]) {
result[key] = state.order[key];
}
return result;
}, {})
};
}
return stateAfterInsert;
};
/**
* Higher-order reducer which targets the combined blocks reducer and handles
* the `SAVE_REUSABLE_BLOCK_SUCCESS` action. This action can't be handled by
* regular reducers and needs a higher-order reducer since it needs access to
* both `byClientId` and `attributes` simultaneously.
*
* @param {Function} reducer Original reducer function.
*
* @return {Function} Enhanced reducer function.
*/
const withSaveReusableBlock = reducer => (state, action) => {
if (state && action.type === 'SAVE_REUSABLE_BLOCK_SUCCESS') {
const {
id,
updatedId
} = action; // If a temporary reusable block is saved, we swap the temporary id with the final one
if (id === updatedId) {
return state;
}
state = { ...state
};
state.attributes = (0, _lodash.mapValues)(state.attributes, (attributes, clientId) => {
const {
name
} = state.byClientId[clientId];
if (name === 'core/block' && attributes.ref === id) {
return { ...attributes,
ref: updatedId
};
}
return attributes;
});
}
return reducer(state, action);
};
/**
* Higher-order reducer which removes blocks from state when switching parent block controlled state.
*
* @param {Function} reducer Original reducer function.
*
* @return {Function} Enhanced reducer function.
*/
const withResetControlledBlocks = reducer => (state, action) => {
if (action.type === 'SET_HAS_CONTROLLED_INNER_BLOCKS') {
// when switching a block from controlled to uncontrolled or inverse,
// we need to remove its content first.
const tempState = reducer(state, {
type: 'REPLACE_INNER_BLOCKS',
rootClientId: action.clientId,
blocks: []
});
return reducer(tempState, action);
}
return reducer(state, action);
};
/**
* Reducer returning the blocks state.
*
* @param {Object} state Current state.
* @param {Object} action Dispatched action.
*
* @return {Object} Updated state.
*/
const blocks = (0, _lodash.flow)(_data.combineReducers, withSaveReusableBlock, // needs to be before withBlockCache
withBlockTree, // needs to be before withInnerBlocksRemoveCascade
withInnerBlocksRemoveCascade, withReplaceInnerBlocks, // needs to be after withInnerBlocksRemoveCascade
withBlockReset, withPersistentBlockChange, withIgnoredBlockChange, withResetControlledBlocks)({
byClientId() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let action = arguments.length > 1 ? arguments[1] : undefined;
switch (action.type) {
case 'RECEIVE_BLOCKS':
case 'INSERT_BLOCKS':
return { ...state,
...getFlattenedBlocksWithoutAttributes(action.blocks)
};
case 'UPDATE_BLOCK':
// Ignore updates if block isn't known
if (!state[action.clientId]) {
return state;
} // Do nothing if only attributes change.
const changes = (0, _lodash.omit)(action.updates, 'attributes');
if ((0, _lodash.isEmpty)(changes)) {
return state;
}
return { ...state,
[action.clientId]: { ...state[action.clientId],
...changes
}
};
case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN':
if (!action.blocks) {
return state;
}
return { ...(0, _lodash.omit)(state, action.replacedClientIds),
...getFlattenedBlocksWithoutAttributes(action.blocks)
};
case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN':
return (0, _lodash.omit)(state, action.removedClientIds);
}
return state;
},
attributes() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let action = arguments.length > 1 ? arguments[1] : undefined;
switch (action.type) {
case 'RECEIVE_BLOCKS':
case 'INSERT_BLOCKS':
return { ...state,
...getFlattenedBlockAttributes(action.blocks)
};
case 'UPDATE_BLOCK':
// Ignore updates if block isn't known or there are no attribute changes.
if (!state[action.clientId] || !action.updates.attributes) {
return state;
}
return { ...state,
[action.clientId]: { ...state[action.clientId],
...action.updates.attributes
}
};
case 'UPDATE_BLOCK_ATTRIBUTES':
{
// Avoid a state change if none of the block IDs are known.
if (action.clientIds.every(id => !state[id])) {
return state;
}
const next = action.clientIds.reduce((accumulator, id) => ({ ...accumulator,
[id]: (0, _lodash.reduce)(action.uniqueByBlock ? action.attributes[id] : action.attributes, (result, value, key) => {
// Consider as updates only changed values.
if (value !== result[key]) {
result = getMutateSafeObject(state[id], result);
result[key] = value;
}
return result;
}, state[id])
}), {});
if (action.clientIds.every(id => next[id] === state[id])) {
return state;
}
return { ...state,
...next
};
}
case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN':
if (!action.blocks) {
return state;
}
return { ...(0, _lodash.omit)(state, action.replacedClientIds),
...getFlattenedBlockAttributes(action.blocks)
};
case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN':
return (0, _lodash.omit)(state, action.removedClientIds);
}
return state;
},
order() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let action = arguments.length > 1 ? arguments[1] : undefined;
switch (action.type) {
case 'RECEIVE_BLOCKS':
{
const blockOrder = mapBlockOrder(action.blocks);
return { ...state,
...(0, _lodash.omit)(blockOrder, ''),
'': ((state === null || state === void 0 ? void 0 : state['']) || []).concat(blockOrder[''])
};
}
case 'INSERT_BLOCKS':
{
const {
rootClientId = ''
} = action;
const subState = state[rootClientId] || [];
const mappedBlocks = mapBlockOrder(action.blocks, rootClientId);
const {
index = subState.length
} = action;
return { ...state,
...mappedBlocks,
[rootClientId]: (0, _array.insertAt)(subState, mappedBlocks[rootClientId], index)
};
}
case 'MOVE_BLOCKS_TO_POSITION':
{
const {
fromRootClientId = '',
toRootClientId = '',
clientIds
} = action;
const {
index = state[toRootClientId].length
} = action; // Moving inside the same parent block
if (fromRootClientId === toRootClientId) {
const subState = state[toRootClientId];
const fromIndex = subState.indexOf(clientIds[0]);
return { ...state,
[toRootClientId]: (0, _array.moveTo)(state[toRootClientId], fromIndex, index, clientIds.length)
};
} // Moving from a parent block to another
return { ...state,
[fromRootClientId]: (0, _lodash.without)(state[fromRootClientId], ...clientIds),
[toRootClientId]: (0, _array.insertAt)(state[toRootClientId], clientIds, index)
};
}
case 'MOVE_BLOCKS_UP':
{
const {
clientIds,
rootClientId = ''
} = action;
const firstClientId = (0, _lodash.first)(clientIds);
const subState = state[rootClientId];
if (!subState.length || firstClientId === (0, _lodash.first)(subState)) {
return state;
}
const firstIndex = subState.indexOf(firstClientId);
return { ...state,
[rootClientId]: (0, _array.moveTo)(subState, firstIndex, firstIndex - 1, clientIds.length)
};
}
case 'MOVE_BLOCKS_DOWN':
{
const {
clientIds,
rootClientId = ''
} = action;
const firstClientId = (0, _lodash.first)(clientIds);
const lastClientId = (0, _lodash.last)(clientIds);
const subState = state[rootClientId];
if (!subState.length || lastClientId === (0, _lodash.last)(subState)) {
return state;
}
const firstIndex = subState.indexOf(firstClientId);
return { ...state,
[rootClientId]: (0, _array.moveTo)(subState, firstIndex, firstIndex + 1, clientIds.length)
};
}
case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN':
{
const {
clientIds
} = action;
if (!action.blocks) {
return state;
}
const mappedBlocks = mapBlockOrder(action.blocks);
return (0, _lodash.flow)([nextState => (0, _lodash.omit)(nextState, action.replacedClientIds), nextState => ({ ...nextState,
...(0, _lodash.omit)(mappedBlocks, '')
}), nextState => (0, _lodash.mapValues)(nextState, subState => (0, _lodash.reduce)(subState, (result, clientId) => {
if (clientId === clientIds[0]) {
return [...result, ...mappedBlocks['']];
}
if (clientIds.indexOf(clientId) === -1) {
result.push(clientId);
}
return result;
}, []))])(state);
}
case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN':
return (0, _lodash.flow)([// Remove inner block ordering for removed blocks
nextState => (0, _lodash.omit)(nextState, action.removedClientIds), // Remove deleted blocks from other blocks' orderings
nextState => (0, _lodash.mapValues)(nextState, subState => (0, _lodash.without)(subState, ...action.removedClientIds))])(state);
}
return state;
},
// While technically redundant data as the inverse of `order`, it serves as
// an optimization for the selectors which derive the ancestry of a block.
parents() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let action = arguments.length > 1 ? arguments[1] : undefined;
switch (action.type) {
case 'RECEIVE_BLOCKS':
return { ...state,
...mapBlockParents(action.blocks)
};
case 'INSERT_BLOCKS':
return { ...state,
...mapBlockParents(action.blocks, action.rootClientId || '')
};
case 'MOVE_BLOCKS_TO_POSITION':
{
return { ...state,
...action.clientIds.reduce((accumulator, id) => {
accumulator[id] = action.toRootClientId || '';
return accumulator;
}, {})
};
}
case 'REPLACE_BLOCKS_AUGMENTED_WITH_CHILDREN':
return { ...(0, _lodash.omit)(state, action.replacedClientIds),
...mapBlockParents(action.blocks, state[action.clientIds[0]])
};
case 'REMOVE_BLOCKS_AUGMENTED_WITH_CHILDREN':
return (0, _lodash.omit)(state, action.removedClientIds);
}
return state;
},
controlledInnerBlocks() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let {
type,
clientId,
hasControlledInnerBlocks
} = arguments.length > 1 ? arguments[1] : undefined;
if (type === 'SET_HAS_CONTROLLED_INNER_BLOCKS') {
return { ...state,
[clientId]: hasControlledInnerBlocks
};
}
return state;
}
});
/**
* Reducer returning typing state.
*
* @param {boolean} state Current state.
* @param {Object} action Dispatched action.
*
* @return {boolean} Updated state.
*/
exports.blocks = blocks;
function isTyping() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
let action = arguments.length > 1 ? arguments[1] : undefined;
switch (action.type) {
case 'START_TYPING':
return true;
case 'STOP_TYPING':
return false;
}
return state;
}
/**
* Reducer returning dragged block client id.
*
* @param {string[]} state Current state.
* @param {Object} action Dispatched action.
*
* @return {string[]} Updated state.
*/
function draggedBlocks() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
let action = arguments.length > 1 ? arguments[1] : undefined;
switch (action.type) {
case 'START_DRAGGING_BLOCKS':
return action.clientIds;
case 'STOP_DRAGGING_BLOCKS':
return [];
}
return state;
}
/**
* Reducer returning whether the caret is within formatted text.
*
* @param {boolean} state Current state.
* @param {Object} action Dispatched action.
*
* @return {boolean} Updated state.
*/
function isCaretWithinFormattedText() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
let action = arguments.length > 1 ? arguments[1] : undefined;
switch (action.type) {
case 'ENTER_FORMATTED_TEXT':
return true;
case 'EXIT_FORMATTED_TEXT':
return false;
}
return state;
}
/**
* Internal helper reducer for selectionStart and selectionEnd. Can hold a block
* selection, represented by an object with property clientId.
*
* @param {Object} state Current state.
* @param {Object} action Dispatched action.
*
* @return {Object} Updated state.
*/
function selectionHelper() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let action = arguments.length > 1 ? arguments[1] : undefined;
switch (action.type) {
case 'CLEAR_SELECTED_BLOCK':
{
if (state.clientId) {
return {};
}
return state;
}
case 'SELECT_BLOCK':
if (action.clientId === state.clientId) {
return state;
}
return {
clientId: action.clientId
};
case 'REPLACE_INNER_BLOCKS':
case 'INSERT_BLOCKS':
{
if (!action.updateSelection || !action.blocks.length) {
return state;
}
return {
clientId: action.blocks[0].clientId
};
}
case 'REMOVE_BLOCKS':
if (!action.clientIds || !action.clientIds.length || action.clientIds.indexOf(state.clientId) === -1) {
return state;
}
return {};
case 'REPLACE_BLOCKS':
{
if (action.clientIds.indexOf(state.clientId) === -1) {
return state;
}
const blockToSelect = action.blocks[action.indexToSelect] || action.blocks[action.blocks.length - 1];
if (!blockToSelect) {
return {};
}
if (blockToSelect.clientId === state.clientId) {
return state;
}
return {
clientId: blockToSelect.clientId
};
}
}
return state;
}
/**
* Reducer returning the selection state.
*
* @param {boolean} state Current state.
* @param {Object} action Dispatched action.
*
* @return {boolean} Updated state.
*/
function selection() {
var _state$selectionStart, _state$selectionEnd;
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let action = arguments.length > 1 ? arguments[1] : undefined;
switch (action.type) {
case 'SELECTION_CHANGE':
return {
selectionStart: {
clientId: action.clientId,
attributeKey: action.attributeKey,
offset: action.startOffset
},
selectionEnd: {
clientId: action.clientId,
attributeKey: action.attributeKey,
offset: action.endOffset
}
};
case 'RESET_SELECTION':
const {
selectionStart,
selectionEnd
} = action;
return {
selectionStart,
selectionEnd
};
case 'MULTI_SELECT':
const {
start,
end
} = action;
return {
selectionStart: {
clientId: start
},
selectionEnd: {
clientId: end
}
};
case 'RESET_BLOCKS':
const startClientId = state === null || state === void 0 ? void 0 : (_state$selectionStart = state.selectionStart) === null || _state$selectionStart === void 0 ? void 0 : _state$selectionStart.clientId;
const endClientId = state === null || state === void 0 ? void 0 : (_state$selectionEnd = state.selectionEnd) === null || _state$selectionEnd === void 0 ? void 0 : _state$selectionEnd.clientId; // Do nothing if there's no selected block.
if (!startClientId && !endClientId) {
return state;
} // If the start of the selection won't exist after reset, remove selection.
if (!action.blocks.some(block => block.clientId === startClientId)) {
return {
selectionStart: {},
selectionEnd: {}
};
} // If the end of the selection won't exist after reset, collapse selection.
if (!action.blocks.some(block => block.clientId === endClientId)) {
return { ...state,
selectionEnd: state.selectionStart
};
}
}
return {
selectionStart: selectionHelper(state.selectionStart, action),
selectionEnd: selectionHelper(state.selectionEnd, action)
};
}
/**
* Reducer returning whether the user is multi-selecting.
*
* @param {boolean} state Current state.
* @param {Object} action Dispatched action.
*
* @return {boolean} Updated state.
*/
function isMultiSelecting() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
let action = arguments.length > 1 ? arguments[1] : undefined;
switch (action.type) {
case 'START_MULTI_SELECT':
return true;
case 'STOP_MULTI_SELECT':
return false;
}
return state;
}
/**
* Reducer returning whether selection is enabled.
*
* @param {boolean} state Current state.
* @param {Object} action Dispatched action.
*
* @return {boolean} Updated state.
*/
function isSelectionEnabled() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
let action = arguments.length > 1 ? arguments[1] : undefined;
switch (action.type) {
case 'TOGGLE_SELECTION':
return action.isSelectionEnabled;
}
return state;
}
/**
* Reducer returning the intial block selection.
*
* Currently this in only used to restore the selection after block deletion and
* pasting new content.This reducer should eventually be removed in favour of setting
* selection directly.
*
* @param {boolean} state Current state.
* @param {Object} action Dispatched action.
*
* @return {number|null} Initial position: 0, -1 or null.
*/
function initialPosition() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
let action = arguments.length > 1 ? arguments[1] : undefined;
if (action.type === 'REPLACE_BLOCKS' && action.initialPosition !== undefined) {
return action.initialPosition;
} else if (['SELECT_BLOCK', 'RESET_SELECTION', 'INSERT_BLOCKS', 'REPLACE_INNER_BLOCKS'].includes(action.type)) {
return action.initialPosition;
}
return state;
}
function blocksMode() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let action = arguments.length > 1 ? arguments[1] : undefined;
if (action.type === 'TOGGLE_BLOCK_MODE') {
const {
clientId
} = action;
return { ...state,
[clientId]: state[clientId] && state[clientId] === 'html' ? 'visual' : 'html'
};
}
return state;
}
/**
* Reducer returning the block insertion point visibility, either null if there
* is not an explicit insertion point assigned, or an object of its `index` and
* `rootClientId`.
*
* @param {Object} state Current state.
* @param {Object} action Dispatched action.
*
* @return {Object} Updated state.
*/
function insertionPoint() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
let action = arguments.length > 1 ? arguments[1] : undefined;
switch (action.type) {
case 'SHOW_INSERTION_POINT':
const {
rootClientId,
index,
__unstableWithInserter
} = action;
return {
rootClientId,
index,
__unstableWithInserter
};
case 'HIDE_INSERTION_POINT':
return null;
}
return state;
}
/**
* Reducer returning whether the post blocks match the defined template or not.
*
* @param {Object} state Current state.
* @param {Object} action Dispatched action.
*
* @return {boolean} Updated state.
*/
function template() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
isValid: true
};
let action = arguments.length > 1 ? arguments[1] : undefined;
switch (action.type) {
case 'SET_TEMPLATE_VALIDITY':
return { ...state,
isValid: action.isValid
};
}
return state;
}
/**
* Reducer returning the editor setting.
*
* @param {Object} state Current state.
* @param {Object} action Dispatched action.
*
* @return {Object} Updated state.
*/
function settings() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _defaults.SETTINGS_DEFAULTS;
let action = arguments.length > 1 ? arguments[1] : undefined;
switch (action.type) {
case 'UPDATE_SETTINGS':
return { ...state,
...action.settings
};
}
return state;
}
/**
* Reducer returning the user preferences.
*
* @param {Object} state Current state.
* @param {Object} action Dispatched action.
*
* @return {string} Updated state.
*/
function preferences() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _defaults.PREFERENCES_DEFAULTS;
let action = arguments.length > 1 ? arguments[1] : undefined;
switch (action.type) {
case 'INSERT_BLOCKS':
case 'REPLACE_BLOCKS':
return action.blocks.reduce((prevState, block) => {
const {
attributes,
name: blockName
} = block;
const match = (0, _data.select)(_blocks.store).getActiveBlockVariation(blockName, attributes); // If a block variation match is found change the name to be the same with the
// one that is used for block variations in the Inserter (`getItemFromVariation`).
let id = match !== null && match !== void 0 && match.name ? `${blockName}/${match.name}` : blockName;
const insert = {
name: id
};
if (blockName === 'core/block') {
insert.ref = attributes.ref;
id += '/' + attributes.ref;
}
return { ...prevState,
insertUsage: { ...prevState.insertUsage,
[id]: {
time: action.time,
count: prevState.insertUsage[id] ? prevState.insertUsage[id].count + 1 : 1,
insert
}
}
};
}, state);
}
return state;
}
/**
* Reducer returning an object where each key is a block client ID, its value
* representing the settings for its nested blocks.
*
* @param {Object} state Current state.
* @param {Object} action Dispatched action.
*
* @return {Object} Updated state.
*/
const blockListSettings = function () {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let action = arguments.length > 1 ? arguments[1] : undefined;
switch (action.type) {
// Even if the replaced blocks have the same client ID, our logic
// should correct the state.
case 'REPLACE_BLOCKS':
case 'REMOVE_BLOCKS':
{
return (0, _lodash.omit)(state, action.clientIds);
}
case 'UPDATE_BLOCK_LIST_SETTINGS':
{
const {
clientId
} = action;
if (!action.settings) {
if (state.hasOwnProperty(clientId)) {
return (0, _lodash.omit)(state, clientId);
}
return state;
}
if ((0, _lodash.isEqual)(state[clientId], action.settings)) {
return state;
}
return { ...state,
[clientId]: action.settings
};
}
}
return state;
};
/**
* Reducer returning whether the navigation mode is enabled or not.
*
* @param {string} state Current state.
* @param {Object} action Dispatched action.
*
* @return {string} Updated state.
*/
exports.blockListSettings = blockListSettings;
function isNavigationMode() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
let action = arguments.length > 1 ? arguments[1] : undefined;
// Let inserting block always trigger Edit mode.
if (action.type === 'INSERT_BLOCKS') {
return false;
}
if (action.type === 'SET_NAVIGATION_MODE') {
return action.isNavigationMode;
}
return state;
}
/**
* Reducer returning whether the block moving mode is enabled or not.
*
* @param {string|null} state Current state.
* @param {Object} action Dispatched action.
*
* @return {string|null} Updated state.
*/
function hasBlockMovingClientId() {
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
let action = arguments.length > 1 ? arguments[1] : undefined;
// Let inserting block always trigger Edit mode.
if (action.type === 'SET_BLOCK_MOVING_MODE') {
return action.hasBlockMovingClientId;
}
if (action.type === 'SET_NAVIGATION_MODE') {
return null;
}
return state;
}
/**
* Reducer return an updated state representing the most recent block attribute
* update. The state is structured as an object where the keys represent the
* client IDs of blocks, the values a subset of attributes from the most recent
* block update. The state is always reset to null if the last action is
* anything other than an attributes update.
*
* @param {Object<string,Object>} state Current state.
* @param {Object} action Action object.
*
* @return {[string,Object]} Updated state.
*/
function lastBlockAttributesChange(state, action) {
switch (action.type) {
case 'UPDATE_BLOCK':
if (!action.updates.attributes) {
break;
}
return {
[action.clientId]: action.updates.attributes
};
case 'UPDATE_BLOCK_ATTRIBUTES':
return action.clientIds.reduce((accumulator, id) => ({ ...accumulator,
[id]: action.uniqueByBlock ? action.attributes[id] : action.attributes
}), {});
}
return null;
}
/**
* Reducer returning automatic change state.
*
* @param {boolean} state Current state.
* @param {Object} action Dispatched action.
*
* @return {string} Updated state.
*/
function automaticChangeStatus(state, action) {
switch (action.type) {
case 'MARK_AUTOMATIC_CHANGE':
return 'pending';
case 'MARK_AUTOMATIC_CHANGE_FINAL':
if (state === 'pending') {
return 'final';
}
return;
case 'SELECTION_CHANGE':
// As long as the state is not final, ignore any selection changes.
if (state !== 'final') {
return state;
}
return;
// Undoing an automatic change should still be possible after mouse
// move.
case 'START_TYPING':
case 'STOP_TYPING':
return state;
} // Reset the state by default (for any action not handled).
}
/**
* Reducer returning current highlighted block.
*
* @param {boolean} state Current highlighted block.
* @param {Object} action Dispatched action.
*
* @return {string} Updated state.
*/
function highlightedBlock(state, action) {
switch (action.type) {
case 'TOGGLE_BLOCK_HIGHLIGHT':
const {
clientId,
isHighlighted
} = action;
if (isHighlighted) {
return clientId;
} else if (state === clientId) {
return null;
}
return state;
case 'SELECT_BLOCK':
if (action.clientId !== state) {
return null;
}
}
return state;
}
/**
* Reducer returning the block insertion event list state.
*
* @param {Object} state Current state.
* @param {Object} action Dispatched action.
*
* @return {Object} Updated state.
*/
function lastBlockInserted() {
var _action$meta;
let state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let action = arguments.length > 1 ? arguments[1] : undefined;
switch (action.type) {
case 'INSERT_BLOCKS':
if (!action.blocks.length) {
return state;
}
const clientId = action.blocks[0].clientId;
const source = (_action$meta = action.meta) === null || _action$meta === void 0 ? void 0 : _action$meta.source;
return {
clientId,
source
};
case 'RESET_BLOCKS':
return {};
}
return state