UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

283 lines (277 loc) • 10.6 kB
"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); };