@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
283 lines (277 loc) • 10.6 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.atTheBeginningOfDoc = atTheBeginningOfDoc;
exports.atTheEndOfDoc = atTheEndOfDoc;
exports.createNewParagraphBelow = exports.createNewParagraphAbove = void 0;
exports.createParagraphNear = createParagraphNear;
exports.insertContentDeleteRange = exports.filterCommand = exports.filter = exports.deleteEmptyParagraphAndMoveBlockUp = void 0;
exports.insertNewLine = insertNewLine;
exports.walkPrevNode = exports.walkNextNode = exports.isEmptySelectionAtStart = exports.isEmptySelectionAtEnd = exports.insertNewLineWithAnalytics = void 0;
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _model = require("@atlaskit/editor-prosemirror/model");
var _state = require("@atlaskit/editor-prosemirror/state");
var _analytics = require("../analytics");
var _editorAnalytics = require("../editor-analytics");
var _selection = require("../selection");
var _editorCoreUtils = require("./editor-core-utils");
var _nodes = require("./nodes");
var filter = exports.filterCommand = exports.filter = function filter(predicates, cmd) {
return function (state, dispatch, view) {
if (!Array.isArray(predicates)) {
predicates = [predicates];
}
if (predicates.some(function (pred) {
return !pred(state, view);
})) {
return false;
}
return cmd(state, dispatch, view) || false;
};
};
/**
* Walk forwards from a position until we encounter the (inside) start of
* the next node, or reach the end of the document.
*
* @param $startPos Position to start walking from.
*/
var walkNextNode = exports.walkNextNode = function walkNextNode($startPos) {
var $pos = $startPos;
// invariant 1: don't walk past the end of the document
// invariant 2: we are at the beginning or
// we haven't walked to the start of *any* node
// parentOffset includes textOffset.
while ($pos.pos < $pos.doc.nodeSize - 2 && ($pos.pos === $startPos.pos || $pos.parentOffset > 0)) {
$pos = $pos.doc.resolve($pos.pos + 1);
}
return {
$pos: $pos,
foundNode: $pos.pos < $pos.doc.nodeSize - 2
};
};
/**
* Walk backwards from a position until we encounter the (inside) end of
* the previous node, or reach the start of the document.
*
* @param $startPos Position to start walking from.
*/
var walkPrevNode = exports.walkPrevNode = function walkPrevNode($startPos) {
var $pos = $startPos;
while ($pos.pos > 0 && ($pos.pos === $startPos.pos || $pos.parentOffset < $pos.parent.nodeSize - 2)) {
$pos = $pos.doc.resolve($pos.pos - 1);
}
return {
$pos: $pos,
foundNode: $pos.pos > 0
};
};
function insertNewLine() {
return function (state, dispatch) {
var $from = state.selection.$from;
var parent = $from.parent;
var hardBreak = state.schema.nodes.hardBreak;
if (hardBreak) {
var hardBreakNode = hardBreak.createChecked();
if (parent && parent.type.validContent(_model.Fragment.from(hardBreakNode))) {
if (dispatch) {
dispatch(state.tr.replaceSelectionWith(hardBreakNode, false));
}
return true;
}
}
if (state.selection instanceof _state.TextSelection) {
if (dispatch) {
dispatch(state.tr.insertText('\n'));
}
return true;
}
return false;
};
}
var insertNewLineWithAnalytics = exports.insertNewLineWithAnalytics = function insertNewLineWithAnalytics(editorAnalyticsAPI) {
return (0, _editorAnalytics.withAnalytics)(editorAnalyticsAPI, {
action: _analytics.ACTION.INSERTED,
actionSubject: _analytics.ACTION_SUBJECT.TEXT,
actionSubjectId: _analytics.ACTION_SUBJECT_ID.LINE_BREAK,
eventType: _analytics.EVENT_TYPE.TRACK
})(insertNewLine());
};
var createNewParagraphAbove = exports.createNewParagraphAbove = function createNewParagraphAbove(state, dispatch) {
var append = false;
if (!canMoveUp(state) && canCreateParagraphNear(state)) {
createParagraphNear(append)(state, dispatch);
return true;
}
return false;
};
var createNewParagraphBelow = exports.createNewParagraphBelow = function createNewParagraphBelow(state, dispatch) {
var append = true;
if (!canMoveDown(state) && canCreateParagraphNear(state)) {
createParagraphNear(append)(state, dispatch);
return true;
}
return false;
};
function canCreateParagraphNear(state) {
var $from = state.selection.$from;
var node = $from.node($from.depth);
var insideCodeBlock = !!node && node.type === state.schema.nodes.codeBlock;
var isNodeSelection = state.selection instanceof _state.NodeSelection;
return $from.depth > 1 || isNodeSelection || insideCodeBlock;
}
function createParagraphNear() {
var append = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
return function (state, dispatch) {
var paragraph = state.schema.nodes.paragraph;
if (!paragraph) {
return false;
}
var insertPos;
if (state.selection instanceof _state.TextSelection) {
if (topLevelNodeIsEmptyTextBlock(state)) {
return false;
}
insertPos = getInsertPosFromTextBlock(state, append);
} else {
insertPos = getInsertPosFromNonTextBlock(state, append);
}
var tr = state.tr.insert(insertPos, paragraph.createAndFill());
tr.setSelection(_state.TextSelection.create(tr.doc, insertPos + 1));
if (dispatch) {
dispatch(tr);
}
return true;
};
}
function getInsertPosFromTextBlock(state, append) {
var _state$selection = state.selection,
$from = _state$selection.$from,
$to = _state$selection.$to;
var pos;
if (!append) {
pos = $from.start(0);
} else {
pos = $to.end(0);
}
return pos;
}
function getInsertPosFromNonTextBlock(state, append) {
var _state$selection2 = state.selection,
$from = _state$selection2.$from,
$to = _state$selection2.$to;
var nodeAtSelection = state.selection instanceof _state.NodeSelection && state.doc.nodeAt(state.selection.$anchor.pos);
var isMediaSelection = nodeAtSelection && nodeAtSelection.type.name === 'mediaGroup';
var pos;
if (!append) {
// The start position is different with text block because it starts from 0
pos = $from.start($from.depth);
// The depth is different with text block because it starts from 0
pos = $from.depth > 0 && !isMediaSelection ? pos - 1 : pos;
} else {
pos = $to.end($to.depth);
pos = $to.depth > 0 && !isMediaSelection ? pos + 1 : pos;
}
return pos;
}
function topLevelNodeIsEmptyTextBlock(state) {
var topLevelNode = state.selection.$from.node(1);
return topLevelNode.isTextblock && topLevelNode.type !== state.schema.nodes.codeBlock && topLevelNode.nodeSize === 2;
}
function canMoveUp(state) {
var selection = state.selection;
/**
* If there's a media element on the selection it will use a gap cursor to move
*/
if (selection instanceof _state.NodeSelection && (0, _nodes.isMediaNode)(selection.node)) {
return true;
}
if (selection instanceof _state.TextSelection) {
if (!selection.empty) {
return true;
}
}
return !atTheBeginningOfDoc(state);
}
function canMoveDown(state) {
var selection = state.selection;
/**
* If there's a media element on the selection it will use a gap cursor to move
*/
if (selection instanceof _state.NodeSelection && (0, _nodes.isMediaNode)(selection.node)) {
return true;
}
if (selection instanceof _state.TextSelection) {
if (!selection.empty) {
return true;
}
}
return !atTheEndOfDoc(state);
}
function atTheEndOfDoc(state) {
var selection = state.selection,
doc = state.doc;
return doc.nodeSize - selection.$to.pos - 2 === selection.$to.depth;
}
function atTheBeginningOfDoc(state) {
var selection = state.selection;
return selection.$from.pos === selection.$from.depth;
}
/**
* If the selection is empty, is inside a paragraph node and `canNextNodeMoveUp` is true then delete current paragraph
* and move the node below it up. The selection will be retained, to be placed in the moved node.
*
* @param canNextNodeMoveUp check if node directly after the selection is able to be brought up to selection
* @returns PM Command
*/
var deleteEmptyParagraphAndMoveBlockUp = exports.deleteEmptyParagraphAndMoveBlockUp = function deleteEmptyParagraphAndMoveBlockUp(canNextNodeMoveUp) {
return function (state, dispatch, view) {
var _state$selection3 = state.selection,
_state$selection3$$fr = _state$selection3.$from,
pos = _state$selection3$$fr.pos,
parent = _state$selection3$$fr.parent,
$head = _state$selection3.$head,
empty = _state$selection3.empty,
tr = state.tr,
doc = state.doc;
var _walkNextNode = walkNextNode($head),
$pos = _walkNextNode.$pos;
var nextPMNode = doc.nodeAt($pos.pos - 1);
if (empty && nextPMNode && canNextNodeMoveUp(nextPMNode) && (0, _editorCoreUtils.isEmptyParagraph)(parent) && view !== null && view !== void 0 && view.endOfTextblock('right')) {
tr.deleteRange(pos - 1, pos + 1);
if (dispatch) {
dispatch(tr);
}
return true;
}
return false;
};
};
var insertContentDeleteRange = exports.insertContentDeleteRange = function insertContentDeleteRange(tr, getSelectionResolvedPos, insertions, deletions) {
insertions.forEach(function (contentInsert) {
var _contentInsert = (0, _slicedToArray2.default)(contentInsert, 2),
content = _contentInsert[0],
pos = _contentInsert[1];
tr.insert(tr.mapping.map(pos), content);
});
deletions.forEach(function (deleteRange) {
var _deleteRange = (0, _slicedToArray2.default)(deleteRange, 2),
firstPos = _deleteRange[0],
lastPos = _deleteRange[1];
tr.delete(tr.mapping.map(firstPos), tr.mapping.map(lastPos));
});
tr.setSelection(new _state.TextSelection(getSelectionResolvedPos(tr)));
};
var isEmptySelectionAtStart = exports.isEmptySelectionAtStart = function isEmptySelectionAtStart(state) {
var _state$selection4 = state.selection,
empty = _state$selection4.empty,
$from = _state$selection4.$from;
return empty && ($from.parentOffset === 0 || state.selection instanceof _selection.GapCursorSelection);
};
var isEmptySelectionAtEnd = exports.isEmptySelectionAtEnd = function isEmptySelectionAtEnd(state) {
var _state$selection5 = state.selection,
empty = _state$selection5.empty,
$from = _state$selection5.$from;
return empty && ($from.end() === $from.pos || state.selection instanceof _selection.GapCursorSelection);
};