UNPKG

@atlaskit/editor-plugin-paste

Version:

Paste plugin for @atlaskit/editor-core

139 lines (136 loc) 6.24 kB
import { createToggleBlockMarkOnRangeNext } from '@atlaskit/editor-common/commands'; import { isListNode } from '@atlaskit/editor-common/utils'; import { Fragment, Slice } from '@atlaskit/editor-prosemirror/model'; import { Selection } from '@atlaskit/editor-prosemirror/state'; import { ReplaceStep } from '@atlaskit/editor-prosemirror/transform'; import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { isCursorSelectionAtTextStartOrEnd, isEmptyNode, isSelectionInsidePanel } from '../index'; import { insertSliceAtNodeEdge, insertSliceInsideOfPanelNodeSelected, insertSliceIntoEmptyNode, insertSliceIntoRangeSelectionInsideList } from './lists'; export function insertSliceForLists(_ref) { var _slice$content$firstC; var tr = _ref.tr, slice = _ref.slice, schema = _ref.schema; var selection = tr.selection, _tr$selection = tr.selection, $to = _tr$selection.$to, $from = _tr$selection.$from; var _ref2 = selection, $cursor = _ref2.$cursor; var panelNode = isSelectionInsidePanel(selection); var selectionIsInsideList = $from.blockRange($to, isListNode); if (!$cursor && selectionIsInsideList) { return insertSliceIntoRangeSelectionInsideList({ tr: tr, slice: slice }); } // if inside an empty panel, try and insert content inside it rather than replace it if (panelNode && isEmptyNode(panelNode) && $from.node() === $to.node()) { return insertSliceInsideOfPanelNodeSelected(panelNode)({ tr: tr, slice: slice, schema: schema }); } if (!$cursor || selectionIsInsideList) { return tr.replaceSelection(slice); } if (isEmptyNode(tr.doc.resolve($cursor.pos).node())) { return insertSliceIntoEmptyNode({ tr: tr, slice: slice }); } // When pasting a single list item into an action or decision, we skip the special "insert at node edge" // logic so that prosemirror pastes the list's content into the action/decision, rather than // pasting a whole list node directly after the action/decision item. (But we still preserve the // existing "insert at" node edge" behaviour if dealing with a list with more than one item, so that // it still inserts whole list node after the action/decision item). var pastingIntoActionOrDecision = Boolean(findParentNodeOfType([schema.nodes.taskList, schema.nodes.decisionList])(selection)); var oneListItem = slice.content.childCount === 1 && isListNode(slice.content.firstChild) && ((_slice$content$firstC = slice.content.firstChild) === null || _slice$content$firstC === void 0 ? void 0 : _slice$content$firstC.childCount) === 1; if (!(pastingIntoActionOrDecision && oneListItem) && isCursorSelectionAtTextStartOrEnd(selection)) { return insertSliceAtNodeEdge({ tr: tr, slice: slice }); } tr.replaceSelection(slice); } var stripFontSizeInsideBlockquoteLists = function stripFontSizeInsideBlockquoteLists(tr, blockquotePos) { var _tr$doc$type$schema = tr.doc.type.schema, fontSize = _tr$doc$type$schema.marks.fontSize, _tr$doc$type$schema$n = _tr$doc$type$schema.nodes, paragraph = _tr$doc$type$schema$n.paragraph, listItem = _tr$doc$type$schema$n.listItem, taskItem = _tr$doc$type$schema$n.taskItem, blockTaskItem = _tr$doc$type$schema$n.blockTaskItem; var listItemParentNodeTypes = [listItem, taskItem, blockTaskItem]; if (!fontSize || !expValEquals('platform_editor_small_font_size', 'isEnabled', true)) { return; } var containingBlockquote = tr.doc.nodeAt(blockquotePos); if (!containingBlockquote) { return; } var from = blockquotePos + 1; var to = blockquotePos + containingBlockquote.nodeSize - 1; createToggleBlockMarkOnRangeNext(fontSize, function () { return false; }, function (_schema, node, parent) { return node.type === paragraph && !!parent && listItemParentNodeTypes.some(function (nodeType) { return nodeType === parent.type; }); })(from, to, tr); }; export function insertSliceInsideBlockquote(_ref3) { var tr = _ref3.tr, slice = _ref3.slice; //insert blockquote explicitly and set the selection in blockquote since replaceSelection will only insert the list var schema = tr.doc.type.schema; tr.replaceSelection(new Slice(Fragment.from(schema.nodes.blockquote.createAndFill()), 0, 0)); updateSelectionAfterReplace({ tr: tr }); tr.replaceSelection(slice); if (expValEquals('platform_editor_small_font_size', 'isEnabled', true)) { var insertedBlockquotePos = findParentNodeOfType(schema.nodes.blockquote)(tr.selection); if (!insertedBlockquotePos) { return; } stripFontSizeInsideBlockquoteLists(tr, insertedBlockquotePos.pos); } } export function updateSelectionAfterReplace(_ref4) { var tr = _ref4.tr; // ProseMirror doesn't give a proper way to tell us where something was inserted. // However, we can know "how" it inserted something. // // So, instead of weird depth calculations, we can use the step produced by the transform. // For instance: // The `replaceStep.to and replaceStep.from`, tell us the real position // where the content will be insert. // Then, we can use the `tr.mapping.map` to the updated position after the replace operation var replaceStep = tr.steps[0]; if (!(replaceStep instanceof ReplaceStep)) { return tr; } var nextPosition = tr.mapping.map(replaceStep.to); // The findFrom will make search for both: TextSelection and NodeSelections. var nextSelection = Selection.findFrom(tr.doc.resolve(Math.min(nextPosition, tr.doc.content.size)), -1); if (nextSelection) { tr.setSelection(nextSelection); } } export function insertSliceForTaskInsideList(_ref5) { var tr = _ref5.tr, slice = _ref5.slice; var schema = tr.doc.type.schema; //To avoid the list being replaced with the tasklist, enclose the slice within a taskItem. var selectionBeforeReplace = tr.selection.from; tr.replaceSelection(new Slice(Fragment.from(schema.nodes.taskItem.createAndFill()), 0, 0)); var nextSelection = Selection.near(tr.doc.resolve(selectionBeforeReplace + 1)); tr.setSelection(nextSelection); tr.replaceSelection(slice); }