UNPKG

@atlaskit/editor-plugin-block-controls

Version:

Block controls plugin for @atlaskit/editor-core

193 lines (189 loc) 7.78 kB
import memoizeOne from 'memoize-one'; import { getParentOfTypeCount, isNestedTablesSupported } from '@atlaskit/editor-common/nesting'; import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model'; import { findChildrenByType } from '@atlaskit/editor-prosemirror/utils'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; export const isInsideTable = nodeType => { const { tableCell, tableHeader } = nodeType.schema.nodes; return [tableCell, tableHeader].indexOf(nodeType) >= 0; }; export const isLayoutColumn = nodeType => { return nodeType === nodeType.schema.nodes.layoutColumn; }; export const isDoc = nodeType => { return nodeType === nodeType.schema.nodes.doc; }; export const isExpand = nodeType => { return nodeType === nodeType.schema.nodes.expand; }; export const isNestedExpand = nodeType => { return nodeType === nodeType.schema.nodes.nestedExpand; }; export const isFontSizeMarkActive = node => { return node.type.name === 'paragraph' && node.marks.some(mark => mark.type.name === 'fontSize'); }; export const isInSameLayout = ($from, $to) => { const fromNode = $from.nodeAfter; const toNode = $to.nodeAfter; return !!(fromNode && toNode && fromNode.type.name === 'layoutColumn' && ['layoutSection', 'layoutColumn'].includes(toNode.type.name) && ( // fromNode can either be in the same layoutSection as toNode or is a layoutColumn inside the toNode (type layoutSection) $from.sameParent($to) || $from.parent === toNode)); }; /** * This function converts an expand into a nested expand, * although it may fail based on the expand's content. * @param expandNode the node to transform. * @returns an nested expand node * @throws RangeError: Invalid content for node nestedExpand */ export const transformExpandToNestedExpand = expandNode => { const { expand, nestedExpand } = expandNode.type.schema.nodes; if (expandNode.type === expand) { return nestedExpand.createChecked(expandNode.attrs, expandNode.content, expandNode.marks); } return null; }; export const transformFragmentExpandToNestedExpand = fragment => { const children = []; try { fragment.forEach(node => { if (isExpand(node.type)) { const nestedExpandNode = transformExpandToNestedExpand(node); if (nestedExpandNode) { children.push(nestedExpandNode); } } else { children.push(node); } }); } catch (e) { return null; } return Fragment.fromArray(children); }; export const transformSliceExpandToNestedExpand = slice => { const fragment = transformFragmentExpandToNestedExpand(slice.content); if (!fragment) { return null; } return new Slice(fragment, slice.openStart, slice.openEnd); }; export const memoizedTransformExpandToNestedExpand = memoizeOne(node => { try { return transformExpandToNestedExpand(node); } catch (e) { return null; } }); export const canCreateNodeWithContentInsideAnotherNode = (nodeTypesToCreate, nodeWithTargetFragment) => { try { return !!nodeTypesToCreate.every(nodeTypeToCreate => nodeTypeToCreate.createChecked({}, nodeWithTargetFragment)); } catch (e) { return false; } }; export function canMoveNodeToIndex(destParent, indexIntoParent, srcNode, $destNodePos, destNode) { let srcNodeType = srcNode.type; const schema = srcNodeType.schema; const { table, tableCell, tableHeader, expand, nestedExpand, doc, panel, layoutColumn, layoutSection } = schema.nodes; const destParentNodeType = destParent === null || destParent === void 0 ? void 0 : destParent.type; const activeNodeType = srcNode === null || srcNode === void 0 ? void 0 : srcNode.type; const layoutColumnContent = srcNode.content; const isNestingTablesSupported = isNestedTablesSupported(schema); if (activeNodeType === layoutColumn && editorExperiment('advanced_layouts', true)) { // Allow drag layout column and drop into layout section if ((destNode === null || destNode === void 0 ? void 0 : destNode.type) === layoutSection || destParentNodeType === doc) { return true; } if (destParentNodeType === tableCell || destParentNodeType === tableHeader) { const contentContainsExpand = findChildrenByType(srcNode, expand).length > 0; //convert expand to nestedExpand if there are expands inside the layout column // otherwise, the createChecked will fail as expand is not a valid child of tableCell/tableHeader, but nestedExpand is const convertedFragment = contentContainsExpand ? transformFragmentExpandToNestedExpand(layoutColumnContent) : layoutColumnContent; if (!convertedFragment) { return false; } return canCreateNodeWithContentInsideAnotherNode([tableCell, tableHeader], convertedFragment); } if (destParentNodeType === panel) { return canCreateNodeWithContentInsideAnotherNode([panel], layoutColumnContent); } if (destParentNodeType === expand) { return canCreateNodeWithContentInsideAnotherNode([expand], layoutColumnContent); } if (destParentNodeType === nestedExpand) { return canCreateNodeWithContentInsideAnotherNode([nestedExpand], layoutColumnContent); } } // NOTE: this will block drop targets from showing for dragging a table into another table // unless nested tables are supported and the nesting depth does not exceed 1 if ((destParentNodeType === tableCell || destParentNodeType === tableHeader) && activeNodeType === table) { const nestingDepth = getParentOfTypeCount(table)($destNodePos); if (!isNestingTablesSupported || isNestingTablesSupported && nestingDepth > 1) { return false; } } if (isInsideTable(destParent.type) && isExpand(srcNodeType)) { if (memoizedTransformExpandToNestedExpand(srcNode)) { srcNodeType = nestedExpand; } else { return false; } } else if ((isDoc(destParent.type) || isLayoutColumn(destParent.type)) && isNestedExpand(srcNodeType)) { srcNodeType = expand; } return destParent.canReplaceWith(indexIntoParent, indexIntoParent, srcNodeType); } export function canMoveSliceToIndex(slice, sliceFromPos, sliceToPos, destParent, indexIntoParent, $destNodePos, destNode) { var _slice$content$firstC; let canMoveNodes = true; const doc = $destNodePos.doc; const nodesPos = []; // Drag multiple nodes to be inside themselves not allowed if ($destNodePos.pos < sliceToPos && $destNodePos.pos >= sliceFromPos) { return false; } // Multiple layout columns do not drop correctly. if (((_slice$content$firstC = slice.content.firstChild) === null || _slice$content$firstC === void 0 ? void 0 : _slice$content$firstC.type.name) === 'layoutColumn') { return false; } for (let i = 0; i < slice.content.childCount; i++) { const node = slice.content.maybeChild(i); if (i === 0) { nodesPos[i] = sliceFromPos; } else { var _slice$content$maybeC; nodesPos[i] = nodesPos[i - 1] + (((_slice$content$maybeC = slice.content.maybeChild(i - 1)) === null || _slice$content$maybeC === void 0 ? void 0 : _slice$content$maybeC.nodeSize) || 0); } if (node && node.isInline) { // If the node is an inline node, we need to find the parent node // as passing in them into canMoveNodeToIndex will return false const $nodePos = doc.resolve(nodesPos[i]); const parentNode = $nodePos.parent; if (!parentNode || parentNode && !canMoveNodeToIndex(destParent, indexIntoParent, parentNode, $destNodePos, destNode)) { canMoveNodes = false; break; } } else if (node && !canMoveNodeToIndex(destParent, indexIntoParent, node, $destNodePos, destNode)) { canMoveNodes = false; break; } } return canMoveNodes; }