UNPKG

@atlaskit/editor-plugin-block-menu

Version:

BlockMenu plugin for @atlaskit/editor-core

151 lines (146 loc) 5.09 kB
import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state'; import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils'; import { CellSelection } from '@atlaskit/editor-tables'; import { NODE_CATEGORY_BY_TYPE } from './types'; /** * Determines if a node is a text node (heading or paragraph). * Text nodes can have their content converted to paragraphs when they can't be wrapped directly. */ export const isTextNode = node => { const category = NODE_CATEGORY_BY_TYPE[node.type.name]; return category === 'text'; }; export const isListNode = node => { return ['bulletList', 'orderedList', 'taskList'].includes(node.type.name); }; export const getSelectedNode = selection => { if (selection instanceof NodeSelection) { return { node: selection.node, pos: selection.$from.pos, start: 0, // ? depth: selection.$from.depth }; } if (selection instanceof CellSelection) { const tableSelected = findParentNodeOfType(selection.$from.doc.type.schema.nodes.table)(selection); return tableSelected; } if (selection instanceof TextSelection) { const { blockquote, bulletList, orderedList, taskList, codeBlock, paragraph, heading } = selection.$from.doc.type.schema.nodes; const quoteSelected = findParentNodeOfType([blockquote])(selection); if (quoteSelected) { return quoteSelected; } const codeBlockSelected = findParentNodeOfType([codeBlock])(selection); if (codeBlockSelected) { return codeBlockSelected; } const listSelected = findParentNodeOfType([bulletList, taskList, orderedList])(selection); if (listSelected) { return listSelected; } const paragraphOrHeading = findParentNodeOfType([paragraph, heading])(selection); if (paragraphOrHeading) { return paragraphOrHeading; } } return undefined; }; export const getTargetNodeTypeNameInContext = (nodeTypeName, isNested, parentNode) => { if (parentNode && isNested && (parentNode.type.name === 'layoutColumn' || parentNode.type.name === 'bodiedSyncBlock')) { return nodeTypeName; } if (nodeTypeName === 'expand' && isNested) { return 'nestedExpand'; } return nodeTypeName; }; /** * Converts a nestedExpand to a regular expand node. * NestedExpands can only exist inside expands, so when breaking out or placing * in containers that don't support nesting, they must be converted. */ export const convertNestedExpandToExpand = (node, schema) => { var _node$attrs; const expandType = schema.nodes.expand; if (!expandType) { return null; } return expandType.createAndFill({ title: ((_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.title) || '', localId: crypto.randomUUID() }, node.content); }; /** * Converts an expand to a nestedExpand node. * When placing an expand inside another expand, it must become a nestedExpand * since expand cannot be a direct child of expand. */ export const convertExpandToNestedExpand = (node, schema) => { var _node$attrs2; const nestedExpandType = schema.nodes.nestedExpand; if (!nestedExpandType) { return null; } return nestedExpandType.createAndFill({ title: ((_node$attrs2 = node.attrs) === null || _node$attrs2 === void 0 ? void 0 : _node$attrs2.title) || '', localId: crypto.randomUUID() }, node.content); }; /** * Converts a text node (heading, paragraph) to a paragraph preserving its inline content. * This is used when a text node can't be wrapped directly in the target container * (e.g., heading can't go in blockquote, so it becomes a paragraph). */ export const convertTextNodeToParagraph = (node, schema) => { var _schema$nodes$paragra; // If it's already a paragraph, return as-is if (node.type.name === 'paragraph') { return node; } // Convert heading (or other text node) to paragraph with same inline content return (_schema$nodes$paragra = schema.nodes.paragraph.createAndFill({}, node.content)) !== null && _schema$nodes$paragra !== void 0 ? _schema$nodes$paragra : null; }; export const getBlockNodesInRange = range => { if (range.startIndex === range.endIndex) { return []; } if (range.endIndex - range.startIndex <= 1) { return [range.parent.child(range.startIndex)]; } const blockNodes = []; for (let i = range.startIndex; i < range.endIndex; i++) { if (range.parent.child(i).isBlock) { blockNodes.push(range.parent.child(i)); } } return blockNodes; }; /** * Iterates over a nodes children and extracting text content, removing all other inline content and converting * hardbreaks to newlines. * * @param node - The node to create text content from (should be paragraph) * @returns The text content string. */ export const createTextContent = node => { const textContent = node.children.map(child => { if (child.isText) { return child.text; } else if (child.type.name === 'hardBreak') { return '\n'; } return ''; }); return textContent.join(''); };