@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
119 lines (114 loc) • 4.56 kB
JavaScript
import { JSONTransformer } from '@atlaskit/editor-json-transformer';
import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils';
/**
* Finds all top level nodes affected by the transaction
* Uses from/to positions in transaction's steps to work out which nodes will
* be changed by the transaction
*/
export const findChangedNodesFromTransaction = tr => {
const nodes = [];
const steps = tr.steps || [];
steps.forEach(step => {
step.getMap().forEach((oldStart, oldEnd, newStart, newEnd) => {
tr.doc.nodesBetween(newStart, Math.min(newEnd, tr.doc.content.size), node => {
if (!nodes.find(n => n === node)) {
nodes.push(node);
}
return false;
});
});
});
return nodes;
};
export const validNode = node => {
try {
node.check(); // this will throw an error if the node is invalid
} catch (error) {
return false;
}
return true;
};
/** Validates prosemirror nodes, and returns true only if all nodes are valid */
export const validateNodes = nodes => nodes.every(validNode);
export const isType = (node, type) => type && node && node.type === type;
export const isParagraph = (node, schema) => isType(node, schema.nodes.paragraph);
export const isText = (node, schema) => isType(node, schema.nodes.text);
export const isLinkMark = (node, schema) => isType(node, schema.marks.link);
export let SelectedState = /*#__PURE__*/function (SelectedState) {
SelectedState[SelectedState["selectedInRange"] = 0] = "selectedInRange";
SelectedState[SelectedState["selectedInside"] = 1] = "selectedInside";
return SelectedState;
}({});
/**
* Returns if the current selection from achor-head is selecting the node.
* If the node is not selected then null is returned.
* If the node is selected then an enum is returned that describes weather the node
* is fully selected by a range or if the "inside" of the node has been selected or clicked.
*/
export const isNodeSelectedOrInRange = (anchorPosition, headPosition, nodePosition, nodeSize) => {
if (typeof nodePosition !== 'number') {
return null;
}
const rangeStart = Math.min(anchorPosition, headPosition);
const rangeEnd = Math.max(anchorPosition, headPosition);
const nodeStart = nodePosition;
const nodeEnd = nodePosition + nodeSize;
if (anchorPosition === headPosition) {
return null;
}
if (rangeStart <= nodeStart && nodeEnd < rangeEnd || rangeStart < nodeStart && nodeEnd <= rangeEnd) {
return SelectedState.selectedInRange;
}
if (nodeStart <= anchorPosition && headPosition <= nodeEnd) {
return SelectedState.selectedInside;
}
return null;
};
/**
* Checks if a particular node fragment is supported in the parent
* @param state EditorState
* @param fragment The fragment to be checked for
*/
export const isSupportedInParent = (state, fragment, currentAppearance) => {
const depth = currentAppearance === 'embed' || currentAppearance === 'block' ? undefined : -1;
const parent = state.selection.$from.node(depth);
return parent && parent.type.validContent(fragment);
};
/**
* Checks if the passed in node is a media node
* Includes media, mediaInline, mediaGroup, mediaSingle
* @param node The PM node to be checked
*/
export const isMediaNode = node => {
return ['media', 'mediaInline', 'mediaGroup', 'mediaSingle'].includes(node.type.name);
};
/**
* Checks if the node before selection is a media node
* If there is no node before, checks the node before the parent node
* Includes media, mediaInline, mediaGroup, mediaSingle
* @param $pos The position of the selection
* @param state The editor state
*/
export const isNodeBeforeMediaNode = ($pos, state) => {
let nodeBefore = $pos.nodeBefore;
if (!nodeBefore) {
const depthOfParent = $pos.depth - 1 || 1;
const parentNode = findParentNodeOfType([state.schema.nodes[`${$pos.node(depthOfParent).type.name}`]])(state.selection);
const resolvedPosOfParentNode = parentNode ? state.tr.doc.resolve(parentNode.pos) : undefined;
const nodeBeforeParent = resolvedPosOfParentNode && resolvedPosOfParentNode.pos < state.doc.nodeSize ? resolvedPosOfParentNode.nodeBefore : undefined;
if (nodeBeforeParent) {
nodeBefore = nodeBeforeParent;
}
}
if (nodeBefore) {
return ['media', 'mediaInline', 'mediaGroup', 'mediaSingle'].includes(nodeBefore.type.name);
}
return false;
};
const transformer = new JSONTransformer();
export function toJSON(node) {
return transformer.encode(node);
}
export function nodeToJSON(node) {
return transformer.encodeNode(node);
}