@atlaskit/editor-core
Version:
A package contains Atlassian editor core functionality
357 lines • 12.8 kB
JavaScript
import { liftTarget, NodeSelection, TextSelection, Slice, Fragment, findWrapping } from '../prosemirror';
import * as commands from '../commands';
import { LEFT } from '../keymaps';
import JSONSerializer from '../renderer/json';
export { default as ErrorReporter, } from './error-reporter';
export { filterContentByType } from './filter';
function validateNode(node) {
return false;
}
function isMarkTypeExcludedFromMark(markType, mark) {
return mark.type.excludes(markType);
}
function isMarkTypeAllowedInNode(markType, state) {
return commands.toggleMark(markType)(state);
}
export function canMoveUp(state) {
var selection = state.selection;
if (selection instanceof TextSelection) {
if (!selection.empty) {
return true;
}
}
return !atTheBeginningOfDoc(state);
}
export function canMoveDown(state) {
var selection = state.selection;
if (selection instanceof TextSelection) {
if (!selection.empty) {
return true;
}
}
return !atTheEndOfDoc(state);
}
export function atTheEndOfDoc(state) {
var selection = state.selection, doc = state.doc;
return doc.nodeSize - selection.$to.pos - 2 === selection.$to.depth;
}
export function atTheBeginningOfDoc(state) {
var selection = state.selection;
return selection.$from.pos === selection.$from.depth;
}
export function atTheEndOfBlock(state) {
var selection = state.selection;
var $to = selection.$to;
if (selection instanceof NodeSelection && selection.node.isBlock) {
return true;
}
return endPositionOfParent($to) === $to.pos + 1;
}
export function atTheBeginningOfBlock(state) {
var selection = state.selection;
var $from = selection.$from;
if (selection instanceof NodeSelection && selection.node.isBlock) {
return true;
}
return startPositionOfParent($from) === $from.pos;
}
export function startPositionOfParent(resolvedPos) {
return resolvedPos.start(resolvedPos.depth);
}
export function endPositionOfParent(resolvedPos) {
return resolvedPos.end(resolvedPos.depth) + 1;
}
/**
* Check if a mark is allowed at the current position based on a given state.
* This method looks both at the currently active marks as well as the node and marks
* at the current position to determine if the given mark type is allowed.
* If there's a non-empty selection, the current position corresponds to the start
* of the selection.
*/
export function isMarkTypeAllowedAtCurrentPosition(markType, state) {
if (!isMarkTypeAllowedInNode(markType, state)) {
return false;
}
var allowedInActiveMarks = true;
var excludesMarkType = function (mark) { return isMarkTypeExcludedFromMark(markType, mark); };
if (state.tr.storedMarks) {
allowedInActiveMarks = !state.tr.storedMarks.some(excludesMarkType);
}
else {
allowedInActiveMarks = !state.selection.$from.marks().some(excludesMarkType);
}
return allowedInActiveMarks;
}
/**
* Step through block-nodes between $from and $to and returns false if a node is
* found that isn't of the specified type
*/
export function isRangeOfType(doc, $from, $to, nodeType) {
return getAncestorNodesBetween(doc, $from, $to).filter(function (node) { return node.type !== nodeType; }).length === 0;
}
export function createSliceWithContent(content, state) {
return new Slice(Fragment.from(state.schema.text(content)), 0, 0);
}
/**
* Determines if content inside a selection can be joined with the next block.
* We need this check since the built-in method for "joinDown" will join a orderedList with bulletList.
*/
export function canJoinDown(selection, doc, nodeType) {
var res = doc.resolve(selection.$to.after(findAncestorPosition(doc, selection.$to).depth));
return res.nodeAfter && res.nodeAfter.type === nodeType;
}
export var setNodeSelection = function (view, pos) {
var state = view.state, dispatch = view.dispatch;
var tr = state.tr.setSelection(NodeSelection.create(state.doc, pos));
dispatch(tr);
};
export function setTextSelection(view, anchor, head) {
var state = view.state;
var tr = state.tr.setSelection(TextSelection.create(state.doc, anchor, head));
view.dispatch(tr);
}
export function moveCursorToTheEnd(view) {
var state = view.state;
var anchor = Math.max(state.doc.nodeSize - 2, 0);
var tr = state.tr.setSelection(TextSelection.create(state.doc, anchor)).scrollIntoView();
view.dispatch(tr);
}
/**
* Determines if content inside a selection can be joined with the previous block.
* We need this check since the built-in method for "joinUp" will join a orderedList with bulletList.
*/
export function canJoinUp(selection, doc, nodeType) {
var res = doc.resolve(selection.$from.before(findAncestorPosition(doc, selection.$from).depth));
return res.nodeBefore && res.nodeBefore.type === nodeType;
}
/**
* Returns all top-level ancestor-nodes between $from and $to
*/
export function getAncestorNodesBetween(doc, $from, $to) {
var nodes = Array();
var maxDepth = findAncestorPosition(doc, $from).depth;
var current = doc.resolve($from.start(maxDepth));
while (current.pos <= $to.start($to.depth)) {
var depth = Math.min(current.depth, maxDepth);
var node = current.node(depth);
if (node) {
nodes.push(node);
}
if (depth === 0) {
break;
}
var next = doc.resolve(current.after(depth));
if (next.start(depth) >= doc.nodeSize - 2) {
break;
}
if (next.depth !== current.depth) {
next = doc.resolve(next.pos + 2);
}
if (next.depth) {
current = doc.resolve(next.start(next.depth));
}
else {
current = doc.resolve(next.end(next.depth));
}
}
return nodes;
}
/**
* Finds all "selection-groups" within a range. A selection group is based on ancestors.
*
* Example:
* Given the following document and selection ({<} = start of selection and {>} = end)
* doc
* blockquote
* ul
* li
* li{<}
* li
* p
* p{>}
*
* The output will be two selection-groups. One within the ul and one with the two paragraphs.
*/
export function getGroupsInRange(doc, $from, $to, isNodeValid) {
if (isNodeValid === void 0) { isNodeValid = validateNode; }
var groups = Array();
var commonAncestor = hasCommonAncestor(doc, $from, $to);
var fromAncestor = findAncestorPosition(doc, $from);
if (commonAncestor || (fromAncestor.depth === 1 && isNodeValid($from.node(1)))) {
groups.push({ $from: $from, $to: $to });
}
else {
var current = $from;
while (current.pos < $to.pos) {
var ancestorPos = findAncestorPosition(doc, current);
while (ancestorPos.depth > 1) {
ancestorPos = findAncestorPosition(doc, ancestorPos);
}
var endPos = doc.resolve(Math.min(
// should not be smaller then start position in case of an empty paragpraph for example.
Math.max(ancestorPos.start(ancestorPos.depth), ancestorPos.end(ancestorPos.depth) - 3), $to.pos));
groups.push({
$from: current,
$to: endPos
});
current = doc.resolve(Math.min(endPos.after(1) + 1, doc.nodeSize - 2));
}
}
return groups;
}
/**
* Traverse the document until an "ancestor" is found. Any nestable block can be an ancestor.
*/
export function findAncestorPosition(doc, pos) {
var nestableBlocks = ['blockquote', 'bulletList', 'orderedList'];
if (pos.depth === 1) {
return pos;
}
var node = pos.node(pos.depth);
var newPos = pos;
while (pos.depth >= 1) {
pos = doc.resolve(pos.before(pos.depth));
node = pos.node(pos.depth);
if (node && nestableBlocks.indexOf(node.type.name) !== -1) {
newPos = pos;
}
}
return newPos;
}
/**
* Determine if two positions have a common ancestor.
*/
export function hasCommonAncestor(doc, $from, $to) {
var current;
var target;
if ($from.depth > $to.depth) {
current = findAncestorPosition(doc, $from);
target = findAncestorPosition(doc, $to);
}
else {
current = findAncestorPosition(doc, $to);
target = findAncestorPosition(doc, $from);
}
while (current.depth > target.depth && current.depth > 1) {
current = findAncestorPosition(doc, current);
}
return current.node(current.depth) === target.node(target.depth);
}
/**
* Takes a selection $from and $to and lift all text nodes from their parents to document-level
*/
export function liftSelection(tr, doc, $from, $to) {
var startPos = $from.start($from.depth);
var endPos = $to.end($to.depth);
var target = Math.max(0, findAncestorPosition(doc, $from).depth - 1);
tr.doc.nodesBetween(startPos, endPos, function (node, pos) {
if (node.isText ||
(node.isTextblock && !node.textContent) // Empty paragraph
) {
var res = tr.doc.resolve(tr.mapping.map(pos));
var sel = new NodeSelection(res);
var range = sel.$from.blockRange(sel.$to);
if (liftTarget(range) !== undefined) {
tr.lift(range, target);
}
}
});
startPos = tr.mapping.map(startPos);
endPos = tr.mapping.map(endPos);
endPos = tr.doc.resolve(endPos).end(tr.doc.resolve(endPos).depth); // We want to select the entire node
tr.setSelection(new TextSelection(tr.doc.resolve(startPos), tr.doc.resolve(endPos)));
return {
tr: tr,
$from: tr.doc.resolve(startPos),
$to: tr.doc.resolve(endPos)
};
}
/**
* Lift nodes in block to one level above.
*/
export function liftSiblingNodes(view) {
var tr = view.state.tr;
var _a = view.state.selection, $from = _a.$from, $to = _a.$to;
var blockStart = tr.doc.resolve($from.start($from.depth - 1));
var blockEnd = tr.doc.resolve($to.end($to.depth - 1));
var range = blockStart.blockRange(blockEnd);
view.dispatch(tr.lift(range, blockStart.depth - 1));
}
/**
* Lift sibling nodes to document-level and select them.
*/
export function liftAndSelectSiblingNodes(view) {
var tr = view.state.tr;
var _a = view.state.selection, $from = _a.$from, $to = _a.$to;
var blockStart = tr.doc.resolve($from.start($from.depth - 1));
var blockEnd = tr.doc.resolve($to.end($to.depth - 1));
var range = blockStart.blockRange(blockEnd);
tr.setSelection(new TextSelection(blockStart, blockEnd));
tr.lift(range, blockStart.depth - 1);
return tr;
}
export function wrapIn(nodeType, tr, $from, $to) {
var range = $from.blockRange($to);
var wrapping = range && findWrapping(range, nodeType);
if (wrapping) {
tr = tr.wrap(range, wrapping).scrollIntoView();
}
return tr;
}
export function toJSON(node) {
return new JSONSerializer().serializeFragment(node.content);
}
/**
* Repeating string for multiple times
*/
export function stringRepeat(text, length) {
var result = '';
for (var x = 0; x < length; x++) {
result += text;
}
return result;
}
/**
* A replacement for `Array.from` until it becomes widely implemented.
*/
export function arrayFrom(obj) {
return Array.prototype.slice.call(obj);
}
export function moveLeft(view) {
var event = new CustomEvent('keydown', {
bubbles: true,
cancelable: true,
});
event.keyCode = LEFT;
view.dispatchEvent(event);
}
/**
* Function will create a list of wrapper blocks present in a selection.
*/
function getSelectedWrapperNodes(state) {
var nodes = [];
if (state.selection) {
var _a = state.selection, $from = _a.$from, $to = _a.$to;
var _b = state.schema.nodes, blockquote_1 = _b.blockquote, panel_1 = _b.panel, orderedList_1 = _b.orderedList, bulletList_1 = _b.bulletList, listItem_1 = _b.listItem, codeBlock_1 = _b.codeBlock;
state.doc.nodesBetween($from.pos, $to.pos, function (node, pos) {
if ((node.isBlock &&
[blockquote_1, panel_1, orderedList_1, bulletList_1, listItem_1].indexOf(node.type) >= 0) ||
node.type === codeBlock_1) {
nodes.push(node.type);
}
});
}
return nodes;
}
/**
* Function will check if changing block types: Paragraph, Heading is enabled.
*/
export function areBlockTypesDisabled(state) {
var nodesTypes = getSelectedWrapperNodes(state);
var panel = state.schema.nodes.panel;
return nodesTypes.filter(function (type) { return type !== panel; }).length > 0;
}
export var isTemporary = function (id) {
return id.indexOf('temporary:') === 0;
};
//# sourceMappingURL=index.js.map